version 1.188, 2003/05/14 18:33:28
|
version 1.195, 2003/06/10 15:45:16
|
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 962 sub render_long_status {
|
Line 1001 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 1021 sub render_long_status {
|
$result .= '(randomly select ' . $resource->randompick() .')'; |
$result .= '(randomly select ' . $resource->randompick() .')'; |
} |
} |
|
|
$result .= " </td>\n"; |
$result .= " - $part </td>\n"; |
|
|
return $result; |
return $result; |
} |
} |
Line 1033 sub render {
|
Line 1072 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 1181 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 1215 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 1275 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 1347 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 1374 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 1383 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 1410 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; |
} |
} |
|
|
Line 1326 sub render {
|
Line 1421 sub render {
|
# 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 1588 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 1669 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 1786 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 1826 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 2314 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 2692 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 3090 sub getErrors {
|
Line 3236 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): |
=item * B<responseType>($part): |
|
|
Returns the response type of the part, without the word "response" on the |
Returns the response type of the part, without the word "response" on the |
end. Example return values: 'string', 'essay', 'numeric', etc. |
end. Example return values: 'string', 'essay', 'numeric', etc. |
|
|
=item * B<responseId>($part): |
=item * B<responseIds>($part): |
|
|
Retreives the response ID for the given part, which may be an empty string. |
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 3119 Retreives the response ID for the given
|
Line 3268 Retreives the response ID for the given
|
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 3129 sub countParts {
|
Line 3278 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 multipart { |
|
my $self = shift; |
|
return $self->countParts() > 1; |
} |
} |
|
|
sub responseType { |
sub responseType { |
Line 3149 sub responseType {
|
Line 3305 sub responseType {
|
return $self->{RESPONSE_TYPE}->{$part}; |
return $self->{RESPONSE_TYPE}->{$part}; |
} |
} |
|
|
sub responseId { |
sub responseIds { |
my $self = shift; |
my $self = shift; |
my $part = shift; |
my $part = shift; |
|
|
Line 3201 sub extractParts {
|
Line 3357 sub extractParts {
|
my %responseIdHash; |
my %responseIdHash; |
my %responseTypeHash; |
my %responseTypeHash; |
|
|
|
|
|
# Init the responseIdHash |
|
foreach (@{$self->{PARTS}}) { |
|
$responseIdHash{$_} = []; |
|
} |
|
|
# 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 if 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. |
Line 3221 sub extractParts {
|
Line 3383 sub extractParts {
|
if ($parts{$partIdSoFar}) { |
if ($parts{$partIdSoFar}) { |
my @otherChunks = @partChunks[$i+1..$#partChunks]; |
my @otherChunks = @partChunks[$i+1..$#partChunks]; |
my $responseId = join('_', @otherChunks); |
my $responseId = join('_', @otherChunks); |
if (!defined($responseIdHash{$partIdSoFar})) { |
|
$responseIdHash{$partIdSoFar} = []; |
|
} |
|
push @{$responseIdHash{$partIdSoFar}}, $responseId; |
push @{$responseIdHash{$partIdSoFar}}, $responseId; |
$responseTypeHash{$partIdSoFar} = $responseType; |
$responseTypeHash{$partIdSoFar} = $responseType; |
last; |
last; |
Line 3452 B<Composite Status>
|
Line 3611 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 3595 sub status {
|
Line 3756 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 |
|
|