";
}
sub render_quick_status {
@@ -1256,56 +1164,62 @@ sub render_quick_status {
$params->{'multipart'} && $part eq "0";
my $link = $params->{"resourceLink"};
- my $target;
- if ($env{'environment.remotenavmap'} eq 'on') {
- $target=' target="loncapaclient" ';
- }
- my $linkopen = "";
+ my $linkopen = "";
my $linkclose = "";
-
+
+ $result .= '
';
if ($resource->is_problem() &&
!$firstDisplayed) {
-
my $icon = $statusIconMap{$resource->simpleStatus($part)};
my $alt = $iconAltTags{$icon};
if ($icon) {
my $location=
&Apache::loncommon::lonhttpdurl("/adm/lonIcons/$icon");
- $result .= "
' ."\n";
+ $result.=&Apache::loncommon::start_data_table("LC_tableOfContent");
+
my $res = "Apache::lonnavmaps::resource";
my %condenseStatuses =
( $res->NETWORK_FAILURE => 1,
$res->NOTHING_SET => 1,
$res->CORRECT => 1 );
- my @backgroundColors = ("#FFFFFF", "#F6F6F6");
# Shared variables
$args->{'counter'} = 0; # counts the rows
$args->{'indentLevel'} = 0;
$args->{'isNewBranch'} = 0;
- $args->{'condensed'} = 0;
- my $location=
- &Apache::loncommon::lonhttpdurl("/adm/lonIcons/whitespace1.gif");
- $args->{'indentString'} = setDefault($args->{'indentString'}, "");
+ $args->{'condensed'} = 0;
+
+ my $location = &Apache::loncommon::lonhttpdurl("/adm/lonIcons/whitespace_21.gif");
+ $args->{'indentString'} = setDefault($args->{'indentString'}, "");
$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);
- my $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;
- }
+ # If we're suppressing empty sequences, look for them here.
+ # We also do this even if $args->{'suppressEmptySequences'}
+ # is not true, so we can hide empty sequences for which the
+ # hiddenresource parameter is set to yes (at map level), or
+ # mark as hidden for users who have $userCanSeeHidden.
+ # Use DFS for speed, since structure actually doesn't matter,
+ # except what map has what resources.
+
+ my $dfsit = Apache::lonnavmaps::DFSiterator->new($navmap,
+ $it->{FIRST_RESOURCE},
+ $it->{FINISH_RESOURCE},
+ {}, undef, 1);
+
+ my $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.
+ } elsif ($curRes->src()) {
+ # 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();
}
+ } continue {
+ $curRes = $dfsit->next();
}
my $displayedJumpMarker = 0;
@@ -1798,6 +1730,28 @@ END
undef($args->{'sort'});
}
+ # Determine if page will be served with https in case
+ # it contains a syllabus which uses an external URL
+ # which points at an http site.
+
+ my ($is_ssl,$cdom,$cnum,$hostname);
+ if ($ENV{'SERVER_PORT'} == 443) {
+ $is_ssl = 1;
+ if ($r) {
+ $hostname = $r->hostname();
+ } else {
+ $hostname = $ENV{'SERVER_NAME'};
+ }
+ }
+ if ($env{'request.course.id'}) {
+ $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+ $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
+ }
+
+ my $inhibitmenu;
+ if ($args->{'modalLink'}) {
+ $inhibitmenu = '&inhibitmenu=yes';
+ }
while (1) {
if ($args->{'sort'}) {
@@ -1833,9 +1787,21 @@ END
}
# 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;
+ $args->{'mapHidden'} = 0;
+ if (($curRes->is_map()) && (!$curRes->{DATA}->{HAS_VISIBLE_CHILDREN})) {
+ if ($args->{'suppressEmptySequences'}) {
+ next;
+ } else {
+ my $mapname = &Apache::lonnet::declutter($curRes->src());
+ $mapname = &Apache::lonnet::deversion($mapname);
+ if (lc($navmap->get_mapparam(undef,$mapname,"0.hiddenresource")) eq 'yes') {
+ if ($userCanSeeHidden) {
+ $args->{'mapHidden'} = 1;
+ } else {
+ next;
+ }
+ }
+ }
}
# If we're suppressing navmaps and this is a navmap, continue on
@@ -1919,19 +1885,42 @@ END
$stack=$it->getStack();
}
($src,$symb,$anchor)=getLinkForResource($stack);
+ my $srcHasQuestion = $src =~ /\?/;
+ if ($env{'request.course.id'}) {
+ if (($is_ssl) && ($src =~ m{^\Q/public/$cdom/$cnum/syllabus\E($|\?)}) &&
+ ($env{'course.'.$env{'request.course.id'}.'.externalsyllabus'} =~ m{^http://})) {
+ unless (&Apache::lonnet::uses_sts()) {
+ if ($hostname ne '') {
+ $src = 'http://'.$hostname.$src;
+ }
+ $src .= ($srcHasQuestion? '&' : '?') . 'usehttp=1';
+ $srcHasQuestion = 1;
+ }
+ } elsif (($is_ssl) && ($src =~ m{^\Q/adm/wrapper/ext/\E(?!https:)})) {
+ unless (&Apache::lonnet::uses_sts()) {
+ if ($hostname ne '') {
+ $src = 'http://'.$hostname.$src;
+ }
+ $src .= ($srcHasQuestion? '&' : '?') . 'usehttp=1';
+ $srcHasQuestion = 1;
+ }
+ }
+ }
if (defined($anchor)) { $anchor='#'.$anchor; }
- my $srcHasQuestion = $src =~ /\?/;
- $args->{"resourceLink"} = $src.
- ($srcHasQuestion?'&':'?') .
- 'symb=' . &Apache::lonnet::escape($symb).$anchor;
+ if (($args->{'caller'} eq 'sequence') && ($curRes->is_map())) {
+ $args->{"resourceLink"} = $src.($srcHasQuestion?'&':'?') .'navmap=1';
+ } else {
+ $args->{"resourceLink"} = $src.
+ ($srcHasQuestion?'&':'?') .
+ 'symb=' . &escape($symb).$inhibitmenu.$anchor;
+ }
}
# Now, we've decided what parts to show. Loop through them and
# show them.
foreach my $part (@parts) {
$rownum ++;
- my $backgroundColor = $backgroundColors[$rownum % scalar(@backgroundColors)];
- $result .= "
\n";
+ $result .= &Apache::loncommon::start_data_table_row();
# Set up some data about the parts that the cols might want
my $filter = $it->{FILTER};
@@ -1952,12 +1941,12 @@ END
$currentJumpDelta) {
# Jam the anchor after the
\n";
+ $result .= &Apache::loncommon::end_data_table_row();
$args->{'isNewBranch'} = 0;
}
@@ -1976,6 +1965,8 @@ END
}
}
}
+
+ $result.=&Apache::loncommon::end_data_table();
# Print out the part that jumps to #curloc if it exists
# delay needed because the browser is processing the jump before
@@ -1985,16 +1976,13 @@ END
# it's quite likely this might fix other browsers, too, and
# certainly won't hurt anything.
if ($displayedJumpMarker) {
- $result .= "
-";
+");
}
- $result .= "
";
-
if ($r) {
$r->print($result);
$result = "";
@@ -2010,42 +1998,75 @@ sub add_linkitem {
$$linkitems{$name}{'text'}=&mt($text);
}
-sub show_linkitems {
- my ($linkitems)=@_;
- my @linkorder = ("blank","launchnav","closenav","firsthomework",
- "everything","uncompleted","changefolder","clearbubbles");
-
- my $result .= (<
-
- '."\n";
-
return $result;
}
1;
+
+
+
+
+
+
+
+
package Apache::lonnavmaps::navmap;
=pod
@@ -2115,12 +2136,21 @@ See iterator documentation below.
use strict;
use GDBM_File;
use Apache::lonnet;
+use LONCAPA;
sub new {
# magic invocation to create a class instance
my $proto = shift;
my $class = ref($proto) || $proto;
my $self = {};
+ bless($self); # So we can call change_user if necessary
+
+ $self->{USERNAME} = shift || $env{'user.name'};
+ $self->{DOMAIN} = shift || $env{'user.domain'};
+ $self->{CODE} = shift;
+ $self->{NOHIDE} = shift;
+
+
# Resource cache stores navmap resources as we reference them. We generate
# them on-demand so we don't pay for creating resources unless we use them.
@@ -2130,38 +2160,105 @@ sub new {
# failed
$self->{NETWORK_FAILURE} = 0;
- # tie the nav hash
+ # We can only tie the nav hash as done below if the username/domain
+ # match the env one. Otherwise change_user does everything we need...since we can't
+ # assume there are course hashes for the specific requested user:domain
+ # Note: change_user is also called if we need the nav hash when printing CODEd
+ # assignments or printing an exam, in which the enclosing folder for the items in
+ # the exam has hidden set.
+ #
- my %navmaphash;
- my %parmhash;
- my $courseFn = $env{"request.course.fn"};
- if (!(tie(%navmaphash, 'GDBM_File', "${courseFn}.db",
- &GDBM_READER(), 0640))) {
- return undef;
+ if (($self->{USERNAME} eq $env{'user.name'}) && ($self->{DOMAIN} eq $env{'user.domain'}) &&
+ !$self->{CODE} && !$self->{NOHIDE}) {
+
+ # tie the nav hash
+
+ my %navmaphash;
+ my %parmhash;
+ my $courseFn = $env{"request.course.fn"};
+ if (!(tie(%navmaphash, 'GDBM_File', "${courseFn}.db",
+ &GDBM_READER(), 0640))) {
+ return undef;
+ }
+
+ if (!(tie(%parmhash, 'GDBM_File', "${courseFn}_parms.db",
+ &GDBM_READER(), 0640)))
+ {
+ untie %{$self->{PARM_HASH}};
+ return undef;
+ }
+
+ $self->{NAV_HASH} = \%navmaphash;
+ $self->{PARM_HASH} = \%parmhash;
+ $self->{PARM_CACHE} = {};
+ } else {
+ $self->change_user($self->{USERNAME}, $self->{DOMAIN}, $self->{CODE}, $self->{NOHIDE});
}
+
+ return $self;
+}
+
+#
+# In some instances it is useful to be able to dynamically change the
+# username/domain associated with a navmap (e.g. to navigate for someone
+# else besides the current user...if sufficiently privileged.
+# Parameters:
+# user - New user.
+# domain- Domain the user belongs to.
+# code - Anonymous CODE in use.
+# Implicit inputs:
+#
+sub change_user {
+ my $self = shift;
+ $self->{USERNAME} = shift;
+ $self->{DOMAIN} = shift;
+ $self->{CODE} = shift;
+ $self->{NOHIDE} = shift;
+
+ # If the hashes are already tied make sure to break that bond:
+
+ untie %{$self->{NAV_HASH}};
+ untie %{$self->{PARM_HASH}};
+
+ # The assumption is that we have to
+ # use lonmap here to re-read the hash and from it reconstruct
+ # new big and parameter hashes. An implicit assumption at this time
+ # is that the course file is probably not created locally yet
+ # an that we will therefore just read without tying.
+
+ my ($cdom, $cnum) = split(/\_/, $env{'request.course.id'});
+
+ my %big_hash;
+ &Apache::lonmap::loadmap($cnum, $cdom, $self->{USERNAME}, $self->{DOMAIN}, $self->{CODE}, $self->{NOHIDE}, \%big_hash);
+ $self->{NAV_HASH} = \%big_hash;
+
+
+
+ # Now clear the parm cache and reconstruct the parm hash fromt he big_hash
+ # param.xxxx keys.
+
+ $self->{PARM_CACHE} = {};
- if (!(tie(%parmhash, 'GDBM_File', "${courseFn}_parms.db",
- &GDBM_READER(), 0640)))
- {
- untie %{$self->{PARM_HASH}};
- return undef;
+ my %parm_hash = {};
+ foreach my $key (keys(%big_hash)) {
+ if ($key =~ /^param\./) {
+ my $param_key = $key;
+ $param_key =~ s/^param\.//;
+ $parm_hash{$param_key} = $big_hash{$key};
+ }
}
- $self->{NAV_HASH} = \%navmaphash;
- $self->{PARM_HASH} = \%parmhash;
- $self->{PARM_CACHE} = {};
+ $self->{PARM_HASH} = \%parm_hash;
- bless($self);
-
- return $self;
}
sub generate_course_user_opt {
my $self = shift;
if ($self->{COURSE_USER_OPT_GENERATED}) { return; }
- my $uname=$env{'user.name'};
- my $udom=$env{'user.domain'};
+ my $uname=$self->{USERNAME};
+ my $udom=$self->{DOMAIN};
+
my $cid=$env{'request.course.id'};
my $cdom=$env{'course.'.$cid.'.domain'};
my $cnum=$env{'course.'.$cid.'.num'};
@@ -2195,6 +2292,8 @@ sub generate_course_user_opt {
return;
}
+
+
sub generate_email_discuss_status {
my $self = shift;
my $symb = shift;
@@ -2204,7 +2303,7 @@ sub generate_email_discuss_status {
my $cdom=$env{'course.'.$cid.'.domain'};
my $cnum=$env{'course.'.$cid.'.num'};
- my %emailstatus = &Apache::lonnet::dump('email_status');
+ my %emailstatus = &Apache::lonnet::dump('email_status',$self->{DOMAIN},$self->{USERNAME});
my $logoutTime = $emailstatus{'logout'};
my $courseLeaveTime = $emailstatus{'logout_'.$env{'request.course.id'}};
$self->{LAST_CHECK} = (($courseLeaveTime > $logoutTime) ?
@@ -2212,37 +2311,51 @@ sub generate_email_discuss_status {
my %discussiontime = &Apache::lonnet::dump('discussiontimes',
$cdom, $cnum);
my %lastread = &Apache::lonnet::dump('nohist_'.$cid.'_discuss',
- $env{'user.domain'},$env{'user.name'},'lastread');
+ $self->{DOMAIN},$self->{USERNAME},'lastread');
my %lastreadtime = ();
- foreach (keys %lastread) {
- my $key = $_;
- $key =~ s/_lastread$//;
- $lastreadtime{$key} = $lastread{$_};
+ foreach my $key (keys(%lastread)) {
+ my $shortkey = $key;
+ $shortkey =~ s/_lastread$//;
+ $lastreadtime{$shortkey} = $lastread{$key};
}
my %feedback=();
my %error=();
- my @keys = &Apache::lonnet::getkeys('nohist_email',$env{'user.domain'},
- $env{'user.name'});
+ my @keys = &Apache::lonnet::getkeys('nohist_email',$self->{DOMAIN},
+ $self->{USERNAME});
foreach my $msgid (@keys) {
if ((!$emailstatus{$msgid}) || ($emailstatus{$msgid} eq 'new')) {
- my $plain=
- &Apache::lonnet::unescape(&Apache::lonnet::unescape($msgid));
- if ($plain=~/ \[([^\]]+)\]\:/) {
- my $url=$1;
- if ($plain=~/\:Error \[/) {
- $error{$url}.=','.$msgid;
- } else {
- $feedback{$url}.=','.$msgid;
- }
- }
+ my ($sendtime,$shortsubj,$fromname,$fromdomain,$status,$fromcid,
+ $symb,$error) = &Apache::lonmsg::unpackmsgid(&LONCAPA::escape($msgid));
+ &Apache::lonenc::check_decrypt(\$symb);
+ if (($fromcid ne '') && ($fromcid ne $cid)) {
+ next;
+ }
+ if (defined($symb)) {
+ if (defined($error) && $error == 1) {
+ $error{$symb}.=','.$msgid;
+ } else {
+ $feedback{$symb}.=','.$msgid;
+ }
+ } else {
+ my $plain=
+ &LONCAPA::unescape(&LONCAPA::unescape($msgid));
+ if ($plain=~/ \[([^\]]+)\]\:/) {
+ my $url=$1;
+ if ($plain=~/\:Error \[/) {
+ $error{$url}.=','.$msgid;
+ } else {
+ $feedback{$url}.=','.$msgid;
+ }
+ }
+ }
}
}
- #url's of resources that have feedbacks
+ #symbs of resources that have feedbacks (will be urls pre-2.3)
$self->{FEEDBACK} = \%feedback;
- #or errors
+ #or errors (will be urls pre 2.3)
$self->{ERROR_MSG} = \%error;
$self->{DISCUSSION_TIME} = \%discussiontime;
$self->{EMAIL_STATUS} = \%emailstatus;
@@ -2257,8 +2370,8 @@ sub get_user_data {
# Retrieve performance data on problems
my %student_data = Apache::lonnet::currentdump($env{'request.course.id'},
- $env{'user.domain'},
- $env{'user.name'});
+ $self->{DOMAIN},
+ $self->{USERNAME});
$self->{STUDENT_DATA} = \%student_data;
$self->{RETRIEVED_USER_DATA} = 1;
@@ -2347,26 +2460,25 @@ sub last_post_time {
return $self->{DISCUSSION_TIME}->{$ressymb};
}
-sub unread_discussion {
+sub discussion_info {
my $self = shift;
my $symb = shift;
+ my $filter = shift;
$self->get_discussion_data();
my $ressymb = $self->wrap_symb($symb);
# keys used to store bulletinboard postings use 'unwrapped' symb.
- my $discsymb = $self->unwrap_symb($ressymb);
+ my $discsymb = &escape($self->unwrap_symb($ressymb));
my $version = $self->{DISCUSSION_DATA}{'version:'.$discsymb};
if (!$version) { return; }
my $prevread = $self->{LAST_READ}{$ressymb};
- my $unreadcount = 0;
+ my $count = 0;
my $hiddenflag = 0;
my $deletedflag = 0;
- my ($hidden,$deleted);
-
- my %subjects;
+ my ($hidden,$deleted,%info);
for (my $id=$version; $id>0; $id--) {
my $vkeys=$self->{DISCUSSION_DATA}{$id.':keys:'.$discsymb};
@@ -2382,18 +2494,24 @@ sub unread_discussion {
$deletedflag = 1;
}
} else {
- if (($hidden !~/\.$id\./) && ($deleted !~/\.$id\./)
- && $prevread < $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':timestamp'}) {
- $unreadcount++;
- $subjects{$unreadcount}=
- $id.': '.$self->{DISCUSSION_DATA}{$id.':'.$discsymb.':subject'};
- }
+ if (($hidden !~/\.$id\./) && ($deleted !~/\.$id\./)) {
+ if ($filter eq 'unread') {
+ if ($prevread >= $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':timestamp'}) {
+ next;
+ }
+ }
+ $count++;
+ $info{$count}{'subject'} =
+ $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':subject'};
+ $info{$count}{'id'} = $id;
+ $info{$count}{'timestamp'} = $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':timestamp'};
+ }
}
}
if (wantarray) {
- return ($unreadcount,\%subjects);
+ return ($count,%info);
}
- return $unreadcount
+ return $count;
}
sub wrap_symb {
@@ -2424,23 +2542,48 @@ sub unwrap_symb {
sub getFeedback {
my $self = shift;
my $symb = shift;
+ my $source = shift;
$self->generate_email_discuss_status();
if (!defined($self->{FEEDBACK})) { return ""; }
- return $self->{FEEDBACK}->{$symb};
+ my $feedback;
+ if ($self->{FEEDBACK}->{$symb}) {
+ $feedback = $self->{FEEDBACK}->{$symb};
+ if ($self->{FEEDBACK}->{$source}) {
+ $feedback .= ','.$self->{FEEDBACK}->{$source};
+ }
+ } else {
+ if ($self->{FEEDBACK}->{$source}) {
+ $feedback = $self->{FEEDBACK}->{$source};
+ }
+ }
+ return $feedback;
}
# Private method: Get the errors for that resource (by source).
sub getErrors {
my $self = shift;
+ my $symb = shift;
my $src = shift;
$self->generate_email_discuss_status();
if (!defined($self->{ERROR_MSG})) { return ""; }
- return $self->{ERROR_MSG}->{$src};
+
+ my $errors;
+ if ($self->{ERROR_MSG}->{$symb}) {
+ $errors = $self->{ERROR_MSG}->{$symb};
+ if ($self->{ERROR_MSG}->{$src}) {
+ $errors .= ','.$self->{ERROR_MSG}->{$src};
+ }
+ } else {
+ if ($self->{ERROR_MSG}->{$src}) {
+ $errors = $self->{ERROR_MSG}->{$src};
+ }
+ }
+ return $errors;
}
=pod
@@ -2457,7 +2600,7 @@ resource object.
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(map_pc):
+=item * B(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.
@@ -2466,7 +2609,7 @@ the given map. This is one of the proper
# The strategy here is to cache the resource objects, and only construct them
# as we use them. The real point is to prevent reading any more from the tied
-# hash then we have to, which should hopefully alleviate speed problems.
+# hash than we have to, which should hopefully alleviate speed problems.
sub getById {
my $self = shift;
@@ -2540,22 +2683,36 @@ sub finishResource {
# the actual lookup; parmval caches the results.
sub parmval {
my $self = shift;
- my ($what,$symb)=@_;
+ my ($what,$symb,$recurse)=@_;
my $hashkey = $what."|||".$symb;
-
+ my $cache = $self->{PARM_CACHE};
if (defined($self->{PARM_CACHE}->{$hashkey})) {
- return $self->{PARM_CACHE}->{$hashkey};
+ if (ref($self->{PARM_CACHE}->{$hashkey}) eq 'ARRAY') {
+ if (defined($self->{PARM_CACHE}->{$hashkey}->[0])) {
+ if (wantarray) {
+ return @{$self->{PARM_CACHE}->{$hashkey}};
+ } else {
+ return $self->{PARM_CACHE}->{$hashkey}->[0];
+ }
+ }
+ } else {
+ return $self->{PARM_CACHE}->{$hashkey};
+ }
}
-
- my $result = $self->parmval_real($what, $symb);
+ my $result = $self->parmval_real($what, $symb, $recurse);
$self->{PARM_CACHE}->{$hashkey} = $result;
- return $result;
+ if (wantarray) {
+ return @{$result};
+ }
+ return $result->[0];
}
+
sub parmval_real {
my $self = shift;
my ($what,$symb,$recurse) = @_;
+
# Make sure the {USER_OPT} and {COURSE_OPT} hashes are populated
$self->generate_course_user_opt();
@@ -2567,14 +2724,14 @@ sub parmval_real {
@cgrps = sort(@cgrps);
$cgroup = $cgrps[0];
}
- my $uname=$env{'user.name'};
- my $udom=$env{'user.domain'};
+ my $uname=$self->{USERNAME};
+ my $udom=$self->{DOMAIN};
- unless ($symb) { return ''; }
+ unless ($symb) { return ['']; }
my $result='';
my ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb);
-
+ $mapname = &Apache::lonnet::deversion($mapname);
# ----------------------------------------------------- Cascading lookup scheme
my $rwhat=$what;
$what=~s/^parameter\_//;
@@ -2584,66 +2741,72 @@ sub parmval_real {
my $mapparm=$mapname.'___(all).'.$what;
my $usercourseprefix=$cid;
+
+
my $grplevel=$usercourseprefix.'.['.$cgroup.'].'.$what;
my $grplevelr=$usercourseprefix.'.['.$cgroup.'].'.$symbparm;
my $grplevelm=$usercourseprefix.'.['.$cgroup.'].'.$mapparm;
+
my $seclevel= $usercourseprefix.'.['.$csec.'].'.$what;
my $seclevelr=$usercourseprefix.'.['.$csec.'].'.$symbparm;
my $seclevelm=$usercourseprefix.'.['.$csec.'].'.$mapparm;
+
my $courselevel= $usercourseprefix.'.'.$what;
my $courselevelr=$usercourseprefix.'.'.$symbparm;
my $courselevelm=$usercourseprefix.'.'.$mapparm;
+
my $useropt = $self->{USER_OPT};
my $courseopt = $self->{COURSE_OPT};
my $parmhash = $self->{PARM_HASH};
# ---------------------------------------------------------- first, check user
if ($uname and defined($useropt)) {
- if (defined($$useropt{$courselevelr})) { return $$useropt{$courselevelr}; }
- if (defined($$useropt{$courselevelm})) { return $$useropt{$courselevelm}; }
- if (defined($$useropt{$courselevel})) { return $$useropt{$courselevel}; }
+ if (defined($$useropt{$courselevelr})) { return [$$useropt{$courselevelr},'resource']; }
+ if (defined($$useropt{$courselevelm})) { return [$$useropt{$courselevelm},'map']; }
+ if (defined($$useropt{$courselevel})) { return [$$useropt{$courselevel},'course']; }
}
# ------------------------------------------------------- second, check course
if ($cgroup ne '' and defined($courseopt)) {
- if (defined($$courseopt{$grplevelr})) { return $$courseopt{$grplevelr}; }
- if (defined($$courseopt{$grplevelm})) { return $$courseopt{$grplevelm}; }
- if (defined($$courseopt{$grplevel})) { return $$courseopt{$grplevel}; }
+ if (defined($$courseopt{$grplevelr})) { return [$$courseopt{$grplevelr},'resource']; }
+ if (defined($$courseopt{$grplevelm})) { return [$$courseopt{$grplevelm},'map']; }
+ if (defined($$courseopt{$grplevel})) { return [$$courseopt{$grplevel},'course']; }
}
if ($csec and defined($courseopt)) {
- if (defined($$courseopt{$seclevelr})) { return $$courseopt{$seclevelr}; }
- if (defined($$courseopt{$seclevelm})) { return $$courseopt{$seclevelm}; }
- if (defined($$courseopt{$seclevel})) { return $$courseopt{$seclevel}; }
+ if (defined($$courseopt{$seclevelr})) { return [$$courseopt{$seclevelr},'resource']; }
+ if (defined($$courseopt{$seclevelm})) { return [$$courseopt{$seclevelm},'map']; }
+ if (defined($$courseopt{$seclevel})) { return [$$courseopt{$seclevel},'course']; }
}
if (defined($courseopt)) {
- if (defined($$courseopt{$courselevelr})) { return $$courseopt{$courselevelr}; }
+ if (defined($$courseopt{$courselevelr})) { return [$$courseopt{$courselevelr},'resource']; }
}
# ----------------------------------------------------- third, check map parms
my $thisparm=$$parmhash{$symbparm};
- if (defined($thisparm)) { return $thisparm; }
+ if (defined($thisparm)) { return [$thisparm,'map']; }
# ----------------------------------------------------- fourth , check default
my $meta_rwhat=$rwhat;
$meta_rwhat=~s/\./_/g;
my $default=&Apache::lonnet::metadata($fn,$meta_rwhat);
- if (defined($default)) { return $default}
+ if (defined($default)) { return [$default,'resource']}
$default=&Apache::lonnet::metadata($fn,'parameter_'.$meta_rwhat);
- if (defined($default)) { return $default}
-
+ if (defined($default)) { return [$default,'resource']}
# --------------------------------------------------- fifth, check more course
if (defined($courseopt)) {
- if (defined($$courseopt{$courselevelm})) { return $$courseopt{$courselevelm}; }
- if (defined($$courseopt{$courselevel})) { return $$courseopt{$courselevel}; }
+ if (defined($$courseopt{$courselevelm})) { return [$$courseopt{$courselevelm},'map']; }
+ if (defined($$courseopt{$courselevel})) {
+ my $ret = [$$courseopt{$courselevel},'course'];
+ return $ret;
+ }
}
-
# --------------------------------------------------- sixth , cascade up parts
my ($space,@qualifier)=split(/\./,$rwhat);
@@ -2653,21 +2816,395 @@ sub parmval_real {
my $id=pop(@parts);
my $part=join('_',@parts);
if ($part eq '') { $part='0'; }
- my $partgeneral=$self->parmval($part.".$qualifier",$symb,1);
- if (defined($partgeneral)) { return $partgeneral; }
+ my @partgeneral=$self->parmval($part.".$qualifier",$symb,1);
+ if (defined($partgeneral[0])) { return \@partgeneral; }
+ }
+ if ($recurse) { return []; }
+ my $pack_def=&Apache::lonnet::packages_tab_default($fn,'resource.'.$rwhat);
+ if (defined($pack_def)) { return [$pack_def,'resource']; }
+ return [''];
+}
+
+sub recurseup_maps {
+ my ($self,$mapname) = @_;
+ my @recurseup;
+ if ($mapname) {
+ my $res = $self->getResourceByUrl($mapname);
+ if (ref($res)) {
+ my @pcs = split(/,/,$res->map_hierarchy());
+ shift(@pcs);
+ if (@pcs) {
+ @recurseup = map { &Apache::lonnet::declutter($self->getByMapPc($_)->src()); } reverse(@pcs);
+ }
+ }
+ }
+ return @recurseup;
+}
+
+sub recursed_crumbs {
+ my ($self,$mapurl,$restitle) = @_;
+ my (@revmapinfo,@revmapres);
+ my $mapres = $self->getResourceByUrl($mapurl);
+ if (ref($mapres)) {
+ @revmapres = map { $self->getByMapPc($_); } split(/,/,$mapres->map_breadcrumbs());
+ shift(@revmapres);
+ }
+ my $allowedlength = 60;
+ my $minlength = 5;
+ my $allowedtitle = 30;
+ if (($env{'environment.icons'} eq 'iconsonly') && (!$env{'browser.mobile'})) {
+ $allowedlength = 100;
+ $allowedtitle = 70;
+ }
+ if (length($restitle) > $allowedtitle) {
+ $restitle = &truncate_crumb_text($restitle,$allowedtitle);
+ }
+ my $totallength = length($restitle);
+ my @links;
+
+ foreach my $map (@revmapres) {
+ my $pc = $map->map_pc();
+ next if ((!$pc) || ($pc == 1));
+ push(@links,$map);
+ push(@revmapinfo,{'href' => $env{'request.use_absolute'}.$map->link().'?navmap=1','text' => $map->title(),'no_mt' => 1,});
+ $totallength += length($map->title());
+ }
+ my $numlinks = scalar(@links);
+ if ($numlinks) {
+ if ($totallength - $allowedlength > 0) {
+ my $available = $allowedlength - length($restitle);
+ my $avg = POSIX::ceil($available/$numlinks);
+ if ($avg < $minlength) {
+ $avg = $minlength;
+ }
+ @revmapinfo = ();
+ foreach my $map (@links) {
+ my $showntitle = &truncate_crumb_text($map->title(),$avg);
+ if ($showntitle ne '') {
+ push(@revmapinfo,{'href' => $env{'request.use_absolute'}.$map->link().'?navmap=1','text' => $showntitle,'no_mt' => 1,});
+ }
+ }
+ }
+ }
+ if ($restitle ne '') {
+ push(@revmapinfo,{'text' => $restitle, 'no_mt' => 1});
+ }
+ return @revmapinfo;
+}
+
+sub truncate_crumb_text {
+ my ($title,$limit) = @_;
+ my $showntitle = '';
+ if (length($title) > $limit) {
+ my @words = split(/\b\s*/,$title);
+ if (@words == 1) {
+ $showntitle = substr($title,0,$limit).' ...';
+ } else {
+ my $linklength = 0;
+ my $num = 0;
+ foreach my $word (@words) {
+ $linklength += 1+length($word);
+ if ($word eq '-') {
+ $showntitle =~ s/ $//;
+ $showntitle .= $word;
+ } elsif ($linklength > $limit) {
+ if ($num < @words) {
+ $showntitle .= $word.' ...';
+ last;
+ } else {
+ $showntitle .= $word;
+ }
+ } else {
+ $showntitle .= $word.' ';
+ }
+ }
+ $showntitle =~ s/ $//;
+ }
+ return $showntitle;
+ } else {
+ return $title;
+ }
+}
+
+#
+# Determines the open/close dates for printing a map that
+# encloses a resource.
+#
+sub map_printdates {
+ my ($self, $res, $part) = @_;
+
+
+
+
+
+ my $opendate = $self->get_mapparam($res->symb(),'',"$part.printstartdate");
+ my $closedate= $self->get_mapparam($res->symb(),'', "$part.printenddate");
+
+
+ return ($opendate, $closedate);
+}
+
+sub get_mapparam {
+ my ($self, $symb, $mapname, $what) = @_;
+
+ # Ensure the course option hash is populated:
+
+ $self->generate_course_user_opt();
+
+ # Get the course id and section if there is one.
+
+ my $cid=$env{'request.course.id'};
+ my $csec=$env{'request.course.sec'};
+ my $cgroup='';
+ my @cgrps=split(/:/,$env{'request.course.groups'});
+ if (@cgrps > 0) {
+ @cgrps = sort(@cgrps);
+ $cgroup = $cgrps[0];
+ }
+ my $uname=$self->{USERNAME};
+ my $udom=$self->{DOMAIN};
+
+ unless ($symb || $mapname) { return; }
+ my $result='';
+ my ($recursed,@recurseup);
+
+ # Figure out which map we are in.
+
+ if ($symb && !$mapname) {
+ my ($id,$fn);
+ ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb);
+ $mapname = &Apache::lonnet::deversion($mapname);
+ }
+
+ my $rwhat=$what;
+ $what=~s/^parameter\_//;
+ $what=~s/\_/\./;
+
+ # Build the hash keys for the lookup:
+
+ my $symbparm=$symb.'.'.$what;
+ my $mapparm=$mapname.'___(all).'.$what;
+ my $usercourseprefix=$cid;
+
+
+ my $grplevel = "$usercourseprefix.[$cgroup].$mapparm";
+ my $seclevel = "$usercourseprefix.[$csec].$mapparm";
+ my $courselevel = "$usercourseprefix.$mapparm";
+
+
+ # Get handy references to the hashes we need in $self:
+
+ my $useropt = $self->{USER_OPT};
+ my $courseopt = $self->{COURSE_OPT};
+ my $parmhash = $self->{PARM_HASH};
+
+ # Check per user
+
+
+
+ if ($uname and defined($useropt)) {
+ if (defined($$useropt{$courselevel})) {
+ return $$useropt{$courselevel};
+ }
+ if ($what =~ /\.(encrypturl|hiddenresource)$/) {
+ unless ($recursed) {
+ @recurseup = $self->recurseup_maps($mapname);
+ $recursed = 1;
+ }
+ foreach my $item (@recurseup) {
+ my $norecursechk=$usercourseprefix.'.'.$item.'___(all).'.$what;
+ if (defined($$useropt{$norecursechk})) {
+ if ($what =~ /\.(encrypturl|hiddenresource)$/) {
+ return $$useropt{$norecursechk};
+ }
+ }
+ }
+ }
+ }
+
+ # Check course -- group
+
+
+
+ if ($cgroup ne '' and defined ($courseopt)) {
+ if (defined($$courseopt{$grplevel})) {
+ return $$courseopt{$grplevel};
+ }
+ if ($what =~ /\.(encrypturl|hiddenresource)$/) {
+ unless ($recursed) {
+ @recurseup = $self->recurseup_maps($mapname);
+ $recursed = 1;
+ }
+ foreach my $item (@recurseup) {
+ my $norecursechk=$usercourseprefix.'.['.$cgroup.'].'.$item.'___(all).'.$what;
+ if (defined($$courseopt{$norecursechk})) {
+ if ($what =~ /\.(encrypturl|hiddenresource)$/) {
+ return $$courseopt{$norecursechk};
+ }
+ }
+ }
+ }
+ }
+
+ # Check course -- section
+
+
+
+
+
+ if ($csec and defined($courseopt)) {
+ if (defined($$courseopt{$seclevel})) {
+ return $$courseopt{$seclevel};
+ }
+ if ($what =~ /\.(encrypturl|hiddenresource)$/) {
+ unless ($recursed) {
+ @recurseup = $self->recurseup_maps($mapname);
+ $recursed = 1;
+ }
+ foreach my $item (@recurseup) {
+ my $norecursechk=$usercourseprefix.'.['.$csec.'].'.$item.'___(all).'.$what;
+ if (defined($$courseopt{$norecursechk})) {
+ if ($what =~ /\.(encrypturl|hiddenresource)$/) {
+ return $$courseopt{$norecursechk};
+ }
+ }
+ }
+ }
+ }
+ # Check the map parameters themselves:
+
+ if ($symb) {
+ my $symbparm=$symb.'.'.$what;
+ my $thisparm = $$parmhash{$symbparm};
+ if (defined($thisparm)) {
+ return $thisparm;
+ }
+ }
+
+
+ # Additional course parameters:
+
+ if (defined($courseopt)) {
+ if (defined($$courseopt{$courselevel})) {
+ return $$courseopt{$courselevel};
+ }
+ if ($what =~ /\.(encrypturl|hiddenresource)$/) {
+ unless ($recursed) {
+ @recurseup = $self->recurseup_maps($mapname);
+ $recursed = 1;
+ }
+ foreach my $item (@recurseup) {
+ my $norecursechk=$usercourseprefix.'.'.$item.'___(all).'.$what;
+ if (defined($$courseopt{$norecursechk})) {
+ if ($what =~ /\.(encrypturl|hiddenresource)$/) {
+ return $$courseopt{$norecursechk};
+ }
+ }
+ }
+ }
+ }
+ return undef; # Unefined if we got here.
+}
+
+sub course_printdates {
+ my ($self, $symb, $part) = @_;
+
+
+ my $opendate = $self->getcourseparam($symb, $part . '.printstartdate');
+ my $closedate = $self->getcourseparam($symb, $part . '.printenddate');
+ return ($opendate, $closedate);
+
+}
+
+sub getcourseparam {
+ my ($self, $symb, $what) = @_;
+
+ $self->generate_course_user_opt(); # If necessary populate the hashes.
+
+ my $uname = $self->{USERNAME};
+ my $udom = $self->{DOMAIN};
+
+ # Course, section, group ids come from the env:
+
+ my $cid = $env{'request.course.id'};
+ my $csec = $env{'request.course.sec'};
+ my $cgroup = ''; # Assume no group
+
+ my @cgroups = split(/:/, $env{'request.course.groups'});
+ if(@cgroups > 0) {
+ @cgroups = sort(@cgroups);
+ $cgroup = $cgroups[0]; # There is a course group.
+ }
+ my ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb);
+ $mapname = &Apache::lonnet::deversion($mapname);
+
+ #
+ # Make the various lookup keys:
+ #
+
+ $what=~s/^parameter\_//;
+ $what=~s/\_/\./;
+
+
+ my $symbparm = $symb . '.' . $what;
+ my $mapparm=$mapname.'___(all).'.$what;
+
+ # Local refs to the hashes we're going to look at:
+
+ my $useropt = $self->{USER_OPT};
+ my $courseopt = $self->{COURSE_OPT};
+
+ #
+ # We want the course level stuff from the way
+ # parmval_real operates
+ # TODO: Factor some of this stuff out of
+ # both parmval_real and here
+ #
+ my $courselevel = $cid . '.' . $what;
+ my $grplevel = $cid . '.[' . $cgroup . ']' . $what;
+ my $seclevel = $cid . '.[' . $csec . ']' . $what;
+
+
+ # Try for the user's course level option:
+
+ if ($uname and defined($useropt)) {
+ if (defined($$useropt{$courselevel})) {
+ return $$useropt{$courselevel};
+ }
+ }
+ # Try for the group's course level option:
+
+ if ($cgroup ne '' and defined($courseopt)) {
+ if (defined($$courseopt{$grplevel})) {
+ return $$courseopt{$grplevel};
+ }
+ }
+
+ # Try for section level parameters:
+
+ if ($csec ne '' and defined($courseopt)) {
+ if (defined($$courseopt{$seclevel})) {
+ return $$courseopt{$seclevel};
+ }
+ }
+ # Try for 'additional' course parameters:
+
+ if (defined($courseopt)) {
+ if (defined($$courseopt{$courselevel})) {
+ return $$courseopt{$courselevel};
+ }
}
- if ($recurse) { return undef; }
- my $pack_def=&Apache::lonnet::packages_tab_default($fn,'resource.'.$what);
- if (defined($pack_def)) { return $pack_def; }
- return '';
+ return undef;
+
}
+
=pod
=item * B(url,multiple):
Retrieves a resource object by URL of the resource, unless the optional
-multiple parameter is included in wahich caes an array of resource
+multiple parameter is included in which case an array of resource
objects is returned. If passed a resource object, it will simply return
it, so it is safe to use this method in code like
"$res = $navmap->getResourceByUrl($res)"
@@ -2676,7 +3213,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
@@ -2685,13 +3222,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
@@ -2702,7 +3244,7 @@ all matching resources.
=item * B(map, filterFunc, recursive, showall):
-Convience method for
+Convenience method for
scalar(retrieveResources($map, $filterFunc, $recursive, 1, $showall)) > 0
@@ -2714,7 +3256,7 @@ in the filter function.
Retrieves version infomation for a url. Returns the version (a number, or
the string "mostrecent") for resources which have version information in
the big hash.
-
+
=cut
@@ -2758,6 +3300,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);
@@ -2780,16 +3323,20 @@ sub retrieveResources {
my @resources = ();
+ if (&$filterFunc($map)) {
+ push(@resources, $map);
+ }
+
# 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;
}
- push @resources, $curRes;
+ push(@resources, $curRes);
if ($bailout) {
return @resources;
@@ -2934,9 +3481,16 @@ 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 then
+code. It is difficult to get right and harder to understand than
this. They should be migrated to this new style.
=cut
@@ -2965,6 +3519,9 @@ sub new {
weaken($self->{NAV_MAP} = shift);
return undef unless ($self->{NAV_MAP});
+ $self->{USERNAME} = $self->{NAV_MAP}->{USERNAME};
+ $self->{DOMAIN} = $self->{NAV_MAP}->{DOMAIN};
+
# Handle the parameters
$self->{FIRST_RESOURCE} = shift || $self->{NAV_MAP}->firstResource();
$self->{FINISH_RESOURCE} = shift || $self->{NAV_MAP}->finishResource();
@@ -3078,13 +3635,11 @@ sub new {
if ($resourceCount == 1 && $resource->is_sequence() && !$self->{FORCE_TOP}) {
my $firstResource = $resource->map_start();
my $finishResource = $resource->map_finish();
- return
- Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource,
- $finishResource, $self->{FILTER},
- $self->{ALREADY_SEEN},
- $self->{CONDITION},
- $self->{FORCE_TOP});
-
+ return Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource,
+ $finishResource, $self->{FILTER},
+ $self->{ALREADY_SEEN},
+ $self->{CONDITION},
+ $self->{FORCE_TOP});
}
# Set up some bookkeeping information.
@@ -3103,13 +3658,13 @@ sub new {
$self->{ALREADY_SEEN}->{$self->{FIRST_RESOURCE}->{ID}} = 1;
bless ($self);
-
return $self;
}
sub next {
my $self = shift;
my $closeAllPages=shift;
+ my $noblockcheck = shift;
if ($self->{FINISHED}) {
return END_ITERATOR();
}
@@ -3118,8 +3673,13 @@ sub next {
# do so.
if ($self->{RETURN_0} && !$self->{HAVE_RETURNED_0}) {
$self->{HAVE_RETURNED_0} = 1;
+ my $nextTopLevel = $self->{NAV_MAP}->getById('0.0');
return $self->{NAV_MAP}->getById('0.0');
}
+ if ($self->{RETURN_0} && !$self->{HAVE_RETURNED_0_BEGIN_MAP}) {
+ $self->{HAVE_RETURNED_0_BEGIN_MAP} = 1;
+ return $self->BEGIN_MAP();
+ }
if ($self->{RECURSIVE_ITERATOR_FLAG}) {
# grab the next from the recursive iterator
@@ -3133,7 +3693,6 @@ sub next {
if ($self->{RECURSIVE_DEPTH} == 0) {
$self->{RECURSIVE_ITERATOR_FLAG} = 0;
}
-
return $next;
}
@@ -3209,6 +3768,8 @@ sub next {
# So we need to look at all the resources we can get to from here,
# categorize them if we haven't seen them, remember if we have a new
my $nextUnfiltered = $here->getNext();
+
+
my $maxDepthAdded = -1;
for (@$nextUnfiltered) {
@@ -3221,7 +3782,7 @@ sub next {
}
# Is this the end of a branch? If so, all of the resources examined above
- # led to lower levels then the one we are currently at, so we push a END_BRANCH
+ # led to lower levels than the one we are currently at, so we push a END_BRANCH
# marker onto the stack so we don't forget.
# Example: For the usual A(BC)(DE)F case, when the iterator goes down the
# BC branch and gets to C, it will see F as the only next resource, but it's
@@ -3238,11 +3799,11 @@ sub next {
# That ends the main iterator logic. Now, do we want to recurse
# down this map (if this resource is a map)?
if ( ($self->{HERE}->is_sequence() || (!$closeAllPages && $self->{HERE}->is_page())) &&
- (defined($self->{FILTER}->{$self->{HERE}->map_pc()}) xor $self->{CONDITION})) {
+ (defined($self->{FILTER}->{$self->{HERE}->map_pc()}) xor $self->{CONDITION}) &&
+ ($env{'request.role.adv'} || !$self->{HERE}->randomout())) {
$self->{RECURSIVE_ITERATOR_FLAG} = 1;
my $firstResource = $self->{HERE}->map_start();
my $finishResource = $self->{HERE}->map_finish();
-
$self->{RECURSIVE_ITERATOR} =
Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource,
$finishResource, $self->{FILTER},
@@ -3254,7 +3815,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);
@@ -3334,6 +3895,9 @@ sub new {
weaken($self->{NAV_MAP} = shift);
return undef unless ($self->{NAV_MAP});
+ $self->{USERNAME} = $self->{NAV_MAP}->{USERNAME};
+ $self->{DOMAIN} = $self->{NAV_MAP}->{DOMAIN};
+
$self->{FIRST_RESOURCE} = shift || $self->{NAV_MAP}->firstResource();
$self->{FINISH_RESOURCE} = shift || $self->{NAV_MAP}->finishResource();
@@ -3430,9 +3994,9 @@ sub next {
# filter the next possibilities to remove things we've
# already seen.
- foreach (@$nextUnfiltered) {
- if (!defined($self->{ALREADY_SEEN}->{$_->{ID}})) {
- push @$next, $_;
+ foreach my $item (@$nextUnfiltered) {
+ if (!defined($self->{ALREADY_SEEN}->{$item->{ID}})) {
+ push @$next, $item;
}
}
@@ -3557,7 +4121,7 @@ X X
All resources also have Bs, which uniquely identify a resource
in a course. Many internal LON-CAPA functions expect a symb. A symb
carries along with it the URL of the resource, and the map it appears
-in. Symbs are much larger then resource IDs.
+in. Symbs are much larger than resource IDs.
=cut
@@ -3570,17 +4134,25 @@ sub new {
weaken($self->{NAV_MAP} = shift);
$self->{ID} = shift;
+ $self->{USERNAME} = $self->{NAV_MAP}->{USERNAME};
+ $self->{DOMAIN} = $self->{NAV_MAP}->{DOMAIN};
+
# Store this new resource in the parent nav map's cache.
$self->{NAV_MAP}->{RESOURCE_CACHE}->{$self->{ID}} = $self;
$self->{RESOURCE_ERROR} = 0;
+ $self->{DUEDATE_CACHE} = undef;
+
# A hash that can be used by two-pass algorithms to store data
# 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;
}
@@ -3591,7 +4163,11 @@ sub navHash {
my $self = shift;
my $param = shift;
my $id = shift;
- return $self->{NAV_MAP}->navhash($param . ($id?$self->{ID}:""));
+ my $arg = $param . ($id?$self->{ID}:"");
+ if (ref($self) && ref($self->{NAV_MAP}) && defined($arg)) {
+ return $self->{NAV_MAP}->navhash($arg);
+ }
+ return;
}
=pod
@@ -3633,8 +4209,13 @@ false.
=item * B:
-Returns true for a map if the randompick feature is being used on the
-map. (?)
+Returns the number of randomly picked items for a map if the randompick
+feature is being used on the map.
+
+=item * B:
+
+Returns true for a map if the randomorder feature is being used on the
+map.
=item * B:
@@ -3664,7 +4245,13 @@ sub kind { my $self=shift; return $self-
sub randomout { my $self=shift; return $self->navHash("randomout_", 1); }
sub randompick {
my $self = shift;
- return $self->parmval('randompick');
+ my $randompick = $self->parmval('randompick');
+ return $randompick;
+}
+sub randomorder {
+ my $self = shift;
+ my $randomorder = $self->parmval('randomorder');
+ return ($randomorder =~ /^yes$/i);
}
sub link {
my $self=shift;
@@ -3677,8 +4264,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;
@@ -3691,6 +4278,7 @@ sub enclosing_map_src {
}
sub symb {
my $self=shift;
+ if (defined($self->{SYMB})) { return $self->{SYMB}; }
(my $first, my $second) = $self->{ID} =~ /(\d+).(\d+)/;
my $symbSrc = &Apache::lonnet::declutter($self->src());
my $symb = &Apache::lonnet::declutter($self->navHash('map_id_'.$first))
@@ -3699,7 +4287,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;
@@ -3742,6 +4330,7 @@ sub compTitle {
}
return $title;
}
+
=pod
B
@@ -3784,7 +4373,8 @@ sub retrieveResources {
sub is_exam {
my ($self,$part) = @_;
- if ($self->parmval('type',$part) eq 'exam') {
+ my $type = $self->parmval('type',$part);
+ if ($type eq 'exam') {
return 1;
}
if ($self->src() =~ /\.(exam)$/) {
@@ -3807,7 +4397,8 @@ sub is_page {
sub is_practice {
my $self=shift;
my ($part) = @_;
- if ($self->parmval('type',$part) eq 'practice') {
+ my $type = $self->parmval('type',$part);
+ if ($type eq 'practice') {
return 1;
}
return 0;
@@ -3815,11 +4406,47 @@ sub is_practice {
sub is_problem {
my $self=shift;
my $src = $self->src();
- if ($src =~ /\.(problem|exam|quiz|assess|survey|form|library|task)$/) {
+ if ($src =~ /$LONCAPA::assess_re/) {
return !($self->is_practice());
}
return 0;
}
+#
+# The has below is the set of status that are considered 'incomplete'
+#
+my %incomplete_hash =
+(
+ TRIES_LEFT() => 1,
+ OPEN() => 1,
+ ATTEMPTED() => 1
+
+ );
+#
+# Return tru if a problem is incomplete... for now incomplete means that
+# any part of the problem is incomplete.
+# Note that if the resources is not a problem, 0 is returned.
+#
+sub is_incomplete {
+ my $self = shift;
+ if ($self->is_problem()) {
+ foreach my $part (@{$self->parts()}) {
+ if (exists($incomplete_hash{$self->status($part)})) {
+ return 1;
+ }
+ }
+ }
+ return 0;
+
+}
+sub is_raw_problem {
+ my $self=shift;
+ my $src = $self->src();
+ if ($src =~ /$LONCAPA::assess_re/) {
+ return 1;
+ }
+ return 0;
+}
+
sub contains_problem {
my $self=shift;
if ($self->is_page()) {
@@ -3828,16 +4455,25 @@ sub contains_problem {
}
return 0;
}
+sub map_contains_problem {
+ my $self=shift;
+ if ($self->is_map()) {
+ my $has_problem=
+ $self->hasResource($self,sub { $_[0]->is_problem() },1);
+ return $has_problem;
+ }
+ return 0;
+}
sub is_sequence {
my $self=shift;
- my $src = $self->src();
return $self->navHash("is_map_", 1) &&
- $self->navHash("map_type_" . $self->map_pc()) eq 'sequence';
+ $self->navHash("map_type_" . $self->map_pc()) eq 'sequence';
}
sub is_survey {
my $self = shift();
my $part = shift();
- if ($self->parmval('type',$part) eq 'survey') {
+ my $type = $self->parmval('type',$part);
+ if (($type eq 'survey') || ($type eq 'surveycred')) {
return 1;
}
if ($self->src() =~ /\.(survey)$/) {
@@ -3845,6 +4481,15 @@ sub is_survey {
}
return 0;
}
+sub is_anonsurvey {
+ my $self = shift();
+ my $part = shift();
+ my $type = $self->parmval('type',$part);
+ if (($type eq 'anonsurvey') || ($type eq 'anonsurveycred')) {
+ return 1;
+ }
+ return 0;
+}
sub is_task {
my $self=shift;
my $src = $self->src();
@@ -3865,7 +4510,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
@@ -3898,6 +4543,18 @@ resource of the map.
Returns a string with the type of the map in it.
+=item * B:
+
+Returns a string with a comma-separated ordered list of map_pc IDs
+for the hierarchy of maps containing a map, with the top level
+map first, then descending to deeper levels, with the enclosing map last.
+
+=item * B:
+
+Same as map_hierarchy, except maps containing only a single itemm if
+it's a map, or containing no items are omitted, unless it's the top
+level map (map_pc = 1), which is always included.
+
=back
=cut
@@ -3905,7 +4562,7 @@ Returns a string with the type of the ma
sub map_finish {
my $self = shift;
my $src = $self->src();
- $src = Apache::lonnet::clutter($src);
+ $src = &Apache::lonnet::clutter($src);
my $res = $self->navHash("map_finish_$src", 0);
$res = $self->{NAV_MAP}->getById($res);
return $res;
@@ -3918,7 +4575,7 @@ sub map_pc {
sub map_start {
my $self = shift;
my $src = $self->src();
- $src = Apache::lonnet::clutter($src);
+ $src = &Apache::lonnet::clutter($src);
my $res = $self->navHash("map_start_$src", 0);
$res = $self->{NAV_MAP}->getById($res);
return $res;
@@ -3928,6 +4585,16 @@ sub map_type {
my $pc = $self->map_pc();
return $self->navHash("map_type_$pc", 0);
}
+sub map_hierarchy {
+ my $self = shift;
+ my $pc = $self->map_pc();
+ return $self->navHash("map_hierarchy_$pc", 0);
+}
+sub map_breadcrumbs {
+ my $self = shift;
+ my $pc = $self->map_pc();
+ return $self->navHash("map_breadcrumbs_$pc", 0);
+}
#####
# Property queries
@@ -3935,9 +4602,9 @@ sub map_type {
# These functions will be responsible for returning the CORRECT
# VALUE for the parameter, no matter what. So while they may look
-# like direct calls to parmval, they can be more then that.
+# like direct calls to parmval, they can be more than that.
# So, for instance, the duedate function should use the "duedatetype"
-# information, rather then the resource object user.
+# information, rather than the resource object user.
=pod
@@ -3958,7 +4625,19 @@ their code.)
=over 4
-=item * B:
+
+=item * B
+
+returns true if the current date is such that the
+specified resource part is printable.
+
+
+=item * B
+
+Returns true if all parts in the resource are printable making the
+entire resource printable.
+
+=item * B
Get the Client IP/Name Access Control information.
@@ -4011,63 +4690,197 @@ Get the weight for the problem.
=cut
+
+
+
+sub printable {
+
+ my ($self, $part) = @_;
+
+ # The following cases apply:
+ # - If a start date is not set, it is replaced by the open date.
+ # - Ditto for start/open replaced by content open.
+ # - If neither start nor printdates are set the part is printable.
+ # - Start date set but no end date: Printable if now >= start date.
+ # - End date set but no start date: Printable if now <= end date.
+ # - both defined: printable if start <= now <= end
+ #
+
+ # Get the print open/close dates for the resource.
+
+ my $start = $self->parmval("printstartdate", $part);
+ my $end = $self->parmval("printenddate", $part);
+
+ if (!$start) {
+ $start = $self->parmval("opendate", $part);
+ }
+ if (!$start) {
+ $start = $self->parmval("contentopen", $part);
+ }
+
+
+ my $now = time();
+
+
+ my $startok = 1;
+ my $endok = 1;
+
+ if ((defined $start) && ($start ne '')) {
+ $startok = $start <= $now;
+ }
+ if ((defined $end) && ($end != '')) {
+ $endok = $end >= $now;
+ }
+ return $startok && $endok;
+}
+
+sub resprintable {
+ my $self = shift;
+
+ # get parts...or realize there are no parts.
+
+ my $partsref = $self->parts();
+ my @parts = @$partsref;
+
+ if (!@parts) {
+ return $self->printable(0);
+ } else {
+ foreach my $part (@parts) {
+ if (!$self->printable($part)) {
+ return 0;
+ }
+ }
+ return 1;
+ }
+}
+
sub acc {
(my $self, my $part) = @_;
- return $self->parmval("acc", $part);
+ my $acc = $self->parmval("acc", $part);
+ return $acc;
}
sub answerdate {
(my $self, my $part) = @_;
# Handle intervals
- if ($self->parmval("answerdate.type", $part) eq 'date_interval') {
- return $self->duedate($part) +
- $self->parmval("answerdate", $part);
+ my $answerdatetype = $self->parmval("answerdate.type", $part);
+ my $answerdate = $self->parmval("answerdate", $part);
+ my $duedate = $self->parmval("duedate", $part);
+ if ($answerdatetype eq 'date_interval') {
+ $answerdate = $duedate + $answerdate;
}
- return $self->parmval("answerdate", $part);
+ return $answerdate;
}
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'};
+}
+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'};
+}
+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'};
+}
+sub checkedin {
+ my $self = shift; my $part = shift;
+ $self->{NAV_MAP}->get_user_data();
+ 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'});
+ } else {
+ 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
+# Why is there a copy in lonhomework? Why not centralized?
+#
+# TODO: Centralize duedate.
+#
+
sub duedate {
(my $self, my $part) = @_;
- my $interval=$self->parmval("interval", $part);
- if ($interval) {
- my $first_access=&Apache::lonnet::get_first_access('map',$self->symb);
- if ($first_access) { return ($first_access+$interval); }
+ if (defined ($self->{DUEDATE_CACHE}->{$part})) {
+ return $self->{DUEDATE_CACHE}->{$part};
+ }
+ my $date;
+ my @interval=$self->parmval("interval", $part);
+ my $due_date=$self->parmval("duedate", $part);
+ if ($interval[0] =~ /^(\d+)/) {
+ my $timelimit = $1;
+ my $first_access=&Apache::lonnet::get_first_access($interval[1],
+ $self->{SYMB});
+ if (defined($first_access)) {
+ my $interval = $first_access+$timelimit;
+ $date = (!$due_date || $interval < $due_date) ? $interval
+ : $due_date;
+ } else {
+ $date = $due_date;
+ }
+ } else {
+ $date = $due_date;
}
- return $self->parmval("duedate", $part);
+ $self->{DUEDATE_CACHE}->{$part} = $date;
+ return $date;
}
sub handgrade {
(my $self, my $part) = @_;
- return $self->parmval("handgrade", $part);
+ my @response_ids = $self->responseIds($part);
+ if (@response_ids) {
+ foreach my $response_id (@response_ids) {
+ my $handgrade = $self->parmval("handgrade",$part.'_'.$response_id);
+ if (lc($handgrade) eq 'yes') {
+ return 'yes';
+ }
+ }
+ }
+ my $handgrade = $self->parmval("handgrade", $part);
+ return $handgrade;
}
sub maxtries {
(my $self, my $part) = @_;
- return $self->parmval("maxtries", $part);
+ my $maxtries = $self->parmval("maxtries", $part);
+ return $maxtries;
}
sub opendate {
(my $self, my $part) = @_;
- if ($self->parmval("opendate.type", $part) eq 'date_interval') {
- return $self->duedate($part) -
- $self->parmval("opendate", $part);
+ my $opendatetype = $self->parmval("opendate.type", $part);
+ my $opendate = $self->parmval("opendate", $part);
+ if ($opendatetype eq 'date_interval') {
+ my $duedate = $self->duedate($part);
+ $opendate = $duedate - $opendate;
}
- return $self->parmval("opendate");
+ return $opendate;
}
sub problemstatus {
(my $self, my $part) = @_;
- return lc $self->parmval("problemstatus", $part);
+ my $problemstatus = $self->parmval("problemstatus", $part);
+ return lc($problemstatus);
}
sub sig {
(my $self, my $part) = @_;
- return $self->parmval("sig", $part);
+ my $sig = $self->parmval("sig", $part);
+ return $sig;
}
sub tol {
(my $self, my $part) = @_;
- return $self->parmval("tol", $part);
+ my $tol = $self->parmval("tol", $part);
+ return $tol;
}
-sub tries {
+sub tries {
my $self = shift;
my $tries = $self->queryRestoreHash('tries', shift);
if (!defined($tries)) { return '0';}
@@ -4075,33 +4888,43 @@ sub tries {
}
sub type {
(my $self, my $part) = @_;
- return $self->parmval("type", $part);
+ my $type = $self->parmval("type", $part);
+ return $type;
}
sub weight {
my $self = shift; my $part = shift;
if (!defined($part)) { $part = '0'; }
- return &Apache::lonnet::EXT('resource.'.$part.'.weight',
- $self->symb(), $env{'user.domain'},
- $env{'user.name'},
- $env{'request.course.sec'});
+ my $weight = &Apache::lonnet::EXT('resource.'.$part.'.weight',
+ $self->{SYMB}, $self->{DOMAIN},
+ $self->{USERNAME},
+ $env{'request.course.sec'});
+ return $weight;
}
sub part_display {
my $self= shift(); my $partID = shift();
if (! defined($partID)) { $partID = '0'; }
my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display',
- $self->symb);
+ $self->{SYMB});
if (! defined($display) || $display eq '') {
$display = $partID;
}
return $display;
}
+sub slot_control {
+ my $self=shift(); my $part = shift();
+ if (!defined($part)) { $part = '0'; }
+ my $useslots = $self->parmval("useslots", $part);
+ my $availablestudent = $self->parmval("availablestudent", $part);
+ my $available = $self->parmval("available", $part);
+ return ($useslots,$availablestudent,$available);
+}
# Multiple things need this
sub getReturnHash {
my $self = shift;
if (!defined($self->{RETURN_HASH})) {
- my %tmpHash = &Apache::lonnet::restore($self->symb());
+ my %tmpHash = &Apache::lonnet::restore($self->{SYMB},undef,$self->{DOMAIN},$self->{USERNAME});
$self->{RETURN_HASH} = \%tmpHash;
}
}
@@ -4139,13 +4962,15 @@ data was not extracted when the nav map
Returns a false value if there hasn't been discussion otherwise returns
unix timestamp of last time a discussion posting (or edit) was made.
-=item * B:
+=item * B:
-returns in scalar context the count of the number of unread discussion
-postings
+optional argument is a filter (currently can be 'unread');
+returns in scalar context the count of the number of discussion postings.
returns in list context both the count of postings and a hash ref
-containing the subjects of all unread postings
+containing information about the postings (subject, id, timestamp) in a hash.
+
+Default is to return counts for all postings. However if called with a second argument set to 'unread', will return information about only unread postings.
=item * B:
@@ -4154,8 +4979,8 @@ for the resource, or the null string if
email data was not extracted when the nav map was constructed. Usually
used like this:
- for (split(/\,/, $res->getFeedback())) {
- my $link = &Apache::lonnet::escape($_);
+ for my $url (split(/\,/, $res->getFeedback())) {
+ my $link = &escape($url);
...
and use the link as appropriate.
@@ -4164,31 +4989,33 @@ 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 unread_discussion {
- my $self = shift;
- return $self->{NAV_MAP}->unread_discussion($self->symb());
+sub discussion_info {
+ my ($self,$filter) = @_;
+ return $self->{NAV_MAP}->discussion_info($self->{SYMB},$filter);
}
sub getFeedback {
my $self = shift;
my $source = $self->src();
+ my $symb = $self->{SYMB};
if ($source =~ /^\/res\//) { $source = substr $source, 5; }
- return $self->{NAV_MAP}->getFeedback($source);
+ return $self->{NAV_MAP}->getFeedback($symb,$source);
}
sub getErrors {
my $self = shift;
my $source = $self->src();
+ my $symb = $self->{SYMB};
if ($source =~ /^\/res\//) { $source = substr $source, 5; }
- return $self->{NAV_MAP}->getErrors($source);
+ return $self->{NAV_MAP}->getErrors($symb,$source);
}
=pod
@@ -4336,7 +5163,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;
}
@@ -4349,22 +5176,22 @@ sub extractParts {
$self->{PART_TYPE} = {};
return;
}
- foreach (split(/\,/,$metadata)) {
- if ($_ =~ /^(?:part|Task)_(.*)$/) {
+ foreach my $entry (split(/\,/,$metadata)) {
+ if ($entry =~ /^(?:part|Task)_(.*)$/) {
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;
}
@@ -4375,8 +5202,8 @@ sub extractParts {
# Init the responseIdHash
- foreach (@{$self->{PARTS}}) {
- $responseIdHash{$_} = [];
+ foreach my $part (@{$self->{PARTS}}) {
+ $responseIdHash{$part} = [];
}
# Now, the unfortunate thing about this is that parts, part name, and
@@ -4385,13 +5212,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 (split /,/, $metadata) {
- if ($_ =~ /^([a-zA-Z]+)response_(.*)/
- || $_ =~ /^(Task)_(.*)/) {
+ 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 .= '_'; }
@@ -4399,8 +5226,15 @@ sub extractParts {
if ($parts{$partIdSoFar}) {
my @otherChunks = @partChunks[$i+1..$#partChunks];
my $responseId = join('_', @otherChunks);
- push @{$responseIdHash{$partIdSoFar}}, $responseId;
- push @{$responseTypeHash{$partIdSoFar}}, $responseType;
+ if ($self->is_task()) {
+ push(@{$responseIdHash{$partIdSoFar}},
+ $partIdSoFar);
+ } else {
+ push(@{$responseIdHash{$partIdSoFar}},
+ $responseId);
+ }
+ push(@{$responseTypeHash{$partIdSoFar}},
+ $responseType);
}
}
}
@@ -4449,13 +5283,13 @@ the completion information.
Idiomatic usage of these two methods would probably look something
like
- foreach ($resource->parts()) {
- my $dateStatus = $resource->getDateStatus($_);
- my $completionStatus = $resource->getCompletionStatus($_);
+ foreach my $part ($resource->parts()) {
+ my $dateStatus = $resource->getDateStatus($part);
+ my $completionStatus = $resource->getCompletionStatus($part);
or
- my $status = $resource->status($_);
+ my $status = $resource->status($part);
... use it here ...
}
@@ -4516,7 +5350,7 @@ sub OPEN { return 1; }
sub PAST_DUE_NO_ANSWER { return 2; }
sub PAST_DUE_ANSWER_LATER { return 3; }
sub ANSWER_OPEN { return 4; }
-sub NOTHING_SET { return 5; }
+sub NOTHING_SET { return 5; }
sub NETWORK_FAILURE { return 100; }
# getDateStatus gets the date status for a given problem part.
@@ -4602,6 +5436,10 @@ Information not available due to network
Attempted, and not yet graded.
+=item * B:
+
+Attempted, and credit received for attempt (survey and anonymous survey only).
+
=back
=cut
@@ -4613,6 +5451,7 @@ sub CORRECT { return 13; }
sub CORRECT_BY_OVERRIDE { return 14; }
sub EXCUSED { return 15; }
sub ATTEMPTED { return 16; }
+sub CREDIT_ATTEMPTED { return 17; }
sub getCompletionStatus {
my $self = shift;
@@ -4631,6 +5470,13 @@ sub getCompletionStatus {
if ($status eq 'incorrect_by_override') {return $self->INCORRECT_BY_OVERRIDE; }
if ($status eq 'excused') {return $self->EXCUSED; }
if ($status eq 'ungraded_attempted') {return $self->ATTEMPTED; }
+ if ($status eq 'credit_attempted') {
+ if ($self->is_anonsurvey($part) || $self->is_survey($part)) {
+ return $self->CREDIT_ATTEMPTED;
+ } else {
+ return $self->ATTEMPTED;
+ }
+ }
return $self->NOT_ATTEMPTED;
}
@@ -4720,6 +5566,10 @@ The item is open and not yet tried.
The problem has been attempted.
+=item * B:
+
+The problem has been attempted, and credit given for the attempt (survey and anonymous survey only).
+
=item * B:
An answer has been submitted, but the student should not see it.
@@ -4728,9 +5578,20 @@ An answer has been submitted, but the st
=cut
-sub TRIES_LEFT { return 20; }
-sub ANSWER_SUBMITTED { return 21; }
-sub PARTIALLY_CORRECT{ return 22; }
+sub TRIES_LEFT { return 20; }
+sub ANSWER_SUBMITTED { return 21; }
+sub PARTIALLY_CORRECT { return 22; }
+
+sub RESERVED_LATER { return 30; }
+sub RESERVED { return 31; }
+sub RESERVED_LOCATION { return 32; }
+sub RESERVABLE { return 33; }
+sub RESERVABLE_LATER { return 34; }
+sub NOTRESERVABLE { return 35; }
+sub NOT_IN_A_SLOT { return 36; }
+sub NEEDS_CHECKIN { return 37; }
+sub WAITING_FOR_GRADE { return 38; }
+sub UNKNOWN { return 39; }
sub status {
my $self = shift;
@@ -4746,7 +5607,11 @@ sub status {
#if ($self->{RESOURCE_ERROR}) { return NETWORK_FAILURE; }
if ($completionStatus == NETWORK_FAILURE) { return NETWORK_FAILURE; }
- my $suppressFeedback = $self->problemstatus($part) eq 'no';
+ my $suppressFeedback = 0;
+ if (($self->problemstatus($part) eq 'no') ||
+ ($self->problemstatus($part) eq 'no_feedback_ever')) {
+ $suppressFeedback = 1;
+ }
# If there's an answer date and we're past it, don't
# suppress the feedback; student should know
if ($self->duedate($part) && $self->duedate($part) < time() &&
@@ -4778,6 +5643,10 @@ sub status {
return ATTEMPTED;
}
+ if ($completionStatus == CREDIT_ATTEMPTED) {
+ return CREDIT_ATTEMPTED;
+ }
+
# If it's EXCUSED, then return that no matter what
if ($completionStatus == EXCUSED) {
return EXCUSED;
@@ -4816,7 +5685,147 @@ sub status {
}
# Otherwise, it's untried and open
- return OPEN;
+ return OPEN;
+}
+
+sub check_for_slot {
+ my $self = shift;
+ my $part = shift;
+ 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));
+ my $cid=$env{'request.course.id'};
+ my $cdom=$env{'course.'.$cid.'.domain'};
+ my $cnum=$env{'course.'.$cid.'.num'};
+ my $now = time;
+ my $num_usable_slots = 0;
+ my ($checkedin,$checkedinslot,%consumed_uniq,%slots);
+ if (@slots > 0) {
+ %slots=&Apache::lonnet::get('slots',[@slots],$cdom,$cnum);
+ if (&Apache::lonnet::error(%slots)) {
+ return (UNKNOWN);
+ }
+ my @sorted_slots = &Apache::loncommon::sorted_slots(\@slots,\%slots,'starttime');
+ foreach my $slot_name (@sorted_slots) {
+ next if (!defined($slots{$slot_name}) || !ref($slots{$slot_name}));
+ my $end = $slots{$slot_name}->{'endtime'};
+ my $start = $slots{$slot_name}->{'starttime'};
+ my $ip = $slots{$slot_name}->{'ip'};
+ if ($self->simpleStatus() == OPEN) {
+ if ($end > $now) {
+ if ($start > $now) {
+ return (RESERVED_LATER,$start,$slot_name);
+ } else {
+ if ($ip ne '') {
+ if (!&Apache::loncommon::check_ip_acc($ip)) {
+ return (RESERVED_LOCATION,$end,$slot_name);
+ }
+ }
+ my @proctors;
+ if ($slots{$slot_name}->{'proctor'} ne '') {
+ @proctors = split(',',$slots{$slot_name}->{'proctor'});
+ }
+ if (@proctors > 0) {
+ ($checkedin,$checkedinslot) = $self->checkedin();
+ unless ((grep(/^\Q$checkedin\E/,@proctors)) &&
+ ($checkedinslot eq $slot_name)) {
+ return (NEEDS_CHECKIN,$end,$slot_name);
+ }
+ }
+ return (RESERVED,$end,$slot_name);
+ }
+ }
+ } elsif ($end > $now) {
+ $num_usable_slots ++;
+ }
+ }
+ my ($is_correct,$wait_for_grade);
+ if ($self->is_task()) {
+ my $taskstatus = $self->taskstatus();
+ $is_correct = (($taskstatus eq 'pass') ||
+ ($self->solved() =~ /^correct_/));
+ unless ($taskstatus =~ /^(?:pass|fail)$/) {
+ $wait_for_grade = 1;
+ }
+ } else {
+ unless ($self->completable()) {
+ $wait_for_grade = 1;
+ }
+ unless (($self->problemstatus($part) eq 'no') ||
+ ($self->problemstatus($part) eq 'no_feedback_ever')) {
+ $is_correct = ($self->solved($part) =~ /^correct_/);
+ $wait_for_grade = 0;
+ }
+ }
+ ($checkedin,$checkedinslot) = $self->checkedin();
+ if ($checkedin) {
+ if (ref($slots{$checkedinslot}) eq 'HASH') {
+ $consumed_uniq{$checkedinslot} = $slots{$checkedinslot}{'uniqueperiod'};
+ }
+ if ($wait_for_grade) {
+ return (WAITING_FOR_GRADE);
+ } elsif ($is_correct) {
+ return (CORRECT);
+ }
+ }
+ if ($num_usable_slots) {
+ return(NOT_IN_A_SLOT);
+ }
+ }
+ my $reservable = &Apache::lonnet::get_reservable_slots($cnum,$cdom,$env{'user.name'},
+ $env{'user.domain'});
+ if (ref($reservable) eq 'HASH') {
+ if ((ref($reservable->{'now_order'}) eq 'ARRAY') && (ref($reservable->{'now'}) eq 'HASH')) {
+ foreach my $slot (reverse (@{$reservable->{'now_order'}})) {
+ my $canuse;
+ if (($reservable->{'now'}{$slot}{'symb'} eq '') ||
+ ($reservable->{'now'}{$slot}{'symb'} eq $symb)) {
+ $canuse = 1;
+ }
+ if ($canuse) {
+ if ($checkedin) {
+ if (ref($consumed_uniq{$checkedinslot}) eq 'ARRAY') {
+ my ($uniqstart,$uniqend)=@{$consumed_uniq{$checkedinslot}};
+ if ($reservable->{'now'}{$slot}{'uniqueperiod'} =~ /^(\d+),(\d+)$/) {
+ my ($new_uniq_start,$new_uniq_end) = ($1,$2);
+ next if (!
+ ($uniqstart < $new_uniq_start && $uniqend < $new_uniq_start) ||
+ ($uniqstart > $new_uniq_end && $uniqend > $new_uniq_end ));
+ }
+ }
+ }
+ return(RESERVABLE,$reservable->{'now'}{$slot}{'endreserve'});
+ }
+ }
+ }
+ if ((ref($reservable->{'future_order'}) eq 'ARRAY') && (ref($reservable->{'future'}) eq 'HASH')) {
+ foreach my $slot (@{$reservable->{'future_order'}}) {
+ my $canuse;
+ if (($reservable->{'future'}{$slot}{'symb'} eq '') ||
+ ($reservable->{'future'}{$slot}{'symb'} eq $symb)) {
+ $canuse = 1;
+ }
+ if ($canuse) {
+ if ($checkedin) {
+ if (ref($consumed_uniq{$checkedinslot}) eq 'ARRAY') {
+ my ($uniqstart,$uniqend)=@{$consumed_uniq{$checkedinslot}};
+ if ($reservable->{'future'}{$slot}{'uniqueperiod'} =~ /^(\d+),(\d+)$/) {
+ my ($new_uniq_start,$new_uniq_end) = ($1,$2);
+ next if (!
+ ($uniqstart < $new_uniq_start && $uniqend < $new_uniq_start) ||
+ ($uniqstart > $new_uniq_end && $uniqend > $new_uniq_end ));
+ }
+ }
+ }
+ return(RESERVABLE_LATER,$reservable->{'future'}{$slot}{'startreserve'});
+ }
+ }
+ }
+ }
+ return(NOTRESERVABLE);
+ }
+ return;
}
sub CLOSED { return 23; }
@@ -4868,6 +5877,7 @@ my %compositeToSimple =
INCORRECT() => INCORRECT,
OPEN() => OPEN,
ATTEMPTED() => ATTEMPTED,
+ CREDIT_ATTEMPTED() => CORRECT,
ANSWER_SUBMITTED() => ATTEMPTED
);
@@ -4942,6 +5952,7 @@ sub completable {
# and it is not "attempted" (manually graded problem), it is
# not "complete"
if ($self->getCompletionStatus($part) == ATTEMPTED() ||
+ $self->getCompletionStatus($part) == CREDIT_ATTEMPTED() ||
$status == ANSWER_SUBMITTED() ) {
# did this part already, as well as we can
next;
@@ -4957,6 +5968,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
@@ -4992,7 +6036,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);
@@ -5004,12 +6048,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