Key: | ';
if ($navmap->{LAST_CHECK}) {
$result .=
- ' New discussion since '.
+ ' '.&mt('New discussion since').' '.
strftime("%A, %b %e at %I:%M %P", localtime($navmap->{LAST_CHECK})).
' '.
- ' New message (click to open) '.
+ ' '.&mt('New message (click to open)').' '.
' | ';
} else {
$result .= ' '.
- ' Discussions | '.
- ' New message (click to open)'.
+ ' '.&mt('Discussions').' | '.
+ ' '.&mt('New message (click to open)').
' | ';
}
@@ -1315,15 +1488,49 @@ sub render {
if ($condition) {
$result.="Close All Folders";
+ "\">".&mt('Close All Folders')."";
} else {
$result.="Open All Folders";
+ "\">".&mt('Open All Folders')."";
}
- $result .= "
\n";
- }
+ $result .= "\n";
+ }
+ # Check for any unread discussions in all resources.
+ if (!$args->{'resource_no_folder_link'}) {
+ my $totdisc = 0;
+ my $haveDisc = '';
+ my @allres=$navmap->retrieveResources();
+ foreach my $resource (@allres) {
+ if ($resource->hasDiscussion()) {
+ my $ressymb;
+ if ($resource->symb() =~ m-(___adm/\w+/\w+)/(\d+)/bulletinboard$-) {
+ $ressymb = 'bulletin___'.$2.$1.'/'.$2.'/bulletinboard';
+ } else {
+ $ressymb = $resource->symb();
+ }
+ $haveDisc .= $ressymb.':';
+ $totdisc ++;
+ }
+ }
+ if ($totdisc > 0) {
+ $haveDisc =~ s/:$//;
+ my %lt = &Apache::lonlocal::texthash(
+ 'mapr' => 'Mark all posts read',
+ );
+ $result .= (<$lt{'mapr'}
+
+END
+ } else {
+ $result .= '
';
+ }
+ }
+ $result .= "
\n";
if ($r) {
$r->print($result);
$r->rflush();
@@ -1355,7 +1562,7 @@ sub render {
$it->{FIRST_RESOURCE},
$it->{FINISH_RESOURCE},
{}, undef, 1);
- $depth = 0;
+ my $depth = 0;
$dfsit->next();
my $curRes = $dfsit->next();
while ($depth > -1) {
@@ -1387,9 +1594,6 @@ sub render {
my $displayedJumpMarker = 0;
# Set up iteration.
- $depth = 1;
- $it->next(); # discard initial BEGIN_MAP
- $curRes = $it->next();
my $now = time();
my $in24Hours = $now + 24 * 60 * 60;
my $rownum = 0;
@@ -1397,9 +1601,33 @@ sub render {
# export "here" marker information
$args->{'here'} = $here;
- while ($depth > 0) {
- if ($curRes == $it->BEGIN_MAP()) { $depth++; }
- if ($curRes == $it->END_MAP()) { $depth--; }
+ $args->{'indentLevel'} = -1; # first BEGIN_MAP takes this to 0
+ my @resources;
+ my $code='';# sub { !(shift->is_map();) };
+ if ($args->{'sort'} eq 'title') {
+ @resources=$navmap->retrieveResources(undef,
+ sub { !shift->is_map(); });
+ @resources= sort {lc($a->compTitle) cmp lc($b->compTitle)} @resources;
+ } elsif ($args->{'sort'} eq 'duedate') {
+ @resources=$navmap->retrieveResources(undef,
+ sub { shift->is_problem(); });
+ @resources= sort
+ {
+ if ($a->duedate ne $b->duedate) {
+ return $a->duedate cmp $b->duedate;
+ } else {
+ lc($a->compTitle) cmp lc($b->compTitle)
+ }
+ } @resources;
+ }
+
+ while (1) {
+ if ($args->{'sort'}) {
+ $curRes = shift(@resources);
+ } else {
+ $curRes = $it->next($closeAllPages);
+ }
+ if (!$curRes) { last; }
# Maintain indentation level.
if ($curRes == $it->BEGIN_MAP() ||
@@ -1514,14 +1742,23 @@ sub render {
# Set up some data about the parts that the cols might want
my $filter = $it->{FILTER};
- my $stack = $it->getStack();
- my $src = getLinkForResource($stack);
-
+ my $src;
+ if ($args->{'sort'}) {
+ $src = $curRes->src(); # FIXME this is wrong for .pages
+ } else {
+ my $stack = $it->getStack();
+ $src=getLinkForResource($stack);
+ }
+ my $anchor='';
+ if ($src=~s/(\#.*)$//) {
+ $anchor=$1;
+ }
my $srcHasQuestion = $src =~ /\?/;
$args->{"resourceLink"} = $src.
($srcHasQuestion?'&':'?') .
- 'symb=' . &Apache::lonnet::escape($curRes->symb());
-
+ 'symb=' . &Apache::lonnet::escape($curRes->symb()).
+ $anchor;
+
# Now, display each column.
foreach my $col (@$cols) {
my $colHTML = '';
@@ -1553,13 +1790,10 @@ sub render {
$r->rflush();
}
} continue {
- $curRes = $it->next();
-
if ($r) {
# If we have the connection, make sure the user is still connected
my $c = $r->connection;
if ($c->aborted()) {
- Apache::lonnet::logthis("navmaps aborted");
# Who cares what we do, nobody will see it anyhow.
return '';
}
@@ -1574,7 +1808,12 @@ sub render {
# it's quite likely this might fix other browsers, too, and
# certainly won't hurt anything.
if ($displayedJumpMarker) {
- $result .= "\n";
+ $result .= "
+";
}
$result .= "";
@@ -1640,28 +1879,13 @@ To create a navmap object, use the follo
=over 4
-=item * Bnew>(
- genCourseAndUserOptions, genMailDiscussStatus, getUserData):
+=item * Bnew>():
-Creates a new navmap object. genCourseAndUserOptions is a flag saying whether
-the course options and user options hash should be generated. This is
-for when you are using the parameters of the resources that require
-them; see documentation in resource object
-documentation. genMailDiscussStatus causes the nav map to retreive
-information about the email and discussion status of
-resources. Returns the navmap object if this is successful, or
-B if not. You must check for undef; errors will occur when you
-try to use the other methods otherwise. getUserData, if true, will
-retreive the user's performance data for various problems.
+Creates a new navmap object. Returns the navmap object if this is
+successful, or B if not.
=back
-Once you have the $navmap object, call ->init() on it when you are ready
-to use it. This allows you to check if the course map is defined (see
-B below) before engaging in potentially expensive
-initialization routines for the genCourseAndUserOptions and
-genMailDiscussStatus option.
-
When you are done with the $navmap object, you I call
$navmap->untieHashes(), or you'll prevent the current user from using that
course until the web server is restarted. (!)
@@ -1685,10 +1909,6 @@ sub new {
my $class = ref($proto) || $proto;
my $self = {};
- $self->{GENERATE_COURSE_USER_OPT} = shift;
- $self->{GENERATE_EMAIL_DISCUSS_STATUS} = shift;
- $self->{GET_USER_DATA} = 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.
$self->{RESOURCE_CACHE} = {};
@@ -1716,128 +1936,145 @@ sub new {
$self->{NAV_HASH} = \%navmaphash;
$self->{PARM_HASH} = \%parmhash;
- $self->{INITED} = 0;
+ $self->{PARM_CACHE} = {};
bless($self);
return $self;
}
-sub init {
+sub generate_course_user_opt {
my $self = shift;
- if ($self->{INITED}) { return; }
+ if ($self->{COURSE_USER_OPT_GENERATED}) { return; }
- # If the course opt hash and the user opt hash should be generated,
- # generate them
- if ($self->{GENERATE_COURSE_USER_OPT}) {
- my $uname=$ENV{'user.name'};
- my $udom=$ENV{'user.domain'};
- my $uhome=$ENV{'user.home'};
- my $cid=$ENV{'request.course.id'};
- my $chome=$ENV{'course.'.$cid.'.home'};
- my ($cdom,$cnum)=split(/\_/,$cid);
-
- my $userprefix=$uname.'_'.$udom.'_';
-
- my %courserdatas; my %useropt; my %courseopt; my %userrdatas;
- unless ($uhome eq 'no_host') {
+ my $uname=$ENV{'user.name'};
+ my $udom=$ENV{'user.domain'};
+ my $uhome=$ENV{'user.home'};
+ my $cid=$ENV{'request.course.id'};
+ my $chome=$ENV{'course.'.$cid.'.home'};
+ my ($cdom,$cnum)=split(/\_/,$cid);
+
+ my $userprefix=$uname.'_'.$udom.'_';
+
+ my %courserdatas; my %useropt; my %courseopt; my %userrdatas;
+ unless ($uhome eq 'no_host') {
# ------------------------------------------------- Get coursedata (if present)
- unless ((time-$courserdatas{$cid.'.last_cache'})<240) {
- my $reply=&Apache::lonnet::reply('dump:'.$cdom.':'.$cnum.
- ':resourcedata',$chome);
- # Check for network failure
- if ( $reply =~ /no.such.host/i || $reply =~ /con_lost/i) {
- $self->{NETWORK_FAILURE} = 1;
- } elsif ($reply!~/^error\:/) {
- $courserdatas{$cid}=$reply;
- $courserdatas{$cid.'.last_cache'}=time;
- }
- }
- foreach (split(/\&/,$courserdatas{$cid})) {
- my ($name,$value)=split(/\=/,$_);
- $courseopt{$userprefix.&Apache::lonnet::unescape($name)}=
- &Apache::lonnet::unescape($value);
- }
+ unless ((time-$courserdatas{$cid.'.last_cache'})<240) {
+ my $reply=&Apache::lonnet::reply('dump:'.$cdom.':'.$cnum.
+ ':resourcedata',$chome);
+ # Check for network failure
+ if ( $reply =~ /no.such.host/i || $reply =~ /con_lost/i) {
+ $self->{NETWORK_FAILURE} = 1;
+ } elsif ($reply!~/^error\:/) {
+ $courserdatas{$cid}=$reply;
+ $courserdatas{$cid.'.last_cache'}=time;
+ }
+ }
+ foreach (split(/\&/,$courserdatas{$cid})) {
+ my ($name,$value)=split(/\=/,$_);
+ $courseopt{$userprefix.&Apache::lonnet::unescape($name)}=
+ &Apache::lonnet::unescape($value);
+ }
# --------------------------------------------------- Get userdata (if present)
- unless ((time-$userrdatas{$uname.'___'.$udom.'.last_cache'})<240) {
- my $reply=&Apache::lonnet::reply('dump:'.$udom.':'.$uname.':resourcedata',$uhome);
- if ($reply!~/^error\:/) {
- $userrdatas{$uname.'___'.$udom}=$reply;
- $userrdatas{$uname.'___'.$udom.'.last_cache'}=time;
- }
- # check to see if network failed
- elsif ( $reply=~/no.such.host/i || $reply=~/con.*lost/i )
- {
- $self->{NETWORK_FAILURE} = 1;
- }
- }
- foreach (split(/\&/,$userrdatas{$uname.'___'.$udom})) {
- my ($name,$value)=split(/\=/,$_);
- $useropt{$userprefix.&Apache::lonnet::unescape($name)}=
- &Apache::lonnet::unescape($value);
- }
- $self->{COURSE_OPT} = \%courseopt;
- $self->{USER_OPT} = \%useropt;
- }
- }
-
- if ($self->{GENERATE_EMAIL_DISCUSS_STATUS}) {
- my $cid=$ENV{'request.course.id'};
- my ($cdom,$cnum)=split(/\_/,$cid);
-
- my %emailstatus = &Apache::lonnet::dump('email_status');
- my $logoutTime = $emailstatus{'logout'};
- my $courseLeaveTime = $emailstatus{'logout_'.$ENV{'request.course.id'}};
- $self->{LAST_CHECK} = (($courseLeaveTime > $logoutTime) ?
- $courseLeaveTime : $logoutTime);
- my %discussiontime = &Apache::lonnet::dump('discussiontimes',
- $cdom, $cnum);
- my %feedback=();
- my %error=();
- my $keys = &Apache::lonnet::reply('keys:'.
- $ENV{'user.domain'}.':'.
- $ENV{'user.name'}.':nohist_email',
- $ENV{'user.home'});
-
- foreach my $msgid (split(/\&/, $keys)) {
- $msgid=&Apache::lonnet::unescape($msgid);
- my $plain=&Apache::lonnet::unescape(&Apache::lonnet::unescape($msgid));
- if ($plain=~/(Error|Feedback) \[([^\]]+)\]/) {
- my ($what,$url)=($1,$2);
- my %status=
- &Apache::lonnet::get('email_status',[$msgid]);
- if ($status{$msgid}=~/^error\:/) {
- $status{$msgid}='';
- }
-
- if (($status{$msgid} eq 'new') ||
- (!$status{$msgid})) {
- if ($what eq 'Error') {
- $error{$url}.=','.$msgid;
- } else {
- $feedback{$url}.=','.$msgid;
- }
- }
- }
- }
-
- $self->{FEEDBACK} = \%feedback;
- $self->{ERROR_MSG} = \%error; # what is this? JB
- $self->{DISCUSSION_TIME} = \%discussiontime;
- $self->{EMAIL_STATUS} = \%emailstatus;
-
+ unless ((time-$userrdatas{$uname.'___'.$udom.'.last_cache'})<240) {
+ my $reply=&Apache::lonnet::reply('dump:'.$udom.':'.$uname.':resourcedata',$uhome);
+ if ($reply!~/^error\:/) {
+ $userrdatas{$uname.'___'.$udom}=$reply;
+ $userrdatas{$uname.'___'.$udom.'.last_cache'}=time;
+ }
+ # check to see if network failed
+ elsif ( $reply=~/no.such.host/i || $reply=~/con.*lost/i )
+ {
+ $self->{NETWORK_FAILURE} = 1;
+ }
+ }
+ foreach (split(/\&/,$userrdatas{$uname.'___'.$udom})) {
+ my ($name,$value)=split(/\=/,$_);
+ $useropt{$userprefix.&Apache::lonnet::unescape($name)}=
+ &Apache::lonnet::unescape($value);
+ }
+ $self->{COURSE_OPT} = \%courseopt;
+ $self->{USER_OPT} = \%useropt;
}
- if ($self->{GET_USER_DATA}) {
- # Retreive performance data on problems
- my %student_data = Apache::lonnet::currentdump($ENV{'request.course.id'},
- $ENV{'user.domain'},
- $ENV{'user.name'});
- $self->{STUDENT_DATA} = \%student_data;
+ $self->{COURSE_USER_OPT_GENERATED} = 1;
+
+ return;
+}
+
+sub generate_email_discuss_status {
+ my $self = shift;
+ my $symb = shift;
+ if ($self->{EMAIL_DISCUSS_GENERATED}) { return; }
+
+ my $cid=$ENV{'request.course.id'};
+ my ($cdom,$cnum)=split(/\_/,$cid);
+
+ my %emailstatus = &Apache::lonnet::dump('email_status');
+ my $logoutTime = $emailstatus{'logout'};
+ my $courseLeaveTime = $emailstatus{'logout_'.$ENV{'request.course.id'}};
+ $self->{LAST_CHECK} = (($courseLeaveTime > $logoutTime) ?
+ $courseLeaveTime : $logoutTime);
+ my %discussiontime = &Apache::lonnet::dump('discussiontimes',
+ $cdom, $cnum);
+ my %lastread = &Apache::lonnet::dump('nohist_'.$cid.'_discuss',
+ $ENV{'user.domain'},$ENV{'user.name'},'lastread');
+ my %lastreadtime = ();
+ foreach (keys %lastread) {
+ my $key = $_;
+ $key =~ s/_lastread$//;
+ $lastreadtime{$key} = $lastread{$_};
+ }
+
+ my %feedback=();
+ my %error=();
+ my $keys = &Apache::lonnet::reply('keys:'.
+ $ENV{'user.domain'}.':'.
+ $ENV{'user.name'}.':nohist_email',
+ $ENV{'user.home'});
+
+ foreach my $msgid (split(/\&/, $keys)) {
+ $msgid=&Apache::lonnet::unescape($msgid);
+ my $plain=&Apache::lonnet::unescape(&Apache::lonnet::unescape($msgid));
+ if ($plain=~/(Error|Feedback) \[([^\]]+)\]/) {
+ my ($what,$url)=($1,$2);
+ my %status=
+ &Apache::lonnet::get('email_status',[$msgid]);
+ if ($status{$msgid}=~/^error\:/) {
+ $status{$msgid}='';
+ }
+
+ if (($status{$msgid} eq 'new') ||
+ (!$status{$msgid})) {
+ if ($what eq 'Error') {
+ $error{$url}.=','.$msgid;
+ } else {
+ $feedback{$url}.=','.$msgid;
+ }
+ }
+ }
}
+
+ $self->{FEEDBACK} = \%feedback;
+ $self->{ERROR_MSG} = \%error; # what is this? JB
+ $self->{DISCUSSION_TIME} = \%discussiontime;
+ $self->{EMAIL_STATUS} = \%emailstatus;
+ $self->{LAST_READ} = \%lastreadtime;
+
+ $self->{EMAIL_DISCUSS_GENERATED} = 1;
+}
- $self->{PARM_CACHE} = {};
- $self->{INITED} = 1;
+sub get_user_data {
+ my $self = shift;
+ if ($self->{RETRIEVED_USER_DATA}) { return; }
+
+ # Retrieve performance data on problems
+ my %student_data = Apache::lonnet::currentdump($ENV{'request.course.id'},
+ $ENV{'user.domain'},
+ $ENV{'user.name'});
+ $self->{STUDENT_DATA} = \%student_data;
+
+ $self->{RETRIEVED_USER_DATA} = 1;
}
# Internal function: Takes a key to look up in the nav hash and implements internal
@@ -1885,11 +2122,27 @@ sub untieHashes {
sub hasDiscussion {
my $self = shift;
my $symb = shift;
+
+ $self->generate_email_discuss_status();
+
if (!defined($self->{DISCUSSION_TIME})) { return 0; }
#return defined($self->{DISCUSSION_TIME}->{$symb});
- return $self->{DISCUSSION_TIME}->{$symb} >
- $self->{LAST_CHECK};
+
+# backward compatibility (bulletin boards used to be 'wrapped')
+ my $ressymb = $symb;
+ if ($ressymb =~ m|adm/(\w+)/(\w+)/(\d+)/bulletinboard$|) {
+ unless ($ressymb =~ m|adm/wrapper/adm|) {
+ $ressymb = 'bulletin___'.$3.'___adm/wrapper/adm/'.$1.'/'.$2.'/'.$3.'/bulletinboard';
+ }
+ }
+
+ if ( defined ( $self->{LAST_READ}->{$ressymb} ) ) {
+ return $self->{DISCUSSION_TIME}->{$ressymb} > $self->{LAST_READ}->{$ressymb};
+ } else {
+# return $self->{DISCUSSION_TIME}->{$ressymb} > $self->{LAST_CHECK}; # v.1.1 behavior
+ return $self->{DISCUSSION_TIME}->{$ressymb} > 0; # in 1.2 will display speech bubble icons for all items with posts until marked as read (even if read in v 1.1).
+ }
}
# Private method: Does the given resource (as a symb string) have
@@ -1899,6 +2152,8 @@ sub getFeedback {
my $self = shift;
my $symb = shift;
+ $self->generate_email_discuss_status();
+
if (!defined($self->{FEEDBACK})) { return ""; }
return $self->{FEEDBACK}->{$symb};
@@ -1908,7 +2163,9 @@ sub getFeedback {
sub getErrors {
my $self = shift;
my $src = shift;
-
+
+ $self->generate_email_discuss_status();
+
if (!defined($self->{ERROR_MSG})) { return ""; }
return $self->{ERROR_MSG}->{$src};
}
@@ -1956,7 +2213,7 @@ sub getById {
sub getBySymb {
my $self = shift;
my $symb = shift;
- my ($mapUrl, $id, $filename) = split (/___/, $symb);
+ my ($mapUrl, $id, $filename) = &Apache::lonnet::decode_symb($symb);
my $map = $self->getResourceByUrl($mapUrl);
return $self->getById($map->map_pc() . '.' . $id);
}
@@ -2021,6 +2278,9 @@ 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();
+
my $cid=$ENV{'request.course.id'};
my $csec=$ENV{'request.course.sec'};
my $uname=$ENV{'user.name'};
@@ -2029,7 +2289,7 @@ sub parmval_real {
unless ($symb) { return ''; }
my $result='';
- my ($mapname,$id,$fn)=split(/\_\_\_/,$symb);
+ my ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb);
# ----------------------------------------------------- Cascading lookup scheme
my $rwhat=$what;
@@ -2079,7 +2339,11 @@ sub parmval_real {
# ----------------------------------------------------- fourth , check default
- my $default=&Apache::lonnet::metadata($fn,$rwhat.'.default');
+ my $meta_rwhat=$rwhat;
+ $meta_rwhat=~s/\./_/g;
+ my $default=&Apache::lonnet::metadata($fn,$meta_rwhat);
+ if (defined($default)) { return $default}
+ $default=&Apache::lonnet::metadata($fn,'parameter_'.$meta_rwhat);
if (defined($default)) { return $default}
# --------------------------------------------------- fifth , cascade up parts
@@ -2134,7 +2398,7 @@ want to know is if I resources matc
parameter will allow you to avoid potentially expensive enumeration of
all matching resources.
-=item * B(map, filterFunc, recursive):
+=item * B(map, filterFunc, recursive):
Convience method for
@@ -2195,18 +2459,9 @@ sub retrieveResources {
my @resources = ();
# Run down the iterator and collect the resources.
- my $depth = 1;
- $it->next();
- my $curRes = $it->next();
-
- while ($depth > 0) {
- if ($curRes == $it->BEGIN_MAP()) {
- $depth++;
- }
- if ($curRes == $it->END_MAP()) {
- $depth--;
- }
-
+ my $curRes;
+
+ while ($curRes = $it->next()) {
if (ref($curRes)) {
if (!&$filterFunc($curRes)) {
next;
@@ -2219,8 +2474,6 @@ sub retrieveResources {
}
}
- } continue {
- $curRes = $it->next();
}
return @resources;
@@ -2296,6 +2549,13 @@ new branch. The possible tokens are:
=over 4
+=item * B:
+
+The iterator has returned all that it's going to. Further calls to the
+iterator will just produce more of these. This is a "false" value, and
+is the only false value the iterator which will be returned, so it can
+be used as a loop sentinel.
+
=item * B:
A new map is being recursed into. This is returned I the map
@@ -2328,10 +2588,31 @@ but only one resource will be returned.
=back
+=head2 Normal Usage
+
+Normal usage of the iterator object is to do the following:
+
+ my $it = $navmap->getIterator([your params here]);
+ my $curRes;
+ while ($curRes = $it->next()) {
+ [your logic here]
+ }
+
+Note that inside of the loop, it's frequently useful to check if
+"$curRes" is a reference or not with the reference function; only
+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 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
+this. They should be migrated to this new style.
+
=cut
# Here are the tokens for the iterator:
+sub END_ITERATOR { return 0; }
sub BEGIN_MAP { return 1; } # begining of a new map
sub END_MAP { return 2; } # end of the map
sub BEGIN_BRANCH { return 3; } # beginning of a branch
@@ -2417,13 +2698,13 @@ sub new {
# prime the recursion
$self->{$firstResourceName}->{DATA}->{$valName} = 0;
- my $depth = 0;
- $iterator->next();
+ $iterator->next();
my $curRes = $iterator->next();
- while ($depth > -1) {
- if ($curRes == $iterator->BEGIN_MAP()) { $depth++; }
- if ($curRes == $iterator->END_MAP()) { $depth--; }
-
+ my $depth = 1;
+ while ($depth > 0) {
+ if ($curRes == $iterator->BEGIN_MAP()) { $depth++; }
+ if ($curRes == $iterator->END_MAP()) { $depth--; }
+
if (ref($curRes)) {
# If there's only one resource, this will save it
# we have to filter empty resources from consideration here,
@@ -2457,13 +2738,13 @@ sub new {
$curRes->{DATA}->{DISPLAY_DEPTH} = $finalDepth;
if ($finalDepth > $maxDepth) {$maxDepth = $finalDepth;}
}
- } continue {
- $curRes = $iterator->next();
+
+ $curRes = $iterator->next();
}
}
# Check: Was this only one resource, a map?
- if ($resourceCount == 1 && $resource->is_map() && !$self->{FORCE_TOP}) {
+ if ($resourceCount == 1 && $resource->is_sequence() && !$self->{FORCE_TOP}) {
my $firstResource = $resource->map_start();
my $finishResource = $resource->map_finish();
return
@@ -2479,6 +2760,7 @@ sub new {
$self->{MAX_DEPTH} = $maxDepth;
$self->{STACK} = [];
$self->{RECURSIVE_ITERATOR_FLAG} = 0;
+ $self->{FINISHED} = 0; # When true, the iterator has finished
for (my $i = 0; $i <= $self->{MAX_DEPTH}; $i++) {
push @{$self->{STACK}}, [];
@@ -2495,6 +2777,10 @@ sub new {
sub next {
my $self = shift;
+ my $closeAllPages=shift;
+ if ($self->{FINISHED}) {
+ return END_ITERATOR();
+ }
# If we want to return the top-level map object, and haven't yet,
# do so.
@@ -2505,7 +2791,7 @@ sub next {
if ($self->{RECURSIVE_ITERATOR_FLAG}) {
# grab the next from the recursive iterator
- my $next = $self->{RECURSIVE_ITERATOR}->next();
+ my $next = $self->{RECURSIVE_ITERATOR}->next($closeAllPages);
# is it a begin or end map? If so, update the depth
if ($next == BEGIN_MAP() ) { $self->{RECURSIVE_DEPTH}++; }
@@ -2555,6 +2841,7 @@ sub next {
$self->{CURRENT_DEPTH}--;
return END_BRANCH();
} else {
+ $self->{FINISHED} = 1;
return END_MAP();
}
}
@@ -2618,7 +2905,7 @@ 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_map() &&
+ if ( ($self->{HERE}->is_sequence() || (!$closeAllPages && $self->{HERE}->is_page())) &&
(defined($self->{FILTER}->{$self->{HERE}->map_pc()}) xor $self->{CONDITION})) {
$self->{RECURSIVE_ITERATOR_FLAG} = 1;
my $firstResource = $self->{HERE}->map_start();
@@ -2636,7 +2923,7 @@ sub next {
my $browsePriv = $self->{HERE}->browsePriv();
if (!$self->{HERE}->src() ||
(!($browsePriv eq 'F') && !($browsePriv eq '2')) ) {
- return $self->next();
+ return $self->next($closeAllPages);
}
return $self->{HERE};
@@ -2688,7 +2975,7 @@ package Apache::lonnavmaps::DFSiterator;
# useful for pre-processing of some kind, and is in fact used by the main
# iterator that way, but that's about it.
# One could imagine merging this into the init routine of the main iterator,
-# but this might as well be left seperate, since it is possible some other
+# but this might as well be left separate, since it is possible some other
# use might be found for it. - Jeremy
# Unlike the main iterator, this DOES return all resources, even blank ones.
@@ -3051,9 +3338,9 @@ sub symb {
my $self=shift;
(my $first, my $second) = $self->{ID} =~ /(\d+).(\d+)/;
my $symbSrc = &Apache::lonnet::declutter($self->src());
- return &Apache::lonnet::declutter(
- $self->navHash('map_id_'.$first))
+ my $symb = &Apache::lonnet::declutter($self->navHash('map_id_'.$first))
. '___' . $second . '___' . $symbSrc;
+ return &Apache::lonnet::symbclean($symb);
}
sub title {
my $self=shift;
@@ -3105,6 +3392,15 @@ Returns true if the resource is a sequen
=cut
+sub hasResource {
+ my $self = shift;
+ return $self->{NAV_MAP}->hasResource(@_);
+}
+
+sub retrieveResources {
+ my $self = shift;
+ return $self->{NAV_MAP}->retrieveResources(@_);
+}
sub is_html {
my $self=shift;
@@ -3121,7 +3417,15 @@ sub is_page {
sub is_problem {
my $self=shift;
my $src = $self->src();
- return ($src =~ /problem$/);
+ return ($src =~ /\.(problem|exam|quiz|assess|survey|form|library)$/)
+}
+sub contains_problem {
+ my $self=shift;
+ if ($self->is_page()) {
+ my $hasProblem=$self->hasResource($self,sub { $_[0]->is_problem() },1);
+ return $hasProblem;
+ }
+ return 0;
}
sub is_sequence {
my $self=shift;
@@ -3129,6 +3433,23 @@ sub is_sequence {
return $self->navHash("is_map_", 1) &&
$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') {
+ return 1;
+ }
+ if ($self->src() =~ /\.(survey)$/) {
+ return 1;
+ }
+ return 0;
+}
+
+sub is_empty_sequence {
+ my $self=shift;
+ my $src = $self->src();
+ return !$self->is_page() && $self->navHash("is_map_", 1) && !$self->navHash("map_type_" . $self->map_pc());
+}
# Private method: Shells out to the parmval in the nav map, handler parts.
sub parmval {
@@ -3299,11 +3620,17 @@ sub 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'};
}
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); }
+ }
return $self->parmval("duedate", $part);
}
sub maxtries {
@@ -3320,7 +3647,7 @@ sub opendate {
}
sub problemstatus {
(my $self, my $part) = @_;
- return $self->parmval("problemstatus", $part);
+ return lc $self->parmval("problemstatus", $part);
}
sub sig {
(my $self, my $part) = @_;
@@ -3488,12 +3815,21 @@ sub multipart {
return $self->countParts() > 1;
}
+sub singlepart {
+ my $self = shift;
+ return $self->countParts() == 1;
+}
+
sub responseType {
my $self = shift;
my $part = shift;
$self->extractParts();
- return $self->{RESPONSE_TYPES}->{$part};
+ if (defined($self->{RESPONSE_TYPES}->{$part})) {
+ return @{$self->{RESPONSE_TYPES}->{$part}};
+ } else {
+ return undef;
+ }
}
sub responseIds {
@@ -3501,7 +3837,11 @@ sub responseIds {
my $part = shift;
$self->extractParts();
- return $self->{RESPONSE_IDS}->{$part};
+ if (defined($self->{RESPONSE_IDS}->{$part})) {
+ return @{$self->{RESPONSE_IDS}->{$part}};
+ } else {
+ return undef;
+ }
}
# Private function: Extracts the parts information, both part names and
@@ -3518,32 +3858,44 @@ sub extractParts {
# Retrieve part count, if this is a problem
if ($self->is_problem()) {
+ my $partorder = &Apache::lonnet::metadata($self->src(), 'partorder');
my $metadata = &Apache::lonnet::metadata($self->src(), 'packages');
- if (!$metadata) {
- $self->{RESOURCE_ERROR} = 1;
- $self->{PARTS} = [];
- $self->{PART_TYPE} = {};
- return;
- }
- foreach (split(/\,/,$metadata)) {
- if ($_ =~ /^part_(.*)$/) {
- 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());
- }
- # check to see if part is turned off.
-
- if (!Apache::loncommon::check_if_partid_hidden($part, $self->symb())) {
- $parts{$part} = 1;
- }
- }
+ if ($partorder) {
+ my @parts;
+ for my $part (split (/,/,$partorder)) {
+ if (!Apache::loncommon::check_if_partid_hidden($part, $self->symb())) {
+ push @parts, $part;
+ $parts{$part} = 1;
+ }
+ }
+ $self->{PARTS} = \@parts;
+ } else {
+ if (!$metadata) {
+ $self->{RESOURCE_ERROR} = 1;
+ $self->{PARTS} = [];
+ $self->{PART_TYPE} = {};
+ return;
+ }
+ foreach (split(/\,/,$metadata)) {
+ if ($_ =~ /^part_(.*)$/) {
+ 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());
+ }
+
+ # check to see if part is turned off.
+
+ if (!Apache::loncommon::check_if_partid_hidden($part, $self->symb())) {
+ $parts{$part} = 1;
+ }
+ }
+ }
+ my @sortedParts = sort keys %parts;
+ $self->{PARTS} = \@sortedParts;
}
-
- my @sortedParts = sort keys %parts;
- $self->{PARTS} = \@sortedParts;
my %responseIdHash;
my %responseTypeHash;
@@ -3555,7 +3907,7 @@ sub extractParts {
}
# Now, the unfortunate thing about this is that parts, part name, and
- # response if are delimited by underscores, but both the part
+ # response id are delimited by underscores, but both the part
# name and response id can themselves have underscores in them.
# So we have to use our knowlege of part names to figure out
# where the part names begin and end, and even then, it is possible
@@ -3567,7 +3919,6 @@ sub extractParts {
my $partIdSoFar = '';
my @partChunks = split /_/, $partStuff;
my $i = 0;
-
for ($i = 0; $i < scalar(@partChunks); $i++) {
if ($partIdSoFar) { $partIdSoFar .= '_'; }
$partIdSoFar .= $partChunks[$i];
@@ -3575,13 +3926,25 @@ sub extractParts {
my @otherChunks = @partChunks[$i+1..$#partChunks];
my $responseId = join('_', @otherChunks);
push @{$responseIdHash{$partIdSoFar}}, $responseId;
- $responseTypeHash{$partIdSoFar} = $responseType;
- last;
+ push @{$responseTypeHash{$partIdSoFar}}, $responseType;
}
}
}
}
-
+ my $resorder = &Apache::lonnet::metadata($self->src(),'responseorder');
+ if ($resorder) {
+ my @resorder=split(/,/,$resorder);
+ foreach my $part (keys(%responseIdHash)) {
+ my %resids = map { ($_,1) } @{ $responseIdHash{$part} };
+ my @neworder;
+ foreach my $possibleid (@resorder) {
+ if (exists($resids{$possibleid})) {
+ push(@neworder,$possibleid);
+ }
+ }
+ $responseIdHash{$part}=\@neworder;
+ }
+ }
$self->{RESPONSE_IDS} = \%responseIdHash;
$self->{RESPONSE_TYPES} = \%responseTypeHash;
}
@@ -3773,7 +4136,7 @@ sub getCompletionStatus {
my $status = $self->queryRestoreHash('solved', shift);
- # Left as seperate if statements in case we ever do more with this
+ # Left as separate if statements in case we ever do more with this
if ($status eq 'correct_by_student') {return $self->CORRECT;}
if ($status eq 'correct_by_override') {return $self->CORRECT_BY_OVERRIDE; }
if ($status eq 'incorrect_attempted') {return $self->INCORRECT; }
@@ -3891,9 +4254,15 @@ sub status {
# dimension and 5 entries on the other, which we want to colorize,
# plus network failure and "no date data at all".
+ #if ($self->{RESOURCE_ERROR}) { return NETWORK_FAILURE; }
if ($completionStatus == NETWORK_FAILURE) { return NETWORK_FAILURE; }
- my $suppressFeedback = lc($self->parmval("problemstatus", $part)) eq 'no';
+ my $suppressFeedback = $self->problemstatus($part) eq 'no';
+ # If there's an answer date and we're past it, don't
+ # suppress the feedback; student should know
+ if ($self->answerdate($part) && $self->answerdate($part) < time()) {
+ $suppressFeedback = 0;
+ }
# There are a few whole rows we can dispose of:
if ($completionStatus == CORRECT ||
@@ -3919,7 +4288,7 @@ sub status {
if ($dateStatus == PAST_DUE_ANSWER_LATER ||
$dateStatus == PAST_DUE_NO_ANSWER ) {
- return $dateStatus;
+ return $suppressFeedback ? ANSWER_SUBMITTED : $dateStatus;
}
if ($dateStatus == ANSWER_OPEN) {
@@ -3937,7 +4306,7 @@ sub status {
if ($completionStatus == INCORRECT || $completionStatus == INCORRECT_BY_OVERRIDE) {
# and there are TRIES LEFT:
if ($self->tries($part) < $self->maxtries($part) || !$self->maxtries($part)) {
- return TRIES_LEFT;
+ return $suppressFeedback ? ANSWER_SUBMITTED : TRIES_LEFT;
}
return $suppressFeedback ? ANSWER_SUBMITTED : INCORRECT; # otherwise, return orange; student can't fix this
}
@@ -3946,6 +4315,96 @@ sub status {
return OPEN;
}
+sub CLOSED { return 23; }
+sub ERROR { return 24; }
+
+=pod
+
+B
+
+Convenience method B provides a "simple status" for the resource.
+"Simple status" corresponds to "which icon is shown on the
+Navmaps". There are six "simple" statuses:
+
+=over 4
+
+=item * B: The problem is currently closed. (No icon shown.)
+
+=item * B: The problem is open and unattempted.
+
+=item * B: The problem is correct for any reason.
+
+=item * B: The problem is incorrect and can still be
+completed successfully.
+
+=item * B: The problem has been attempted, but the student
+does not know if they are correct. (The ellipsis icon.)
+
+=item * B: There is an error retrieving information about this
+problem.
+
+=back
+
+=cut
+
+# This hash maps the composite status to this simple status, and
+# can be used directly, if you like
+my %compositeToSimple =
+ (
+ NETWORK_FAILURE() => ERROR,
+ NOTHING_SET() => CLOSED,
+ CORRECT() => CORRECT,
+ EXCUSED() => CORRECT,
+ PAST_DUE_NO_ANSWER() => INCORRECT,
+ PAST_DUE_ANSWER_LATER() => INCORRECT,
+ ANSWER_OPEN() => INCORRECT,
+ OPEN_LATER() => CLOSED,
+ TRIES_LEFT() => OPEN,
+ INCORRECT() => INCORRECT,
+ OPEN() => OPEN,
+ ATTEMPTED() => ATTEMPTED,
+ ANSWER_SUBMITTED() => ATTEMPTED
+ );
+
+sub simpleStatus {
+ my $self = shift;
+ my $part = shift;
+ my $status = $self->status($part);
+ return $compositeToSimple{$status};
+}
+
+=pod
+
+B will return an array reference containing, in
+this order, the number of OPEN, CLOSED, CORRECT, INCORRECT, ATTEMPTED,
+and ERROR parts the given problem has.
+
+=cut
+
+# This maps the status to the slot we want to increment
+my %statusToSlotMap =
+ (
+ OPEN() => 0,
+ CLOSED() => 1,
+ CORRECT() => 2,
+ INCORRECT() => 3,
+ ATTEMPTED() => 4,
+ ERROR() => 5
+ );
+
+sub statusToSlot { return $statusToSlotMap{shift()}; }
+
+sub simpleStatusCount {
+ my $self = shift;
+
+ my @counts = (0, 0, 0, 0, 0, 0, 0);
+ foreach my $part (@{$self->parts()}) {
+ $counts[$statusToSlotMap{$self->simpleStatus($part)}]++;
+ }
+
+ return \@counts;
+}
+
=pod
B