--- loncom/interface/lonnavmaps.pm 2014/11/18 03:33:53 1.503 +++ loncom/interface/lonnavmaps.pm 2015/06/19 15:36:53 1.508 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Navigate Maps Handler # -# $Id: lonnavmaps.pm,v 1.503 2014/11/18 03:33:53 raeburn Exp $ +# $Id: lonnavmaps.pm,v 1.508 2015/06/19 15:36:53 damieng Exp $ # # Copyright Michigan State University Board of Trustees @@ -2117,7 +2117,7 @@ sub change_user { $self->{PARM_CACHE} = {}; my %parm_hash = {}; - foreach my $key (keys %big_hash) { + foreach my $key (keys(%big_hash)) { if ($key =~ /^param\./) { my $param_key = $key; $param_key =~ s/^param\.//; @@ -2190,7 +2190,7 @@ sub generate_email_discuss_status { my %lastread = &Apache::lonnet::dump('nohist_'.$cid.'_discuss', $self->{DOMAIN},$self->{USERNAME},'lastread'); my %lastreadtime = (); - foreach my $key (keys %lastread) { + foreach my $key (keys(%lastread)) { my $shortkey = $key; $shortkey =~ s/_lastread$//; $lastreadtime{$shortkey} = $lastread{$key}; @@ -2927,7 +2927,7 @@ resource appears multiple times in the c will be returned (useful for maps), unless the multiple parameter has been included, in which case all instances are returned in an array. -=item * B(map, filterFunc, recursive, bailout, showall): +=item * B(map, filterFunc, recursive, bailout, showall, noblockcheck): The map is a specification of a map to retreive the resources from, either as a url or as an object. The filterFunc is a reference to a @@ -2936,13 +2936,18 @@ true if the resource should be included, be. If recursive is true, the map will be recursively examined, otherwise it will not be. If bailout is true, the function will return as soon as it finds a resource, if false it will finish. If showall is -true it will not hide maps that contain nothing but one other map. By -default, the map is the top-level map of the course, filterFunc is a -function that always returns 1, recursive is true, bailout is false, -showall is false. The resources will be returned in a list containing -the resource objects for the corresponding resources, with B in the list; regardless of branching, -recursion, etc., it will be a flat list. +true it will not hide maps that contain nothing but one other map. The +noblockcheck arg is propagated to become the sixth arg in the call to +lonnet::allowed when checking a resource's availability during collection +of resources using the iterator. noblockcheck needs to be true if +retrieveResources() was called by a routine that itself was called by +lonnet::allowed, in order to avoid recursion. By default the map +is the top-level map of the course, filterFunc is a function that +always returns 1, recursive is true, bailout is false, showall is +false. The resources will be returned in a list containing the +resource objects for the corresponding resources, with B in the list; regardless of branching, recursion, etc., +it will be a flat list. Thus, this is suitable for cases where you don't want the structure, just a list of all resources. It is also suitable for finding out how @@ -3009,6 +3014,7 @@ sub retrieveResources { my $bailout = shift; if (!defined($bailout)) { $bailout = 0; } my $showall = shift; + my $noblockcheck = shift; # Create the necessary iterator. if (!ref($map)) { # assume it's a url of a map. $map = $self->getResourceByUrl($map); @@ -3038,7 +3044,7 @@ sub retrieveResources { # Run down the iterator and collect the resources. my $curRes; - while ($curRes = $it->next()) { + while ($curRes = $it->next(undef,$noblockcheck)) { if (ref($curRes)) { if (!&$filterFunc($curRes)) { next; @@ -3189,7 +3195,14 @@ Note that inside of the loop, it's frequ resource objects will be references, and any non-references will be the tokens described above. -Also note there is some old code floating around that trys to track +The next() routine can take two (optional) arguments: +closeAllPages - if true will not recurse down a .page +noblockcheck - passed to browsePriv() for passing as sixth arg to +call to lonnet::allowed. This needs to be set if retrieveResources +was already called from another routine called within lonnet::allowed, +so as to prevent recursion. + +Also note there is some old code floating around that tries to track the depth of the iterator to see when it's done; do not copy that code. It is difficult to get right and harder to understand than this. They should be migrated to this new style. @@ -3365,6 +3378,7 @@ sub new { sub next { my $self = shift; my $closeAllPages=shift; + my $noblockcheck = shift; if ($self->{FINISHED}) { return END_ITERATOR(); } @@ -3514,7 +3528,7 @@ sub next { # If this is a blank resource, don't actually return it. # Should you ever find you need it, make sure to add an option to the code # that you can use; other things depend on this behavior. - my $browsePriv = $self->{HERE}->browsePriv(); + my $browsePriv = $self->{HERE}->browsePriv($noblockcheck); if (!$self->{HERE}->src() || (!($browsePriv eq 'F') && !($browsePriv eq '2')) ) { return $self->next($closeAllPages); @@ -3846,9 +3860,12 @@ sub new { # about this resource in. Not used by the resource object # directly. $self->{DATA} = {}; - + bless($self); + # This is a speed optimization, to avoid calling symb() too often. + $self->{SYMB} = $self->symb(); + return $self; } @@ -3960,8 +3977,8 @@ sub src { } sub shown_symb { my $self=shift; - if ($self->encrypted()) {return &Apache::lonenc::encrypted($self->symb());} - return $self->symb(); + if ($self->encrypted()) {return &Apache::lonenc::encrypted($self->{SYMB});} + return $self->{SYMB}; } sub id { my $self=shift; @@ -3982,7 +3999,7 @@ sub symb { } sub wrap_symb { my $self = shift; - return $self->{NAV_MAP}->wrap_symb($self->symb()); + return $self->{NAV_MAP}->wrap_symb($self->{SYMB}); } sub title { my $self=shift; @@ -4205,7 +4222,7 @@ sub parmval { if (!defined($part)) { $part = '0'; } - return $self->{NAV_MAP}->parmval($part.'.'.$what, $self->symb()); + return $self->{NAV_MAP}->parmval($part.'.'.$what, $self->{SYMB}); } =pod @@ -4458,25 +4475,25 @@ sub awarded { my $self = shift; my $part = shift; $self->{NAV_MAP}->get_user_data(); if (!defined($part)) { $part = '0'; } - return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.awarded'}; + return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$part.'.awarded'}; } sub taskversion { my $self = shift; my $part = shift; $self->{NAV_MAP}->get_user_data(); if (!defined($part)) { $part = '0'; } - return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.version'}; + return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$part.'.version'}; } sub taskstatus { my $self = shift; my $part = shift; $self->{NAV_MAP}->get_user_data(); if (!defined($part)) { $part = '0'; } - return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$self->taskversion($part).'.'.$part.'.status'}; + return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$self->taskversion($part).'.'.$part.'.status'}; } sub solved { my $self = shift; my $part = shift; $self->{NAV_MAP}->get_user_data(); if (!defined($part)) { $part = '0'; } - return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.solved'}; + return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$part.'.solved'}; } sub checkedin { my $self = shift; my $part = shift; @@ -4484,9 +4501,9 @@ sub checkedin { if (!defined($part)) { $part = '0'; } if ($self->is_task()) { my $version = $self->taskversion($part); - return ($self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$version .'.'.$part.'.checkedin'},$self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$version .'.'.$part.'.checkedin.slot'}); + return ($self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$version .'.'.$part.'.checkedin'},$self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$version .'.'.$part.'.checkedin.slot'}); } else { - return ($self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.checkedin'},$self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.checkedin.slot'}); + return ($self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$part.'.checkedin'},$self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$part.'.checkedin.slot'}); } } # this should work exactly like the copy in lonhomework.pm @@ -4578,7 +4595,7 @@ sub weight { my $self = shift; my $part = shift; if (!defined($part)) { $part = '0'; } my $weight = &Apache::lonnet::EXT('resource.'.$part.'.weight', - $self->symb(), $self->{DOMAIN}, + $self->{SYMB}, $self->{DOMAIN}, $self->{USERNAME}, $env{'request.course.sec'}); return $weight; @@ -4607,7 +4624,7 @@ sub getReturnHash { my $self = shift; if (!defined($self->{RETURN_HASH})) { - my %tmpHash = &Apache::lonnet::restore($self->symb(),undef,$self->{DOMAIN},$self->{USERNAME}); + my %tmpHash = &Apache::lonnet::restore($self->{SYMB},undef,$self->{DOMAIN},$self->{USERNAME}); $self->{RETURN_HASH} = \%tmpHash; } } @@ -4672,23 +4689,23 @@ and use the link as appropriate. sub hasDiscussion { my $self = shift; - return $self->{NAV_MAP}->hasDiscussion($self->symb()); + return $self->{NAV_MAP}->hasDiscussion($self->{SYMB}); } sub last_post_time { my $self = shift; - return $self->{NAV_MAP}->last_post_time($self->symb()); + return $self->{NAV_MAP}->last_post_time($self->{SYMB}); } sub discussion_info { my ($self,$filter) = @_; - return $self->{NAV_MAP}->discussion_info($self->symb(),$filter); + return $self->{NAV_MAP}->discussion_info($self->{SYMB},$filter); } sub getFeedback { my $self = shift; my $source = $self->src(); - my $symb = $self->symb(); + my $symb = $self->{SYMB}; if ($source =~ /^\/res\//) { $source = substr $source, 5; } return $self->{NAV_MAP}->getFeedback($symb,$source); } @@ -4696,7 +4713,7 @@ sub getFeedback { sub getErrors { my $self = shift; my $source = $self->src(); - my $symb = $self->symb(); + my $symb = $self->{SYMB}; if ($source =~ /^\/res\//) { $source = substr $source, 5; } return $self->{NAV_MAP}->getErrors($symb,$source); } @@ -4846,7 +4863,7 @@ sub extractParts { if ($partorder) { my @parts; for my $part (split (/,/,$partorder)) { - if (!Apache::loncommon::check_if_partid_hidden($part, $self->symb())) { + if (!Apache::loncommon::check_if_partid_hidden($part, $self->{SYMB})) { push @parts, $part; $parts{$part} = 1; } @@ -4864,17 +4881,17 @@ sub extractParts { my $part = $1; # This floods the logs if it blows up if (defined($parts{$part})) { - &Apache::lonnet::logthis("$part multiply defined in metadata for " . $self->symb()); + &Apache::lonnet::logthis("$part multiply defined in metadata for " . $self->{SYMB}); } # check to see if part is turned off. - if (!Apache::loncommon::check_if_partid_hidden($part, $self->symb())) { + if (!Apache::loncommon::check_if_partid_hidden($part, $self->{SYMB})) { $parts{$part} = 1; } } } - my @sortedParts = sort keys %parts; + my @sortedParts = sort(keys(%parts)); $self->{PARTS} = \@sortedParts; } @@ -4895,13 +4912,13 @@ sub extractParts { # 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 my $data (split /,/, $metadata) { + foreach my $data (split(/,/, $metadata)) { if ($data =~ /^([a-zA-Z]+)response_(.*)/ || $data =~ /^(Task)_(.*)/) { my $responseType = $1; my $partStuff = $2; my $partIdSoFar = ''; - my @partChunks = split /_/, $partStuff; + my @partChunks = split(/_/, $partStuff); my $i = 0; for ($i = 0; $i < scalar(@partChunks); $i++) { if ($partIdSoFar) { $partIdSoFar .= '_'; } @@ -5374,7 +5391,7 @@ sub status { sub check_for_slot { my $self = shift; my $part = shift; - my $symb = $self->symb(); + my $symb = $self->{SYMB}; my ($use_slots,$available,$availablestudent) = $self->slot_control($part); if (($use_slots ne '') && ($use_slots !~ /^\s*no\s*$/i)) { my @slots = (split(/:/,$availablestudent),split(/:/,$available)); @@ -5610,6 +5627,39 @@ sub completable { =pod +B + +The answerable method differs from the completable method in its handling of problem parts +for which feedback on correctness is suppressed, but the student still has tries left, and +the problem part is not past due, (i.e., the student could submit a different answer if +he/she so chose). For that case completable will return 0, whereas answerable will return 1. + +=cut + +sub answerable { + 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 ($self->getCompletionStatus($part) == ATTEMPTED() || + $self->getCompletionStatus($part) == CREDIT_ATTEMPTED() || + $status == ANSWER_SUBMITTED() ) { + if ($self->tries($part) < $self->maxtries($part) || !$self->maxtries($part)) { + return 1; + } + } + if ($status == OPEN() || $status == TRIES_LEFT() || $status == NETWORK_FAILURE()) { + return 1; + } + } + # None of the parts were answerable, so neither is this problem. + return 0; +} + +=pod + =head2 Resource/Nav Map Navigation =over 4 @@ -5645,7 +5695,7 @@ sub getPrevious { my $self = shift; my @branches; my $from = $self->from(); - foreach my $branch ( split /,/, $from) { + foreach my $branch ( split(/,/, $from)) { my $choice = $self->{NAV_MAP}->getById($branch); my $prev = $choice->comesfrom(); $prev = $self->{NAV_MAP}->getById($prev); @@ -5657,12 +5707,14 @@ sub getPrevious { sub browsePriv { my $self = shift; + my $noblockcheck = shift; if (defined($self->{BROWSE_PRIV})) { return $self->{BROWSE_PRIV}; } $self->{BROWSE_PRIV} = &Apache::lonnet::allowed('bre',$self->src(), - $self->symb()); + $self->{SYMB},undef, + undef,$noblockcheck); } =pod