--- loncom/interface/lonnavmaps.pm 2003/05/16 14:17:08 1.191
+++ loncom/interface/lonnavmaps.pm 2003/06/25 18:32:06 1.212
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# Navigate Maps Handler
#
-# $Id: lonnavmaps.pm,v 1.191 2003/05/16 14:17:08 bowersj2 Exp $
+# $Id: lonnavmaps.pm,v 1.212 2003/06/25 18:32:06 bowersj2 Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -47,6 +47,7 @@ use Apache::Constants qw(:common :http);
use Apache::loncommon();
use Apache::lonmenu();
use POSIX qw (floor strftime);
+use Data::Dumper; # for debugging, not always used
# symbolic constants
sub SYMB { return 1; }
@@ -71,7 +72,7 @@ my %statusIconMap =
$resObj->TRIES_LEFT => 'navmap.open.gif',
$resObj->INCORRECT => 'navmap.wrong.gif',
$resObj->OPEN => 'navmap.open.gif',
- $resObj->ATTEMPTED => 'navmap.open.gif',
+ $resObj->ATTEMPTED => 'navmap.ellipsis.gif',
$resObj->ANSWER_SUBMITTED => '' );
my %iconAltTags =
@@ -91,7 +92,10 @@ my %colormap =
$resObj->TRIES_LEFT => '',
$resObj->INCORRECT => '',
$resObj->OPEN => '',
- $resObj->NOTHING_SET => '' );
+ $resObj->NOTHING_SET => '',
+ $resObj->ATTEMPTED => '',
+ $resObj->ANSWER_SUBMITTED => ''
+ );
# And a special case in the nav map; what to do when the assignment
# is not yet done and due in less then 24 hours
my $hurryUpColor = "#FF0000";
@@ -160,7 +164,6 @@ sub real_handler {
# Now that we've displayed some stuff to the user, init the navmap
$navmap->init();
- $r->print('
');
$r->rflush();
# Check that it's defined
@@ -243,25 +246,28 @@ sub real_handler {
}
} else {
$r->print("" .
- "Go To My First Homework Problem
");
+ "Go To My First Homework Problem ");
}
my $suppressEmptySequences = 0;
my $filterFunc = undef;
+ my $resource_no_folder_link = 0;
+
# Display only due homework.
my $showOnlyHomework = 0;
if ($ENV{QUERY_STRING} eq 'showOnlyHomework') {
$showOnlyHomework = 1;
$suppressEmptySequences = 1;
$filterFunc = sub { my $res = shift;
- return $res->completable() || $res->is_sequence();
+ return $res->completable() || $res->is_map();
};
$r->print("
Uncompleted Homework
");
$ENV{'form.filter'} = '';
$ENV{'form.condition'} = 1;
+ $resource_no_folder_link = 1;
} else {
$r->print("" .
- "Show Only Uncompleted Homework
");
+ "Show Only Uncompleted Homework ");
}
# renderer call
@@ -271,6 +277,7 @@ sub real_handler {
'suppressNavmap' => 1,
'suppressEmptySequences' => $suppressEmptySequences,
'filterFunc' => $filterFunc,
+ 'resource_no_folder_link' => $resource_no_folder_link,
'r' => $r};
my $render = render($renderArgs);
$navmap->untieHashes();
@@ -377,7 +384,7 @@ sub getDescription {
return "Excused by instructor";
}
if ($status == $res->ATTEMPTED) {
- return "Not yet graded.";
+ return "Answer submitted, not yet graded.";
}
if ($status == $res->TRIES_LEFT) {
my $tries = $res->tries($part);
@@ -409,7 +416,7 @@ sub dueInLessThen24Hours {
my $part = shift;
my $status = $res->status($part);
- return ($status == $res->OPEN() || $status == $res->ATTEMPTED() ||
+ return ($status == $res->OPEN() ||
$status == $res->TRIES_LEFT()) &&
$res->duedate() && $res->duedate() < time()+(24*60*60) &&
$res->duedate() > time();
@@ -551,9 +558,9 @@ Apache::lonnavmap - Subroutines to handl
The main handler generates the navigational listing for the course,
the other objects export this information in a usable fashion for
-other modules
+other modules.
-=head1 Object: render
+=head1 Subroutine: render
The navmap renderer package provides a sophisticated rendering of the
standard navigation maps interface into HTML. The provided nav map
@@ -567,7 +574,7 @@ understand then "undef, undef, undef, 1,
undef, 0" when you mostly want default behaviors.
The package provides a function called 'render', called as
-Apache::lonnavmaps::renderer->render({}).
+Apache::lonnavmaps::render({}).
=head2 Overview of Columns
@@ -575,7 +582,7 @@ The renderer will build an HTML table fo
it. The table is consists of several columns, and a row for each
resource (or possibly each part). You tell the renderer how many
columns to create and what to place in each column, optionally using
-one or more of the preparent columns, and the renderer will assemble
+one or more of the prepared columns, and the renderer will assemble
the table.
Any additional generally useful column types should be placed in the
@@ -593,7 +600,7 @@ argument hash passed to the renderer, an
be inserted into the HTML representation as it.
The pre-packaged column names are refered to by constants in the
-Apache::lonnavmaps::renderer namespace. The following currently exist:
+Apache::lonnavmaps namespace. The following currently exist:
=over 4
@@ -601,7 +608,7 @@ Apache::lonnavmaps::renderer namespace.
The general info about the resource: Link, icon for the type, etc. The
first column in the standard nav map display. This column also accepts
-the following parameter in the renderer hash:
+the following parameters in the renderer hash:
=over 4
@@ -834,15 +841,22 @@ sub render_resource {
my $linkopen = "";
my $linkclose = "";
- # Default icon: HTML page
- my $icon = "";
+ # Default icon: unknown page
+ my $icon = "";
if ($resource->is_problem()) {
- if ($part eq "" || $params->{'condensed'}) {
+ if ($part eq '0' || $params->{'condensed'}) {
$icon = '';
} else {
$icon = $params->{'indentString'};
}
+ } else {
+ my $curfext= (split (/\./,$resource->src))[-1];
+ my $embstyle = &Apache::loncommon::fileembstyle($curfext);
+ # The unless conditional that follows is a bit of overkill
+ if (!(!defined($embstyle) || $embstyle eq 'unk' || $embstyle eq 'hdn')) {
+ $icon = "";
+ }
}
# Display the correct map icon to open or shut map
@@ -853,8 +867,10 @@ sub render_resource {
$nowOpen = !$nowOpen;
}
+ my $folderType = $resource->is_sequence() ? 'folder' : 'page';
+
if (!$params->{'resource_no_folder_link'}) {
- $icon = 'navmap.folder.' . ($nowOpen ? 'closed' : 'open') . '.gif';
+ $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif';
$icon = "";
$linkopen = "";
} else {
# Don't allow users to manipulate folder
- $icon = 'navmap.folder.' . ($nowOpen ? 'closed' : 'open') .
+ $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') .
'.nomanip.gif';
$icon = "";
@@ -908,7 +924,7 @@ sub render_resource {
$params->{'displayedHereMarker'} = 1;
}
- if ($resource->is_problem() && $part ne "" &&
+ if ($resource->is_problem() && $part ne '0' &&
!$params->{'condensed'}) {
$partLabel = " (Part $part)";
$title = "";
@@ -918,7 +934,8 @@ sub render_resource {
$nonLinkedText .= ' (' . $resource->countParts() . ' parts)';
}
- if (!$params->{'resource_nolink'}) {
+ if (!$params->{'resource_nolink'} && $src !~ /^\/uploaded\// &&
+ !$resource->is_sequence()) {
$result .= " $curMarkerBegin$title$partLabel$curMarkerEnd $nonLinkedText";
} else {
$result .= " $curMarkerBegin$title$partLabel$curMarkerEnd $nonLinkedText";
@@ -965,6 +982,10 @@ sub render_communication_status {
}
}
+ if ($params->{'multipart'} && $part != '0') {
+ $discussionHTML = $feedbackHTML = $errorHTML = '';
+ }
+
return "$discussionHTML$feedbackHTML$errorHTML | ";
}
@@ -1019,8 +1040,8 @@ sub render_long_status {
if ($resource->is_map() && advancedUser() && $resource->randompick()) {
$result .= '(randomly select ' . $resource->randompick() .')';
}
-
- $result .= " \n";
+
+ $result .= "\n";
return $result;
}
@@ -1290,7 +1311,7 @@ sub render {
if (ref($curRes)) {
# Parallel pre-processing: Do sequences have non-filtered-out children?
- if ($curRes->is_sequence()) {
+ 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.
@@ -1353,7 +1374,7 @@ sub render {
}
# If this is an empty sequence and we're filtering them, continue on
- if ($curRes->is_sequence() && $args->{'suppressEmptySequences'} &&
+ if ($curRes->is_map() && $args->{'suppressEmptySequences'} &&
!$curRes->{DATA}->{HAS_VISIBLE_CHILDREN}) {
next;
}
@@ -1373,7 +1394,7 @@ sub render {
# Decide what parts to show.
if ($curRes->is_problem() && $showParts) {
@parts = @{$curRes->parts()};
- $args->{'multipart'} = scalar(@parts) > 1;
+ $args->{'multipart'} = $curRes->multipart();
if ($condenseParts) { # do the condensation
if (!$curRes->opendate("0")) {
@@ -1382,13 +1403,13 @@ sub render {
}
if (!$args->{'condensed'}) {
# Decide whether to condense based on similarity
- my $status = $curRes->status($parts[1]);
- my $due = $curRes->duedate($parts[1]);
- my $open = $curRes->opendate($parts[1]);
+ my $status = $curRes->status($parts[0]);
+ my $due = $curRes->duedate($parts[0]);
+ my $open = $curRes->opendate($parts[0]);
my $statusAllSame = 1;
my $dueAllSame = 1;
my $openAllSame = 1;
- for (my $i = 2; $i < scalar(@parts); $i++) {
+ for (my $i = 1; $i < scalar(@parts); $i++) {
if ($curRes->status($parts[$i]) != $status){
$statusAllSame = 0;
}
@@ -1409,25 +1430,30 @@ sub render {
if (($statusAllSame && defined($condenseStatuses{$status})) ||
($dueAllSame && $status == $curRes->OPEN && $statusAllSame)||
($openAllSame && $status == $curRes->OPEN_LATER && $statusAllSame) ){
- @parts = ();
+ @parts = ($parts[0]);
$args->{'condensed'} = 1;
}
-
}
+ # Multipart problem with one part: always "condense" (happens
+ # to match the desirable behavior)
+ if ($curRes->countParts() == 1) {
+ @parts = ($parts[0]);
+ $args->{'condensed'} = 1;
+ }
}
}
# If the multipart problem was condensed, "forget" it was multipart
if (scalar(@parts) == 1) {
$args->{'multipart'} = 0;
+ } else {
+ # Add part 0 so we display it correctly.
+ unshift @parts, '0';
}
# Now, we've decided what parts to show. Loop through them and
# show them.
- foreach my $part ('', @parts) {
- if ($part eq '0') {
- next;
- }
+ foreach my $part (@parts) {
$rownum ++;
my $backgroundColor = $backgroundColors[$rownum % scalar(@backgroundColors)];
@@ -1475,6 +1501,16 @@ sub render {
}
} 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 '';
+ }
+ }
}
# Print out the part that jumps to #curloc if it exists
@@ -1530,7 +1566,7 @@ You must obtain resource objects through
=over 4
=item * B(navHashFile, parmHashFile, genCourseAndUserOptions,
- genMailDiscussStatus):
+ genMailDiscussStatus, getUserData):
Binds a new navmap object to the compiled nav map hash and parm hash
given as filenames. genCourseAndUserOptions is a flag saying whether
@@ -1541,7 +1577,8 @@ documentation. genMailDiscussStatus caus
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.
+try to use the other methods otherwise. getUserData, if true, will
+retreive the user's performance data for various problems.
=item * B(first, finish, filter, condition):
@@ -1562,6 +1599,7 @@ sub new {
$self->{PARM_HASH_FILE} = shift;
$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.
@@ -1661,7 +1699,7 @@ sub init {
my %emailstatus = &Apache::lonnet::dump('email_status');
my $logoutTime = $emailstatus{'logout'};
my $courseLeaveTime = $emailstatus{'logout_'.$ENV{'request.course.id'}};
- $self->{LAST_CHECK} = ($courseLeaveTime < $logoutTime ?
+ $self->{LAST_CHECK} = (($courseLeaveTime > $logoutTime) ?
$courseLeaveTime : $logoutTime);
my %discussiontime = &Apache::lonnet::dump('discussiontimes',
$cdom, $cnum);
@@ -1699,7 +1737,15 @@ sub init {
$self->{DISCUSSION_TIME} = \%discussiontime;
$self->{EMAIL_STATUS} = \%emailstatus;
- }
+ }
+
+ 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->{PARM_CACHE} = {};
$self->{INITED} = 1;
@@ -1778,6 +1824,16 @@ object for that resource. This method, o
(as in the resource object) is the only proper way to obtain a
resource object.
+=item * B(symb):
+
+Based on the symb of the resource, get a resource object for that
+resource. This is one of the proper ways to get a resource object.
+
+=item * B(map_pc):
+
+Based on the map_pc of the resource, get a resource object for
+the given map. This is one of the proper ways to get a resource object.
+
=cut
# The strategy here is to cache the resource objects, and only construct them
@@ -1808,6 +1864,14 @@ sub getBySymb {
return $self->getById($map->map_pc() . '.' . $id);
}
+sub getByMapPc {
+ my $self = shift;
+ my $map_pc = shift;
+ my $map_id = $self->{NAV_HASH}->{'map_id_' . $map_pc};
+ $map_id = $self->{NAV_HASH}->{'ids_' . $map_id};
+ return $self->getById($map_id);
+}
+
=pod
=item * B():
@@ -2102,7 +2166,7 @@ corresponds to where you want the iterat
navmap->finishResource(). filterHash is a hash used as a set
containing strings representing the resource IDs, defaulting to
empty. Condition is a 1 or 0 that sets what to do with the filter
-hash: If a 0, then only resource that exist IN the filterHash will be
+hash: If a 0, then only resources that exist IN the filterHash will be
recursed on. If it is a 1, only resources NOT in the filterHash will
be recursed on. Defaults to 0. forceTop is a boolean value. If it is
false (default), the iterator will only return the first level of map
@@ -2174,11 +2238,6 @@ sub min {
if ($a < $b) { return $a; } else { return $b; }
}
-# In the CVS repository, documentation of this algorithm is included
-# in /doc/lonnavdocs, as a PDF and .tex source. Markers like **1**
-# will reference the same location in the text as the part of the
-# algorithm is running through.
-
sub new {
# magic invocation to create a class instance
my $proto = shift;
@@ -2234,6 +2293,9 @@ sub new {
# that isn't just a redirector.
my $resource; my $resourceCount = 0;
+ # Documentation on this algorithm can be found in the CVS repository at
+ # /docs/lonnavdocs; these "**#**" markers correspond to documentation
+ # in that file.
# **1**
foreach my $pass (@iterations) {
@@ -2912,7 +2974,8 @@ sub is_map { my $self=shift; return defi
sub is_page {
my $self=shift;
my $src = $self->src();
- return ($src =~ /page$/);
+ return $self->navHash("is_map_", 1) &&
+ $self->navHash("map_type_" . $self->map_pc()) eq 'page';
}
sub is_problem {
my $self=shift;
@@ -2922,7 +2985,8 @@ sub is_problem {
sub is_sequence {
my $self=shift;
my $src = $self->src();
- return ($src =~ /sequence$/);
+ return $self->navHash("is_map_", 1) &&
+ $self->navHash("map_type_" . $self->map_pc()) eq 'sequence';
}
# Private method: Shells out to the parmval in the nav map, handler parts.
@@ -2997,8 +3061,6 @@ sub map_type {
return $self->navHash("map_type_$pc", 0);
}
-
-
#####
# Property queries
#####
@@ -3036,6 +3098,11 @@ Get the Client IP/Name Access Control in
Get the answer-reveal date for the problem.
+=item * B:
+
+Gets the awarded value for the problem part. Requires genUserData set to
+true when the navmap object was created.
+
=item * B:
Get the due date for the problem.
@@ -3089,7 +3156,11 @@ sub answerdate {
}
return $self->parmval("answerdate", $part);
}
-sub awarded { my $self = shift; return $self->queryRestoreHash('awarded', shift); }
+sub awarded {
+ my $self = shift; my $part = shift;
+ if (!defined($part)) { $part = '0'; }
+ return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.awarded'};
+}
sub duedate {
(my $self, my $part) = @_;
return $self->parmval("duedate", $part);
@@ -3130,7 +3201,12 @@ sub type {
}
sub weight {
my $self = shift; my $part = shift;
- return $self->parmval("weight", $part);
+ if (!defined($part)) { $part = '0'; }
+ return &Apache::lonnet::EXT('resource.'.$part.'.weight',
+ $self->symb(), $ENV{'user.domain'},
+ $ENV{'user.name'},
+ $ENV{'request.course.sec'});
+
}
# Multiple things need this
@@ -3210,27 +3286,30 @@ sub getErrors {
=item * B():
Returns a list reference containing sorted strings corresponding to
-each part of the problem. To count the number of parts, use the list
-in a scalar context, and subtract one if greater than two. (One part
-problems have a part 0. Multi-parts have a part 0, plus a part for
-each part. Filtering part 0 if you want it is up to you.)
+each part of the problem. Single part problems have only a part '0'.
+Multipart problems do not return their part '0', since they typically
+do not really matter.
=item * B():
Returns the number of parts of the problem a student can answer. Thus,
for single part problems, returns 1. For multipart, it returns the
-number of parts in the problem, not including psuedo-part 0. Thus,
-B may return an array with more parts in it then countParts
-might lead you to believe.
+number of parts in the problem, not including psuedo-part 0.
+
+=item * B():
+
+Returns true if the problem is multipart, false otherwise. Use this instead
+of countParts if all you want is multipart/not multipart.
=item * B($part):
Returns the response type of the part, without the word "response" on the
end. Example return values: 'string', 'essay', 'numeric', etc.
-=item * B($part):
+=item * B($part):
-Retreives the response ID for the given part, which may be an empty string.
+Retreives the response IDs for the given part as an array reference containing
+strings naming the response IDs. This may be empty.
=back
@@ -3239,7 +3318,7 @@ Retreives the response ID for the given
sub parts {
my $self = shift;
- if ($self->ext) { return ['0']; }
+ if ($self->ext) { return []; }
$self->extractParts();
return $self->{PARTS};
@@ -3263,6 +3342,11 @@ sub countParts {
return scalar(@{$parts}); # + $delta;
}
+sub multipart {
+ my $self = shift;
+ return $self->countParts() > 1;
+}
+
sub responseType {
my $self = shift;
my $part = shift;
@@ -3271,7 +3355,7 @@ sub responseType {
return $self->{RESPONSE_TYPE}->{$part};
}
-sub responseId {
+sub responseIds {
my $self = shift;
my $part = shift;