version 1.190, 2003/05/14 20:16:56
|
version 1.215, 2003/07/16 19:22:49
|
Line 47 use Apache::Constants qw(:common :http);
|
Line 47 use Apache::Constants qw(:common :http);
|
use Apache::loncommon(); |
use Apache::loncommon(); |
use Apache::lonmenu(); |
use Apache::lonmenu(); |
use POSIX qw (floor strftime); |
use POSIX qw (floor strftime); |
|
use Data::Dumper; # for debugging, not always used |
|
|
# symbolic constants |
# symbolic constants |
sub SYMB { return 1; } |
sub SYMB { return 1; } |
Line 71 my %statusIconMap =
|
Line 72 my %statusIconMap =
|
$resObj->TRIES_LEFT => 'navmap.open.gif', |
$resObj->TRIES_LEFT => 'navmap.open.gif', |
$resObj->INCORRECT => 'navmap.wrong.gif', |
$resObj->INCORRECT => 'navmap.wrong.gif', |
$resObj->OPEN => 'navmap.open.gif', |
$resObj->OPEN => 'navmap.open.gif', |
$resObj->ATTEMPTED => 'navmap.open.gif', |
$resObj->ATTEMPTED => 'navmap.ellipsis.gif', |
$resObj->ANSWER_SUBMITTED => '' ); |
$resObj->ANSWER_SUBMITTED => '' ); |
|
|
my %iconAltTags = |
my %iconAltTags = |
Line 91 my %colormap =
|
Line 92 my %colormap =
|
$resObj->TRIES_LEFT => '', |
$resObj->TRIES_LEFT => '', |
$resObj->INCORRECT => '', |
$resObj->INCORRECT => '', |
$resObj->OPEN => '', |
$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 |
# 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 |
# is not yet done and due in less then 24 hours |
my $hurryUpColor = "#FF0000"; |
my $hurryUpColor = "#FF0000"; |
Line 160 sub real_handler {
|
Line 164 sub real_handler {
|
# Now that we've displayed some stuff to the user, init the navmap |
# Now that we've displayed some stuff to the user, init the navmap |
$navmap->init(); |
$navmap->init(); |
|
|
$r->print('<br> '); |
|
$r->rflush(); |
$r->rflush(); |
|
|
# Check that it's defined |
# Check that it's defined |
Line 200 sub real_handler {
|
Line 203 sub real_handler {
|
} |
} |
} |
} |
|
|
|
my $jumpToFirstHomework = 0; |
# Check to see if the student is jumping to next open, do-able problem |
# Check to see if the student is jumping to next open, do-able problem |
if ($ENV{QUERY_STRING} eq 'jumpToFirstHomework') { |
if ($ENV{QUERY_STRING} eq 'jumpToFirstHomework') { |
|
$jumpToFirstHomework = 1; |
# Find the next homework problem that they can do. |
# Find the next homework problem that they can do. |
my $iterator = $navmap->getIterator(undef, undef, undef, 1); |
my $iterator = $navmap->getIterator(undef, undef, undef, 1); |
my $depth = 1; |
my $depth = 1; |
Line 216 sub real_handler {
|
Line 221 sub real_handler {
|
|
|
if (ref($curRes) && $curRes->is_problem()) { |
if (ref($curRes) && $curRes->is_problem()) { |
my $status = $curRes->status(); |
my $status = $curRes->status(); |
if (($status == $curRes->OPEN || |
if ($curRes->completable()) { |
$status == $curRes->TRIES_LEFT()) && |
|
$curRes->getCompletionStatus() != $curRes->ATTEMPTED()) { |
|
$problemRes = $curRes; |
$problemRes = $curRes; |
$foundDoableProblem = 1; |
$foundDoableProblem = 1; |
|
|
Line 243 sub real_handler {
|
Line 246 sub real_handler {
|
} |
} |
} else { |
} else { |
$r->print("<a href='navmaps?jumpToFirstHomework'>" . |
$r->print("<a href='navmaps?jumpToFirstHomework'>" . |
"Go To My First Homework Problem</a><br />"); |
"Go To My First Homework Problem</a> "); |
} |
} |
|
|
# renderer call |
my $suppressEmptySequences = 0; |
my $render = render({ 'cols' => [0,1,2,3], |
my $filterFunc = undef; |
'url' => '/adm/navmaps', |
my $resource_no_folder_link = 0; |
'navmap' => $navmap, |
|
'suppressNavmap' => 1, |
# Display only due homework. |
'r' => $r}); |
my $showOnlyHomework = 0; |
|
if ($ENV{QUERY_STRING} eq 'showOnlyHomework') { |
|
$showOnlyHomework = 1; |
|
$suppressEmptySequences = 1; |
|
$filterFunc = sub { my $res = shift; |
|
return $res->completable() || $res->is_map(); |
|
}; |
|
$r->print("<p><font size='+2'>Uncompleted Homework</font></p>"); |
|
$ENV{'form.filter'} = ''; |
|
$ENV{'form.condition'} = 1; |
|
$resource_no_folder_link = 1; |
|
} else { |
|
$r->print("<a href='navmaps?showOnlyHomework'>" . |
|
"Show Only Uncompleted Homework</a> "); |
|
} |
|
|
|
# renderer call |
|
my $renderArgs = { 'cols' => [0,1,2,3], |
|
'url' => '/adm/navmaps', |
|
'navmap' => $navmap, |
|
'suppressNavmap' => 1, |
|
'suppressEmptySequences' => $suppressEmptySequences, |
|
'filterFunc' => $filterFunc, |
|
'resource_no_folder_link' => $resource_no_folder_link, |
|
'r' => $r}; |
|
my $render = render($renderArgs); |
$navmap->untieHashes(); |
$navmap->untieHashes(); |
|
|
|
# If no resources were printed, print a reassuring message so the |
|
# user knows there was no error. |
|
if ($renderArgs->{'counter'} == 0) { |
|
if ($showOnlyHomework) { |
|
$r->print("<p><font size='+1'>All homework is currently completed.</font></p>"); |
|
} else { # both jumpToFirstHomework and normal use the same: course must be empty |
|
$r->print("<p><font size='+1'>This course is empty.</font></p>"); |
|
} |
|
} |
|
|
$r->print("</body></html>"); |
$r->print("</body></html>"); |
$r->rflush(); |
$r->rflush(); |
|
|
Line 347 sub getDescription {
|
Line 384 sub getDescription {
|
return "Excused by instructor"; |
return "Excused by instructor"; |
} |
} |
if ($status == $res->ATTEMPTED) { |
if ($status == $res->ATTEMPTED) { |
return "Not yet graded."; |
return "Answer submitted, not yet graded."; |
} |
} |
if ($status == $res->TRIES_LEFT) { |
if ($status == $res->TRIES_LEFT) { |
my $tries = $res->tries($part); |
my $tries = $res->tries($part); |
Line 379 sub dueInLessThen24Hours {
|
Line 416 sub dueInLessThen24Hours {
|
my $part = shift; |
my $part = shift; |
my $status = $res->status($part); |
my $status = $res->status($part); |
|
|
return ($status == $res->OPEN() || $status == $res->ATTEMPTED() || |
return ($status == $res->OPEN() || |
$status == $res->TRIES_LEFT()) && |
$status == $res->TRIES_LEFT()) && |
$res->duedate() && $res->duedate() < time()+(24*60*60) && |
$res->duedate() && $res->duedate() < time()+(24*60*60) && |
$res->duedate() > time(); |
$res->duedate() > time(); |
Line 521 Apache::lonnavmap - Subroutines to handl
|
Line 558 Apache::lonnavmap - Subroutines to handl
|
|
|
The main handler generates the navigational listing for the course, |
The main handler generates the navigational listing for the course, |
the other objects export this information in a usable fashion for |
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 |
The navmap renderer package provides a sophisticated rendering of the |
standard navigation maps interface into HTML. The provided nav map |
standard navigation maps interface into HTML. The provided nav map |
Line 537 understand then "undef, undef, undef, 1,
|
Line 574 understand then "undef, undef, undef, 1,
|
undef, 0" when you mostly want default behaviors. |
undef, 0" when you mostly want default behaviors. |
|
|
The package provides a function called 'render', called as |
The package provides a function called 'render', called as |
Apache::lonnavmaps::renderer->render({}). |
Apache::lonnavmaps::render({}). |
|
|
=head2 Overview of Columns |
=head2 Overview of Columns |
|
|
Line 545 The renderer will build an HTML table fo
|
Line 582 The renderer will build an HTML table fo
|
it. The table is consists of several columns, and a row for each |
it. The table is consists of several columns, and a row for each |
resource (or possibly each part). You tell the renderer how many |
resource (or possibly each part). You tell the renderer how many |
columns to create and what to place in each column, optionally using |
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. |
the table. |
|
|
Any additional generally useful column types should be placed in the |
Any additional generally useful column types should be placed in the |
Line 563 argument hash passed to the renderer, an
|
Line 600 argument hash passed to the renderer, an
|
be inserted into the HTML representation as it. |
be inserted into the HTML representation as it. |
|
|
The pre-packaged column names are refered to by constants in the |
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 |
=over 4 |
|
|
Line 571 Apache::lonnavmaps::renderer namespace.
|
Line 608 Apache::lonnavmaps::renderer namespace.
|
|
|
The general info about the resource: Link, icon for the type, etc. The |
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 |
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 |
=over 4 |
|
|
Line 804 sub render_resource {
|
Line 841 sub render_resource {
|
my $linkopen = "<a href='$link'>"; |
my $linkopen = "<a href='$link'>"; |
my $linkclose = "</a>"; |
my $linkclose = "</a>"; |
|
|
# Default icon: HTML page |
# Default icon: unknown page |
my $icon = "<img src='/adm/lonIcons/html.gif' alt='' border='0' />"; |
my $icon = "<img src='/adm/lonIcons/unknown.gif' alt='' border='0' />"; |
|
|
if ($resource->is_problem()) { |
if ($resource->is_problem()) { |
if ($part eq "" || $params->{'condensed'}) { |
if ($part eq '0' || $params->{'condensed'}) { |
$icon = '<img src="/adm/lonIcons/problem.gif" alt="" border="0" />'; |
$icon = '<img src="/adm/lonIcons/problem.gif" alt="" border="0" />'; |
} else { |
} else { |
$icon = $params->{'indentString'}; |
$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 = "<img src='/adm/lonIcons/$curfext.gif' alt='' border='0' />"; |
|
} |
} |
} |
|
|
# Display the correct map icon to open or shut map |
# Display the correct map icon to open or shut map |
Line 823 sub render_resource {
|
Line 867 sub render_resource {
|
$nowOpen = !$nowOpen; |
$nowOpen = !$nowOpen; |
} |
} |
|
|
|
my $folderType = $resource->is_sequence() ? 'folder' : 'page'; |
|
|
if (!$params->{'resource_no_folder_link'}) { |
if (!$params->{'resource_no_folder_link'}) { |
$icon = 'navmap.folder.' . ($nowOpen ? 'closed' : 'open') . '.gif'; |
$icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif'; |
$icon = "<img src='/adm/lonIcons/$icon' alt='' border='0' />"; |
$icon = "<img src='/adm/lonIcons/$icon' alt='' border='0' />"; |
|
|
$linkopen = "<a href='" . $params->{'url'} . '?' . |
$linkopen = "<a href='" . $params->{'url'} . '?' . |
Line 840 sub render_resource {
|
Line 886 sub render_resource {
|
"&folderManip=1'>"; |
"&folderManip=1'>"; |
} else { |
} else { |
# Don't allow users to manipulate folder |
# Don't allow users to manipulate folder |
$icon = 'navmap.folder.' . ($nowOpen ? 'closed' : 'open') . |
$icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . |
'.nomanip.gif'; |
'.nomanip.gif'; |
$icon = "<img src='/adm/lonIcons/$icon' alt='' border='0' />"; |
$icon = "<img src='/adm/lonIcons/$icon' alt='' border='0' />"; |
|
|
Line 878 sub render_resource {
|
Line 924 sub render_resource {
|
$params->{'displayedHereMarker'} = 1; |
$params->{'displayedHereMarker'} = 1; |
} |
} |
|
|
if ($resource->is_problem() && $part ne "" && |
if ($resource->is_problem() && $part ne '0' && |
!$params->{'condensed'}) { |
!$params->{'condensed'}) { |
$partLabel = " (Part $part)"; |
$partLabel = " (Part $part)"; |
$title = ""; |
$title = ""; |
Line 888 sub render_resource {
|
Line 934 sub render_resource {
|
$nonLinkedText .= ' (' . $resource->countParts() . ' parts)'; |
$nonLinkedText .= ' (' . $resource->countParts() . ' parts)'; |
} |
} |
|
|
if (!$params->{'resource_nolink'}) { |
if (!$params->{'resource_nolink'} && $src !~ /^\/uploaded\// && |
|
!$resource->is_sequence()) { |
$result .= " $curMarkerBegin<a href='$link'>$title$partLabel</a>$curMarkerEnd $nonLinkedText</td>"; |
$result .= " $curMarkerBegin<a href='$link'>$title$partLabel</a>$curMarkerEnd $nonLinkedText</td>"; |
} else { |
} else { |
$result .= " $curMarkerBegin$title$partLabel$curMarkerEnd $nonLinkedText</td>"; |
$result .= " $curMarkerBegin$title$partLabel$curMarkerEnd $nonLinkedText</td>"; |
Line 935 sub render_communication_status {
|
Line 982 sub render_communication_status {
|
} |
} |
} |
} |
|
|
|
if ($params->{'multipart'} && $part != '0') { |
|
$discussionHTML = $feedbackHTML = $errorHTML = ''; |
|
} |
|
|
return "<td width=\"75\" align=\"left\" valign=\"center\">$discussionHTML$feedbackHTML$errorHTML </td>"; |
return "<td width=\"75\" align=\"left\" valign=\"center\">$discussionHTML$feedbackHTML$errorHTML </td>"; |
|
|
} |
} |
Line 989 sub render_long_status {
|
Line 1040 sub render_long_status {
|
if ($resource->is_map() && advancedUser() && $resource->randompick()) { |
if ($resource->is_map() && advancedUser() && $resource->randompick()) { |
$result .= '(randomly select ' . $resource->randompick() .')'; |
$result .= '(randomly select ' . $resource->randompick() .')'; |
} |
} |
|
|
$result .= " </td>\n"; |
# Debugging code |
|
#$result .= " " . $resource->awarded($part) . '/' . $resource->weight($part) . |
|
# ' - Part: ' . $part; |
|
|
|
$result .= "</td>\n"; |
|
|
return $result; |
return $result; |
} |
} |
Line 1041 sub render {
|
Line 1096 sub render {
|
} |
} |
} |
} |
|
|
|
# Filter: Remember filter function and add our own filter: Refuse |
|
# to show hidden resources unless the user can see them. |
|
my $userCanSeeHidden = advancedUser(); |
|
my $filterFunc = setDefault($args->{'filterFunc'}, |
|
sub {return 1;}); |
|
if (!$userCanSeeHidden) { |
|
# Without renaming the filterfunc, the server seems to go into |
|
# an infinite loop |
|
my $oldFilterFunc = $filterFunc; |
|
$filterFunc = sub { my $res = shift; return !$res->randomout() && |
|
&$oldFilterFunc($res);}; |
|
} |
|
|
my $condition = 0; |
my $condition = 0; |
if ($ENV{'form.condition'}) { |
if ($ENV{'form.condition'}) { |
$condition = 1; |
$condition = 1; |
Line 1137 sub render {
|
Line 1205 sub render {
|
} |
} |
|
|
# (re-)Locate the jump point, if any |
# (re-)Locate the jump point, if any |
|
# Note this does not take filtering or hidden into account... need |
|
# to be fixed? |
my $mapIterator = $navmap->getIterator(undef, undef, $filterHash, 0); |
my $mapIterator = $navmap->getIterator(undef, undef, $filterHash, 0); |
my $depth = 1; |
my $depth = 1; |
$mapIterator->next(); |
$mapIterator->next(); |
Line 1169 sub render {
|
Line 1239 sub render {
|
my $printKey = $args->{'printKey'}; |
my $printKey = $args->{'printKey'}; |
my $printCloseAll = $args->{'printCloseAll'}; |
my $printCloseAll = $args->{'printCloseAll'}; |
if (!defined($printCloseAll)) { $printCloseAll = 1; } |
if (!defined($printCloseAll)) { $printCloseAll = 1; } |
my $filterFunc = setDefault($args->{'filterFunc'}, |
|
sub {return 1;}); |
|
|
|
# Print key? |
# Print key? |
if ($printKey) { |
if ($printKey) { |
Line 1247 sub render {
|
Line 1315 sub render {
|
|
|
if (ref($curRes)) { |
if (ref($curRes)) { |
# Parallel pre-processing: Do sequences have non-filtered-out children? |
# Parallel pre-processing: Do sequences have non-filtered-out children? |
if ($curRes->is_sequence()) { |
if ($curRes->is_map()) { |
$curRes->{DATA}->{HAS_VISIBLE_CHILDREN} = 0; |
$curRes->{DATA}->{HAS_VISIBLE_CHILDREN} = 0; |
# Sequences themselves do not count as visible children, |
# Sequences themselves do not count as visible children, |
# unless those sequences also have visible children. |
# unless those sequences also have visible children. |
Line 1303 sub render {
|
Line 1371 sub render {
|
next; |
next; |
} |
} |
|
|
$args->{'counter'}++; |
|
|
|
# If this has been filtered out, continue on |
# If this has been filtered out, continue on |
if (!(&$filterFunc($curRes))) { |
if (!(&$filterFunc($curRes))) { |
$args->{'isNewBranch'} = 0; # Don't falsely remember this |
$args->{'isNewBranch'} = 0; # Don't falsely remember this |
Line 1312 sub render {
|
Line 1378 sub render {
|
} |
} |
|
|
# If this is an empty sequence and we're filtering them, continue on |
# 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}) { |
!$curRes->{DATA}->{HAS_VISIBLE_CHILDREN}) { |
next; |
next; |
} |
} |
Line 1322 sub render {
|
Line 1388 sub render {
|
next; |
next; |
} |
} |
|
|
|
$args->{'counter'}++; |
|
|
# Does it have multiple parts? |
# Does it have multiple parts? |
$args->{'multipart'} = 0; |
$args->{'multipart'} = 0; |
$args->{'condensed'} = 0; |
$args->{'condensed'} = 0; |
Line 1330 sub render {
|
Line 1398 sub render {
|
# Decide what parts to show. |
# Decide what parts to show. |
if ($curRes->is_problem() && $showParts) { |
if ($curRes->is_problem() && $showParts) { |
@parts = @{$curRes->parts()}; |
@parts = @{$curRes->parts()}; |
$args->{'multipart'} = scalar(@parts) > 1; |
$args->{'multipart'} = $curRes->multipart(); |
|
|
if ($condenseParts) { # do the condensation |
if ($condenseParts) { # do the condensation |
if (!$curRes->opendate("0")) { |
if (!$curRes->opendate("0")) { |
Line 1339 sub render {
|
Line 1407 sub render {
|
} |
} |
if (!$args->{'condensed'}) { |
if (!$args->{'condensed'}) { |
# Decide whether to condense based on similarity |
# Decide whether to condense based on similarity |
my $status = $curRes->status($parts[1]); |
my $status = $curRes->status($parts[0]); |
my $due = $curRes->duedate($parts[1]); |
my $due = $curRes->duedate($parts[0]); |
my $open = $curRes->opendate($parts[1]); |
my $open = $curRes->opendate($parts[0]); |
my $statusAllSame = 1; |
my $statusAllSame = 1; |
my $dueAllSame = 1; |
my $dueAllSame = 1; |
my $openAllSame = 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){ |
if ($curRes->status($parts[$i]) != $status){ |
$statusAllSame = 0; |
$statusAllSame = 0; |
} |
} |
Line 1366 sub render {
|
Line 1434 sub render {
|
if (($statusAllSame && defined($condenseStatuses{$status})) || |
if (($statusAllSame && defined($condenseStatuses{$status})) || |
($dueAllSame && $status == $curRes->OPEN && $statusAllSame)|| |
($dueAllSame && $status == $curRes->OPEN && $statusAllSame)|| |
($openAllSame && $status == $curRes->OPEN_LATER && $statusAllSame) ){ |
($openAllSame && $status == $curRes->OPEN_LATER && $statusAllSame) ){ |
@parts = (); |
@parts = ($parts[0]); |
$args->{'condensed'} = 1; |
$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 the multipart problem was condensed, "forget" it was multipart |
if (scalar(@parts) == 1) { |
if (scalar(@parts) == 1) { |
$args->{'multipart'} = 0; |
$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 |
# Now, we've decided what parts to show. Loop through them and |
# show them. |
# show them. |
foreach my $part ('', @parts) { |
foreach my $part (@parts) { |
if ($part eq '0') { |
|
next; |
|
} |
|
$rownum ++; |
$rownum ++; |
my $backgroundColor = $backgroundColors[$rownum % scalar(@backgroundColors)]; |
my $backgroundColor = $backgroundColors[$rownum % scalar(@backgroundColors)]; |
|
|
Line 1432 sub render {
|
Line 1505 sub render {
|
} |
} |
} continue { |
} continue { |
$curRes = $it->next(); |
$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 |
# Print out the part that jumps to #curloc if it exists |
Line 1487 You must obtain resource objects through
|
Line 1570 You must obtain resource objects through
|
=over 4 |
=over 4 |
|
|
=item * B<new>(navHashFile, parmHashFile, genCourseAndUserOptions, |
=item * B<new>(navHashFile, parmHashFile, genCourseAndUserOptions, |
genMailDiscussStatus): |
genMailDiscussStatus, getUserData): |
|
|
Binds a new navmap object to the compiled nav map hash and parm hash |
Binds a new navmap object to the compiled nav map hash and parm hash |
given as filenames. genCourseAndUserOptions is a flag saying whether |
given as filenames. genCourseAndUserOptions is a flag saying whether |
Line 1498 documentation. genMailDiscussStatus caus
|
Line 1581 documentation. genMailDiscussStatus caus
|
information about the email and discussion status of |
information about the email and discussion status of |
resources. Returns the navmap object if this is successful, or |
resources. Returns the navmap object if this is successful, or |
B<undef> if not. You must check for undef; errors will occur when you |
B<undef> 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<getIterator>(first, finish, filter, condition): |
=item * B<getIterator>(first, finish, filter, condition): |
|
|
Line 1519 sub new {
|
Line 1603 sub new {
|
$self->{PARM_HASH_FILE} = shift; |
$self->{PARM_HASH_FILE} = shift; |
$self->{GENERATE_COURSE_USER_OPT} = shift; |
$self->{GENERATE_COURSE_USER_OPT} = shift; |
$self->{GENERATE_EMAIL_DISCUSS_STATUS} = shift; |
$self->{GENERATE_EMAIL_DISCUSS_STATUS} = shift; |
|
$self->{GET_USER_DATA} = shift; |
|
|
# Resource cache stores navmap resources as we reference them. We generate |
# 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. |
# them on-demand so we don't pay for creating resources unless we use them. |
Line 1618 sub init {
|
Line 1703 sub init {
|
my %emailstatus = &Apache::lonnet::dump('email_status'); |
my %emailstatus = &Apache::lonnet::dump('email_status'); |
my $logoutTime = $emailstatus{'logout'}; |
my $logoutTime = $emailstatus{'logout'}; |
my $courseLeaveTime = $emailstatus{'logout_'.$ENV{'request.course.id'}}; |
my $courseLeaveTime = $emailstatus{'logout_'.$ENV{'request.course.id'}}; |
$self->{LAST_CHECK} = ($courseLeaveTime < $logoutTime ? |
$self->{LAST_CHECK} = (($courseLeaveTime > $logoutTime) ? |
$courseLeaveTime : $logoutTime); |
$courseLeaveTime : $logoutTime); |
my %discussiontime = &Apache::lonnet::dump('discussiontimes', |
my %discussiontime = &Apache::lonnet::dump('discussiontimes', |
$cdom, $cnum); |
$cdom, $cnum); |
Line 1656 sub init {
|
Line 1741 sub init {
|
$self->{DISCUSSION_TIME} = \%discussiontime; |
$self->{DISCUSSION_TIME} = \%discussiontime; |
$self->{EMAIL_STATUS} = \%emailstatus; |
$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->{PARM_CACHE} = {}; |
$self->{INITED} = 1; |
$self->{INITED} = 1; |
Line 1735 object for that resource. This method, o
|
Line 1828 object for that resource. This method, o
|
(as in the resource object) is the only proper way to obtain a |
(as in the resource object) is the only proper way to obtain a |
resource object. |
resource object. |
|
|
|
=item * B<getBySymb>(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<getMapByMapPc>(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 |
=cut |
|
|
# The strategy here is to cache the resource objects, and only construct them |
# The strategy here is to cache the resource objects, and only construct them |
Line 1765 sub getBySymb {
|
Line 1868 sub getBySymb {
|
return $self->getById($map->map_pc() . '.' . $id); |
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 |
=pod |
|
|
=item * B<firstResource>(): |
=item * B<firstResource>(): |
Line 1970 sub retrieveResources {
|
Line 2081 sub retrieveResources {
|
$map = $self->getResourceByUrl($map); |
$map = $self->getResourceByUrl($map); |
} |
} |
|
|
|
# If nothing was passed, assume top-level map |
|
if (!$map) { |
|
$map = $self->getById('0.0'); |
|
} |
|
|
# Check the map's validity. |
# Check the map's validity. |
if (!$map || !$map->is_map()) { |
if (!$map->is_map()) { |
# Oh, to throw an exception.... how I'd love that! |
# Oh, to throw an exception.... how I'd love that! |
return (); |
return (); |
} |
} |
|
|
# Get an iterator. |
# Get an iterator. |
my $it = $self->getIterator($map->map_start(), $map->map_finish(), |
my $it = $self->getIterator($map->map_start(), $map->map_finish(), |
!$recursive); |
undef, $recursive); |
|
|
my @resources = (); |
my @resources = (); |
|
|
Line 2007 sub retrieveResources {
|
Line 2123 sub retrieveResources {
|
} |
} |
} |
} |
|
|
|
} continue { |
$curRes = $it->next(); |
$curRes = $it->next(); |
} |
} |
|
|
Line 2059 corresponds to where you want the iterat
|
Line 2176 corresponds to where you want the iterat
|
navmap->finishResource(). filterHash is a hash used as a set |
navmap->finishResource(). filterHash is a hash used as a set |
containing strings representing the resource IDs, defaulting to |
containing strings representing the resource IDs, defaulting to |
empty. Condition is a 1 or 0 that sets what to do with the filter |
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 |
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 |
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 |
false (default), the iterator will only return the first level of map |
Line 2131 sub min {
|
Line 2248 sub min {
|
if ($a < $b) { return $a; } else { return $b; } |
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 { |
sub new { |
# magic invocation to create a class instance |
# magic invocation to create a class instance |
my $proto = shift; |
my $proto = shift; |
Line 2191 sub new {
|
Line 2303 sub new {
|
# that isn't just a redirector. |
# that isn't just a redirector. |
my $resource; my $resourceCount = 0; |
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** |
# **1** |
|
|
foreach my $pass (@iterations) { |
foreach my $pass (@iterations) { |
Line 2817 sub symb {
|
Line 2932 sub symb {
|
$self->navHash('map_id_'.$first)) |
$self->navHash('map_id_'.$first)) |
. '___' . $second . '___' . $symbSrc; |
. '___' . $second . '___' . $symbSrc; |
} |
} |
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); } |
sub to { my $self=shift; return $self->navHash("to_", 1); } |
sub to { my $self=shift; return $self->navHash("to_", 1); } |
sub compTitle { |
sub compTitle { |
my $self = shift; |
my $self = shift; |
Line 2869 sub is_map { my $self=shift; return defi
|
Line 2991 sub is_map { my $self=shift; return defi
|
sub is_page { |
sub is_page { |
my $self=shift; |
my $self=shift; |
my $src = $self->src(); |
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 { |
sub is_problem { |
my $self=shift; |
my $self=shift; |
Line 2879 sub is_problem {
|
Line 3002 sub is_problem {
|
sub is_sequence { |
sub is_sequence { |
my $self=shift; |
my $self=shift; |
my $src = $self->src(); |
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. |
# Private method: Shells out to the parmval in the nav map, handler parts. |
Line 2954 sub map_type {
|
Line 3078 sub map_type {
|
return $self->navHash("map_type_$pc", 0); |
return $self->navHash("map_type_$pc", 0); |
} |
} |
|
|
|
|
|
|
##### |
##### |
# Property queries |
# Property queries |
##### |
##### |
Line 2993 Get the Client IP/Name Access Control in
|
Line 3115 Get the Client IP/Name Access Control in
|
|
|
Get the answer-reveal date for the problem. |
Get the answer-reveal date for the problem. |
|
|
|
=item * B<awarded>: |
|
|
|
Gets the awarded value for the problem part. Requires genUserData set to |
|
true when the navmap object was created. |
|
|
=item * B<duedate>: |
=item * B<duedate>: |
|
|
Get the due date for the problem. |
Get the due date for the problem. |
Line 3046 sub answerdate {
|
Line 3173 sub answerdate {
|
} |
} |
return $self->parmval("answerdate", $part); |
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 { |
sub duedate { |
(my $self, my $part) = @_; |
(my $self, my $part) = @_; |
return $self->parmval("duedate", $part); |
return $self->parmval("duedate", $part); |
Line 3087 sub type {
|
Line 3218 sub type {
|
} |
} |
sub weight { |
sub weight { |
my $self = shift; my $part = shift; |
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 |
# Multiple things need this |
Line 3167 sub getErrors {
|
Line 3303 sub getErrors {
|
=item * B<parts>(): |
=item * B<parts>(): |
|
|
Returns a list reference containing sorted strings corresponding to |
Returns a list reference containing sorted strings corresponding to |
each part of the problem. To count the number of parts, use the list |
each part of the problem. Single part problems have only a part '0'. |
in a scalar context, and subtract one if greater than two. (One part |
Multipart problems do not return their part '0', since they typically |
problems have a part 0. Multi-parts have a part 0, plus a part for |
do not really matter. |
each part. Filtering part 0 if you want it is up to you.) |
|
|
|
=item * B<countParts>(): |
=item * B<countParts>(): |
|
|
Returns the number of parts of the problem a student can answer. Thus, |
Returns the number of parts of the problem a student can answer. Thus, |
for single part problems, returns 1. For multipart, it returns the |
for single part problems, returns 1. For multipart, it returns the |
number of parts in the problem, not including psuedo-part 0. Thus, |
number of parts in the problem, not including psuedo-part 0. |
B<parts> may return an array with fewer parts in it then countParts |
|
might lead you to believe. |
=item * B<multipart>(): |
|
|
|
Returns true if the problem is multipart, false otherwise. Use this instead |
|
of countParts if all you want is multipart/not multipart. |
|
|
=item * B<responseType>($part): |
=item * B<responseType>($part): |
|
|
Returns the response type of the part, without the word "response" on the |
Returns the response type of the part, without the word "response" on the |
end. Example return values: 'string', 'essay', 'numeric', etc. |
end. Example return values: 'string', 'essay', 'numeric', etc. |
|
|
=item * B<responseId>($part): |
=item * B<responseIds>($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 |
=back |
|
|
Line 3196 Retreives the response ID for the given
|
Line 3335 Retreives the response ID for the given
|
sub parts { |
sub parts { |
my $self = shift; |
my $self = shift; |
|
|
if ($self->ext) { return ['0']; } |
if ($self->ext) { return []; } |
|
|
$self->extractParts(); |
$self->extractParts(); |
return $self->{PARTS}; |
return $self->{PARTS}; |
Line 3206 sub countParts {
|
Line 3345 sub countParts {
|
my $self = shift; |
my $self = shift; |
|
|
my $parts = $self->parts(); |
my $parts = $self->parts(); |
my $delta = 0; |
|
for my $part (@$parts) { |
# If I left this here, then it's not necessary. |
if ($part eq '0') { $delta--; } |
#my $delta = 0; |
} |
#for my $part (@$parts) { |
|
# if ($part eq '0') { $delta--; } |
|
#} |
|
|
if ($self->{RESOURCE_ERROR}) { |
if ($self->{RESOURCE_ERROR}) { |
return 0; |
return 0; |
} |
} |
|
|
return scalar(@{$parts}) + $delta; |
return scalar(@{$parts}); # + $delta; |
|
} |
|
|
|
sub multipart { |
|
my $self = shift; |
|
return $self->countParts() > 1; |
} |
} |
|
|
sub responseType { |
sub responseType { |
Line 3223 sub responseType {
|
Line 3369 sub responseType {
|
my $part = shift; |
my $part = shift; |
|
|
$self->extractParts(); |
$self->extractParts(); |
return $self->{RESPONSE_TYPE}->{$part}; |
return $self->{RESPONSE_TYPES}->{$part}; |
} |
} |
|
|
sub responseId { |
sub responseIds { |
my $self = shift; |
my $self = shift; |
my $part = shift; |
my $part = shift; |
|
|
Line 3532 B<Composite Status>
|
Line 3678 B<Composite Status>
|
Along with directly returning the date or completion status, the |
Along with directly returning the date or completion status, the |
resource object includes a convenience function B<status>() that will |
resource object includes a convenience function B<status>() that will |
combine the two status tidbits into one composite status that can |
combine the two status tidbits into one composite status that can |
represent the status of the resource as a whole. The precise logic is |
represent the status of the resource as a whole. This method represents |
|
the concept of the thing we want to display to the user on the nav maps |
|
screen, which is a combination of completion and open status. The precise logic is |
documented in the comments of the status method. The following results |
documented in the comments of the status method. The following results |
may be returned, all available as methods on the resource object |
may be returned, all available as methods on the resource object |
($res->NETWORK_FAILURE): In addition to the return values that match |
($res->NETWORK_FAILURE): In addition to the return values that match |
Line 3675 sub status {
|
Line 3823 sub status {
|
} |
} |
|
|
=pod |
=pod |
|
|
|
B<Completable> |
|
|
|
The completable method represents the concept of I<whether the student can |
|
currently do the problem>. If the student can do the problem, which means |
|
that it is open, there are tries left, and if the problem is manually graded |
|
or the grade is suppressed via problemstatus, the student has not tried it |
|
yet, then the method returns 1. Otherwise, it returns 0, to indicate that |
|
either the student has tried it and there is no feedback, or that for |
|
some reason it is no longer completable (not open yet, successfully completed, |
|
out of tries, etc.). As an example, this is used as the filter for the |
|
"Uncompleted Homework" option for the nav maps. |
|
|
|
If this does not quite meet your needs, do not fiddle with it (unless you are |
|
fixing it to better match the student's conception of "completable" because |
|
it's broken somehow)... make a new method. |
|
|
|
=cut |
|
|
|
sub completable { |
|
my $self = shift; |
|
if (!$self->is_problem()) { return 0; } |
|
my $partCount = $self->countParts(); |
|
|
|
foreach my $part (@{$self->parts()}) { |
|
if ($part eq '0' && $partCount != 1) { next; } |
|
my $status = $self->status($part); |
|
# "If 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 all the parts were complete, so was this problem. |
|
return 1; |
|
} |
|
|
|
=pod |
|
|
=head2 Resource/Nav Map Navigation |
=head2 Resource/Nav Map Navigation |
|
|