Diff for /loncom/interface/lonnavmaps.pm between versions 1.223 and 1.285

version 1.223, 2003/09/03 20:27:08 version 1.285, 2004/08/31 15:28:03
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;
   
Line 46  use strict; Line 33  use strict;
 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::lonlocal;
 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 used
   
Line 61  my $resObj = "Apache::lonnavmaps::resour Line 49  my $resObj = "Apache::lonnavmaps::resour
 # Keep these mappings in sync with lonquickgrades, which uses the colors  # Keep these mappings in sync with lonquickgrades, which uses the colors
 # instead of the icons.  # instead of the icons.
 my %statusIconMap =   my %statusIconMap = 
     ( $resObj->NETWORK_FAILURE    => '',      (
       $resObj->NOTHING_SET        => '',       $resObj->CLOSED       => '',
       $resObj->CORRECT            => 'navmap.correct.gif',       $resObj->OPEN         => 'navmap.open.gif',
       $resObj->EXCUSED            => 'navmap.correct.gif',       $resObj->CORRECT      => 'navmap.correct.gif',
       $resObj->PAST_DUE_NO_ANSWER => 'navmap.wrong.gif',       $resObj->INCORRECT    => 'navmap.wrong.gif',
       $resObj->PAST_DUE_ANSWER_LATER => 'navmap.wrong.gif',       $resObj->ATTEMPTED    => 'navmap.ellipsis.gif',
       $resObj->ANSWER_OPEN        => 'navmap.wrong.gif',       $resObj->ERROR        => ''
       $resObj->OPEN_LATER         => '',       );
       $resObj->TRIES_LEFT         => 'navmap.open.gif',  
       $resObj->INCORRECT          => 'navmap.wrong.gif',  
       $resObj->OPEN               => 'navmap.open.gif',  
       $resObj->ATTEMPTED          => 'navmap.ellipsis.gif',  
       $resObj->ANSWER_SUBMITTED   => 'navmap.ellipsis.gif' );  
   
 my %iconAltTags =   my %iconAltTags = 
     ( 'navmap.correct.gif' => 'Correct',      ( 'navmap.correct.gif' => 'Correct',
Line 100  my %colormap = Line 83  my %colormap =
 # is not yet done and due in less then 24 hours  # is not yet done and due in less then 24 hours
 my $hurryUpColor = "#FF0000";  my $hurryUpColor = "#FF0000";
   
   sub launch_win {
       my ($mode,$script,$toplinkitems)=@_;
       my $result;
       if ($script ne 'no') {
    $result.='<script type="text/javascript">';
       }
       $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 nav_control_js {
       my $nav=($ENV{'environment.remotenavmap'} eq 'on');
       return (<<NAVCONTROL);
       var w_loncapanav_flag="$nav";
   
   
   function gonav(url) {
      if (w_loncapanav_flag != 1) {
         go(url);
      }  else {
         navwindow=window.open(url,
                     "loncapanav","height=600,width=400,scrollbars=1"); 
      }
   }
   NAVCONTROL
   }
   
   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 111  sub real_handler { Line 161  sub real_handler {
     # Handle header-only request      # Handle header-only request
     if ($r->header_only) {      if ($r->header_only) {
         if ($ENV{'browser.mathml'}) {          if ($ENV{'browser.mathml'}) {
             $r->content_type('text/xml');              &Apache::loncommon::content_type($r,'text/xml');
         } else {          } else {
             $r->content_type('text/html');              &Apache::loncommon::content_type($r,'text/html');
         }          }
         $r->send_http_header;          $r->send_http_header;
         return OK;          return OK;
Line 121  sub real_handler { Line 171  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'}) {
         $r->content_type('text/xml');          &Apache::loncommon::content_type($r,'text/xml');
     } else {      } else {
         $r->content_type('text/html');          &Apache::loncommon::content_type($r,'text/html');
     }      }
     &Apache::loncommon::no_cache($r);      &Apache::loncommon::no_cache($r);
     $r->send_http_header;      $r->send_http_header;
   
       my %toplinkitems=();
   
       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->print(<<"ENDSUBM");
    <html>
           <head>
     <script type="text/javascript">
        function submitthis() {
       $menu
       self.close();
       }
   
      </script>
           </head>
    <body bgcolor="#FFFFFF" onLoad="submitthis()"></body>
           </html>
   ENDSUBM
           return;
       }
       if ($ENV{QUERY_STRING} eq 'launchExternal') {
    &Apache::lonnet::put('environment',{'remotenavmap' => 'on'});
    &Apache::lonnet::appenv('environment.remotenavmap' => 'on');
       }
   
     # 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";
Line 139  sub real_handler { Line 230  sub real_handler {
     }      }
   
     $r->print("<html><head>\n");      $r->print("<html><head>\n");
     $r->print("<title>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']);
           
 # ----------------------------------------------------- Force menu registration  # ----------------------------------------------------- Force menu registration
     my $addentries='';      my $addentries='';
       my $more_unload;
       my $body_only='';
       if ($ENV{'environment.remotenavmap'} eq 'on') {
    $r->print('<script type="text/javascript">
                         function collapse() {
                            this.document.location="/adm/navmaps?collapseExternal";
                         }
                      </script>');
   # FIXME need to be smarter to only catch window close events
   # $more_unload="collapse()"
    $body_only=1;
       }
     if ($ENV{'form.register'}) {      if ($ENV{'form.register'}) {
        $addentries=' onLoad="'.&Apache::lonmenu::loadevents().   $addentries=' onLoad="'.&Apache::lonmenu::loadevents().
    '" onUnload="'.&Apache::lonmenu::unloadevents().'"';      '" onUnload="'.&Apache::lonmenu::unloadevents().';'.
        $r->print(&Apache::lonmenu::registerurl(1));      $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 190  sub real_handler { Line 298  sub real_handler {
         }          }
     }      }
   
       if ($ENV{QUERY_STRING} eq 'launchExternal') {
    $r->print('
             <form name="returnwin" action="/adm/flip?postdata=return%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 225  sub real_handler { Line 352  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',
                   "Go To My First Homework Problem</a>&nbsp;&nbsp;&nbsp;&nbsp;");        'location.href="navmaps?jumpToFirstHomework"',
         "Go To My First Homework Problem");
     }      }
   
     my $suppressEmptySequences = 0;      my $suppressEmptySequences = 0;
Line 235  sub real_handler { Line 363  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();
                         };                          };
         $r->print("<p><font size='+2'>Uncompleted Homework</font></p>");   &add_linkitem(\%toplinkitems,'everything',
        'location.href="locatnavmaps?sort='.$ENV{'form.sort'}.'"',
         "Show Everything");
           $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',
                   "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>
                       </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();      $navmap->untieHashes();
   
Line 266  sub real_handler { Line 415  sub real_handler {
     # user knows there was no error.      # user knows there was no error.
     if ($renderArgs->{'counter'} == 0) {      if ($renderArgs->{'counter'} == 0) {
         if ($showOnlyHomework) {          if ($showOnlyHomework) {
             $r->print("<p><font size='+1'>All homework is currently completed.</font></p>");              $r->print("<p><font size='+1'>".&mt("All homework is currently completed").".</font></p>");
         } else { # both jumpToFirstHomework and normal use the same: course must be empty          } else { # both jumpToFirstHomework and normal use the same: course must be empty
             $r->print("<p><font size='+1'>This course is empty.</font></p>");              $r->print("<p><font size='+1'>This course is empty.</font></p>");
         }          }
Line 308  sub getLinkForResource { Line 457  sub getLinkForResource {
   
     # Check to see if there are any pages in the stack      # Check to see if there are any pages in the stack
     foreach $res (@$stack) {      foreach $res (@$stack) {
         if (defined($res) && $res->is_page()) {          if (defined($res)) {
             return $res->src();      if ($res->is_page()) {
    return $res->src();
       }
               # in case folder was skipped over as "only sequence"
       my ($map,$id,$src)=&Apache::lonnet::decode_symb($res->symb());
       if ($map=~/\.page$/) {
    return &Apache::lonnet::clutter($map).'#'.
       &Apache::lonnet::escape(&Apache::lonnet::declutter($src));
       }
         }          }
     }      }
   
Line 324  sub getLinkForResource { Line 481  sub getLinkForResource {
     return $res->src();      return $res->src();
 }  }
   
 # 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 336  sub getDescription { Line 493  sub getDescription {
     my $status = $res->status($part);      my $status = $res->status($part);
   
     if ($status == $res->NETWORK_FAILURE) {       if ($status == $res->NETWORK_FAILURE) { 
         return "Having technical difficulties; please check status later";           return &mt("Having technical difficulties; please check status later"); 
     }      }
     if ($status == $res->NOTHING_SET) {      if ($status == $res->NOTHING_SET) {
         return "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));
     }      }
     if ($status == $res->OPEN) {      if ($status == $res->OPEN) {
         if ($res->duedate($part)) {          if ($res->duedate($part)) {
             return "Due " . timeToHumanString($res->duedate($part));              return &mt("Due")."  " .timeToHumanString($res->duedate($part));
         } else {          } else {
             return "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 "Answer open " . timeToHumanString($res->answerdate($part));          return &mt("Answer open")." " . timeToHumanString($res->answerdate($part));
     }      }
     if ($status == $res->PAST_DUE_NO_ANSWER) {      if ($status == $res->PAST_DUE_NO_ANSWER) {
         return "Was due " . timeToHumanString($res->duedate($part));          return &mt("Was due")." " . timeToHumanString($res->duedate($part));
     }      }
     if ($status == $res->ANSWER_OPEN) {      if ($status == $res->ANSWER_OPEN) {
         return "Answer available";          return &mt("Answer available");
     }      }
     if ($status == $res->EXCUSED) {      if ($status == $res->EXCUSED) {
         return "Excused by instructor";          return &mt("Excused by instructor");
     }      }
     if ($status == $res->ATTEMPTED) {      if ($status == $res->ATTEMPTED) {
         return "Answer submitted, not yet graded.";          return &mt("Answer submitted, not yet graded");
     }      }
     if ($status == $res->TRIES_LEFT) {      if ($status == $res->TRIES_LEFT) {
         my $tries = $res->tries($part);          my $tries = $res->tries($part);
Line 376  sub getDescription { Line 533  sub getDescription {
                 $triesString = "<b>$triesString</b>";                  $triesString = "<b>$triesString</b>";
             }              }
         }          }
         if ($res->duedate()) {          if ($res->duedate($part)) {
             return "Due " . timeToHumanString($res->duedate($part)) .              return &mt("Due")." " . timeToHumanString($res->duedate($part)) .
                 " $triesString";                  " $triesString";
         } else {          } else {
             return "No due date $triesString";              return &mt("No due date")." $triesString";
         }          }
     }      }
     if ($status == $res->ANSWER_SUBMITTED) {      if ($status == $res->ANSWER_SUBMITTED) {
         return 'Answer submitted';          return &mt('Answer submitted');
     }      }
 }  }
   
 # 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 411  sub lastTry { Line 568  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.
Line 434  sub timeToHumanString { Line 591  sub timeToHumanString {
     my ($time) = @_;      my ($time) = @_;
     # zero, '0' and blank are bad times      # zero, '0' and blank are bad times
     if (!$time) {      if (!$time) {
         return 'never';          return &mt('never');
     }      }
       unless (&Apache::lonlocal::current_language()=~/^en/) {
    return &Apache::lonlocal::locallocaltime($time);
       } 
     my $now = time();      my $now = time();
   
     my @time = localtime($time);      my @time = localtime($time);
Line 504  sub timeToHumanString { Line 663  sub timeToHumanString {
         # 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/midnight/;              $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 " : "next ") .
                 $timeStr;                  $timeStr;
Line 514  sub timeToHumanString { Line 673  sub timeToHumanString {
         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("on %A, %b %e at %I:%M %P", localtime($time));
             $timeStr =~ s/12:00 am/midnight/;              $timeStr =~ s/12:00 am/00:00/;
             $timeStr =~ s/12:00 pm/noon/;              $timeStr =~ s/12:00 pm/noon/;
             return $timeStr;              return $timeStr;
         }          }
   
         # Not this year, so show the year          # Not this year, so show the year
         my $timeStr = strftime("on %A, %b %e %G at %I:%M %P", localtime($time));          my $timeStr = strftime("on %A, %b %e %Y at %I:%M %P", localtime($time));
         $timeStr =~ s/12:00 am/midnight/;          $timeStr =~ s/12:00 am/00:00/;
         $timeStr =~ s/12:00 pm/noon/;          $timeStr =~ s/12:00 pm/noon/;
         return $timeStr;          return $timeStr;
     }      }
Line 660  can't close or open folders when this is Line 819  can't close or open folders when this is
   
 =back  =back
   
 =item B<Apache::lonnavmaps::communication_status>:  =item * B<Apache::lonnavmaps::communication_status>:
   
 Whether there is discussion on the resource, email for the user, or  Whether there is discussion on the resource, email for the user, or
 (lumped in here) perl errors in the execution of the problem. This is  (lumped in here) perl errors in the execution of the problem. This is
 the second column in the main nav map.  the second column in the main nav map.
   
 =item B<Apache::lonnavmaps::quick_status>:  =item * B<Apache::lonnavmaps::quick_status>:
   
 An icon for the status of a problem, with five possible states:  An icon for the status of a problem, with five possible states:
 Correct, incorrect, open, awaiting grading (for a problem where the  Correct, incorrect, open, awaiting grading (for a problem where the
Line 674  computer's grade is suppressed, or the c Line 833  computer's grade is suppressed, or the c
 essay problem), or none (not open yet, not a problem). The  essay problem), or none (not open yet, not a problem). The
 third column of the standard navmap.  third column of the standard navmap.
   
 =item B<Apache::lonnavmaps::long_status>:  =item * B<Apache::lonnavmaps::long_status>:
   
 A text readout of the details of the current status of the problem,  A text readout of the details of the current status of the problem,
 such as "Due in 22 hours". The fourth column of the standard navmap.  such as "Due in 22 hours". The fourth column of the standard navmap.
   
   =item * B<Apache::lonnavmaps::part_status_summary>:
   
   A text readout summarizing the status of the problem. If it is a
   single part problem, will display "Correct", "Incorrect", 
   "Not yet open", "Open", "Attempted", or "Error". If there are
   multiple parts, this will output a string that in HTML will show a
   status of how many parts are in each status, in color coding, trying
   to match the colors of the icons within reason.
   
   Note this only makes sense if you are I<not> showing parts. If 
   C<showParts> is true (see below), this column will not output
   anything. 
   
 =back  =back
   
 If you add any others please be sure to document them here.  If you add any others please be sure to document them here.
Line 846  sub resource { return 0; } Line 1018  sub resource { return 0; }
 sub communication_status { return 1; }  sub communication_status { return 1; }
 sub quick_status { return 2; }  sub quick_status { return 2; }
 sub long_status { return 3; }  sub long_status { return 3; }
   sub part_status_summary { return 4; }
 # Data for render_resource  
   
 sub render_resource {  sub render_resource {
     my ($resource, $part, $params) = @_;      my ($resource, $part, $params) = @_;
Line 860  sub render_resource { Line 1031  sub render_resource {
     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 = "";
           
Line 886  sub render_resource { Line 1054  sub render_resource {
             $icon = $params->{'indentString'};              $icon = $params->{'indentString'};
         }          }
     } else {      } else {
  my $curfext= (split (/\./,$resource->src))[-1];   $icon = "<img src='".&Apache::loncommon::icon($resource->src).
  my $embstyle = &Apache::loncommon::fileembstyle($curfext);      "' alt='' border='0' />";
  # 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 961  sub render_resource { Line 1125  sub render_resource {
   
     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 = " (Part: $displaypart)";
    $link.='#'.&Apache::lonnet::escape($part);
         $title = "";          $title = "";
     }      }
   
Line 969  sub render_resource { Line 1135  sub render_resource {
         $nonLinkedText .= ' (' . $resource->countParts() . ' parts)';          $nonLinkedText .= ' (' . $resource->countParts() . ' parts)';
     }      }
   
     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 1007  sub render_communication_status { Line 1176  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 ($_) {
                   $errorcount++;
                 $errorHTML .= '&nbsp;<a href="/adm/email?display='                  $errorHTML .= '&nbsp;<a href="/adm/email?display='
                     . &Apache::lonnet::escape($_) . '">'                      . &Apache::lonnet::escape($_) . '">'
                     . '<img src="/adm/lonMisc/bomb.gif" '                      . '<img src="/adm/lonMisc/bomb.gif" '
Line 1036  sub render_quick_status { Line 1208  sub render_quick_status {
   
     if ($resource->is_problem() &&      if ($resource->is_problem() &&
         !$firstDisplayed) {          !$firstDisplayed) {
         my $icon = $statusIconMap{$resource->status($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";              $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";
Line 1059  sub render_long_status { Line 1232  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 1085  sub render_long_status { Line 1258  sub render_long_status {
     return $result;      return $result;
 }  }
   
   # Colors obtained by taking the icons, matching the colors, and
   # possibly reducing the Value (HSV) of the color, if it's too bright
   # for text, generally by one third or so.
   my %statusColors = 
       (
        $resObj->CLOSED => '#000000',
        $resObj->OPEN   => '#998b13',
        $resObj->CORRECT => '#26933f',
        $resObj->INCORRECT => '#c48207',
        $resObj->ATTEMPTED => '#a87510',
        $resObj->ERROR => '#000000'
        );
   my %statusStrings = 
       (
        $resObj->CLOSED => 'Not yet open',
        $resObj->OPEN   => 'Open',
        $resObj->CORRECT => 'Correct',
        $resObj->INCORRECT => 'Incorrect',
        $resObj->ATTEMPTED => 'Attempted',
        $resObj->ERROR => 'Network Error'
        );
   my @statuses = ($resObj->CORRECT, $resObj->ATTEMPTED, $resObj->INCORRECT, $resObj->OPEN, $resObj->CLOSED, $resObj->ERROR);
   
   use Data::Dumper;
   sub render_parts_summary_status {
       my ($resource, $part, $params) = @_;
       if (!$resource->is_problem() && !$resource->contains_problem) { return '<td></td>'; }
       if ($params->{showParts}) { 
    return '<td></td>';
       }
   
       my $td = "<td align='right'>\n";
       my $endtd = "</td>\n";
       my @probs;
   
       if ($resource->contains_problem) {
    @probs=$resource->retrieveResources($resource,sub { $_[0]->is_problem() },1,0);
       } else {
    @probs=($resource);
       }
       my $return;
       my %overallstatus;
       my $totalParts;
       foreach my $resource (@probs) {
    # If there is a single part, just show the simple status
    if ($resource->singlepart()) {
       my $status = $resource->simpleStatus(${$resource->parts}[0]);
       $overallstatus{$status}++;
       $totalParts++;
       next;
    }
    # 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>";
    }
       }
       $return.= $endtd;
       return $return;
   }
   
 my @preparedColumns = (\&render_resource, \&render_communication_status,  my @preparedColumns = (\&render_resource, \&render_communication_status,
                        \&render_quick_status, \&render_long_status);                         \&render_quick_status, \&render_long_status,
          \&render_parts_summary_status);
   
 sub setDefault {  sub setDefault {
     my ($val, $default) = @_;      my ($val, $default) = @_;
Line 1116  sub render { Line 1365  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 1224  sub render { Line 1474  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 1262  sub render { Line 1512  sub render {
         $result.='<tr><td align="right" valign="bottom">Key:&nbsp;&nbsp;</td>';          $result.='<tr><td align="right" valign="bottom">Key:&nbsp;&nbsp;</td>';
         if ($navmap->{LAST_CHECK}) {          if ($navmap->{LAST_CHECK}) {
             $result .=               $result .= 
                 '<img src="/adm/lonMisc/chat.gif"> New discussion since '.                  '<img src="/adm/lonMisc/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"> New message (click to open)<p>'.                  '<img src="/adm/lonMisc/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"> Discussions</td><td align="center" valign="bottom">'.                  '<img src="/adm/lonMisc/chat.gif"> '.&mt('Discussions').'</td><td align="center" valign="bottom">'.
                 '&nbsp;&nbsp;<img src="/adm/lonMisc/feedback.gif"> New message (click to open)'.                  '&nbsp;&nbsp;<img src="/adm/lonMisc/feedback.gif"> '.&mt('New message (click to open)').
                 '</td>';                   '</td>'; 
         }          }
   
Line 1278  sub render { Line 1528  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).'"';
                 "\">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') {
    my $totdisc = 0;
    my $haveDisc = '';
    my @allres=$navmap->retrieveResources();
    foreach my $resource (@allres) {
       if ($resource->hasDiscussion()) {
    my $ressymb;
    if ($resource->symb() =~ m-(___adm/\w+/\w+)/(\d+)/bulletinboard$-) {
       $ressymb = 'bulletin___'.$2.$1.'/'.$2.'/bulletinboard';
    } else {
       $ressymb = $resource->symb();
    }
    $haveDisc .= $ressymb.':';
    $totdisc ++;
       }
    }
    if ($totdisc > 0) {
       $haveDisc =~ s/:$//;
               my $navurl = $ENV{'QUERY_STRING'};
       &add_linkitem($args->{'linkitems'},'clearbubbles',
     'document.clearbubbles.submit()',
     'Mark all posts read');
               $result .= (<<END);
    <form name="clearbubbles" method="post" action="/adm/feedback">
    <input type="hidden" name="navurl" value="$ENV{'QUERY_STRING'}" />
    <input type="hidden" name="navmaps" value="$haveDisc" />
    </form>
   END
    }
       }
   
       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) .   
                 "\">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 1361  sub render { Line 1674  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 {lc($a->compTitle) cmp lc($b->compTitle)} @resources;
       } elsif ($args->{'sort'} eq 'duedate') {
    @resources=$navmap->retrieveResources(undef,
          sub { shift->is_problem(); });
    @resources= sort
    {
       if ($a->duedate ne $b->duedate) {
           return $a->duedate cmp $b->duedate;
       } else {
    lc($a->compTitle) cmp lc($b->compTitle)
       }
    } @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 1475  sub render { Line 1824  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;
             my $src = getLinkForResource($stack);      if ($args->{'sort'}) {
                $src = $curRes->src(); # FIXME this is wrong for .pages
       } else {
    my $stack = $it->getStack();
    $src=getLinkForResource($stack);
       }
               my $anchor='';
               if ($src=~s/(\#.*)$//) {
    $anchor=$1;
       }
             my $srcHasQuestion = $src =~ /\?/;              my $srcHasQuestion = $src =~ /\?/;
             $args->{"resourceLink"} = $src.              $args->{"resourceLink"} = $src.
                 ($srcHasQuestion?'&':'?') .                  ($srcHasQuestion?'&':'?') .
                 'symb=' . &Apache::lonnet::escape($curRes->symb());                  'symb=' . &Apache::lonnet::escape($curRes->symb()).
                $anchor;
   
             # Now, display each column.              # Now, display each column.
             foreach my $col (@$cols) {              foreach my $col (@$cols) {
                 my $colHTML = '';                  my $colHTML = '';
Line 1518  sub render { Line 1876  sub render {
     # If we have the connection, make sure the user is still connected      # If we have the connection, make sure the user is still connected
     my $c = $r->connection;      my $c = $r->connection;
     if ($c->aborted()) {      if ($c->aborted()) {
  Apache::lonnet::logthis("navmaps aborted");  
  # Who cares what we do, nobody will see it anyhow.   # Who cares what we do, nobody will see it anyhow.
  return '';   return '';
     }      }
Line 1533  sub render { Line 1890  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 1549  sub render { Line 1911  sub render {
     return $result;      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 = ("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;
   }
   
 1;  1;
   
 package Apache::lonnavmaps::navmap;  package Apache::lonnavmaps::navmap;
Line 1724  sub generate_course_user_opt { Line 2125  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'};
Line 1736  sub generate_email_discuss_status { Line 2138  sub generate_email_discuss_status {
    $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::reply('keys:'.
Line 1769  sub generate_email_discuss_status { Line 2180  sub generate_email_discuss_status {
     $self->{ERROR_MSG} = \%error; # what is this? JB      $self->{ERROR_MSG} = \%error; # what is this? JB
     $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 1837  sub hasDiscussion { Line 2249  sub hasDiscussion {
     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 = $symb;
       if ($ressymb =~ m|adm/(\w+)/(\w+)/(\d+)/bulletinboard$|) {
           unless ($ressymb =~ m|adm/wrapper/adm|) {
               $ressymb = 'bulletin___'.$3.'___adm/wrapper/adm/'.$1.'/'.$2.'/'.$3.'/bulletinboard';
           }
       }
   
       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).
       }
 }  }
   
 # Private method: Does the given resource (as a symb string) have  # Private method: Does the given resource (as a symb string) have
Line 1909  sub getById { Line 2334  sub getById {
 sub getBySymb {  sub getBySymb {
     my $self = shift;      my $self = shift;
     my $symb = shift;      my $symb = shift;
     my ($mapUrl, $id, $filename) = split (/___/, $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 1985  sub parmval_real { Line 2415  sub parmval_real {
     unless ($symb) { return ''; }      unless ($symb) { return ''; }
     my $result='';      my $result='';
   
     my ($mapname,$id,$fn)=split(/\_\_\_/,$symb);      my ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb);
   
 # ----------------------------------------------------- Cascading lookup scheme  # ----------------------------------------------------- Cascading lookup scheme
     my $rwhat=$what;      my $rwhat=$what;
Line 2035  sub parmval_real { Line 2465  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}
       $default=&Apache::lonnet::metadata($fn,'parameter_'.$meta_rwhat);
     if (defined($default)) { return $default}      if (defined($default)) { return $default}
   
 # --------------------------------------------------- fifth , cascade up parts  # --------------------------------------------------- fifth , cascade up parts
Line 2090  want to know is if I<any> resources matc Line 2524  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):
   
 Convience method for  Convience method for
   
Line 2101  in the filter function. Line 2535  in the filter function.
   
 =cut  =cut
   
   
 sub getResourceByUrl {  sub getResourceByUrl {
     my $self = shift;      my $self = shift;
     my $resUrl = shift;      my $resUrl = shift;
Line 2278  consisting entirely of empty resources e Line 2713  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 2298  the depth of the iterator to see when it Line 2735  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 2436  sub new { Line 2871  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 
Line 2469  sub new { Line 2904  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 2483  sub next { Line 2918  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 2597  sub next { Line 3032  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 2615  sub next { Line 3050  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 2667  package Apache::lonnavmaps::DFSiterator; Line 3102  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 3084  Returns true if the resource is a sequen Line 3519  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 3100  sub is_page { Line 3544  sub is_page {
 sub is_problem {  sub is_problem {
     my $self=shift;      my $self=shift;
     my $src = $self->src();      my $src = $self->src();
     return ($src =~ /problem$/);      return ($src =~ /\.(problem|exam|quiz|assess|survey|form|library)$/)
   }
   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 3108  sub is_sequence { Line 3560  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_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 3284  sub awarded { Line 3753  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 maxtries {  sub maxtries {
Line 3300  sub opendate { Line 3774  sub opendate {
 }  }
 sub problemstatus {  sub problemstatus {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     return $self->parmval("problemstatus", $part);      return lc $self->parmval("problemstatus", $part);
 }  }
 sub sig {  sub sig {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
Line 3327  sub weight { Line 3801  sub 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 3468  sub multipart { Line 3951  sub multipart {
     return $self->countParts() > 1;      return $self->countParts() > 1;
 }  }
   
   sub singlepart {
       my $self = shift;
       return $self->countParts() == 1;
   }
   
 sub responseType {  sub responseType {
     my $self = shift;      my $self = shift;
     my $part = shift;      my $part = shift;
   
     $self->extractParts();      $self->extractParts();
     return $self->{RESPONSE_TYPES}->{$part};      if (defined($self->{RESPONSE_TYPES}->{$part})) {
    return @{$self->{RESPONSE_TYPES}->{$part}};
       } else {
    return undef;
       }
 }  }
   
 sub responseIds {  sub responseIds {
Line 3481  sub responseIds { Line 3973  sub responseIds {
     my $part = shift;      my $part = shift;
   
     $self->extractParts();      $self->extractParts();
     return $self->{RESPONSE_IDS}->{$part};      if (defined($self->{RESPONSE_IDS}->{$part})) {
    return @{$self->{RESPONSE_IDS}->{$part}};
       } else {
    return undef;
       }
 }  }
   
 # Private function: Extracts the parts information, both part names and  # Private function: Extracts the parts information, both part names and
Line 3498  sub extractParts { Line 3994  sub extractParts {
   
     # Retrieve part count, if this is a problem      # Retrieve part count, if this is a problem
     if ($self->is_problem()) {      if ($self->is_problem()) {
    my $partorder = &Apache::lonnet::metadata($self->src(), 'partorder');
         my $metadata = &Apache::lonnet::metadata($self->src(), 'packages');          my $metadata = &Apache::lonnet::metadata($self->src(), 'packages');
         if (!$metadata) {  
             $self->{RESOURCE_ERROR} = 1;  
             $self->{PARTS} = [];  
             $self->{PART_TYPE} = {};  
             return;  
         }  
         foreach (split(/\,/,$metadata)) {  
             if ($_ =~ /^part_(.*)$/) {  
                 my $part = $1;  
                 # This floods the logs if it blows up  
                 if (defined($parts{$part})) {  
                     Apache::lonnet::logthis("$part multiply defined in metadata for " . $self->symb());  
                   }  
   
                 # check to see if part is turned off.  
   
                 if (!Apache::loncommon::check_if_partid_hidden($part, $self->symb())) {   if ($partorder) {
                     $parts{$part} = 1;      my @parts;
                 }      for my $part (split (/,/,$partorder)) {
             }   if (!Apache::loncommon::check_if_partid_hidden($part, $self->symb())) {
       push @parts, $part;
       $parts{$part} = 1;
    }
       }
       $self->{PARTS} = \@parts;
    } else {
       if (!$metadata) {
    $self->{RESOURCE_ERROR} = 1;
    $self->{PARTS} = [];
    $self->{PART_TYPE} = {};
    return;
       }
       foreach (split(/\,/,$metadata)) {
    if ($_ =~ /^part_(.*)$/) {
       my $part = $1;
       # This floods the logs if it blows up
       if (defined($parts{$part})) {
    &Apache::lonnet::logthis("$part multiply defined in metadata for " . $self->symb());
       }
       
       # check to see if part is turned off.
       
       if (!Apache::loncommon::check_if_partid_hidden($part, $self->symb())) {
    $parts{$part} = 1;
       }
    }
       }
       my @sortedParts = sort keys %parts;
       $self->{PARTS} = \@sortedParts;
         }          }
                   
           
         my @sortedParts = sort keys %parts;  
         $self->{PARTS} = \@sortedParts;  
   
           # These hashes probably do not need names that end with "Hash"....
         my %responseIdHash;          my %responseIdHash;
         my %responseTypeHash;          my %responseTypeHash;
   
Line 3535  sub extractParts { Line 4044  sub extractParts {
         }          }
   
         # Now, the unfortunate thing about this is that parts, part name, and          # Now, the unfortunate thing about this is that parts, part name, and
         # response if are delimited by underscores, but both the part          # response id are delimited by underscores, but both the part
         # name and response id can themselves have underscores in them.          # name and response id can themselves have underscores in them.
         # So we have to use our knowlege of part names to figure out           # So we have to use our knowlege of part names to figure out 
         # where the part names begin and end, and even then, it is possible          # where the part names begin and end, and even then, it is possible
         # to construct ambiguous situations.          # to construct ambiguous situations.
           #
           my %response_type_by_id;
         foreach (split /,/, $metadata) {          foreach (split /,/, $metadata) {
             if ($_ =~ /^([a-zA-Z]+)response_(.*)/) {              if ($_ =~ /^([a-zA-Z]+)response_(.*)/) {
                 my $responseType = $1;                  my $responseType = $1;
Line 3547  sub extractParts { Line 4058  sub extractParts {
                 my $partIdSoFar = '';                  my $partIdSoFar = '';
                 my @partChunks = split /_/, $partStuff;                  my @partChunks = split /_/, $partStuff;
                 my $i = 0;                  my $i = 0;
   
                 for ($i = 0; $i < scalar(@partChunks); $i++) {                  for ($i = 0; $i < scalar(@partChunks); $i++) {
                     if ($partIdSoFar) { $partIdSoFar .= '_'; }                      if ($partIdSoFar) { $partIdSoFar .= '_'; }
                     $partIdSoFar .= $partChunks[$i];                      $partIdSoFar .= $partChunks[$i];
Line 3555  sub extractParts { Line 4065  sub extractParts {
                         my @otherChunks = @partChunks[$i+1..$#partChunks];                          my @otherChunks = @partChunks[$i+1..$#partChunks];
                         my $responseId = join('_', @otherChunks);                          my $responseId = join('_', @otherChunks);
                         push @{$responseIdHash{$partIdSoFar}}, $responseId;                          push @{$responseIdHash{$partIdSoFar}}, $responseId;
                         $responseTypeHash{$partIdSoFar} = $responseType;                          push @{$responseTypeHash{$partIdSoFar}}, $responseType;
                         last;                          $response_type_by_id{$responseId} = $responseType;
                     }                      }
                 }                  }
             }              }
         }          }
    my $resorder = &Apache::lonnet::metadata($self->src(),'responseorder');
           #
           # Reorder the arrays in the %responseIdHash and %responseTypeHash
    if ($resorder) {
       my @resorder=split(/,/,$resorder);
       foreach my $part (keys(%responseIdHash)) {
    my %resids = map { ($_,1) } @{ $responseIdHash{$part} };
    my @neworder;
    foreach my $possibleid (@resorder) {
       if (exists($resids{$possibleid})) {
    push(@neworder,$possibleid);
       }
    }
    $responseIdHash{$part}=\@neworder;
       }
    }
           #
           # Reorder the response types
           foreach my $partid (keys(%responseIdHash)) {
               delete($responseTypeHash{$partid});
               foreach my $respid (@{$responseIdHash{$partid}}) {
                   push(@{$responseTypeHash{$partid}},
                        $response_type_by_id{$respid});
               }
           }
         $self->{RESPONSE_IDS} = \%responseIdHash;          $self->{RESPONSE_IDS} = \%responseIdHash;
         $self->{RESPONSE_TYPES} = \%responseTypeHash;          $self->{RESPONSE_TYPES} = \%responseTypeHash;
     }      }
Line 3753  sub getCompletionStatus { Line 4287  sub getCompletionStatus {
   
     my $status = $self->queryRestoreHash('solved', shift);      my $status = $self->queryRestoreHash('solved', shift);
   
     # 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_override') {return $self->CORRECT_BY_OVERRIDE; }
     if ($status eq 'incorrect_attempted') {return $self->INCORRECT; }      if ($status eq 'incorrect_attempted') {return $self->INCORRECT; }
Line 3874  sub status { Line 4408  sub status {
     #if ($self->{RESOURCE_ERROR}) { return NETWORK_FAILURE; }      #if ($self->{RESOURCE_ERROR}) { return NETWORK_FAILURE; }
     if ($completionStatus == NETWORK_FAILURE) { return NETWORK_FAILURE; }      if ($completionStatus == NETWORK_FAILURE) { return NETWORK_FAILURE; }
   
     my $suppressFeedback = lc($self->parmval("problemstatus", $part)) eq 'no';      my $suppressFeedback = $self->problemstatus($part) eq 'no';
       # If there's an answer date and we're past it, don't
       # suppress the feedback; student should know
       if ($self->answerdate($part) && $self->answerdate($part) < time()) {
    $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 ||
Line 3900  sub status { Line 4439  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 3927  sub status { Line 4466  sub status {
     return OPEN;       return OPEN; 
 }  }
   
   sub CLOSED { return 23; }
   sub ERROR { return 24; }
   
   =pod
   
   B<Simple Status>
   
   Convenience method B<simpleStatus> provides a "simple status" for the resource.
   "Simple status" corresponds to "which icon is shown on the
   Navmaps". There are six "simple" statuses:
   
   =over 4
   
   =item * B<CLOSED>: The problem is currently closed. (No icon shown.)
   
   =item * B<OPEN>: The problem is open and unattempted.
   
   =item * B<CORRECT>: The problem is correct for any reason.
   
   =item * B<INCORRECT>: The problem is incorrect and can still be
   completed successfully.
   
   =item * B<ATTEMPTED>: The problem has been attempted, but the student
   does not know if they are correct. (The ellipsis icon.)
   
   =item * B<ERROR>: There is an error retrieving information about this
   problem.
   
   =back
   
   =cut
   
   # This hash maps the composite status to this simple status, and
   # can be used directly, if you like
   my %compositeToSimple = 
       (
         NETWORK_FAILURE()       => ERROR,
         NOTHING_SET()           => CLOSED,
         CORRECT()               => CORRECT,
         EXCUSED()               => CORRECT,
         PAST_DUE_NO_ANSWER()    => INCORRECT,
         PAST_DUE_ANSWER_LATER() => INCORRECT,
         ANSWER_OPEN()           => INCORRECT,
         OPEN_LATER()            => CLOSED,
         TRIES_LEFT()            => OPEN,
         INCORRECT()             => INCORRECT,
         OPEN()                  => OPEN,
         ATTEMPTED()             => ATTEMPTED,
         ANSWER_SUBMITTED()      => ATTEMPTED
        );
   
   sub simpleStatus {
       my $self = shift;
       my $part = shift;
       my $status = $self->status($part);
       return $compositeToSimple{$status};
   }
   
   =pod
   
   B<simpleStatusCount> will return an array reference containing, in
   this order, the number of OPEN, CLOSED, CORRECT, INCORRECT, ATTEMPTED,
   and ERROR parts the given problem has.
   
   =cut
       
   # This maps the status to the slot we want to increment
   my %statusToSlotMap = 
       (
        OPEN()      => 0,
        CLOSED()    => 1,
        CORRECT()   => 2,
        INCORRECT() => 3,
        ATTEMPTED() => 4,
        ERROR()     => 5
        );
   
   sub statusToSlot { return $statusToSlotMap{shift()}; }
   
   sub simpleStatusCount {
       my $self = shift;
   
       my @counts = (0, 0, 0, 0, 0, 0, 0);
       foreach my $part (@{$self->parts()}) {
    $counts[$statusToSlotMap{$self->simpleStatus($part)}]++;
       }
   
       return \@counts;
   }
   
 =pod  =pod
   
 B<Completable>  B<Completable>

Removed from v.1.223  
changed lines
  Added in v.1.285


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