Diff for /loncom/interface/lonnavmaps.pm between versions 1.183 and 1.203

version 1.183, 2003/04/25 18:54:36 version 1.203, 2003/06/13 13:58:06
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 71  my %statusIconMap = Line 72  my %statusIconMap =
       $resObj->TRIES_LEFT         => 'navmap.open.gif',        $resObj->TRIES_LEFT         => 'navmap.open.gif',
       $resObj->INCORRECT          => 'navmap.wrong.gif',        $resObj->INCORRECT          => 'navmap.wrong.gif',
       $resObj->OPEN               => 'navmap.open.gif',        $resObj->OPEN               => 'navmap.open.gif',
       $resObj->ATTEMPTED          => 'navmap.open.gif' );        $resObj->ATTEMPTED          => 'navmap.ellipsis.gif',
         $resObj->ANSWER_SUBMITTED   => '' );
   
 my %iconAltTags =   my %iconAltTags = 
     ( 'navmap.correct.gif' => 'Correct',      ( 'navmap.correct.gif' => 'Correct',
Line 199  sub real_handler { Line 201  sub real_handler {
         }          }
     }      }
   
     # renderer call      my $jumpToFirstHomework = 0;
     my $render = render({ 'cols' => [0,1,2,3],      # Check to see if the student is jumping to next open, do-able problem
                           'url' => '/adm/navmaps',      if ($ENV{QUERY_STRING} eq 'jumpToFirstHomework') {
                           'navmap' => $navmap,          $jumpToFirstHomework = 1;
                           'suppressNavmap' => 1,          # Find the next homework problem that they can do.
                           'r' => $r});          my $iterator = $navmap->getIterator(undef, undef, undef, 1);
           my $depth = 1;
           $iterator->next();
           my $curRes = $iterator->next();
           my $foundDoableProblem = 0;
           my $problemRes;
           
           while ($depth > 0 && !$foundDoableProblem) {
               if ($curRes == $iterator->BEGIN_MAP()) { $depth++; }
               if ($curRes == $iterator->END_MAP()) { $depth--; }
   
               if (ref($curRes) && $curRes->is_problem()) {
                   my $status = $curRes->status();
                   if ($curRes->completable()) {
                       $problemRes = $curRes;
                       $foundDoableProblem = 1;
   
                       # Pop open all previous maps
                       my $stack = $iterator->getStack();
                       pop @$stack; # last resource in the stack is the problem
                                    # itself, which we don't need in the map stack
                       my @mapPcs = map {$_->map_pc()} @$stack;
                       $ENV{'form.filter'} = join(',', @mapPcs);
   
                       # Mark as both "here" and "jump"
                       $ENV{'form.postsymb'} = $curRes->symb();
                   }
               }
           } continue {
               $curRes = $iterator->next();
           }
   
           # If we found no problems, print a note to that effect.
           if (!$foundDoableProblem) {
               $r->print("<font size='+2'>All homework assignments have been completed.</font><br /><br />");
           }
       } else {
           $r->print("<a href='navmaps?jumpToFirstHomework'>" .
                     "Go To My First Homework Problem</a><br />");
       }
   
       my $suppressEmptySequences = 0;
       my $filterFunc = undef;
       my $resource_no_folder_link = 0;
   
       # Display only due homework.
       my $showOnlyHomework = 0;
       if ($ENV{QUERY_STRING} eq 'showOnlyHomework') {
           $showOnlyHomework = 1;
           $suppressEmptySequences = 1;
           $filterFunc = sub { my $res = shift; 
                               return $res->completable() || $res->is_map();
                           };
           $r->print("<p><font size='+2'>Uncompleted Homework</font></p>");
           $ENV{'form.filter'} = '';
           $ENV{'form.condition'} = 1;
    $resource_no_folder_link = 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,
          'resource_no_folder_link' => $resource_no_folder_link,
                          '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 300  sub getDescription { Line 382  sub getDescription {
         return "Excused by instructor";          return "Excused by instructor";
     }      }
     if ($status == $res->ATTEMPTED) {      if ($status == $res->ATTEMPTED) {
         return "Not yet graded.";          return "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 319  sub getDescription { Line 401  sub getDescription {
             return "No due date $triesString";              return "No due date $triesString";
         }          }
     }      }
       if ($status == $res->ANSWER_SUBMITTED) {
           return '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
Line 687  returns a true or false value. If true, Line 772  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 750  sub render_resource { Line 843  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 820  sub render_resource { Line 913  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 830  sub render_resource { Line 923  sub render_resource {
         $nonLinkedText .= ' (' . $resource->countParts() . ' parts)';          $nonLinkedText .= ' (' . $resource->countParts() . ' parts)';
     }      }
   
     if (!$params->{'resource_nolink'}) {      if (!$params->{'resource_nolink'} && $src !~ /^\/uploaded\// &&
    !$resource->is_map()) {
         $result .= "  $curMarkerBegin<a href='$link'>$title$partLabel</a>$curMarkerEnd $nonLinkedText</td>";          $result .= "  $curMarkerBegin<a 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 877  sub render_communication_status { Line 971  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 912  sub render_long_status { Line 1010  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 932  sub render_long_status { Line 1030  sub render_long_status {
         $result .= '(randomly select ' . $resource->randompick() .')';          $result .= '(randomly select ' . $resource->randompick() .')';
     }      }
           
     $result .= "&nbsp;</td>\n";  
       
     return $result;      return $result;
 }  }
   
Line 983  sub render { Line 1079  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 1079  sub render { Line 1188  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 1111  sub render { Line 1222  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 1173  sub render { Line 1282  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_map()) {
                       $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 1208  sub render { Line 1354  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_map() && $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 1229  sub render { Line 1381  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 1238  sub render { Line 1390  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 1265  sub render { Line 1417  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 1517  sub init { Line 1674  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 1634  object for that resource. This method, o Line 1791  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 1664  sub getBySymb { Line 1831  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 2144  sub new { Line 2319  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 2521  sub next { Line 2697  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 2544  You will probably never need to instanti Line 2745  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 2753  sub is_sequence { Line 2960  sub is_sequence {
 sub parmval {  sub parmval {
     my $self = shift;      my $self = shift;
     my $what = shift;      my $what = shift;
     my $part = shift || "0";      my $part = shift;
       if (!defined($part)) { 
           $part = '0'; 
       }
     return $self->{NAV_MAP}->parmval($part.'.'.$what, $self->symb());      return $self->{NAV_MAP}->parmval($part.'.'.$what, $self->symb());
 }  }
   
Line 2927  sub opendate { Line 3137  sub opendate {
     }      }
     return $self->parmval("opendate");      return $self->parmval("opendate");
 }  }
   sub problemstatus {
       (my $self, my $part) = @_;
       return $self->parmval("problemstatus", $part);
   }
 sub sig {  sub sig {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     return $self->parmval("sig", $part);      return $self->parmval("sig", $part);
Line 3027  sub getErrors { Line 3241  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 3047  might lead you to believe. Line 3273  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 3057  sub countParts { Line 3283  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;
 }  }
   
 # Private function: Extracts the parts information and saves it  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 $part = shift;
   
       $self->extractParts();
       return $self->{RESPONSE_IDS}->{$part};
   }
   
   # Private function: Extracts the parts information, both part names and
   # part types, and saves it. 
 sub extractParts {   sub extractParts { 
     my $self = shift;      my $self = shift;
           
Line 3086  sub extractParts { Line 3336  sub extractParts {
         if (!$metadata) {          if (!$metadata) {
             $self->{RESOURCE_ERROR} = 1;              $self->{RESOURCE_ERROR} = 1;
             $self->{PARTS} = [];              $self->{PARTS} = [];
               $self->{PART_TYPE} = {};
             return;              return;
         }          }
         foreach (split(/\,/,$metadata)) {          foreach (split(/\,/,$metadata)) {
Line 3107  sub extractParts { Line 3358  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 3310  sub queryRestoreHash { Line 3601  sub queryRestoreHash {
     my $self = shift;      my $self = shift;
     my $hashentry = shift;      my $hashentry = shift;
     my $part = shift;      my $part = shift;
     $part = "0" if (!defined($part));      $part = "0" if (!defined($part) || $part eq '');
     return $self->NETWORK_FAILURE if ($self->{NAV_MAP}->{NETWORK_FAILURE});      return $self->NETWORK_FAILURE if ($self->{NAV_MAP}->{NETWORK_FAILURE});
   
     $self->getReturnHash();      $self->getReturnHash();
Line 3325  B<Composite Status> Line 3616  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):  ($res->NETWORK_FAILURE): In addition to the return values that match
   the date or completion status, this function can return "ANSWER_SUBMITTED"
   if that problemstatus parameter value is set to No, suppressing the
   incorrect/correct feedback.
   
 =over 4  =over 4
   
Line 3387  The item is open and not yet tried. Line 3683  The item is open and not yet tried.
   
 The problem has been attempted.  The problem has been attempted.
   
   =item * B<ANSWER_SUBMITTED>:
   
   An answer has been submitted, but the student should not see it.
   
 =back  =back
   
 =cut  =cut
   
 sub TRIES_LEFT { return 10; }  sub TRIES_LEFT       { return 20; }
   sub ANSWER_SUBMITTED { return 21; }
   
 sub status {  sub status {
     my $self = shift;      my $self = shift;
Line 3406  sub status { Line 3707  sub status {
   
     if ($completionStatus == NETWORK_FAILURE) { return NETWORK_FAILURE; }      if ($completionStatus == NETWORK_FAILURE) { return NETWORK_FAILURE; }
   
       my $suppressFeedback = lc($self->parmval("problemstatus", $part)) eq 'no';
   
     # There are a few whole rows we can dispose of:      # There are a few whole rows we can dispose of:
     if ($completionStatus == CORRECT ||      if ($completionStatus == CORRECT ||
         $completionStatus == CORRECT_BY_OVERRIDE ) {          $completionStatus == CORRECT_BY_OVERRIDE ) {
         return CORRECT;           return $suppressFeedback? ANSWER_SUBMITTED : CORRECT; 
     }      }
   
     if ($completionStatus == ATTEMPTED) {      if ($completionStatus == ATTEMPTED) {
Line 3450  sub status { Line 3753  sub status {
         if ($self->tries($part) < $self->maxtries($part) || !$self->maxtries($part)) {          if ($self->tries($part) < $self->maxtries($part) || !$self->maxtries($part)) {
             return TRIES_LEFT;              return TRIES_LEFT;
         }          }
         return INCORRECT; # otherwise, return orange; student can't fix this          return $suppressFeedback ? ANSWER_SUBMITTED : INCORRECT; # otherwise, return orange; student can't fix this
     }      }
   
     # Otherwise, it's untried and open      # Otherwise, it's untried and open
Line 3458  sub status { Line 3761  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.183  
changed lines
  Added in v.1.203


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