--- loncom/interface/lonnavmaps.pm 2003/06/10 15:45:16 1.195
+++ loncom/interface/lonnavmaps.pm 2003/09/24 15:02:34 1.233
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# Navigate Maps Handler
#
-# $Id: lonnavmaps.pm,v 1.195 2003/06/10 15:45:16 bowersj2 Exp $
+# $Id: lonnavmaps.pm,v 1.233 2003/09/24 15:02:34 matthew Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -46,6 +46,7 @@ use strict;
use Apache::Constants qw(:common :http);
use Apache::loncommon();
use Apache::lonmenu();
+use Apache::lonlocal;
use POSIX qw (floor strftime);
use Data::Dumper; # for debugging, not always used
@@ -61,19 +62,14 @@ my $resObj = "Apache::lonnavmaps::resour
# Keep these mappings in sync with lonquickgrades, which uses the colors
# instead of the icons.
my %statusIconMap =
- ( $resObj->NETWORK_FAILURE => '',
- $resObj->NOTHING_SET => '',
- $resObj->CORRECT => 'navmap.correct.gif',
- $resObj->EXCUSED => 'navmap.correct.gif',
- $resObj->PAST_DUE_NO_ANSWER => 'navmap.wrong.gif',
- $resObj->PAST_DUE_ANSWER_LATER => 'navmap.wrong.gif',
- $resObj->ANSWER_OPEN => 'navmap.wrong.gif',
- $resObj->OPEN_LATER => '',
- $resObj->TRIES_LEFT => 'navmap.open.gif',
- $resObj->INCORRECT => 'navmap.wrong.gif',
- $resObj->OPEN => 'navmap.open.gif',
- $resObj->ATTEMPTED => 'navmap.open.gif',
- $resObj->ANSWER_SUBMITTED => '' );
+ (
+ $resObj->CLOSED => '',
+ $resObj->OPEN => 'navmap.open.gif',
+ $resObj->CORRECT => 'navmap.correct.gif',
+ $resObj->INCORRECT => 'navmap.wrong.gif',
+ $resObj->ATTEMPTED => 'navmap.ellipsis.gif',
+ $resObj->ERROR => ''
+ );
my %iconAltTags =
( 'navmap.correct.gif' => 'Correct',
@@ -92,7 +88,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";
@@ -108,9 +107,9 @@ sub real_handler {
# Handle header-only request
if ($r->header_only) {
if ($ENV{'browser.mathml'}) {
- $r->content_type('text/xml');
+ &Apache::loncommon::content_type($r,'text/xml');
} else {
- $r->content_type('text/html');
+ &Apache::loncommon::content_type($r,'text/html');
}
$r->send_http_header;
return OK;
@@ -118,17 +117,15 @@ sub real_handler {
# Send header, don't cache this page
if ($ENV{'browser.mathml'}) {
- $r->content_type('text/xml');
+ &Apache::loncommon::content_type($r,'text/xml');
} else {
- $r->content_type('text/html');
+ &Apache::loncommon::content_type($r,'text/html');
}
&Apache::loncommon::no_cache($r);
$r->send_http_header;
# Create the nav map
- my $navmap = Apache::lonnavmaps::navmap->new(
- $ENV{"request.course.fn"}.".db",
- $ENV{"request.course.fn"}."_parms.db", 1, 1);
+ my $navmap = Apache::lonnavmaps::navmap->new();
if (!defined($navmap)) {
@@ -138,7 +135,7 @@ sub real_handler {
}
$r->print("
\n");
- $r->print("Navigate Course Contents ");
+ $r->print("".&mt('Navigate Course Contents')." ");
# ------------------------------------------------------------ Get query string
&Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['register']);
@@ -158,12 +155,6 @@ sub real_handler {
$r->rflush();
- # Now that we've displayed some stuff to the user, init the navmap
- $navmap->init();
-
- $r->print(' ');
- $r->rflush();
-
# Check that it's defined
if (!($navmap->courseMapDefined())) {
$r->print('Coursemap undefined. ' .
@@ -173,23 +164,17 @@ sub real_handler {
# See if there's only one map in the top-level, if we don't
# already have a filter... if so, automatically display it
+ # (older code; should use retrieveResources)
if ($ENV{QUERY_STRING} !~ /filter/) {
my $iterator = $navmap->getIterator(undef, undef, undef, 0);
- my $depth = 1;
- $iterator->next();
- my $curRes = $iterator->next();
+ my $curRes;
my $sequenceCount = 0;
my $sequenceId;
- while ($depth > 0) {
- if ($curRes == $iterator->BEGIN_MAP()) { $depth++; }
- if ($curRes == $iterator->END_MAP()) { $depth--; }
-
+ while ($curRes = $iterator->next()) {
if (ref($curRes) && $curRes->is_sequence()) {
$sequenceCount++;
$sequenceId = $curRes->map_pc();
}
-
- $curRes = $iterator->next();
}
if ($sequenceCount == 1) {
@@ -207,16 +192,11 @@ sub real_handler {
$jumpToFirstHomework = 1;
# Find the next homework problem that they can do.
my $iterator = $navmap->getIterator(undef, undef, undef, 1);
- my $depth = 1;
- $iterator->next();
- my $curRes = $iterator->next();
+ my $curRes;
my $foundDoableProblem = 0;
my $problemRes;
- while ($depth > 0 && !$foundDoableProblem) {
- if ($curRes == $iterator->BEGIN_MAP()) { $depth++; }
- if ($curRes == $iterator->END_MAP()) { $depth--; }
-
+ while (($curRes = $iterator->next()) && !$foundDoableProblem) {
if (ref($curRes) && $curRes->is_problem()) {
my $status = $curRes->status();
if ($curRes->completable()) {
@@ -234,8 +214,6 @@ sub real_handler {
$ENV{'form.postsymb'} = $curRes->symb();
}
}
- } continue {
- $curRes = $iterator->next();
}
# If we found no problems, print a note to that effect.
@@ -244,25 +222,28 @@ sub real_handler {
}
} else {
$r->print("" .
- "Go To My First Homework Problem ");
+ &mt("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
");
+ $r->print("".&mt("Uncompleted Homework")."
");
$ENV{'form.filter'} = '';
$ENV{'form.condition'} = 1;
+ $resource_no_folder_link = 1;
} else {
$r->print("" .
- "Show Only Uncompleted Homework ");
+ &mt("Show Only Uncompleted Homework")." ");
}
# renderer call
@@ -272,6 +253,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();
@@ -280,7 +262,7 @@ sub real_handler {
# user knows there was no error.
if ($renderArgs->{'counter'} == 0) {
if ($showOnlyHomework) {
- $r->print("All homework is currently completed.
");
+ $r->print("".&mt("All homework is currently completed").".
");
} else { # both jumpToFirstHomework and normal use the same: course must be empty
$r->print("This course is empty.
");
}
@@ -350,35 +332,35 @@ sub getDescription {
my $status = $res->status($part);
if ($status == $res->NETWORK_FAILURE) {
- return "Having technical difficulties; please check status later";
+ return &mt("Having technical difficulties; please check status later");
}
if ($status == $res->NOTHING_SET) {
- return "Not currently assigned.";
+ return &mt("Not currently assigned.");
}
if ($status == $res->OPEN_LATER) {
return "Open " . timeToHumanString($res->opendate($part));
}
if ($status == $res->OPEN) {
if ($res->duedate($part)) {
- return "Due " . timeToHumanString($res->duedate($part));
+ return &mt("Due")." " .timeToHumanString($res->duedate($part));
} else {
- return "Open, no due date";
+ return &mt("Open, no due date");
}
}
if ($status == $res->PAST_DUE_ANSWER_LATER) {
- return "Answer open " . timeToHumanString($res->answerdate($part));
+ return &mt("Answer open")." " . timeToHumanString($res->answerdate($part));
}
if ($status == $res->PAST_DUE_NO_ANSWER) {
- return "Was due " . timeToHumanString($res->duedate($part));
+ return &mt("Was due")." " . timeToHumanString($res->duedate($part));
}
if ($status == $res->ANSWER_OPEN) {
- return "Answer available";
+ return &mt("Answer available");
}
if ($status == $res->EXCUSED) {
- return "Excused by instructor";
+ return &mt("Excused by instructor");
}
if ($status == $res->ATTEMPTED) {
- return "Not yet graded.";
+ return &mt("Answer submitted, not yet graded");
}
if ($status == $res->TRIES_LEFT) {
my $tries = $res->tries($part);
@@ -391,14 +373,14 @@ sub getDescription {
}
}
if ($res->duedate()) {
- return "Due " . timeToHumanString($res->duedate($part)) .
+ return &mt("Due")." " . timeToHumanString($res->duedate($part)) .
" $triesString";
} else {
- return "No due date $triesString";
+ return &mt("No due date")." $triesString";
}
}
if ($status == $res->ANSWER_SUBMITTED) {
- return 'Answer submitted';
+ return &mt('Answer submitted');
}
}
@@ -410,7 +392,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();
@@ -448,9 +430,11 @@ sub timeToHumanString {
my ($time) = @_;
# zero, '0' and blank are bad times
if (!$time) {
- return 'never';
+ return &mt('never');
}
-
+ unless (&Apache::lonlocal::current_language()=~/^en/) {
+ return localtime($time);
+ }
my $now = time();
my @time = localtime($time);
@@ -546,29 +530,74 @@ sub timeToHumanString {
=head1 NAME
-Apache::lonnavmap - Subroutines to handle and render the navigation maps
+Apache::lonnavmap - Subroutines to handle and render the navigation
+ maps
=head1 SYNOPSIS
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 OVERVIEW
+
+X When a user enters a course, LON-CAPA examines the
+course structure and caches it in what is often referred to as the
+"big hash" X. You can see it if you are logged into
+LON-CAPA, in a course, by going to /adm/test. (You may need to
+tweak the /home/httpd/lonTabs/htpasswd file to view it.) The
+content of the hash will be under the heading "Big Hash".
+
+Big Hash contains, among other things, how resources are related
+to each other (next/previous), what resources are maps, which
+resources are being chosen to not show to the student (for random
+selection), and a lot of other things that can take a lot of time
+to compute due to the amount of data that needs to be collected and
+processed.
+
+Apache::lonnavmaps provides an object model for manipulating this
+information in a higher-level fashion then directly manipulating
+the hash. It also provides access to several auxilary functions
+that aren't necessarily stored in the Big Hash, but are a per-
+resource sort of value, like whether there is any feedback on
+a given resource.
+
+Apache::lonnavmaps also abstracts away branching, and someday,
+conditions, for the times where you don't really care about those
+things.
+
+Apache::lonnavmaps also provides fairly powerful routines for
+rendering navmaps, and last but not least, provides the navmaps
+view for when the user clicks the NAV button.
+
+B: Apache::lonnavmaps I works for the "currently
+logged in user"; if you want things like "due dates for another
+student" lonnavmaps can not directly retrieve information like
+that. You need the EXT function. This module can still help,
+because many things, such as the course structure, are constant
+between users, and Apache::lonnavmaps can help by providing
+symbs for the EXT call.
+
+The rest of this file will cover the provided rendering routines,
+which can often be used without fiddling with the navmap object at
+all, then documents the Apache::lonnavmaps::navmap object, which
+is the key to accessing the Big Hash information, covers the use
+of the Iterator (which provides the logic for traversing the
+somewhat-complicated Big Hash data structure), documents the
+Apache::lonnavmaps::Resource objects that are returned by
+
+=head1 Subroutine: render
The navmap renderer package provides a sophisticated rendering of the
standard navigation maps interface into HTML. The provided nav map
handler is actually just a glorified call to this.
-Because of the large number of parameters this function presents,
+Because of the large number of parameters this function accepts,
instead of passing it arguments as is normal, pass it in an anonymous
-hash with the given options. This is because there is no obvious order
-you may wish to override these in and a hash is easier to read and
-understand then "undef, undef, undef, 1, undef, undef, renderButton,
-undef, 0" when you mostly want default behaviors.
+hash with the desired options.
The package provides a function called 'render', called as
-Apache::lonnavmaps::renderer->render({}).
+Apache::lonnavmaps::render({}).
=head2 Overview of Columns
@@ -576,7 +605,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,29 +622,33 @@ that takes a resource reference, a part
argument hash passed to the renderer, and returns a string that will
be inserted into the HTML representation as it.
+All other parameters are ways of either changing how the columns
+are printing, or which rows are shown.
+
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
-=item * B:
+=item * B:
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:
+first column in the standard nav map display. This column provides the
+indentation effect seen in the B screen. This column also accepts
+the following parameters in the renderer hash:
=over 4
-=item * B:
+=item * B: default false
-If true, the resource will not be linked. Default: false, resource
-will have links.
+If true, the resource will not be linked. By default, all non-folder
+resources are linked.
-=item * B:
+=item * B: default true
-If true (default), the resource will show a part count if the full
-part list is not displayed. If false, the resource will never show a
-part count.
+If true, the resource will show a part count B the full
+part list is not displayed. (See "condense_parts" later.) If false,
+the resource will never show a part count.
=item * B:
@@ -625,23 +658,38 @@ can't close or open folders when this is
=back
-=item B:
+=item * B:
Whether there is discussion on the resource, email for the user, or
(lumped in here) perl errors in the execution of the problem. This is
the second column in the main nav map.
-=item B:
+=item * B:
-An icon for the status of a problem, with four possible states:
-Correct, incorrect, open, or none (not open yet, not a problem). The
+An icon for the status of a problem, with five possible states:
+Correct, incorrect, open, awaiting grading (for a problem where the
+computer's grade is suppressed, or the computer can't grade, like
+essay problem), or none (not open yet, not a problem). The
third column of the standard navmap.
-=item B:
+=item * B:
A text readout of the details of the current status of the problem,
such as "Due in 22 hours". The fourth column of the standard navmap.
+=item * B:
+
+A text readout summarizing the status of the problem. If it is a
+single part problem, will display "Correct", "Incorrect",
+"Not yet open", "Open", "Attempted", or "Error". If there are
+multiple parts, this will output a string that in HTML will show a
+status of how many parts are in each status, in color coding, trying
+to match the colors of the icons within reason.
+
+Note this only makes sense if you are I showing parts. If
+C is true (see below), this column will not output
+anything.
+
=back
If you add any others please be sure to document them here.
@@ -660,9 +708,7 @@ to override vertical and horizontal alig
=head2 Parameters
-Most of these parameters are only useful if you are *not* using the
-folder interface (i.e., the default first column), which is probably
-the common case. If you are using this interface, then you should be
+Minimally, you should be
able to get away with just using 'cols' (to specify the columns
shown), 'url' (necessary for the folders to link to the current screen
correctly), and possibly 'queryString' if your app calls for it. In
@@ -671,7 +717,7 @@ automatically.
=over 4
-=item * B:
+=item * B: default: constructs one from %ENV
A reference to a fresh ::iterator to use from the navmaps. The
rendering will reflect the options passed to the iterator, so you can
@@ -680,106 +726,106 @@ one is not passed, the renderer will att
ENV{'form.filter'} and ENV{'form.condition'} information, plus the
'iterator_map' parameter if any.
-=item * B:
+=item * B: default: not used
If you are letting the renderer do the iterator handling, you can
instruct the renderer to render only a particular map by passing it
the source of the map you want to process, like
'/res/103/jerf/navmap.course.sequence'.
-=item * B:
+=item * B: default: constructs one from %ENV
A reference to a navmap, used only if an iterator is not passed in. If
this is necessary to make an iterator but it is not passed in, a new
one will be constructed based on ENV info. This is useful to do basic
error checking before passing it off to render.
-=item * B:
+=item * B: default: must be passed in
The standard Apache response object. This must be passed to the
renderer or the course hash will be locked.
-=item * B:
+=item * B: default: empty (useless)
An array reference
-=item * B:
+=item * B:default true
-A flag. If yes (default), a line for the resource itself, and a line
+A flag. If true, a line for the resource itself, and a line
for each part will be displayed. If not, only one line for each
resource will be displayed.
-=item * B:
+=item * B: default true
-A flag. If yes (default), if all parts of the problem have the same
+A flag. If true, if all parts of the problem have the same
status and that status is Nothing Set, Correct, or Network Failure,
then only one line will be displayed for that resource anyhow. If no,
all parts will always be displayed. If showParts is 0, this is
ignored.
-=item * B:
+=item * B: default: determined from %ENV
-A string identifying the URL to place the anchor 'curloc' at. Default
-to no anchor at all. It is the responsibility of the renderer user to
+A string identifying the URL to place the anchor 'curloc' at.
+It is the responsibility of the renderer user to
ensure that the #curloc is in the URL. By default, determined through
the use of the ENV{} 'jump' information, and should normally "just
work" correctly.
-=item * B:
+=item * B: default: empty string
-A Symb identifying where to place the 'here' marker. Default empty,
-which means no marker.
+A Symb identifying where to place the 'here' marker. The empty
+string means no marker.
-=item * B:
+=item * B: default: 25 pixel whitespace image
-A string identifying the indentation string to use. By default, this
-is a 25 pixel whitespace image with no alt text.
+A string identifying the indentation string to use.
-=item * B:
+=item * B: default: empty
A string which will be prepended to the query string used when the
-folders are opened or closed.
+folders are opened or closed. You can use this to pass
+application-specific values.
-=item * B:
+=item * B: default: none
The url the folders will link to, which should be the current
-page. Required if the resource info column is shown.
+page. Required if the resource info column is shown, and you
+are allowing the user to open and close folders.
-=item * B:
+=item * B: default: no jumping
Describes the currently-open row number to cause the browser to jump
to, because the user just opened that folder. By default, pulled from
the Jump information in the ENV{'form.*'}.
-=item * B:
+=item * B: default: false
If true, print the key that appears on the top of the standard
-navmaps. Default is false.
+navmaps.
-=item * B:
+=item * B: default: true
If true, print the "Close all folders" or "open all folders"
-links. Default is true.
+links.
-=item * B:
+=item * B: default: sub {return 1;} (accept everything)
A function that takes the resource object as its only parameter and
returns a true or false value. If true, the resource is displayed. If
-false, it is simply skipped in the display. By default, all resources
-are shown.
+false, it is simply skipped in the display.
-=item * B:
+=item * B: default: false
If you're using a filter function, and displaying sequences to orient
the user, then frequently some sequences will be empty. Setting this to
true will cause those sequences not to display, so as not to confuse the
user into thinking that if the sequence is there there should be things
-under it.
+under it; for example, see the "Show Uncompleted Homework" view on the
+B screen.
-=item * B:
+=item * B: default: false
-If true, will not display Navigate Content resources. Default to
-false.
+If true, will not display Navigate Content resources.
=back
@@ -790,13 +836,19 @@ be passed through unchange to the column
generate the following information which your renderer may find
useful:
-If you want to know how many rows were printed, the 'counter' element
-of the hash passed into the render function will contain the
-count. You may want to check whether any resources were printed at
-all.
-
=over 4
+=item * B:
+
+Contains the number of rows printed. Useful after calling the render
+function, as you can detect whether anything was printed at all.
+
+=item * B:
+
+Useful for renderers: If this resource is currently the first resource
+of a new branch, this will be true. The Resource column (leftmost in the
+navmaps screen) uses this to display the "new branch" icon
+
=back
=cut
@@ -805,8 +857,7 @@ sub resource { return 0; }
sub communication_status { return 1; }
sub quick_status { return 2; }
sub long_status { return 3; }
-
-# Data for render_resource
+sub part_status_summary { return 4; }
sub render_resource {
my ($resource, $part, $params) = @_;
@@ -835,8 +886,8 @@ 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 '0' || $params->{'condensed'}) {
@@ -844,6 +895,13 @@ sub render_resource {
} 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
@@ -854,8 +912,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 = " ";
@@ -919,7 +979,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";
@@ -966,6 +1027,10 @@ sub render_communication_status {
}
}
+ if ($params->{'multipart'} && $part != '0') {
+ $discussionHTML = $feedbackHTML = $errorHTML = '';
+ }
+
return "$discussionHTML$feedbackHTML$errorHTML ";
}
@@ -981,7 +1046,8 @@ sub render_quick_status {
if ($resource->is_problem() &&
!$firstDisplayed) {
- my $icon = $statusIconMap{$resource->status($part)};
+
+ my $icon = $statusIconMap{$resource->simpleStatus($part)};
my $alt = $iconAltTags{$icon};
if ($icon) {
$result .= "$linkopen $linkclose \n";
@@ -1001,7 +1067,7 @@ sub render_long_status {
$params->{'multipart'} && $part eq "0";
my $color;
- if ($resource->is_problem() && ($resource->countParts() <= 1 || $part ne '') ) {
+ if ($resource->is_problem()) {
$color = $colormap{$resource->status};
if (dueInLessThen24Hours($resource, $part) ||
@@ -1020,14 +1086,76 @@ sub render_long_status {
if ($resource->is_map() && advancedUser() && $resource->randompick()) {
$result .= '(randomly select ' . $resource->randompick() .')';
}
-
- $result .= " - $part \n";
+
+ # Debugging code
+ #$result .= " " . $resource->awarded($part) . '/' . $resource->weight($part) .
+ # ' - Part: ' . $part;
+
+ $result .= "\n";
return $result;
}
+# Colors obtained by taking the icons, matching the colors, and
+# possibly reducing the Value (HSV) of the color, if it's too bright
+# for text, generally by one third or so.
+my %statusColors =
+ (
+ $resObj->CLOSED => '#000000',
+ $resObj->OPEN => '#998b13',
+ $resObj->CORRECT => '#26933f',
+ $resObj->INCORRECT => '#c48207',
+ $resObj->ATTEMPTED => '#a87510',
+ $resObj->ERROR => '#000000'
+ );
+my %statusStrings =
+ (
+ $resObj->CLOSED => 'Not yet open',
+ $resObj->OPEN => 'Open',
+ $resObj->CORRECT => 'Correct',
+ $resObj->INCORRECT => 'Incorrect',
+ $resObj->ATTEMPTED => 'Attempted',
+ $resObj->ERROR => 'Network Error'
+ );
+my @statuses = ($resObj->CORRECT, $resObj->ATTEMPTED, $resObj->INCORRECT, $resObj->OPEN, $resObj->CLOSED, $resObj->ERROR);
+
+use Data::Dumper;
+sub render_parts_summary_status {
+ my ($resource, $part, $params) = @_;
+ if (!$resource->is_problem()) { return ' '; }
+ if ($params->{showParts}) {
+ return ' ';
+ }
+
+ my $td = "\n";
+ my $endtd = " \n";
+
+ # If there is a single part, just show the simple status
+ if ($resource->singlepart()) {
+ my $status = $resource->simpleStatus('0');
+ return $td . ""
+ . $statusStrings{$status} . " " . $endtd;
+ }
+
+ # Now we can be sure the $part doesn't really matter.
+ my $statusCount = $resource->simpleStatusCount();
+ my @counts;
+ foreach my $status(@statuses) {
+ # decouple display order from the simpleStatusCount order
+ my $slot = Apache::lonnavmaps::resource::statusToSlot($status);
+ if ($statusCount->[$slot]) {
+ push @counts, "" . $statusCount->[$slot] . ' '
+ . $statusStrings{$status} . " ";
+ }
+ }
+
+ return $td . $resource->countParts() . ' parts: ' . join (', ', @counts) . $endtd;
+}
+
my @preparedColumns = (\&render_resource, \&render_communication_status,
- \&render_quick_status, \&render_long_status);
+ \&render_quick_status, \&render_long_status,
+ \&render_parts_summary_status);
sub setDefault {
my ($val, $default) = @_;
@@ -1093,12 +1221,9 @@ sub render {
if (!$ENV{'form.folderManip'} && !defined($args->{'iterator'})) {
# Step 1: Check to see if we have a navmap
if (!defined($navmap)) {
- $navmap = Apache::lonnavmaps::navmap->new(
- $ENV{"request.course.fn"}.".db",
- $ENV{"request.course.fn"}."_parms.db", 1, 1);
+ $navmap = Apache::lonnavmaps::navmap->new();
$mustCloseNavMap = 1;
}
- $navmap->init();
# Step two: Locate what kind of here marker is necessary
# Determine where the "here" marker is and where the screen jumps to.
@@ -1116,18 +1241,13 @@ sub render {
# Step three: Ensure the folders are open
my $mapIterator = $navmap->getIterator(undef, undef, undef, 1);
- my $depth = 1;
- $mapIterator->next(); # discard the first BEGIN_MAP
- my $curRes = $mapIterator->next();
+ my $curRes;
my $found = 0;
# We only need to do this if we need to open the maps to show the
# current position. This will change the counter so we can't count
# for the jump marker with this loop.
- while ($depth > 0 && !$found) {
- if ($curRes == $mapIterator->BEGIN_MAP()) { $depth++; }
- if ($curRes == $mapIterator->END_MAP()) { $depth--; }
-
+ while (($curRes = $mapIterator->next()) && !$found) {
if (ref($curRes) && $curRes->symb() eq $here) {
my $mapStack = $mapIterator->getStack();
@@ -1141,8 +1261,6 @@ sub render {
}
$found = 1;
}
-
- $curRes = $mapIterator->next();
}
}
@@ -1159,13 +1277,9 @@ sub render {
# Step 1: Check to see if we have a navmap
if (!defined($navmap)) {
- $navmap = Apache::lonnavmaps::navmap->new($r,
- $ENV{"request.course.fn"}.".db",
- $ENV{"request.course.fn"}."_parms.db", 1, 1);
+ $navmap = Apache::lonnavmaps::navmap->new();
$mustCloseNavMap = 1;
}
- # Paranoia: Make sure it's ready
- $navmap->init();
# See if we're being passed a specific map
if ($args->{'iterator_map'}) {
@@ -1184,15 +1298,11 @@ sub render {
# Note this does not take filtering or hidden into account... need
# to be fixed?
my $mapIterator = $navmap->getIterator(undef, undef, $filterHash, 0);
- my $depth = 1;
- $mapIterator->next();
- my $curRes = $mapIterator->next();
+ my $curRes;
my $foundJump = 0;
my $counter = 0;
- while ($depth > 0 && !$foundJump) {
- if ($curRes == $mapIterator->BEGIN_MAP()) { $depth++; }
- if ($curRes == $mapIterator->END_MAP()) { $depth--; }
+ while (($curRes = $mapIterator->next()) && !$foundJump) {
if (ref($curRes)) { $counter++; }
if (ref($curRes) && $jump eq $curRes->symb()) {
@@ -1203,8 +1313,6 @@ sub render {
$args->{'currentJumpIndex'} = $counter;
$foundJump = 1;
}
-
- $curRes = $mapIterator->next();
}
my $showParts = setDefault($args->{'showParts'}, 1);
@@ -1223,15 +1331,15 @@ sub render {
$result.='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)').
' ';
}
@@ -1242,11 +1350,11 @@ sub render {
if ($condition) {
$result.="Close All Folders ";
+ "\">".&mt('Close All Folders')."";
} else {
$result.="Open All Folders ";
+ "\">".&mt('Open All Folders')."";
}
$result .= " \n";
}
@@ -1282,7 +1390,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) {
@@ -1291,7 +1399,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.
@@ -1314,9 +1422,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;
@@ -1324,10 +1429,8 @@ 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
+ while ($curRes = $it->next()) {
# Maintain indentation level.
if ($curRes == $it->BEGIN_MAP() ||
$curRes == $it->BEGIN_BRANCH() ) {
@@ -1354,7 +1457,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;
}
@@ -1413,8 +1516,13 @@ sub render {
@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;
+ }
}
}
@@ -1475,7 +1583,15 @@ 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 '';
+ }
+ }
}
# Print out the part that jumps to #curloc if it exists
@@ -1508,41 +1624,64 @@ package Apache::lonnavmaps::navmap;
=pod
-lonnavmaps provides functions and objects for dealing with the
-compiled course hashes generated when a user enters the course, the
-Apache handler for the "Navigation Map" button, and a flexible
-prepared renderer for navigation maps that are easy to use anywhere.
+=head1 Object: Apache::lonnavmaps::navmap
+
+=head2 Overview
-=head1 Object: navmap
+The navmap object's job is to provide access to the resources
+in the course as Apache::lonnavmaps::resource objects, and to
+query and manage the relationship between those resource objects.
-Encapsulating the compiled nav map
+Generally, you'll use the navmap object in one of three basic ways.
+In order of increasing complexity and power:
-navmap is an object that encapsulates a compiled course map and
-provides a reasonable interface to it.
+=over 4
-Most notably it provides a way to navigate the map sensibly and a
-flexible iterator that makes it easy to write various renderers based
-on nav maps.
+=item * C<$navmap-EgetByX>, where X is B, B, B or B. This provides
+ various ways to obtain resource objects, based on various identifiers.
+ Use this when you want to request information about one object or
+ a handful of resources you already know the identities of, from some
+ other source. For more about Ids, Symbs, and MapPcs, see the
+ Resource documentation. Note that Url should be a B,
+ not your first choice; it only works when there is only one
+ instance of the resource in the course, which only applies to
+ maps, and even that may change in the future.
+
+=item * CretrieveResources(args)>. This
+ retrieves resources matching some criterion and returns them
+ in a flat array, with no structure information. Use this when
+ you are manipulating a series of resources, based on what map
+ the are in, but do not care about branching, or exactly how
+ the maps and resources are related. This is the most common case.
+
+=item * C<$it = $navmap-EgetIterator(args)>. This allows you traverse
+ the course's navmap in various ways without writing the traversal
+ code yourself. See iterator documentation below. Use this when
+ you need to know absolutely everything about the course, including
+ branches and the precise relationship between maps and resources.
-You must obtain resource objects through the navmap object.
+=back
-=head2 Methods
+=head2 Creation And Destruction
+
+To create a navmap object, use the following function:
=over 4
-=item * B(navHashFile, parmHashFile, genCourseAndUserOptions,
- genMailDiscussStatus):
+=item * Bnew>():
+
+Creates a new navmap object. Returns the navmap object if this is
+successful, or B if not.
+
+=back
+
+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. (!)
-Binds a new navmap object to the compiled nav map hash and parm hash
-given as filenames. 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.
+=head2 Methods
+
+=over 4
=item * B(first, finish, filter, condition):
@@ -1559,11 +1698,6 @@ sub new {
my $class = ref($proto) || $proto;
my $self = {};
- $self->{NAV_HASH_FILE} = shift;
- $self->{PARM_HASH_FILE} = shift;
- $self->{GENERATE_COURSE_USER_OPT} = shift;
- $self->{GENERATE_EMAIL_DISCUSS_STATUS} = 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} = {};
@@ -1576,141 +1710,149 @@ sub new {
my %navmaphash;
my %parmhash;
- if (!(tie(%navmaphash, 'GDBM_File', $self->{NAV_HASH_FILE},
+ my $courseFn = $ENV{"request.course.fn"};
+ if (!(tie(%navmaphash, 'GDBM_File', "${courseFn}.db",
&GDBM_READER(), 0640))) {
return undef;
}
- if (!(tie(%parmhash, 'GDBM_File', $self->{PARM_HASH_FILE},
+ if (!(tie(%parmhash, 'GDBM_File', "${courseFn}_parms.db",
&GDBM_READER(), 0640)))
{
untie %{$self->{PARM_HASH}};
return undef;
}
- # try copying into memory
- my %tmpnavhash;
- while (my ($k, $v) = each(%navmaphash)) {
- $tmpnavhash{$k} = $v;
- }
- untie %navmaphash;
-
- $self->{NAV_HASH} = \%tmpnavhash;
+ $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;
- }
- }
+ 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;
-
- }
+ $self->{COURSE_USER_OPT_GENERATED} = 1;
+
+ return;
+}
- $self->{PARM_CACHE} = {};
- $self->{INITED} = 1;
+sub generate_email_discuss_status {
+ my $self = 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 %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->{EMAIL_DISCUSS_GENERATED} = 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
@@ -1720,6 +1862,15 @@ sub navhash {
return $self->{NAV_HASH}->{$key};
}
+=pod
+
+=item * B(): Returns true if the course map is defined,
+ false otherwise. Undefined course maps indicate an error somewhere in
+ LON-CAPA, and you will not be able to proceed with using the navmap.
+ See the B screen for an example of using this.
+
+=cut
+
# Checks to see if coursemap is defined, matching test in old lonnavmaps
sub courseMapDefined {
my $self = shift;
@@ -1749,6 +1900,9 @@ 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});
@@ -1763,6 +1917,8 @@ sub getFeedback {
my $self = shift;
my $symb = shift;
+ $self->generate_email_discuss_status();
+
if (!defined($self->{FEEDBACK})) { return ""; }
return $self->{FEEDBACK}->{$symb};
@@ -1772,7 +1928,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};
}
@@ -1801,7 +1959,6 @@ 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.
-# Caching is just an incidental detail I throw in because it makes sense.
sub getById {
my $self = shift;
@@ -1821,7 +1978,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);
}
@@ -1884,7 +2041,10 @@ sub parmval {
sub parmval_real {
my $self = shift;
- my ($what,$symb) = @_;
+ 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'};
@@ -1894,7 +2054,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;
@@ -1956,9 +2116,12 @@ sub parmval_real {
my $id=pop(@parts);
my $part=join('_',@parts);
if ($part eq '') { $part='0'; }
- my $partgeneral=$self->parmval($part.".$qualifier",$symb);
+ my $partgeneral=$self->parmval($part.".$qualifier",$symb,1);
if (defined($partgeneral)) { return $partgeneral; }
}
+ if ($recurse) { return undef; }
+ my $pack_def=&Apache::lonnet::packages_tab_default($fn,'resource.'.$what);
+ if (defined($pack_def)) { return $pack_def; }
return '';
}
@@ -2039,31 +2202,27 @@ sub retrieveResources {
$map = $self->getResourceByUrl($map);
}
+ # If nothing was passed, assume top-level map
+ if (!$map) {
+ $map = $self->getById('0.0');
+ }
+
# Check the map's validity.
- if (!$map || !$map->is_map()) {
+ if (!$map->is_map()) {
# Oh, to throw an exception.... how I'd love that!
return ();
}
# Get an iterator.
my $it = $self->getIterator($map->map_start(), $map->map_finish(),
- !$recursive);
+ undef, $recursive);
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;
@@ -2076,7 +2235,6 @@ sub retrieveResources {
}
}
- $curRes = $it->next();
}
return @resources;
@@ -2128,7 +2286,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
@@ -2152,21 +2310,28 @@ new branch. The possible tokens are:
=over 4
-=item * BEGIN_MAP:
+=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
resource itself is returned.
-=item * END_MAP:
+=item * B:
The map is now done.
-=item * BEGIN_BRANCH:
+=item * B:
A branch is now starting. The next resource returned will be the first
in that branch.
-=item * END_BRANCH:
+=item * B:
The branch is now done.
@@ -2182,12 +2347,33 @@ consisting entirely of empty resources e
ending resource, will cause a lot of BRANCH_STARTs and BRANCH_ENDs,
but only one resource will be returned.
+=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.
+
=back
=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
@@ -2200,11 +2386,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;
@@ -2260,6 +2441,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) {
@@ -2275,13 +2459,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,
@@ -2315,8 +2499,8 @@ sub new {
$curRes->{DATA}->{DISPLAY_DEPTH} = $finalDepth;
if ($finalDepth > $maxDepth) {$maxDepth = $finalDepth;}
}
- } continue {
- $curRes = $iterator->next();
+
+ $curRes = $iterator->next();
}
}
@@ -2337,6 +2521,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}}, [];
@@ -2354,6 +2539,10 @@ sub new {
sub next {
my $self = shift;
+ if ($self->{FINISHED}) {
+ return END_ITERATOR();
+ }
+
# If we want to return the top-level map object, and haven't yet,
# do so.
if ($self->{RETURN_0} && !$self->{HAVE_RETURNED_0}) {
@@ -2413,6 +2602,7 @@ sub next {
$self->{CURRENT_DEPTH}--;
return END_BRANCH();
} else {
+ $self->{FINISHED} = 1;
return END_MAP();
}
}
@@ -2725,8 +2915,9 @@ use Apache::lonnet;
=pod
-=head1 Object: resource
+=head1 Object: resource
+X
A resource object encapsulates a resource in a resource map, allowing
easy manipulation of the resource, querying the properties of the
resource (including user properties), and represents a reference that
@@ -2746,24 +2937,52 @@ writing, there is no way to override thi
parts will never be returned, nor will their response types or ids be
stored.
-=head2 Public Members
-
-resource objects have a hash called DATA ($resourceRef->{DATA}) that
-you can store whatever you want in. This allows you to easily do
-two-pass algorithms without worrying about managing your own
-resource->data hash.
+=head2 Overview
-=head2 Methods
-
-=over 4
-
-=item * B($navmapRef, $idString):
-
-The first arg is a reference to the parent navmap object. The second
-is the idString of the resource itself. Very rarely, if ever, called
-directly. Use the nav map->getByID() method.
-
-=back
+A B is the most granular type of object in LON-CAPA that can
+be included in a course. It can either be a particular resource, like
+an HTML page, external resource, problem, etc., or it can be a
+container sequence, such as a "page" or a "map".
+
+To see a sequence from the user's point of view, please see the
+B chapter of the Author's
+Manual.
+
+A Resource Object, once obtained from a navmap object via a B
+method of the navmap, or from an iterator, allows you to query
+information about that resource.
+
+Generally, you do not ever want to create a resource object yourself,
+so creation has been left undocumented. Always retrieve resources
+from navmap objects.
+
+=head3 Identifying Resources
+
+XEvery resource is identified by a Resource ID in the big hash that is
+unique to that resource for a given course. X
+The Resource ID has the form #.#, where the first number is the same
+for every resource in a map, and the second is unique. For instance,
+for a course laid out like this:
+
+ * Problem 1
+ * Map
+ * Resource 2
+ * Resource 3
+
+C and C will share a first number, and C
+C will share a first number. The second number may end up
+re-used between the two groups.
+
+The resource ID is only used in the big hash, but can be used in the
+context of a course to identify a resource easily. (For instance, the
+printing system uses it to record which resources from a sequence you
+wish to print.)
+
+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.
=cut
@@ -2802,14 +3021,21 @@ sub navHash {
=pod
-B
+=head2 Methods
+
+Once you have a resource object, here's what you can do with it:
+
+=head3 Attribute Retrieval
-These are methods that help you retrieve metadata about the resource:
-Method names are based on the fields in the compiled course
-representation.
+Every resource has certain attributes that can be retrieved and used:
=over 4
+=item * B: Every resource has an ID that is unique for that
+ resource in the course it is in. The ID is actually in the hash
+ representing the resource, so for a resource object $res, obtain
+ it via C<$res->{ID}).
+
=item * B:
Returns a "composite title", that is equal to $res->title() if the
@@ -2820,11 +3046,6 @@ resource has a title, and is otherwise t
Returns true if the resource is external.
-=item * B:
-
-Returns the "goesto" value from the compiled nav map. (It is likely
-you want to use B instead.)
-
=item * B:
Returns the kind of the resource from the compiled nav map.
@@ -2852,11 +3073,6 @@ Returns the symb for the resource.
Returns the title of the resource.
-=item * B:
-
-Returns the "to" value from the compiled nav map. (It is likely you
-want to use B instead.)
-
=back
=cut
@@ -2866,6 +3082,7 @@ want to use B instead.)
sub comesfrom { my $self=shift; return $self->navHash("comesfrom_", 1); }
sub ext { my $self=shift; return $self->navHash("ext_", 1) eq 'true:'; }
sub from { my $self=shift; return $self->navHash("from_", 1); }
+# considered private and undocumented
sub goesto { my $self=shift; return $self->navHash("goesto_", 1); }
sub kind { my $self=shift; return $self->navHash("kind_", 1); }
sub randomout { my $self=shift; return $self->navHash("randomout_", 1); }
@@ -2882,11 +3099,19 @@ 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; return $self->navHash("title_", 1); }
+sub title {
+ my $self=shift;
+ if ($self->{ID} eq '0.0') {
+ # If this is the top-level map, return the title of the course
+ # since this map can not be titled otherwise.
+ return $ENV{'course.'.$ENV{'request.course.id'}.'.description'};
+ }
+ return $self->navHash("title_", 1); }
+# considered private and undocumented
sub to { my $self=shift; return $self->navHash("to_", 1); }
sub compTitle {
my $self = shift;
@@ -2938,7 +3163,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;
@@ -2948,7 +3174,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.
@@ -3023,8 +3250,6 @@ sub map_type {
return $self->navHash("map_type_$pc", 0);
}
-
-
#####
# Property queries
#####
@@ -3062,6 +3287,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.
@@ -3115,7 +3345,12 @@ 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;
+ $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) = @_;
return $self->parmval("duedate", $part);
@@ -3156,7 +3391,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
@@ -3297,12 +3537,17 @@ 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_TYPE}->{$part};
+ return $self->{RESPONSE_TYPES}->{$part};
}
sub responseIds {
@@ -3384,8 +3629,7 @@ sub extractParts {
my @otherChunks = @partChunks[$i+1..$#partChunks];
my $responseId = join('_', @otherChunks);
push @{$responseIdHash{$partIdSoFar}}, $responseId;
- $responseTypeHash{$partIdSoFar} = $responseType;
- last;
+ push @{$responseTypeHash{$partIdSoFar}}, $responseType;
}
}
}
@@ -3700,6 +3944,7 @@ 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';
@@ -3746,7 +3991,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
}
@@ -3755,6 +4000,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
@@ -3786,15 +4121,18 @@ sub completable {
# "If any of the parts are open, or have tries left (implies open),
# and it is not "attempted" (manually graded problem), it is
# not "complete"
- if (!(($status == OPEN() || $status == TRIES_LEFT())
- && $self->getCompletionStatus($part) != ATTEMPTED()
- && $status != ANSWER_SUBMITTED())) {
- return 0;
- }
+ if ($self->getCompletionStatus($part) == ATTEMPTED() ||
+ $status == ANSWER_SUBMITTED() ) {
+ # did this part already, as well as we can
+ next;
+ }
+ if ($status == OPEN() || $status == TRIES_LEFT()) {
+ return 1;
+ }
}
# If all the parts were complete, so was this problem.
- return 1;
+ return 0;
}
=pod