Diff for /loncom/interface/lonnavmaps.pm between versions 1.186 and 1.198

version 1.186, 2003/05/12 19:22:39 version 1.198, 2003/06/10 20:07:58
Line 47  use Apache::Constants qw(:common :http); Line 47  use Apache::Constants qw(:common :http);
 use Apache::loncommon();  use Apache::loncommon();
 use Apache::lonmenu();  use Apache::lonmenu();
 use POSIX qw (floor strftime);  use POSIX qw (floor strftime);
   use Data::Dumper; # for debugging, not always used
   
 # symbolic constants  # symbolic constants
 sub SYMB { return 1; }  sub SYMB { return 1; }
Line 200  sub real_handler { Line 201  sub real_handler {
         }          }
     }      }
   
       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} eq 'jumpToFirstHomework') {
           $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);
         my $depth = 1;          my $depth = 1;
Line 216  sub real_handler { Line 219  sub real_handler {
   
             if (ref($curRes) && $curRes->is_problem()) {              if (ref($curRes) && $curRes->is_problem()) {
                 my $status = $curRes->status();                  my $status = $curRes->status();
                 if (($status == $curRes->OPEN ||                   if ($curRes->completable()) {
                      $status == $curRes->TRIES_LEFT()) &&  
                     $curRes->getCompletionStatus() != $curRes->ATTEMPTED()) {  
                     $problemRes = $curRes;                      $problemRes = $curRes;
                     $foundDoableProblem = 1;                      $foundDoableProblem = 1;
   
Line 246  sub real_handler { Line 247  sub real_handler {
                   "Go To My First Homework Problem</a><br />");                    "Go To My First Homework Problem</a><br />");
     }      }
   
     # renderer call      my $suppressEmptySequences = 0;
     my $render = render({ 'cols' => [0,1,2,3],      my $filterFunc = undef;
                           'url' => '/adm/navmaps',      # Display only due homework.
                           'navmap' => $navmap,      my $showOnlyHomework = 0;
                           'suppressNavmap' => 1,      if ($ENV{QUERY_STRING} eq 'showOnlyHomework') {
                           'r' => $r});          $showOnlyHomework = 1;
           $suppressEmptySequences = 1;
           $filterFunc = sub { my $res = shift; 
                               return $res->completable() || $res->is_sequence();
                           };
           $r->print("<p><font size='+2'>Uncompleted Homework</font></p>");
           $ENV{'form.filter'} = '';
           $ENV{'form.condition'} = 1;
       } else {
           $r->print("<a href='navmaps?showOnlyHomework'>" .
                     "Show Only Uncompleted Homework</a><br />");
       }
   
       # renderer call
       my $renderArgs = { 'cols' => [0,1,2,3],
                          'url' => '/adm/navmaps',
                          'navmap' => $navmap,
                          'suppressNavmap' => 1,
                          'suppressEmptySequences' => $suppressEmptySequences,
                          'filterFunc' => $filterFunc,
                          'r' => $r};
       my $render = render($renderArgs);
     $navmap->untieHashes();      $navmap->untieHashes();
   
       # If no resources were printed, print a reassuring message so the
       # user knows there was no error.
       if ($renderArgs->{'counter'} == 0) {
           if ($showOnlyHomework) {
               $r->print("<p><font size='+1'>All homework is currently completed.</font></p>");
           } else { # both jumpToFirstHomework and normal use the same: course must be empty
               $r->print("<p><font size='+1'>This course is empty.</font></p>");
           }
       }
   
     $r->print("</body></html>");      $r->print("</body></html>");
     $r->rflush();      $r->rflush();
   
Line 737  returns a true or false value. If true, Line 768  returns a true or false value. If true,
 false, it is simply skipped in the display. By default, all resources  false, it is simply skipped in the display. By default, all resources
 are shown.  are shown.
   
   =item * B<suppressEmptySequences>:
   
   If you're using a filter function, and displaying sequences to orient
   the user, then frequently some sequences will be empty. Setting this to
   true will cause those sequences not to display, so as not to confuse the
   user into thinking that if the sequence is there there should be things
   under it.
   
 =item * B<suppressNavmaps>:  =item * B<suppressNavmaps>:
   
 If true, will not display Navigate Content resources. Default to  If true, will not display Navigate Content resources. Default to
Line 800  sub render_resource { Line 839  sub render_resource {
     my $icon = "<img src='/adm/lonIcons/html.gif' alt='' border='0' />";      my $icon = "<img src='/adm/lonIcons/html.gif' alt='' border='0' />";
           
     if ($resource->is_problem()) {      if ($resource->is_problem()) {
         if ($part eq "" || $params->{'condensed'}) {          if ($part eq '0' || $params->{'condensed'}) {
             $icon = '<img src="/adm/lonIcons/problem.gif" alt="" border="0" />';              $icon = '<img src="/adm/lonIcons/problem.gif" alt="" border="0" />';
         } else {          } else {
             $icon = $params->{'indentString'};              $icon = $params->{'indentString'};
Line 870  sub render_resource { Line 909  sub render_resource {
         $params->{'displayedHereMarker'} = 1;          $params->{'displayedHereMarker'} = 1;
     }      }
   
     if ($resource->is_problem() && $part ne "" &&       if ($resource->is_problem() && $part ne '0' && 
         !$params->{'condensed'}) {          !$params->{'condensed'}) {
         $partLabel = " (Part $part)";          $partLabel = " (Part $part)";
         $title = "";          $title = "";
Line 927  sub render_communication_status { Line 966  sub render_communication_status {
         }          }
     }      }
   
       if ($params->{'multipart'} && $part != '0') {
    $discussionHTML = $feedbackHTML = $errorHTML = '';
       }
   
     return "<td width=\"75\" align=\"left\" valign=\"center\">$discussionHTML$feedbackHTML$errorHTML&nbsp;</td>";      return "<td width=\"75\" align=\"left\" valign=\"center\">$discussionHTML$feedbackHTML$errorHTML&nbsp;</td>";
   
 }  }
Line 962  sub render_long_status { Line 1005  sub render_long_status {
         $params->{'multipart'} && $part eq "0";          $params->{'multipart'} && $part eq "0";
                                   
     my $color;      my $color;
     if ($resource->is_problem()) {      if ($resource->is_problem() && ($resource->countParts() <= 1 || $part ne '') ) {
         $color = $colormap{$resource->status};          $color = $colormap{$resource->status};
                   
         if (dueInLessThen24Hours($resource, $part) ||          if (dueInLessThen24Hours($resource, $part) ||
Line 982  sub render_long_status { Line 1025  sub render_long_status {
         $result .= '(randomly select ' . $resource->randompick() .')';          $result .= '(randomly select ' . $resource->randompick() .')';
     }      }
           
     $result .= "&nbsp;</td>\n";  
       
     return $result;      return $result;
 }  }
   
Line 1033  sub render { Line 1074  sub render {
         }          }
     }      }
   
       # Filter: Remember filter function and add our own filter: Refuse
       # to show hidden resources unless the user can see them.
       my $userCanSeeHidden = advancedUser();
       my $filterFunc = setDefault($args->{'filterFunc'},
                                   sub {return 1;});
       if (!$userCanSeeHidden) {
           # Without renaming the filterfunc, the server seems to go into
           # an infinite loop
           my $oldFilterFunc = $filterFunc;
           $filterFunc = sub { my $res = shift; return !$res->randomout() && 
                                   &$oldFilterFunc($res);};
       }
   
     my $condition = 0;      my $condition = 0;
     if ($ENV{'form.condition'}) {      if ($ENV{'form.condition'}) {
         $condition = 1;          $condition = 1;
Line 1129  sub render { Line 1183  sub render {
     }      }
           
     # (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
       # to be fixed?
     my $mapIterator = $navmap->getIterator(undef, undef, $filterHash, 0);      my $mapIterator = $navmap->getIterator(undef, undef, $filterHash, 0);
     my $depth = 1;      my $depth = 1;
     $mapIterator->next();      $mapIterator->next();
Line 1161  sub render { Line 1217  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; }
     my $filterFunc = setDefault($args->{'filterFunc'},  
                                 sub {return 1;});  
           
     # Print key?      # Print key?
     if ($printKey) {      if ($printKey) {
Line 1223  sub render { Line 1277  sub render {
     $args->{'indentString'} = setDefault($args->{'indentString'}, "<img src='/adm/lonIcons/whitespace1.gif' width='25' height='1' alt='' border='0' />");      $args->{'indentString'} = setDefault($args->{'indentString'}, "<img src='/adm/lonIcons/whitespace1.gif' width='25' height='1' alt='' border='0' />");
     $args->{'displayedHereMarker'} = 0;      $args->{'displayedHereMarker'} = 0;
   
       # If we're suppressing empty sequences, look for them here. Use DFS for speed,
       # since structure actually doesn't matter, except what map has what resources.
       if ($args->{'suppressEmptySequences'}) {
           my $dfsit = Apache::lonnavmaps::DFSiterator->new($navmap,
                                                            $it->{FIRST_RESOURCE},
                                                            $it->{FINISH_RESOURCE},
                                                            {}, undef, 1);
           $depth = 0;
           $dfsit->next();
           my $curRes = $dfsit->next();
           while ($depth > -1) {
               if ($curRes == $dfsit->BEGIN_MAP()) { $depth++; }
               if ($curRes == $dfsit->END_MAP()) { $depth--; }
   
               if (ref($curRes)) { 
                   # Parallel pre-processing: Do sequences have non-filtered-out children?
                   if ($curRes->is_sequence()) {
                       $curRes->{DATA}->{HAS_VISIBLE_CHILDREN} = 0;
                       # Sequences themselves do not count as visible children,
                       # unless those sequences also have visible children.
                       # This means if a sequence appears, there's a "promise"
                       # that there's something under it if you open it, somewhere.
                   } else {
                       # Not a sequence: if it's filtered, ignore it, otherwise
                       # rise up the stack and mark the sequences as having children
                       if (&$filterFunc($curRes)) {
                           for my $sequence (@{$dfsit->getStack()}) {
                               $sequence->{DATA}->{HAS_VISIBLE_CHILDREN} = 1;
                           }
                       }
                   }
               }
           } continue {
               $curRes = $dfsit->next();
           }
       }
   
     my $displayedJumpMarker = 0;      my $displayedJumpMarker = 0;
     # Set up iteration.      # Set up iteration.
     $depth = 1;      $depth = 1;
Line 1258  sub render { Line 1349  sub render {
             next;              next;
         }          }
   
         $args->{'counter'}++;  
   
         # If this has been filtered out, continue on          # If this has been filtered out, continue on
         if (!(&$filterFunc($curRes))) {          if (!(&$filterFunc($curRes))) {
             $args->{'isNewBranch'} = 0; # Don't falsely remember this              $args->{'isNewBranch'} = 0; # Don't falsely remember this
             next;              next;
         }           } 
   
           # If this is an empty sequence and we're filtering them, continue on
           if ($curRes->is_sequence() && $args->{'suppressEmptySequences'} &&
               !$curRes->{DATA}->{HAS_VISIBLE_CHILDREN}) {
               next;
           }
   
         # If we're suppressing navmaps and this is a navmap, continue on          # If we're suppressing navmaps and this is a navmap, continue on
         if ($suppressNavmap && $curRes->src() =~ /^\/adm\/navmaps/) {          if ($suppressNavmap && $curRes->src() =~ /^\/adm\/navmaps/) {
             next;              next;
         }          }
   
           $args->{'counter'}++;
   
         # Does it have multiple parts?          # Does it have multiple parts?
         $args->{'multipart'} = 0;          $args->{'multipart'} = 0;
         $args->{'condensed'} = 0;          $args->{'condensed'} = 0;
Line 1279  sub render { Line 1376  sub render {
         # Decide what parts to show.          # Decide what parts to show.
         if ($curRes->is_problem() && $showParts) {          if ($curRes->is_problem() && $showParts) {
             @parts = @{$curRes->parts()};              @parts = @{$curRes->parts()};
             $args->{'multipart'} = scalar(@parts) > 1;              $args->{'multipart'} = $curRes->multipart();
                           
             if ($condenseParts) { # do the condensation              if ($condenseParts) { # do the condensation
                 if (!$curRes->opendate("0")) {                  if (!$curRes->opendate("0")) {
Line 1288  sub render { Line 1385  sub render {
                 }                  }
                 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[1]);                      my $status = $curRes->status($parts[0]);
                     my $due = $curRes->duedate($parts[1]);                      my $due = $curRes->duedate($parts[0]);
                     my $open = $curRes->opendate($parts[1]);                      my $open = $curRes->opendate($parts[0]);
                     my $statusAllSame = 1;                      my $statusAllSame = 1;
                     my $dueAllSame = 1;                      my $dueAllSame = 1;
                     my $openAllSame = 1;                      my $openAllSame = 1;
                     for (my $i = 2; $i < scalar(@parts); $i++) {                      for (my $i = 1; $i < scalar(@parts); $i++) {
                         if ($curRes->status($parts[$i]) != $status){                          if ($curRes->status($parts[$i]) != $status){
                             $statusAllSame = 0;                              $statusAllSame = 0;
                         }                          }
Line 1315  sub render { Line 1412  sub render {
                     if (($statusAllSame && defined($condenseStatuses{$status})) ||                      if (($statusAllSame && defined($condenseStatuses{$status})) ||
                         ($dueAllSame && $status == $curRes->OPEN && $statusAllSame)||                          ($dueAllSame && $status == $curRes->OPEN && $statusAllSame)||
                         ($openAllSame && $status == $curRes->OPEN_LATER && $statusAllSame) ){                          ($openAllSame && $status == $curRes->OPEN_LATER && $statusAllSame) ){
                         @parts = ();                          @parts = ($parts[0]);
                         $args->{'condensed'} = 1;                          $args->{'condensed'} = 1;
                     }                      }
                       
                 }                  }
    # Multipart problem with one part: always "condense" (happens
    #  to match the desirable behavior)
    if ($curRes->countParts() == 1) {
       @parts = ($parts[0]);
       $args->{'condensed'} = 1;
    }
             }              }
         }           } 
                           
         # If the multipart problem was condensed, "forget" it was multipart          # If the multipart problem was condensed, "forget" it was multipart
         if (scalar(@parts) == 1) {          if (scalar(@parts) == 1) {
             $args->{'multipart'} = 0;              $args->{'multipart'} = 0;
           } else {
               # Add part 0 so we display it correctly.
               unshift @parts, '0';
         }          }
   
         # 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) {
             if ($part eq '0') {  
                 next;  
             }  
             $rownum ++;              $rownum ++;
             my $backgroundColor = $backgroundColors[$rownum % scalar(@backgroundColors)];              my $backgroundColor = $backgroundColors[$rownum % scalar(@backgroundColors)];
                           
Line 1493  sub new { Line 1595  sub new {
         return undef;          return undef;
     }      }
   
     $self->{NAV_HASH} = \%navmaphash;      # try copying into memory
       my %tmpnavhash;
       while (my ($k, $v) = each(%navmaphash)) {
    $tmpnavhash{$k} = $v;
       }
       untie %navmaphash;
   
       $self->{NAV_HASH} = \%tmpnavhash;
     $self->{PARM_HASH} = \%parmhash;      $self->{PARM_HASH} = \%parmhash;
     $self->{INITED} = 0;      $self->{INITED} = 0;
   
Line 1567  sub init { Line 1676  sub init {
         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);
Line 1684  object for that resource. This method, o Line 1793  object for that resource. This method, o
 (as in the resource object) is the only proper way to obtain a  (as in the resource object) is the only proper way to obtain a
 resource object.  resource object.
   
   =item * B<getBySymb>(symb):
   
   Based on the symb of the resource, get a resource object for that
   resource. This is one of the proper ways to get a resource object.
   
   =item * B<getMapByMapPc>(map_pc):
   
   Based on the map_pc of the resource, get a resource object for
   the given map. This is one of the proper ways to get a resource object.
   
 =cut  =cut
   
 # The strategy here is to cache the resource objects, and only construct them  # The strategy here is to cache the resource objects, and only construct them
Line 1714  sub getBySymb { Line 1833  sub getBySymb {
     return $self->getById($map->map_pc() . '.' . $id);      return $self->getById($map->map_pc() . '.' . $id);
 }  }
   
   sub getByMapPc {
       my $self = shift;
       my $map_pc = shift;
       my $map_id = $self->{NAV_HASH}->{'map_id_' . $map_pc};
       $map_id = $self->{NAV_HASH}->{'ids_' . $map_id};
       return $self->getById($map_id);
   }
   
 =pod  =pod
   
 =item * B<firstResource>():  =item * B<firstResource>():
Line 2194  sub new { Line 2321  sub new {
                                   
                 $curRes->{DATA}->{DISPLAY_DEPTH} = $finalDepth;                  $curRes->{DATA}->{DISPLAY_DEPTH} = $finalDepth;
                 if ($finalDepth > $maxDepth) {$maxDepth = $finalDepth;}                  if ($finalDepth > $maxDepth) {$maxDepth = $finalDepth;}
                 }              }
           } continue {
             $curRes = $iterator->next();              $curRes = $iterator->next();
         }          }
     }      }
Line 2571  sub next { Line 2699  sub next {
     return $self->{HERE};      return $self->{HERE};
 }  }
   
   # Identical to the full iterator methods of the same name. Hate to copy/paste
   # but I also hate to "inherit" either iterator from the other.
   
   sub getStack {
       my $self=shift;
   
       my @stack;
   
       $self->populateStack(\@stack);
   
       return \@stack;
   }
   
   # Private method: Calls the iterators recursively to populate the stack.
   sub populateStack {
       my $self=shift;
       my $stack = shift;
   
       push @$stack, $self->{HERE} if ($self->{HERE});
   
       if ($self->{RECURSIVE_ITERATOR_FLAG}) {
           $self->{RECURSIVE_ITERATOR}->populateStack($stack);
       }
   }
   
 1;  1;
   
 package Apache::lonnavmaps::resource;  package Apache::lonnavmaps::resource;
Line 2594  You will probably never need to instanti Line 2747  You will probably never need to instanti
 Apache::lonnavmaps::navmap, and use the "start" method to obtain the  Apache::lonnavmaps::navmap, and use the "start" method to obtain the
 starting resource.  starting resource.
   
   Resource objects respect the parameter_hiddenparts, which suppresses 
   various parts according to the wishes of the map author. As of this
   writing, there is no way to override this parameter, and suppressed
   parts will never be returned, nor will their response types or ids be
   stored.
   
 =head2 Public Members  =head2 Public Members
   
 resource objects have a hash called DATA ($resourceRef->{DATA}) that  resource objects have a hash called DATA ($resourceRef->{DATA}) that
Line 3084  sub getErrors { Line 3243  sub getErrors {
 =item * B<parts>():  =item * B<parts>():
   
 Returns a list reference containing sorted strings corresponding to  Returns a list reference containing sorted strings corresponding to
 each part of the problem. To count the number of parts, use the list  each part of the problem. Single part problems have only a part '0'.
 in a scalar context, and subtract one if greater than two. (One part  Multipart problems do not return their part '0', since they typically
 problems have a part 0. Multi-parts have a part 0, plus a part for  do not really matter. 
 each part. Filtering part 0 if you want it is up to you.)  
   
 =item * B<countParts>():  =item * B<countParts>():
   
 Returns the number of parts of the problem a student can answer. Thus,  Returns the number of parts of the problem a student can answer. Thus,
 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. Thus,  number of parts in the problem, not including psuedo-part 0. 
 B<parts> may return an array with fewer parts in it then countParts  
 might lead you to believe.  =item * B<multipart>():
   
   Returns true if the problem is multipart, false otherwise. Use this instead
   of countParts if all you want is multipart/not multipart.
   
   =item * B<responseType>($part):
   
   Returns the response type of the part, without the word "response" on the
   end. Example return values: 'string', 'essay', 'numeric', etc.
   
   =item * B<responseIds>($part):
   
   Retreives the response IDs for the given part as an array reference containing
   strings naming the response IDs. This may be empty.
   
 =back  =back
   
Line 3104  might lead you to believe. Line 3275  might lead you to believe.
 sub parts {  sub parts {
     my $self = shift;      my $self = shift;
   
     if ($self->ext) { return ['0']; }      if ($self->ext) { return []; }
   
     $self->extractParts();      $self->extractParts();
     return $self->{PARTS};      return $self->{PARTS};
Line 3114  sub countParts { Line 3285  sub countParts {
     my $self = shift;      my $self = shift;
           
     my $parts = $self->parts();      my $parts = $self->parts();
     my $delta = 0;  
     for my $part (@$parts) {      # If I left this here, then it's not necessary.
         if ($part eq '0') { $delta--; }      #my $delta = 0;
     }      #for my $part (@$parts) {
       #    if ($part eq '0') { $delta--; }
       #}
   
     if ($self->{RESOURCE_ERROR}) {      if ($self->{RESOURCE_ERROR}) {
         return 0;          return 0;
     }      }
   
     return scalar(@{$parts}) + $delta;      return scalar(@{$parts}); # + $delta;
 }  }
   
 sub partType {  sub multipart {
       my $self = shift;
       return $self->countParts() > 1;
   }
   
   sub responseType {
       my $self = shift;
       my $part = shift;
   
       $self->extractParts();
       return $self->{RESPONSE_TYPE}->{$part};
   }
   
   sub responseIds {
     my $self = shift;      my $self = shift;
     my $part = shift;      my $part = shift;
   
     $self->extractParts();      $self->extractParts();
     return $self->{PART_TYPE}->{$part};      return $self->{RESPONSE_IDS}->{$part};
 }  }
   
 # Private function: Extracts the parts information, both part names and  # Private function: Extracts the parts information, both part names and
 # part types, and saves it  # part types, and saves it. 
 sub extractParts {   sub extractParts { 
     my $self = shift;      my $self = shift;
           
Line 3174  sub extractParts { Line 3360  sub extractParts {
                   
         my @sortedParts = sort keys %parts;          my @sortedParts = sort keys %parts;
         $self->{PARTS} = \@sortedParts;          $self->{PARTS} = \@sortedParts;
   
           my %responseIdHash;
           my %responseTypeHash;
   
   
           # Init the responseIdHash
           foreach (@{$self->{PARTS}}) {
               $responseIdHash{$_} = [];
           }
   
           # Now, the unfortunate thing about this is that parts, part name, and
           # response if are delimited by underscores, but both the part
           # name and response id can themselves have underscores in them.
           # 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
           # to construct ambiguous situations.
           foreach (split /,/, $metadata) {
               if ($_ =~ /^([a-zA-Z]+)response_(.*)/) {
                   my $responseType = $1;
                   my $partStuff = $2;
                   my $partIdSoFar = '';
                   my @partChunks = split /_/, $partStuff;
                   my $i = 0;
   
                   for ($i = 0; $i < scalar(@partChunks); $i++) {
                       if ($partIdSoFar) { $partIdSoFar .= '_'; }
                       $partIdSoFar .= $partChunks[$i];
                       if ($parts{$partIdSoFar}) {
                           my @otherChunks = @partChunks[$i+1..$#partChunks];
                           my $responseId = join('_', @otherChunks);
                           push @{$responseIdHash{$partIdSoFar}}, $responseId;
                           $responseTypeHash{$partIdSoFar} = $responseType;
                           last;
                       }
                   }
               }
           }
   
           $self->{RESPONSE_IDS} = \%responseIdHash;
           $self->{RESPONSE_TYPES} = \%responseTypeHash;
     }      }
   
     return;      return;
Line 3392  B<Composite Status> Line 3618  B<Composite Status>
 Along with directly returning the date or completion status, the  Along with directly returning the date or completion status, the
 resource object includes a convenience function B<status>() that will  resource object includes a convenience function B<status>() that will
 combine the two status tidbits into one composite status that can  combine the two status tidbits into one composite status that can
 represent the status of the resource as a whole. The precise logic is  represent the status of the resource as a whole. This method represents
   the concept of the thing we want to display to the user on the nav maps
   screen, which is a combination of completion and open status. The precise logic is
 documented in the comments of the status method. The following results  documented in the comments of the status method. The following results
 may be returned, all available as methods on the resource object  may be returned, all available as methods on the resource object
 ($res->NETWORK_FAILURE): In addition to the return values that match  ($res->NETWORK_FAILURE): In addition to the return values that match
Line 3535  sub status { Line 3763  sub status {
 }  }
   
 =pod  =pod
   
   B<Completable>
   
   The completable method represents the concept of I<whether the student can
   currently do the problem>. If the student can do the problem, which means
   that it is open, there are tries left, and if the problem is manually graded
   or the grade is suppressed via problemstatus, the student has not tried it
   yet, then the method returns 1. Otherwise, it returns 0, to indicate that 
   either the student has tried it and there is no feedback, or that for
   some reason it is no longer completable (not open yet, successfully completed,
   out of tries, etc.). As an example, this is used as the filter for the
   "Uncompleted Homework" option for the nav maps.
   
   If this does not quite meet your needs, do not fiddle with it (unless you are
   fixing it to better match the student's conception of "completable" because
   it's broken somehow)... make a new method.
   
   =cut
   
   sub completable {
       my $self = shift;
       if (!$self->is_problem()) { return 0; }
       my $partCount = $self->countParts();
   
       foreach my $part (@{$self->parts()}) {
           if ($part eq '0' && $partCount != 1) { next; }
           my $status = $self->status($part);
           # "If any of the parts are open, or have tries left (implies open),
           # and it is not "attempted" (manually graded problem), it is
           # not "complete"
           if (!(($status == OPEN() || $status == TRIES_LEFT()) 
                 && $self->getCompletionStatus($part) != ATTEMPTED()
                 && $status != ANSWER_SUBMITTED())) {
               return 0;
           }
       }
           
       # If all the parts were complete, so was this problem.
       return 1;
   }
   
   =pod
   
 =head2 Resource/Nav Map Navigation  =head2 Resource/Nav Map Navigation
   

Removed from v.1.186  
changed lines
  Added in v.1.198


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