version 1.242.2.1, 2004/05/06 15:04:00
|
version 1.509.2.14.2.4, 2022/10/06 16:32:04
|
Line 2
|
Line 2
|
# Navigate Maps Handler |
# Navigate Maps Handler |
# |
# |
# $Id$ |
# $Id$ |
|
|
# |
# |
# Copyright Michigan State University Board of Trustees |
# Copyright Michigan State University Board of Trustees |
# |
# |
Line 25
|
Line 26
|
# |
# |
# http://www.lon-capa.org/ |
# http://www.lon-capa.org/ |
# |
# |
# (Page Handler |
### |
# |
|
# (TeX Content Handler |
|
# |
|
# 05/29/00,05/30 Gerd Kortemeyer) |
|
# 08/30,08/31,09/06,09/14,09/15,09/16,09/19,09/20,09/21,09/23, |
|
# 10/02,10/10,10/14,10/16,10/18,10/19,10/31,11/6,11/14,11/16 Gerd Kortemeyer) |
|
# |
|
# 3/1/1,6/1,17/1,29/1,30/1,2/8,9/21,9/24,9/25 Gerd Kortemeyer |
|
# YEAR=2002 |
|
# 1/1 Gerd Kortemeyer |
|
# Oct-Nov Jeremy Bowers |
|
# YEAR=2003 |
|
# Jeremy Bowers ... lots of days |
|
|
|
package Apache::lonnavmaps; |
|
|
|
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 |
|
|
|
# symbolic constants |
|
sub SYMB { return 1; } |
|
sub URL { return 2; } |
|
sub NOTHING { return 3; } |
|
|
|
# Some data |
|
|
|
my $resObj = "Apache::lonnavmaps::resource"; |
|
|
|
# Keep these mappings in sync with lonquickgrades, which uses the colors |
|
# instead of the icons. |
|
my %statusIconMap = |
|
( |
|
$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', |
|
'navmap.wrong.gif' => 'Incorrect', |
|
'navmap.open.gif' => 'Open' ); |
|
|
|
# Defines a status->color mapping, null string means don't color |
|
my %colormap = |
|
( $resObj->NETWORK_FAILURE => '', |
|
$resObj->CORRECT => '', |
|
$resObj->EXCUSED => '#3333FF', |
|
$resObj->PAST_DUE_ANSWER_LATER => '', |
|
$resObj->PAST_DUE_NO_ANSWER => '', |
|
$resObj->ANSWER_OPEN => '#006600', |
|
$resObj->OPEN_LATER => '', |
|
$resObj->TRIES_LEFT => '', |
|
$resObj->INCORRECT => '', |
|
$resObj->OPEN => '', |
|
$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"; |
|
|
|
sub handler { |
|
my $r = shift; |
|
real_handler($r); |
|
} |
|
|
|
sub real_handler { |
|
my $r = shift; |
|
|
|
# Handle header-only request |
|
if ($r->header_only) { |
|
if ($ENV{'browser.mathml'}) { |
|
&Apache::loncommon::content_type($r,'text/xml'); |
|
} else { |
|
&Apache::loncommon::content_type($r,'text/html'); |
|
} |
|
$r->send_http_header; |
|
return OK; |
|
} |
|
|
|
# Send header, don't cache this page |
|
if ($ENV{'browser.mathml'}) { |
|
&Apache::loncommon::content_type($r,'text/xml'); |
|
} else { |
|
&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(); |
|
|
|
if (!defined($navmap)) { |
|
my $requrl = $r->uri; |
|
$ENV{'user.error.msg'} = "$requrl:bre:0:0:Course not initialized"; |
|
return HTTP_NOT_ACCEPTABLE; |
|
} |
|
|
|
$r->print("<html><head>\n"); |
|
$r->print("<title>".&mt('Navigate Course Contents')."</title>"); |
|
# ------------------------------------------------------------ Get query string |
|
&Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['register']); |
|
|
|
# ----------------------------------------------------- Force menu registration |
|
my $addentries=''; |
|
if ($ENV{'form.register'}) { |
|
$addentries=' onLoad="'.&Apache::lonmenu::loadevents(). |
|
'" onUnload="'.&Apache::lonmenu::unloadevents().'"'; |
|
$r->print(&Apache::lonmenu::registerurl(1)); |
|
} |
|
|
|
# Header |
|
$r->print('</head>'. |
|
&Apache::loncommon::bodytag('Navigate Course Contents','', |
|
$addentries,'','',$ENV{'form.register'})); |
|
$r->print('<script>window.focus();</script>'); |
|
|
|
$r->rflush(); |
|
|
|
# Check that it's defined |
|
if (!($navmap->courseMapDefined())) { |
|
$r->print('<font size="+2" color="red">Coursemap undefined.</font>' . |
|
'</body></html>'); |
|
return OK; |
|
} |
|
|
|
# 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 $curRes; |
|
my $sequenceCount = 0; |
|
my $sequenceId; |
|
while ($curRes = $iterator->next()) { |
|
if (ref($curRes) && $curRes->is_sequence()) { |
|
$sequenceCount++; |
|
$sequenceId = $curRes->map_pc(); |
|
} |
|
} |
|
|
|
if ($sequenceCount == 1) { |
|
# The automatic iterator creation in the render call |
|
# will pick this up. We know the condition because |
|
# the defined($ENV{'form.filter'}) also ensures this |
|
# is a fresh call. |
|
$ENV{'form.filter'} = "$sequenceId"; |
|
} |
|
} |
|
|
|
my $jumpToFirstHomework = 0; |
|
# Check to see if the student is jumping to next open, do-able problem |
|
if ($ENV{QUERY_STRING} eq 'jumpToFirstHomework') { |
|
$jumpToFirstHomework = 1; |
|
# Find the next homework problem that they can do. |
|
my $iterator = $navmap->getIterator(undef, undef, undef, 1); |
|
my $curRes; |
|
my $foundDoableProblem = 0; |
|
my $problemRes; |
|
|
|
while (($curRes = $iterator->next()) && !$foundDoableProblem) { |
|
if (ref($curRes) && $curRes->is_problem()) { |
|
my $status = $curRes->status(); |
|
if ($curRes->completable()) { |
|
$problemRes = $curRes; |
|
$foundDoableProblem = 1; |
|
|
|
# Pop open all previous maps |
|
my $stack = $iterator->getStack(); |
|
pop @$stack; # last resource in the stack is the problem |
|
# itself, which we don't need in the map stack |
|
my @mapPcs = map {$_->map_pc()} @$stack; |
|
$ENV{'form.filter'} = join(',', @mapPcs); |
|
|
|
# Mark as both "here" and "jump" |
|
$ENV{'form.postsymb'} = $curRes->symb(); |
|
} |
|
} |
|
} |
|
|
|
# If we found no problems, print a note to that effect. |
|
if (!$foundDoableProblem) { |
|
$r->print("<font size='+2'>All homework assignments have been completed.</font><br /><br />"); |
|
} |
|
} else { |
|
$r->print("<a href='navmaps?jumpToFirstHomework'>" . |
|
&mt("Go To My First Homework Problem")."</a> "); |
|
} |
|
|
|
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_map(); |
|
}; |
|
$r->print("<p><font size='+2'>".&mt("Uncompleted Homework")."</font></p>"); |
|
$ENV{'form.filter'} = ''; |
|
$ENV{'form.condition'} = 1; |
|
$resource_no_folder_link = 1; |
|
} else { |
|
$r->print("<a href='navmaps?showOnlyHomework'>" . |
|
&mt("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(); |
|
|
|
# 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'>".&mt("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->rflush(); |
|
|
|
return OK; |
|
} |
|
|
|
# Convenience functions: Returns a string that adds or subtracts |
|
# the second argument from the first hash, appropriate for the |
|
# query string that determines which folders to recurse on |
|
sub addToFilter { |
|
my $hashIn = shift; |
|
my $addition = shift; |
|
my %hash = %$hashIn; |
|
$hash{$addition} = 1; |
|
|
|
return join (",", keys(%hash)); |
|
} |
|
|
|
sub removeFromFilter { |
|
my $hashIn = shift; |
|
my $subtraction = shift; |
|
my %hash = %$hashIn; |
|
|
|
delete $hash{$subtraction}; |
|
return join(",", keys(%hash)); |
|
} |
|
|
|
# Convenience function: Given a stack returned from getStack on the iterator, |
|
# return the correct src() value. |
|
# Later, this should add an anchor when we start putting anchors in pages. |
|
sub getLinkForResource { |
|
my $stack = shift; |
|
my $res; |
|
|
|
# Check to see if there are any pages in the stack |
|
foreach $res (@$stack) { |
|
if (defined($res) && $res->is_page()) { |
|
return $res->src(); |
|
} |
|
} |
|
|
|
# Failing that, return the src of the last resource that is defined |
|
# (when we first recurse on a map, it puts an undefined resource |
|
# on the bottom because $self->{HERE} isn't defined yet, and we |
|
# want the src for the map anyhow) |
|
foreach (@$stack) { |
|
if (defined($_)) { $res = $_; } |
|
} |
|
|
|
return $res->src(); |
|
} |
|
|
|
# Convenience function: This seperates the logic of how to create |
|
# the problem text strings ("Due: DATE", "Open: DATE", "Not yet assigned", |
|
# etc.) into a seperate function. It takes a resource object as the |
|
# first parameter, and the part number of the resource as the second. |
|
# It's basically a big switch statement on the status of the resource. |
|
|
|
sub getDescription { |
|
my $res = shift; |
|
my $part = shift; |
|
my $status = $res->status($part); |
|
|
|
if ($status == $res->NETWORK_FAILURE) { |
|
return &mt("Having technical difficulties; please check status later"); |
|
} |
|
if ($status == $res->NOTHING_SET) { |
|
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 &mt("Due")." " .timeToHumanString($res->duedate($part)); |
|
} else { |
|
return &mt("Open, no due date"); |
|
} |
|
} |
|
if ($status == $res->PAST_DUE_ANSWER_LATER) { |
|
return &mt("Answer open")." " . timeToHumanString($res->answerdate($part)); |
|
} |
|
if ($status == $res->PAST_DUE_NO_ANSWER) { |
|
return &mt("Was due")." " . timeToHumanString($res->duedate($part)); |
|
} |
|
if ($status == $res->ANSWER_OPEN) { |
|
return &mt("Answer available"); |
|
} |
|
if ($status == $res->EXCUSED) { |
|
return &mt("Excused by instructor"); |
|
} |
|
if ($status == $res->ATTEMPTED) { |
|
return &mt("Answer submitted, not yet graded"); |
|
} |
|
if ($status == $res->TRIES_LEFT) { |
|
my $tries = $res->tries($part); |
|
my $maxtries = $res->maxtries($part); |
|
my $triesString = ""; |
|
if ($tries && $maxtries) { |
|
$triesString = "<font size=\"-1\"><i>($tries of $maxtries tries used)</i></font>"; |
|
if ($maxtries > 1 && $maxtries - $tries == 1) { |
|
$triesString = "<b>$triesString</b>"; |
|
} |
|
} |
|
if ($res->duedate()) { |
|
return &mt("Due")." " . timeToHumanString($res->duedate($part)) . |
|
" $triesString"; |
|
} else { |
|
return &mt("No due date")." $triesString"; |
|
} |
|
} |
|
if ($status == $res->ANSWER_SUBMITTED) { |
|
return &mt('Answer submitted'); |
|
} |
|
} |
|
|
|
# Convenience function, so others can use it: Is the problem due in less then |
|
# 24 hours, and still can be done? |
|
|
|
sub dueInLessThen24Hours { |
|
my $res = shift; |
|
my $part = shift; |
|
my $status = $res->status($part); |
|
|
|
return ($status == $res->OPEN() || |
|
$status == $res->TRIES_LEFT()) && |
|
$res->duedate() && $res->duedate() < time()+(24*60*60) && |
|
$res->duedate() > time(); |
|
} |
|
|
|
# Convenience function, so others can use it: Is there only one try remaining for the |
|
# part, with more then one try to begin with, not due yet and still can be done? |
|
sub lastTry { |
|
my $res = shift; |
|
my $part = shift; |
|
|
|
my $tries = $res->tries($part); |
|
my $maxtries = $res->maxtries($part); |
|
return $tries && $maxtries && $maxtries > 1 && |
|
$maxtries - $tries == 1 && $res->duedate() && |
|
$res->duedate() > time(); |
|
} |
|
|
|
# This puts a human-readable name on the ENV variable. |
|
|
|
sub advancedUser { |
|
return $ENV{'request.role.adv'}; |
|
} |
|
|
|
|
|
# timeToHumanString takes a time number and converts it to a |
|
# human-readable representation, meant to be used in the following |
|
# manner: |
|
# print "Due $timestring" |
|
# print "Open $timestring" |
|
# print "Answer available $timestring" |
|
# Very, very, very, VERY English-only... goodness help a localizer on |
|
# this func... |
|
sub timeToHumanString { |
|
my ($time) = @_; |
|
# zero, '0' and blank are bad times |
|
if (!$time) { |
|
return &mt('never'); |
|
} |
|
unless (&Apache::lonlocal::current_language()=~/^en/) { |
|
return &Apache::lonlocal::locallocaltime($time); |
|
} |
|
my $now = time(); |
|
|
|
my @time = localtime($time); |
|
my @now = localtime($now); |
|
|
|
# Positive = future |
|
my $delta = $time - $now; |
|
|
|
my $minute = 60; |
|
my $hour = 60 * $minute; |
|
my $day = 24 * $hour; |
|
my $week = 7 * $day; |
|
my $inPast = 0; |
|
|
|
# Logic in comments: |
|
# Is it now? (extremely unlikely) |
|
if ( $delta == 0 ) { |
|
return "this instant"; |
|
} |
|
|
|
if ($delta < 0) { |
|
$inPast = 1; |
|
$delta = -$delta; |
|
} |
|
|
|
if ( $delta > 0 ) { |
|
|
|
my $tense = $inPast ? " ago" : ""; |
|
my $prefix = $inPast ? "" : "in "; |
|
|
|
# Less then a minute |
|
if ( $delta < $minute ) { |
|
if ($delta == 1) { return "${prefix}1 second$tense"; } |
|
return "$prefix$delta seconds$tense"; |
|
} |
|
|
|
# Less then an hour |
|
if ( $delta < $hour ) { |
|
# If so, use minutes |
|
my $minutes = floor($delta / 60); |
|
if ($minutes == 1) { return "${prefix}1 minute$tense"; } |
|
return "$prefix$minutes minutes$tense"; |
|
} |
|
|
|
# Is it less then 24 hours away? If so, |
|
# display hours + minutes |
|
if ( $delta < $hour * 24) { |
|
my $hours = floor($delta / $hour); |
|
my $minutes = floor(($delta % $hour) / $minute); |
|
my $hourString = "$hours hours"; |
|
my $minuteString = ", $minutes minutes"; |
|
if ($hours == 1) { |
|
$hourString = "1 hour"; |
|
} |
|
if ($minutes == 1) { |
|
$minuteString = ", 1 minute"; |
|
} |
|
if ($minutes == 0) { |
|
$minuteString = ""; |
|
} |
|
return "$prefix$hourString$minuteString$tense"; |
|
} |
|
|
|
# Less then 5 days away, display day of the week and |
|
# HH:MM |
|
if ( $delta < $day * 5 ) { |
|
my $timeStr = strftime("%A, %b %e at %I:%M %P", localtime($time)); |
|
$timeStr =~ s/12:00 am/00:00/; |
|
$timeStr =~ s/12:00 pm/noon/; |
|
return ($inPast ? "last " : "next ") . |
|
$timeStr; |
|
} |
|
|
|
# Is it this year? |
|
if ( $time[5] == $now[5]) { |
|
# Return on Month Day, HH:MM meridian |
|
my $timeStr = strftime("on %A, %b %e at %I:%M %P", localtime($time)); |
|
$timeStr =~ s/12:00 am/00:00/; |
|
$timeStr =~ s/12:00 pm/noon/; |
|
return $timeStr; |
|
} |
|
|
|
# Not this year, so show the year |
|
my $timeStr = strftime("on %A, %b %e %G at %I:%M %P", localtime($time)); |
|
$timeStr =~ s/12:00 am/00:00/; |
|
$timeStr =~ s/12:00 pm/noon/; |
|
return $timeStr; |
|
} |
|
} |
|
|
|
|
|
=pod |
=pod |
|
|
=head1 NAME |
=head1 NAME |
|
|
Apache::lonnavmap - Subroutines to handle and render the navigation |
Apache::lonnavmaps - Subroutines to handle and render the navigation |
maps |
|
|
|
=head1 SYNOPSIS |
=head1 SYNOPSIS |
|
|
|
Handles navigational maps. |
|
|
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. |
|
|
|
|
|
This is part of the LearningOnline Network with CAPA project |
|
described at http://www.lon-capa.org. |
|
|
|
|
=head1 OVERVIEW |
=head1 OVERVIEW |
|
|
X<lonnavmaps, overview> When a user enters a course, LON-CAPA examines the |
X<lonnavmaps, overview> When a user enters a course, LON-CAPA examines the |
Line 555 to compute due to the amount of data tha
|
Line 64 to compute due to the amount of data tha
|
processed. |
processed. |
|
|
Apache::lonnavmaps provides an object model for manipulating this |
Apache::lonnavmaps provides an object model for manipulating this |
information in a higher-level fashion then directly manipulating |
information in a higher-level fashion than directly manipulating |
the hash. It also provides access to several auxilary functions |
the hash. It also provides access to several auxilary functions |
that aren't necessarily stored in the Big Hash, but are a per- |
that aren't necessarily stored in the Big Hash, but are a per- |
resource sort of value, like whether there is any feedback on |
resource sort of value, like whether there is any feedback on |
Line 601 Apache::lonnavmaps::render({}).
|
Line 110 Apache::lonnavmaps::render({}).
|
=head2 Overview of Columns |
=head2 Overview of Columns |
|
|
The renderer will build an HTML table for the navmap and return |
The renderer will build an HTML table for the navmap and return |
it. The table is consists of several columns, and a row for each |
it. The table 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 prepared columns, and the renderer will assemble |
one or more of the prepared columns, and the renderer will assemble |
Line 655 If true, the resource's folder will not
|
Line 164 If true, the resource's folder will not
|
it. Default is false. True implies printCloseAll is false, since you |
it. Default is false. True implies printCloseAll is false, since you |
can't close or open folders when this is on anyhow. |
can't close or open folders when this is on anyhow. |
|
|
|
=item * B<map_no_edit_link>: |
|
|
|
If true, the title of the folder or page will not be followed by an |
|
icon/link to direct editing of a folder or composite page, originally |
|
added via the Course Editor. |
|
|
=back |
=back |
|
|
=item * B<Apache::lonnavmaps::communication_status>: |
=item * B<Apache::lonnavmaps::communication_status>: |
Line 716 automatically.
|
Line 231 automatically.
|
|
|
=over 4 |
=over 4 |
|
|
=item * B<iterator>: default: constructs one from %ENV |
=item * B<iterator>: default: constructs one from %env |
|
|
A reference to a fresh ::iterator to use from the navmaps. The |
A reference to a fresh ::iterator to use from the navmaps. The |
rendering will reflect the options passed to the iterator, so you can |
rendering will reflect the options passed to the iterator, so you can |
use that to just render a certain part of the course, if you like. If |
use that to just render a certain part of the course, if you like. If |
one is not passed, the renderer will attempt to construct one from |
one is not passed, the renderer will attempt to construct one from |
ENV{'form.filter'} and ENV{'form.condition'} information, plus the |
env{'form.filter'} and env{'form.condition'} information, plus the |
'iterator_map' parameter if any. |
'iterator_map' parameter if any. |
|
|
=item * B<iterator_map>: default: not used |
=item * B<iterator_map>: default: not used |
Line 732 instruct the renderer to render only a p
|
Line 247 instruct the renderer to render only a p
|
the source of the map you want to process, like |
the source of the map you want to process, like |
'/res/103/jerf/navmap.course.sequence'. |
'/res/103/jerf/navmap.course.sequence'. |
|
|
=item * B<navmap>: default: constructs one from %ENV |
=item * B<include_top_level_map>: default: false |
|
|
|
If you need to include the top level map (meaning the course) in the |
|
rendered output set this to true |
|
|
|
=item * B<navmap>: default: constructs one from %env |
|
|
A reference to a navmap, used only if an iterator is not passed in. If |
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 |
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 |
one will be constructed based on env info. This is useful to do basic |
error checking before passing it off to render. |
error checking before passing it off to render. |
|
|
=item * B<r>: default: must be passed in |
=item * B<r>: default: must be passed in |
Line 762 then only one line will be displayed for
|
Line 282 then only one line will be displayed for
|
all parts will always be displayed. If showParts is 0, this is |
all parts will always be displayed. If showParts is 0, this is |
ignored. |
ignored. |
|
|
=item * B<jumpCount>: default: determined from %ENV |
=item * B<jumpCount>: default: determined from %env |
|
|
A string identifying the URL to place the anchor 'curloc' at. |
A string identifying the URL to place the anchor 'curloc' at. |
It is the responsibility of the renderer user to |
It is the responsibility of the renderer user to |
ensure that the #curloc is in the URL. By default, determined through |
ensure that the #curloc is in the URL. By default, determined through |
the use of the ENV{} 'jump' information, and should normally "just |
the use of the env{} 'jump' information, and should normally "just |
work" correctly. |
work" correctly. |
|
|
=item * B<here>: default: empty string |
=item * B<here>: default: empty string |
Line 795 are allowing the user to open and close
|
Line 315 are allowing the user to open and close
|
|
|
Describes the currently-open row number to cause the browser to jump |
Describes the currently-open row number to cause the browser to jump |
to, because the user just opened that folder. By default, pulled from |
to, because the user just opened that folder. By default, pulled from |
the Jump information in the ENV{'form.*'}. |
the Jump information in the env{'form.*'}. |
|
|
=item * B<printKey>: default: false |
=item * B<printKey>: default: false |
|
|
Line 852 navmaps screen) uses this to display the
|
Line 372 navmaps screen) uses this to display the
|
|
|
=cut |
=cut |
|
|
|
|
|
=head1 SUBROUTINES |
|
|
|
=over |
|
|
|
=item update() |
|
|
|
=item addToFilter() |
|
|
|
Convenience functions: Returns a string that adds or subtracts |
|
the second argument from the first hash, appropriate for the |
|
query string that determines which folders to recurse on |
|
|
|
=item removeFromFilter() |
|
|
|
=item getLinkForResource() |
|
|
|
Convenience function: Given a stack returned from getStack on the iterator, |
|
return the correct src() value. |
|
|
|
=item getDescription() |
|
|
|
Convenience function: This separates the logic of how to create |
|
the problem text strings ("Due: DATE", "Open: DATE", "Not yet assigned", |
|
etc.) into a separate function. It takes a resource object as the |
|
first parameter, and the part number of the resource as the second. |
|
It's basically a big switch statement on the status of the resource. |
|
|
|
=item dueInLessThan24Hours() |
|
|
|
Convenience function, so others can use it: Is the problem due in less than 24 hours, and still can be done? |
|
|
|
=item lastTry() |
|
|
|
Convenience function, so others can use it: Is there only one try remaining for the |
|
part, with more than one try to begin with, not due yet and still can be done? |
|
|
|
=item advancedUser() |
|
|
|
This puts a human-readable name on the env variable. |
|
|
|
=item timeToHumanString() |
|
|
|
timeToHumanString takes a time number and converts it to a |
|
human-readable representation, meant to be used in the following |
|
manner: |
|
|
|
=over 4 |
|
|
|
=item * print "Due $timestring" |
|
|
|
=item * print "Open $timestring" |
|
|
|
=item * print "Answer available $timestring" |
|
|
|
=back |
|
|
|
Very, very, very, VERY English-only... goodness help a localizer on |
|
this func... |
|
|
|
=item resource() |
|
|
|
returns 0 |
|
|
|
=item communication_status() |
|
|
|
returns 1 |
|
|
|
=item quick_status() |
|
|
|
returns 2 |
|
|
|
=item long_status() |
|
|
|
returns 3 |
|
|
|
=item part_status_summary() |
|
|
|
returns 4 |
|
|
|
=item render_resource() |
|
|
|
=item render_communication_status() |
|
|
|
=item render_quick_status() |
|
|
|
=item render_long_status() |
|
|
|
=item render_parts_summary_status() |
|
|
|
=item setDefault() |
|
|
|
=item cmp_title() |
|
|
|
=item render() |
|
|
|
=item add_linkitem() |
|
|
|
=item show_linkitems_toolbar() |
|
|
|
=back |
|
|
|
=cut |
|
|
|
package Apache::lonnavmaps; |
|
|
|
use strict; |
|
use GDBM_File; |
|
use Apache::loncommon(); |
|
use Apache::lonenc(); |
|
use Apache::lonlocal; |
|
use Apache::lonnet; |
|
use Apache::lonmap; |
|
|
|
use POSIX qw (ceil floor strftime); |
|
use Time::HiRes qw( gettimeofday tv_interval ); |
|
use LONCAPA; |
|
use DateTime(); |
|
use HTML::Entities; |
|
|
|
# For debugging |
|
|
|
#use Data::Dumper; |
|
|
|
|
|
# symbolic constants |
|
sub SYMB { return 1; } |
|
sub URL { return 2; } |
|
sub NOTHING { return 3; } |
|
|
|
# Some data |
|
|
|
my $resObj = "Apache::lonnavmaps::resource"; |
|
|
|
# Keep these mappings in sync with lonquickgrades, which usesthe colors |
|
# instead of the icons. |
|
my %statusIconMap = |
|
( |
|
$resObj->CLOSED => '', |
|
$resObj->OPEN => 'navmap.open.gif', |
|
$resObj->CORRECT => 'navmap.correct.gif', |
|
$resObj->PARTIALLY_CORRECT => 'navmap.partial.gif', |
|
$resObj->INCORRECT => 'navmap.wrong.gif', |
|
$resObj->ATTEMPTED => 'navmap.ellipsis.gif', |
|
$resObj->ERROR => '' |
|
); |
|
|
|
my %iconAltTags = #texthash does not work here |
|
( 'navmap.correct.gif' => 'Correct', |
|
'navmap.wrong.gif' => 'Incorrect', |
|
'navmap.open.gif' => 'Is Open', |
|
'navmap.partial.gif' => 'Partially Correct', |
|
'navmap.ellipsis.gif' => 'Attempted', |
|
); |
|
|
|
# Defines a status->color mapping, null string means don't color |
|
my %colormap = |
|
( $resObj->NETWORK_FAILURE => '', |
|
$resObj->CORRECT => '', |
|
$resObj->EXCUSED => '#3333FF', |
|
$resObj->PAST_DUE_ANSWER_LATER => '', |
|
$resObj->PAST_DUE_NO_ANSWER => '', |
|
$resObj->ANSWER_OPEN => '#006600', |
|
$resObj->OPEN_LATER => '', |
|
$resObj->TRIES_LEFT => '', |
|
$resObj->INCORRECT => '', |
|
$resObj->OPEN => '', |
|
$resObj->NOTHING_SET => '', |
|
$resObj->ATTEMPTED => '', |
|
$resObj->CREDIT_ATTEMPTED => '', |
|
$resObj->ANSWER_SUBMITTED => '', |
|
$resObj->PARTIALLY_CORRECT => '#006600' |
|
); |
|
# And a special case in the nav map; what to do when the assignment |
|
# is not yet done and due in less than 24 hours |
|
my $hurryUpColor = "#FF0000"; |
|
|
|
sub addToFilter { |
|
my $hashIn = shift; |
|
my $addition = shift; |
|
my %hash = %$hashIn; |
|
$hash{$addition} = 1; |
|
|
|
return join (",", keys(%hash)); |
|
} |
|
|
|
sub removeFromFilter { |
|
my $hashIn = shift; |
|
my $subtraction = shift; |
|
my %hash = %$hashIn; |
|
|
|
delete $hash{$subtraction}; |
|
return join(",", keys(%hash)); |
|
} |
|
|
|
sub getLinkForResource { |
|
my $stack = shift; |
|
my $res; |
|
|
|
# Check to see if there are any pages in the stack |
|
foreach $res (@$stack) { |
|
if (defined($res)) { |
|
my $anchor; |
|
if ($res->is_page()) { |
|
foreach my $item (@$stack) { if (defined($item)) { $anchor = $item; } } |
|
if ($anchor->encrypted() && !&advancedUser()) { |
|
$anchor='LC_'.$anchor->id(); |
|
} else { |
|
$anchor=&escape($anchor->shown_symb()); |
|
} |
|
return ($res->link(),$res->shown_symb(),$anchor); |
|
} |
|
# in case folder was skipped over as "only sequence" |
|
my ($map,$id,$src)=&Apache::lonnet::decode_symb($res->symb()); |
|
if ($map=~/\.page$/) { |
|
my $url=&Apache::lonnet::clutter($map); |
|
$anchor=&escape($res->shown_symb()); |
|
return ($url,$res->shown_symb(),$anchor); |
|
} |
|
} |
|
} |
|
|
|
# Failing that, return the src of the last resource that is defined |
|
# (when we first recurse on a map, it puts an undefined resource |
|
# on the bottom because $self->{HERE} isn't defined yet, and we |
|
# want the src for the map anyhow) |
|
foreach my $item (@$stack) { |
|
if (defined($item)) { $res = $item; } |
|
} |
|
|
|
if ($res) { |
|
return ($res->link(),$res->shown_symb()); |
|
} |
|
return; |
|
} |
|
|
|
|
|
|
|
sub getDescription { |
|
my $res = shift; |
|
my $part = shift; |
|
my $status = $res->status($part); |
|
|
|
my $open = $res->opendate($part); |
|
my $due = $res->duedate($part); |
|
my $answer = $res->answerdate($part); |
|
|
|
if ($status == $res->NETWORK_FAILURE) { |
|
return &mt("Having technical difficulties; please check status later"); |
|
} |
|
if ($status == $res->NOTHING_SET) { |
|
return &Apache::lonhtmlcommon::direct_parm_link(&mt('Not currently assigned'),$res->symb(),'opendate',$part); |
|
} |
|
if ($status == $res->OPEN_LATER) { |
|
return &mt("Open [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($open,'start'),$res->symb(),'opendate',$part)); |
|
} |
|
my $slotinfo; |
|
if ($res->simpleStatus($part) == $res->OPEN) { |
|
unless (&Apache::lonnet::allowed('mgr',$env{'request.course.id'})) { |
|
my ($slot_status,$slot_time,$slot_name)=$res->check_for_slot($part); |
|
my $slotmsg; |
|
if ($slot_status == $res->UNKNOWN) { |
|
$slotmsg = &mt('Reservation status unknown'); |
|
} elsif ($slot_status == $res->RESERVED) { |
|
$slotmsg = &mt('Reserved - ends [_1]', |
|
timeToHumanString($slot_time,'end')); |
|
} elsif ($slot_status == $res->RESERVED_LOCATION) { |
|
$slotmsg = &mt('Reserved - specific location(s) - ends [_1]', |
|
timeToHumanString($slot_time,'end')); |
|
} elsif ($slot_status == $res->RESERVED_LATER) { |
|
$slotmsg = &mt('Reserved - next open [_1]', |
|
timeToHumanString($slot_time,'start')); |
|
} elsif ($slot_status == $res->RESERVABLE) { |
|
$slotmsg = &mt('Reservable, reservations close [_1]', |
|
timeToHumanString($slot_time,'end')); |
|
} elsif ($slot_status == $res->NEEDS_CHECKIN) { |
|
$slotmsg = &mt('Reserved, check-in needed - ends [_1]', |
|
timeToHumanString($slot_time,'end')); |
|
} elsif ($slot_status == $res->RESERVABLE_LATER) { |
|
$slotmsg = &mt('Reservable, reservations open [_1]', |
|
timeToHumanString($slot_time,'start')); |
|
} elsif ($slot_status == $res->NOT_IN_A_SLOT) { |
|
$slotmsg = &mt('Reserve a time/place to work'); |
|
} elsif ($slot_status == $res->NOTRESERVABLE) { |
|
$slotmsg = &mt('Reservation not available'); |
|
} elsif ($slot_status == $res->WAITING_FOR_GRADE) { |
|
$slotmsg = &mt('Submission in grading queue'); |
|
} |
|
if ($slotmsg) { |
|
if ($res->is_task() || !$due) { |
|
return $slotmsg; |
|
} |
|
$slotinfo = (' ' x 2).'('.$slotmsg.')'; |
|
} |
|
} |
|
} |
|
if ($status == $res->OPEN) { |
|
if ($due) { |
|
if ($res->is_practice()) { |
|
return &mt("Closes [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'start'),$res->symb(),'duedate',$part)).$slotinfo; |
|
} else { |
|
return &mt("Due [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'end'),$res->symb(),'duedate',$part)).$slotinfo; |
|
} |
|
} else { |
|
return &Apache::lonhtmlcommon::direct_parm_link(&mt("Open, no due date"),$res->symb(),'duedate',$part).$slotinfo; |
|
} |
|
} |
|
if ($status == $res->PAST_DUE_ANSWER_LATER) { |
|
return &mt("Answer open [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($answer,'start'),$res->symb(),'answerdate',$part)); |
|
} |
|
if ($status == $res->PAST_DUE_NO_ANSWER) { |
|
if ($res->is_practice()) { |
|
return &mt("Closed [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'start'),$res->symb(),'answerdate,duedate',$part)); |
|
} else { |
|
return &mt("Was due [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'end'),$res->symb(),'answerdate,duedate',$part)); |
|
} |
|
} |
|
if (($status == $res->ANSWER_OPEN || $status == $res->PARTIALLY_CORRECT) |
|
&& $res->handgrade($part) ne 'yes') { |
|
return &Apache::lonhtmlcommon::direct_parm_link(&mt("Answer available"),$res->symb(),'answerdate,duedate',$part); |
|
} |
|
if ($status == $res->EXCUSED) { |
|
return &mt("Excused by instructor"); |
|
} |
|
if ($status == $res->ATTEMPTED) { |
|
if ($res->is_anonsurvey($part) || $res->is_survey($part)) { |
|
return &mt("Survey submission recorded"); |
|
} else { |
|
return &mt("Answer submitted, not yet graded"); |
|
} |
|
} |
|
if ($status == $res->CREDIT_ATTEMPTED) { |
|
if ($res->is_anonsurvey($part) || $res->is_survey($part)) { |
|
return &mt("Credit for survey submission"); |
|
} |
|
} |
|
if ($status == $res->TRIES_LEFT) { |
|
my $tries = $res->tries($part); |
|
my $maxtries = $res->maxtries($part); |
|
my $triesString = ""; |
|
if ($tries && $maxtries) { |
|
$triesString = '<span class="LC_fontsize_medium"><i>('.&mt('[_1] of [quant,_2,try,tries] used',$tries,$maxtries).')</i></span>'; |
|
if ($maxtries > 1 && $maxtries - $tries == 1) { |
|
$triesString = "<b>$triesString</b>"; |
|
} |
|
} |
|
if ($due) { |
|
return &mt("Due [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'end'),$res->symb(),'duedate',$part)) . |
|
" $triesString"; |
|
} else { |
|
return &Apache::lonhtmlcommon::direct_parm_link(&mt("No due date"),$res->symb(),'duedate',$part)." $triesString"; |
|
} |
|
} |
|
if ($status == $res->ANSWER_SUBMITTED) { |
|
return &mt('Answer submitted'); |
|
} |
|
} |
|
|
|
|
|
sub dueInLessThan24Hours { |
|
my $res = shift; |
|
my $part = shift; |
|
my $status = $res->status($part); |
|
|
|
return ($status == $res->OPEN() || |
|
$status == $res->TRIES_LEFT()) && |
|
$res->duedate($part) && $res->duedate($part) < time()+(24*60*60) && |
|
$res->duedate($part) > time(); |
|
} |
|
|
|
|
|
sub lastTry { |
|
my $res = shift; |
|
my $part = shift; |
|
|
|
my $tries = $res->tries($part); |
|
my $maxtries = $res->maxtries($part); |
|
return $tries && $maxtries && $maxtries > 1 && |
|
$maxtries - $tries == 1 && $res->duedate($part) && |
|
$res->duedate($part) > time(); |
|
} |
|
|
|
|
|
sub advancedUser { |
|
return $env{'request.role.adv'}; |
|
} |
|
|
|
sub timeToHumanString { |
|
my ($time,$type,$format) = @_; |
|
|
|
# zero, '0' and blank are bad times |
|
if (!$time) { |
|
return &mt('never'); |
|
} |
|
unless (&Apache::lonlocal::current_language()=~/^en/) { |
|
return &Apache::lonlocal::locallocaltime($time); |
|
} |
|
my $now = time(); |
|
|
|
# Positive = future |
|
my $delta = $time - $now; |
|
|
|
my $minute = 60; |
|
my $hour = 60 * $minute; |
|
my $day = 24 * $hour; |
|
my $week = 7 * $day; |
|
my $inPast = 0; |
|
|
|
# Logic in comments: |
|
# Is it now? (extremely unlikely) |
|
if ( $delta == 0 ) { |
|
return "this instant"; |
|
} |
|
|
|
if ($delta < 0) { |
|
$inPast = 1; |
|
$delta = -$delta; |
|
} |
|
|
|
if ( $delta > 0 ) { |
|
|
|
my $tense = $inPast ? " ago" : ""; |
|
my $prefix = $inPast ? "" : "in "; |
|
|
|
# Less than a minute |
|
if ( $delta < $minute ) { |
|
if ($delta == 1) { return "${prefix}1 second$tense"; } |
|
return "$prefix$delta seconds$tense"; |
|
} |
|
|
|
# Less than an hour |
|
if ( $delta < $hour ) { |
|
# If so, use minutes; or minutes, seconds (if format requires) |
|
my $minutes = floor($delta / 60); |
|
if (($format ne '') && ($format =~ /\%(T|S)/)) { |
|
my $display; |
|
if ($minutes == 1) { |
|
$display = "${prefix}1 minute"; |
|
} else { |
|
$display = "$prefix$minutes minutes"; |
|
} |
|
my $seconds = $delta % $minute; |
|
if ($seconds == 0) { |
|
$display .= $tense; |
|
} elsif ($seconds == 1) { |
|
$display .= ", 1 second$tense"; |
|
} else { |
|
$display .= ", $seconds seconds$tense"; |
|
} |
|
return $display; |
|
} |
|
if ($minutes == 1) { return "${prefix}1 minute$tense"; } |
|
return "$prefix$minutes minutes$tense"; |
|
} |
|
|
|
# Is it less than 24 hours away? If so, |
|
# display hours + minutes, (and + seconds, if format specified it) |
|
if ( $delta < $hour * 24) { |
|
my $hours = floor($delta / $hour); |
|
my $minutes = floor(($delta % $hour) / $minute); |
|
my $hourString = "$hours hours"; |
|
my $minuteString = ", $minutes minutes"; |
|
if ($hours == 1) { |
|
$hourString = "1 hour"; |
|
} |
|
if ($minutes == 1) { |
|
$minuteString = ", 1 minute"; |
|
} |
|
if ($minutes == 0) { |
|
$minuteString = ""; |
|
} |
|
if (($format ne '') && ($format =~ /\%(T|S)/)) { |
|
my $display = "$prefix$hourString$minuteString"; |
|
my $seconds = $delta-(($hours * $hour)+($minutes * $minute)); |
|
if ($seconds == 0) { |
|
$display .= $tense; |
|
} elsif ($seconds == 1) { |
|
$display .= ", 1 second$tense"; |
|
} else { |
|
$display .= ", $seconds seconds$tense"; |
|
} |
|
return $display; |
|
} |
|
return "$prefix$hourString$minuteString$tense"; |
|
} |
|
|
|
# Date/time is more than 24 hours away |
|
|
|
my $dt = DateTime->from_epoch(epoch => $time) |
|
->set_time_zone(&Apache::lonlocal::gettimezone()); |
|
|
|
# If there's a caller supplied format, use it, unless it only displays |
|
# H:M:S or H:M. |
|
|
|
if (($format ne '') && ($format ne '%T') && ($format ne '%R')) { |
|
my $timeStr = $dt->strftime($format); |
|
return $timeStr.' ('.$dt->time_zone_short_name().')'; |
|
} |
|
|
|
# Less than 5 days away, display day of the week and |
|
# HH:MM |
|
|
|
if ( $delta < $day * 5 ) { |
|
my $timeStr = $dt->strftime("%A, %b %e at %I:%M %P (%Z)"); |
|
$timeStr =~ s/12:00 am/00:00/; |
|
$timeStr =~ s/12:00 pm/noon/; |
|
return ($inPast ? "last " : "this ") . |
|
$timeStr; |
|
} |
|
|
|
my $conjunction='on'; |
|
if ($type eq 'start') { |
|
$conjunction='at'; |
|
} elsif ($type eq 'end') { |
|
$conjunction='by'; |
|
} |
|
# Is it this year? |
|
my $dt_now = DateTime->from_epoch(epoch => $now) |
|
->set_time_zone(&Apache::lonlocal::gettimezone()); |
|
if ( $dt->year() == $dt_now->year()) { |
|
# Return on Month Day, HH:MM meridian |
|
my $timeStr = $dt->strftime("$conjunction %A, %b %e at %I:%M %P (%Z)"); |
|
$timeStr =~ s/12:00 am/00:00/; |
|
$timeStr =~ s/12:00 pm/noon/; |
|
return $timeStr; |
|
} |
|
|
|
# Not this year, so show the year |
|
my $timeStr = |
|
$dt->strftime("$conjunction %A, %b %e %Y at %I:%M %P (%Z)"); |
|
$timeStr =~ s/12:00 am/00:00/; |
|
$timeStr =~ s/12:00 pm/noon/; |
|
return $timeStr; |
|
} |
|
} |
|
|
|
|
sub resource { return 0; } |
sub resource { return 0; } |
sub communication_status { return 1; } |
sub communication_status { return 1; } |
sub quick_status { return 2; } |
sub quick_status { return 2; } |
Line 861 sub part_status_summary { return 4; }
|
Line 918 sub part_status_summary { return 4; }
|
sub render_resource { |
sub render_resource { |
my ($resource, $part, $params) = @_; |
my ($resource, $part, $params) = @_; |
|
|
|
my $editmapLink; |
my $nonLinkedText = ''; # stuff after resource title not in link |
my $nonLinkedText = ''; # stuff after resource title not in link |
|
|
my $link = $params->{"resourceLink"}; |
my $link = $params->{"resourceLink"}; |
|
if ($resource->ext()) { |
|
$link =~ s/\#.+(\?)/$1/g; |
|
} |
|
|
|
# The URL part is not escaped at this point, but the symb is... |
|
|
my $src = $resource->src(); |
my $src = $resource->src(); |
my $it = $params->{"iterator"}; |
my $it = $params->{"iterator"}; |
my $filter = $it->{FILTER}; |
my $filter = $it->{FILTER}; |
|
|
my $title = $resource->compTitle(); |
my $title = $resource->compTitle(); |
if ($src =~ /^\/uploaded\//) { |
|
$nonLinkedText=$title; |
|
$title = ''; |
|
} |
|
my $partLabel = ""; |
my $partLabel = ""; |
my $newBranchText = ""; |
my $newBranchText = ""; |
|
my $location=&Apache::loncommon::lonhttpdurl("/adm/lonIcons"); |
# If this is a new branch, label it so |
# If this is a new branch, label it so |
if ($params->{'isNewBranch'}) { |
if ($params->{'isNewBranch'}) { |
$newBranchText = "<img src='/adm/lonIcons/branch.gif' border='0' />"; |
$newBranchText = "<img src='$location/branch.gif' alt=".mt('Branch')." />"; |
} |
} |
|
|
# links to open and close the folder |
# links to open and close the folder |
my $linkopen = "<a href='$link'>"; |
|
my $linkclose = "</a>"; |
my $whitespace = $location.'/whitespace_21.gif'; |
|
my ($nomodal,$linkopen,$linkclose); |
|
unless ($resource->is_map() || $params->{'resource_nolink'}) { |
|
$linkopen = "<img src='$whitespace' alt='' />"; |
|
$linkclose = "</a>"; |
|
if (($params->{'modalLink'}) && (!$resource->is_sequence())) { |
|
if ($link =~m{^(?:|/adm/wrapper)/ext/([^#]+)}) { |
|
my $exturl = $1; |
|
if (($ENV{'SERVER_PORT'} == 443) && ($exturl !~ /^https:/)) { |
|
$nomodal = 1; |
|
} |
|
} elsif (($link eq "/public/$LONCAPA::match_domain/$LONCAPA::match_courseid/syllabus") && |
|
($env{'request.course.id'}) && ($ENV{'SERVER_PORT'} == 443) && |
|
($env{'course.'.$env{'request.course.id'}.'.externalsyllabus'} =~ m{^http://})) { |
|
$nomodal = 1; |
|
} |
|
my $esclink = &js_escape($link); |
|
if ($nomodal) { |
|
$linkopen .= "<a href=\"#\" onclick=\"javascript:window.open('$esclink','resourcepreview','height=400,width=500,scrollbars=1,resizable=1,menubar=0,location=1'); return false;\" />"; |
|
} else { |
|
$linkopen .= "<a href=\"$link\" onclick=\"javascript:openMyModal('$esclink',600,500,'yes','true'); return false;\">"; |
|
} |
|
} else { |
|
$linkopen .= "<a href=\"$link\">"; |
|
} |
|
} |
|
|
# Default icon: unknown page |
# Default icon: unknown page |
my $icon = "<img src='/adm/lonIcons/unknown.gif' alt='' border='0' />"; |
my $icon = "<img class=\"LC_contentImage\" src='$location/unknown.gif' alt='' />"; |
|
|
if ($resource->is_problem()) { |
if ($resource->is_problem()) { |
if ($part eq '0' || $params->{'condensed'}) { |
if ($part eq '0' || $params->{'condensed'}) { |
$icon = '<img src="/adm/lonIcons/problem.gif" alt="" border="0" />'; |
$icon = '<img class="LC_contentImage" src="'.$location.'/'; |
|
if ($resource->is_task()) { |
|
$icon .= 'task.gif" alt="'.&mt('Task'); |
|
} else { |
|
$icon .= 'problem.gif" alt="'.&mt('Problem'); |
|
} |
|
$icon .='" />'; |
} else { |
} else { |
$icon = $params->{'indentString'}; |
$icon = $params->{'indentString'}; |
} |
} |
} else { |
} else { |
my $curfext= (split (/\./,$resource->src))[-1]; |
$icon = "<img class=\"LC_contentImage\" src='".&Apache::loncommon::icon($resource->src)."' alt='' />"; |
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 910 sub render_resource {
|
Line 997 sub render_resource {
|
if ($it->{CONDITION}) { |
if ($it->{CONDITION}) { |
$nowOpen = !$nowOpen; |
$nowOpen = !$nowOpen; |
} |
} |
|
my $folderType; |
my $folderType = $resource->is_sequence() ? 'folder' : 'page'; |
if (&advancedUser() && $resource->is_missing_map()) { |
|
$folderType = 'none'; |
|
} else { |
|
$folderType = $resource->is_sequence() ? 'folder' : 'page'; |
|
} |
|
my $title=$resource->title; |
|
$title=~s/\"/\&qout;/g; |
if (!$params->{'resource_no_folder_link'}) { |
if (!$params->{'resource_no_folder_link'}) { |
$icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif'; |
$icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif'; |
$icon = "<img src='/adm/lonIcons/$icon' alt='' border='0' />"; |
$icon = "<img src='$location/arrow." . ($nowOpen ? 'closed' : 'open') . ".gif' alt='' />" |
|
."<img class=\"LC_contentImage\" src='$location/$icon' alt=\"" |
$linkopen = "<a href='" . $params->{'url'} . '?' . |
.($nowOpen ? &mt('Open Folder') : &mt('Close Folder')).' '.$title."\" />"; |
$params->{'queryString'} . '&filter='; |
$linkopen = "<a href=\"" . $params->{'url'} . '?' . |
|
$params->{'queryString'} . '&filter='; |
$linkopen .= ($nowOpen xor $it->{CONDITION}) ? |
$linkopen .= ($nowOpen xor $it->{CONDITION}) ? |
addToFilter($filter, $mapId) : |
addToFilter($filter, $mapId) : |
removeFromFilter($filter, $mapId); |
removeFromFilter($filter, $mapId); |
$linkopen .= "&condition=" . $it->{CONDITION} . '&hereType=' |
$linkopen .= "&condition=" . $it->{CONDITION} . '&hereType=' |
. $params->{'hereType'} . '&here=' . |
. $params->{'hereType'} . '&here=' . |
&Apache::lonnet::escape($params->{'here'}) . |
&escape($params->{'here'}) . |
'&jump=' . |
'&jump=' . |
&Apache::lonnet::escape($resource->symb()) . |
&escape($resource->symb()) . |
"&folderManip=1'>"; |
"&folderManip=1\">"; |
|
$linkclose = '</a>'; |
} else { |
} else { |
# Don't allow users to manipulate folder |
# Don't allow users to manipulate folder |
$icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . |
$icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif'; |
'.nomanip.gif'; |
$icon = "<img class=\"LC_space\" src='$whitespace' alt='' />"."<img class=\"LC_contentImage\" src='$location/$icon' alt=\"".($nowOpen ? &mt('Open Folder') : &mt('Close Folder')).' '.$title."\" />"; |
$icon = "<img src='/adm/lonIcons/$icon' alt='' border='0' />"; |
if ($params->{'caller'} eq 'sequence') { |
|
$linkopen = "<a href=\"$link\">"; |
$linkopen = ""; |
$linkclose = '</a>'; |
$linkclose = ""; |
} else { |
|
$linkopen = ""; |
|
$linkclose = ""; |
|
} |
|
} |
|
if (((&Apache::lonnet::allowed('mdc',$env{'request.course.id'})) || |
|
(&Apache::lonnet::allowed('cev',$env{'request.course.id'}))) && |
|
($resource->symb=~/\_\_\_[^\_]+\_\_\_uploaded/)) { |
|
if (!$params->{'map_no_edit_link'}) { |
|
my $icon = &Apache::loncommon::lonhttpdurl('/res/adm/pages').'/editmap.png'; |
|
$editmapLink=' '. |
|
'<a href="/adm/coursedocs?command=directnav&symb='.&escape($resource->symb()).'">'. |
|
'<img src="'.$icon.'" alt="'.&mt('Edit Content').'" title="'.&mt('Edit Content').'" />'. |
|
'</a>'; |
|
} |
|
} |
|
if ($params->{'mapHidden'} || $resource->randomout()) { |
|
$nonLinkedText .= ' <span class="LC_warning">('.&mt('hidden').')</span> '; |
|
} elsif ($params->{'mapUnlisted'}) { |
|
$nonLinkedText .= ' <span class="LC_warning">('.&mt('unlisted').')</span> '; |
|
} elsif ($params->{'mapHiddenDeepLink'} || $resource->deeplinkout()) { |
|
$nonLinkedText .= ' <span class="LC_warning">('.&mt('not shown').')</span> '; |
|
} |
|
} else { |
|
if ($resource->randomout()) { |
|
$nonLinkedText .= ' <span class="LC_warning">('.&mt('hidden').')</span> '; |
|
} elsif ($resource->deeplinkout()) { |
|
$nonLinkedText .= ' <span class="LC_warning">('.&mt('not shown').')</span> '; |
|
} else { |
|
my $deeplink = $resource->deeplink($params->{caller}); |
|
if ((($deeplink eq 'absent') || ($deeplink eq 'grades')) && |
|
&advancedUser()) { |
|
$nonLinkedText .= ' <span class="LC_warning">('.&mt('unlisted').')</span> '; |
|
} elsif (($deeplink) && ($deeplink) ne 'full') { |
|
if (&advancedUser()) { |
|
$nonLinkedText .= ' <span class="LC_warning">('.&mt('deep-link access'). |
|
')</span> '; |
|
} else { |
|
$nonLinkedText .= ' <span class="LC_warning">('.&mt('access via external site'). |
|
')</span> '; |
|
} |
|
} |
} |
} |
} |
} |
|
if (!$resource->condval()) { |
if ($resource->randomout()) { |
$nonLinkedText .= ' <span class="LC_info">('.&mt('conditionally hidden').')</span> '; |
$nonLinkedText .= ' <i>(hidden)</i> '; |
} |
|
if (($resource->is_practice()) && ($resource->is_raw_problem())) { |
|
$nonLinkedText .=' <span class="LC_info"><b>'.&mt('not graded').'</b></span>'; |
} |
} |
|
|
# We're done preparing and finally ready to start the rendering |
|
my $result = "<td align='left' valign='center'>"; |
|
|
|
|
# We're done preparing and finally ready to start the rendering |
|
my $result = '<td class="LC_middle">'; |
|
my $newfolderType = $resource->is_sequence() ? 'folder' : 'page'; |
|
|
my $indentLevel = $params->{'indentLevel'}; |
my $indentLevel = $params->{'indentLevel'}; |
if ($newBranchText) { $indentLevel--; } |
if ($newBranchText) { $indentLevel--; } |
|
|
Line 963 sub render_resource {
|
Line 1101 sub render_resource {
|
# Is this the current resource? |
# Is this the current resource? |
if (!$params->{'displayedHereMarker'} && |
if (!$params->{'displayedHereMarker'} && |
$resource->symb() eq $params->{'here'} ) { |
$resource->symb() eq $params->{'here'} ) { |
$curMarkerBegin = '<font color="red" size="+2">> </font>'; |
unless ($resource->is_map()) { |
$curMarkerEnd = '<font color="red" size="+2"><</font>'; |
$curMarkerBegin = '<span class="LC_current_nav_location">'; |
$params->{'displayedHereMarker'} = 1; |
$curMarkerEnd = '</span>'; |
|
} |
|
$params->{'displayedHereMarker'} = 1; |
} |
} |
|
|
if ($resource->is_problem() && $part ne '0' && |
if ($resource->is_problem() && $part ne '0' && |
!$params->{'condensed'}) { |
!$params->{'condensed'}) { |
$partLabel = " (Part $part)"; |
my $displaypart=$resource->part_display($part); |
|
$partLabel = " (".&mt('Part: [_1]', $displaypart).")"; |
|
if ($link!~/\#/) { $link.='#'.&escape($part); } |
$title = ""; |
$title = ""; |
} |
} |
|
|
if ($params->{'condensed'} && $resource->countParts() > 1) { |
if ($params->{'condensed'} && $resource->countParts() > 1) { |
$nonLinkedText .= ' (' . $resource->countParts() . ' parts)'; |
$nonLinkedText .= ' ('.&mt('[_1] parts', $resource->countParts()).')'; |
} |
} |
|
|
if (!$params->{'resource_nolink'} && $src !~ /^\/uploaded\// && |
if (!$params->{'resource_nolink'} && !$resource->is_sequence() && !$resource->is_empty_sequence) { |
!$resource->is_sequence()) { |
$linkclose = '</a>'; |
$result .= " $curMarkerBegin<a href='$link'>$title$partLabel</a>$curMarkerEnd $nonLinkedText</td>"; |
if ($params->{'modalLink'}) { |
} else { |
my $esclink = &js_escape($link); |
$result .= " $curMarkerBegin$title$partLabel$curMarkerEnd $nonLinkedText</td>"; |
if ($nomodal) { |
|
$linkopen = "<a href=\"#\" onclick=\"javascript:window.open('$esclink','resourcepreview','height=400,width=500,scrollbars=1,resizable=1,menubar=0,location=1'); return false;\" />"; |
|
} else { |
|
$linkopen = "<a href=\"$link\" onclick=\"javascript:openMyModal('$esclink',600,500,'yes','true'); return false;\">"; |
|
} |
|
} else { |
|
$linkopen = "<a href=\"$link\">"; |
|
} |
} |
} |
|
$result .= "$curMarkerBegin$linkopen$title$partLabel$linkclose$curMarkerEnd$editmapLink$nonLinkedText</td>"; |
|
|
return $result; |
return $result; |
} |
} |
Line 993 sub render_communication_status {
|
Line 1143 sub render_communication_status {
|
my $discussionHTML = ""; my $feedbackHTML = ""; my $errorHTML = ""; |
my $discussionHTML = ""; my $feedbackHTML = ""; my $errorHTML = ""; |
|
|
my $link = $params->{"resourceLink"}; |
my $link = $params->{"resourceLink"}; |
my $linkopen = "<a href='$link'>"; |
my $linkopen = "<a href=\"$link\">"; |
my $linkclose = "</a>"; |
my $linkclose = "</a>"; |
|
my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc"); |
|
|
if ($resource->hasDiscussion()) { |
if ($resource->hasDiscussion()) { |
$discussionHTML = $linkopen . |
$discussionHTML = $linkopen . |
'<img border="0" src="/adm/lonMisc/chat.gif" />' . |
'<img alt="'.&mt('New Discussion').'" src="'.$location.'/chat.gif" title="'.&mt('New Discussion').'"/>' . |
$linkclose; |
$linkclose; |
} |
} |
|
|
if ($resource->getFeedback()) { |
if ($resource->getFeedback()) { |
my $feedback = $resource->getFeedback(); |
my $feedback = $resource->getFeedback(); |
foreach (split(/\,/, $feedback)) { |
foreach my $msgid (split(/\,/, $feedback)) { |
if ($_) { |
if ($msgid) { |
$feedbackHTML .= ' <a href="/adm/email?display=' |
$feedbackHTML .= ' <a href="/adm/email?display=' |
. &Apache::lonnet::escape($_) . '">' |
. &escape($msgid) . '">' |
. '<img src="/adm/lonMisc/feedback.gif" ' |
. '<img alt="'.&mt('New E-mail').'" src="'.$location.'/feedback.gif" title="'.&mt('New E-mail').'"/></a>'; |
. 'border="0" /></a>'; |
|
} |
} |
} |
} |
} |
} |
|
|
if ($resource->getErrors()) { |
if ($resource->getErrors()) { |
my $errors = $resource->getErrors(); |
my $errors = $resource->getErrors(); |
foreach (split(/,/, $errors)) { |
my $errorcount = 0; |
if ($_) { |
foreach my $msgid (split(/,/, $errors)) { |
|
last if ($errorcount>=10); # Only output 10 bombs maximum |
|
if ($msgid) { |
|
$errorcount++; |
$errorHTML .= ' <a href="/adm/email?display=' |
$errorHTML .= ' <a href="/adm/email?display=' |
. &Apache::lonnet::escape($_) . '">' |
. &escape($msgid) . '">' |
. '<img src="/adm/lonMisc/bomb.gif" ' |
. '<img alt="'.&mt('New Error').'" src="'.$location.'/bomb.gif" title="'.&mt('New Error').'"/></a>'; |
. 'border="0" /></a>'; |
|
} |
} |
} |
} |
} |
} |
Line 1029 sub render_communication_status {
|
Line 1181 sub render_communication_status {
|
if ($params->{'multipart'} && $part != '0') { |
if ($params->{'multipart'} && $part != '0') { |
$discussionHTML = $feedbackHTML = $errorHTML = ''; |
$discussionHTML = $feedbackHTML = $errorHTML = ''; |
} |
} |
|
return "<td class=\"LC_middle\">$discussionHTML$feedbackHTML$errorHTML </td>"; |
return "<td width=\"75\" align=\"left\" valign=\"center\">$discussionHTML$feedbackHTML$errorHTML </td>"; |
|
|
|
} |
} |
sub render_quick_status { |
sub render_quick_status { |
Line 1040 sub render_quick_status {
|
Line 1191 sub render_quick_status {
|
$params->{'multipart'} && $part eq "0"; |
$params->{'multipart'} && $part eq "0"; |
|
|
my $link = $params->{"resourceLink"}; |
my $link = $params->{"resourceLink"}; |
my $linkopen = "<a href='$link'>"; |
my $linkopen = "<a href=\"$link\">"; |
my $linkclose = "</a>"; |
my $linkclose = "</a>"; |
|
|
|
$result .= '<td class="LC_middle">'; |
if ($resource->is_problem() && |
if ($resource->is_problem() && |
!$firstDisplayed) { |
!$firstDisplayed) { |
|
|
my $icon = $statusIconMap{$resource->simpleStatus($part)}; |
my $icon = $statusIconMap{$resource->simpleStatus($part)}; |
my $alt = $iconAltTags{$icon}; |
my $alt = $iconAltTags{$icon}; |
if ($icon) { |
if ($icon) { |
$result .= "<td width='30' valign='center' width='50' align='right'>$linkopen<img width='25' height='25' src='/adm/lonIcons/$icon' border='0' alt='$alt' />$linkclose</td>\n"; |
my $location= |
|
&Apache::loncommon::lonhttpdurl("/adm/lonIcons/$icon"); |
|
$result .= $linkopen.'<img src="'.$location.'" alt="'.&mt($alt).'" title="'.&mt($alt).'" />'.$linkclose; |
} else { |
} else { |
$result .= "<td width='30'> </td>\n"; |
$result .= " "; |
} |
} |
} else { # not problem, no icon |
} else { # not problem, no icon |
$result .= "<td width='30'> </td>\n"; |
$result .= " "; |
} |
} |
|
$result .= "</td>\n"; |
return $result; |
return $result; |
} |
} |
sub render_long_status { |
sub render_long_status { |
my ($resource, $part, $params) = @_; |
my ($resource, $part, $params) = @_; |
my $result = "<td align='right' valign='center'>\n"; |
my $result = '<td class="LC_middle LC_right">'; |
my $firstDisplayed = !$params->{'condensed'} && |
my $firstDisplayed = !$params->{'condensed'} && |
$params->{'multipart'} && $part eq "0"; |
$params->{'multipart'} && $part eq "0"; |
|
|
my $color; |
my $color; |
if ($resource->is_problem()) { |
my $info = ''; |
|
if ($resource->is_problem() || $resource->is_practice()) { |
$color = $colormap{$resource->status}; |
$color = $colormap{$resource->status}; |
|
|
if (dueInLessThen24Hours($resource, $part) || |
if (dueInLessThan24Hours($resource, $part)) { |
lastTry($resource, $part)) { |
|
$color = $hurryUpColor; |
$color = $hurryUpColor; |
} |
$info = ' title="'.&mt('Due in less than 24 hours!').'"'; |
|
} elsif (lastTry($resource, $part)) { |
|
unless (($resource->problemstatus($part) eq 'no') || |
|
($resource->problemstatus($part) eq 'no_feedback_ever')) { |
|
$color = $hurryUpColor; |
|
$info = ' title="'.&mt('One try remaining!').'"'; |
|
} |
|
} |
} |
} |
|
|
if ($resource->kind() eq "res" && |
if ($resource->kind() eq "res" && |
$resource->is_problem() && |
$resource->is_raw_problem() && |
!$firstDisplayed) { |
!$firstDisplayed) { |
if ($color) {$result .= "<font color=\"$color\"><b>"; } |
if ($color) {$result .= '<span style="color:'.$color.'"'.$info.'><b>'; } |
$result .= getDescription($resource, $part); |
$result .= getDescription($resource, $part); |
if ($color) {$result .= "</b></font>"; } |
if ($color) {$result .= "</b></span>"; } |
|
} |
|
if ($resource->is_map() && &advancedUser() && $resource->randompick()) { |
|
$result .= &mt('(randomly select [_1])', $resource->randompick()); |
} |
} |
if ($resource->is_map() && advancedUser() && $resource->randompick()) { |
if ($resource->is_map() && &advancedUser() && $resource->randomorder()) { |
$result .= '(randomly select ' . $resource->randompick() .')'; |
$result .= &mt('(randomly ordered)'); |
} |
} |
|
|
# Debugging code |
# Debugging code |
Line 1118 my %statusStrings =
|
Line 1281 my %statusStrings =
|
); |
); |
my @statuses = ($resObj->CORRECT, $resObj->ATTEMPTED, $resObj->INCORRECT, $resObj->OPEN, $resObj->CLOSED, $resObj->ERROR); |
my @statuses = ($resObj->CORRECT, $resObj->ATTEMPTED, $resObj->INCORRECT, $resObj->OPEN, $resObj->CLOSED, $resObj->ERROR); |
|
|
use Data::Dumper; |
|
sub render_parts_summary_status { |
sub render_parts_summary_status { |
my ($resource, $part, $params) = @_; |
my ($resource, $part, $params) = @_; |
if (!$resource->is_problem()) { return '<td></td>'; } |
if (!$resource->is_problem() && !$resource->contains_problem) { return '<td></td>'; } |
if ($params->{showParts}) { |
if ($params->{showParts}) { |
return '<td></td>'; |
return '<td></td>'; |
} |
} |
|
|
my $td = "<td align='right'>\n"; |
my $td = "<td align='right'>\n"; |
my $endtd = "</td>\n"; |
my $endtd = "</td>\n"; |
|
my @probs; |
|
|
# If there is a single part, just show the simple status |
if ($resource->contains_problem) { |
if ($resource->singlepart()) { |
@probs=$resource->retrieveResources($resource,sub { $_[0]->is_problem() },1,0); |
my $status = $resource->simpleStatus('0'); |
} else { |
return $td . "<font color='" . $statusColors{$status} . "'>" |
@probs=($resource); |
. $statusStrings{$status} . "</font>" . $endtd; |
} |
} |
my $return; |
|
my %overallstatus; |
# Now we can be sure the $part doesn't really matter. |
my $totalParts; |
my $statusCount = $resource->simpleStatusCount(); |
foreach my $resource (@probs) { |
my @counts; |
# If there is a single part, just show the simple status |
foreach my $status(@statuses) { |
if ($resource->singlepart()) { |
# decouple display order from the simpleStatusCount order |
my $status = $resource->simpleStatus(${$resource->parts}[0]); |
my $slot = Apache::lonnavmaps::resource::statusToSlot($status); |
$overallstatus{$status}++; |
if ($statusCount->[$slot]) { |
$totalParts++; |
push @counts, "<font color='" . $statusColors{$status} . |
next; |
"'>" . $statusCount->[$slot] . ' ' |
} |
. $statusStrings{$status} . "</font>"; |
# 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]) { |
|
$overallstatus{$status}+=$statusCount->[$slot]; |
|
$totalParts+=$statusCount->[$slot]; |
|
} |
} |
} |
} |
} |
|
$return.= $td . $totalParts . ' parts: '; |
return $td . $resource->countParts() . ' parts: ' . join (', ', @counts) . $endtd; |
foreach my $status (@statuses) { |
|
if ($overallstatus{$status}) { |
|
$return.='<span style="color:' . $statusColors{$status} |
|
. '">' . $overallstatus{$status} . ' ' |
|
. $statusStrings{$status} . '</span>'; |
|
} |
|
} |
|
$return.= $endtd; |
|
return $return; |
} |
} |
|
|
my @preparedColumns = (\&render_resource, \&render_communication_status, |
my @preparedColumns = (\&render_resource, \&render_communication_status, |
Line 1162 sub setDefault {
|
Line 1342 sub setDefault {
|
return $val; |
return $val; |
} |
} |
|
|
|
sub cmp_title { |
|
my ($atitle,$btitle) = (lc($_[0]->compTitle),lc($_[1]->compTitle)); |
|
$atitle=~s/^\s*//; |
|
$btitle=~s/^\s*//; |
|
return $atitle cmp $btitle; |
|
} |
|
|
sub render { |
sub render { |
my $args = shift; |
my $args = shift; |
&Apache::loncommon::get_unprocessed_cgi($ENV{QUERY_STRING}); |
&Apache::loncommon::get_unprocessed_cgi($ENV{QUERY_STRING}); |
my $result = ''; |
my $result = ''; |
|
|
# Configure the renderer. |
# Configure the renderer. |
my $cols = $args->{'cols'}; |
my $cols = $args->{'cols'}; |
if (!defined($cols)) { |
if (!defined($cols)) { |
# no columns, no nav maps. |
# no columns, no nav maps. |
return ''; |
return ''; |
} |
} |
my $mustCloseNavMap = 0; |
|
my $navmap; |
my $navmap; |
if (defined($args->{'navmap'})) { |
if (defined($args->{'navmap'})) { |
$navmap = $args->{'navmap'}; |
$navmap = $args->{'navmap'}; |
Line 1184 sub render {
|
Line 1369 sub render {
|
my $jump = $args->{'jump'}; |
my $jump = $args->{'jump'}; |
my $here = $args->{'here'}; |
my $here = $args->{'here'}; |
my $suppressNavmap = setDefault($args->{'suppressNavmap'}, 0); |
my $suppressNavmap = setDefault($args->{'suppressNavmap'}, 0); |
|
my $closeAllPages = setDefault($args->{'closeAllPages'}, 0); |
my $currentJumpDelta = 2; # change this to change how many resources are displayed |
my $currentJumpDelta = 2; # change this to change how many resources are displayed |
# before the current resource when using #current |
# before the current resource when using #current |
|
|
Line 1193 sub render {
|
Line 1379 sub render {
|
# marker |
# marker |
my $filterHash = {}; |
my $filterHash = {}; |
# Figure out what we're not displaying |
# Figure out what we're not displaying |
foreach (split(/\,/, $ENV{"form.filter"})) { |
foreach my $item (split(/\,/, $env{"form.filter"})) { |
if ($_) { |
if ($item) { |
$filterHash->{$_} = "1"; |
$filterHash->{$item} = "1"; |
} |
} |
} |
} |
|
|
Line 1209 sub render {
|
Line 1395 sub render {
|
# an infinite loop |
# an infinite loop |
my $oldFilterFunc = $filterFunc; |
my $oldFilterFunc = $filterFunc; |
$filterFunc = sub { my $res = shift; return !$res->randomout() && |
$filterFunc = sub { my $res = shift; return !$res->randomout() && |
|
($res->deeplink($args->{'caller'}) ne 'absent') && |
|
($res->deeplink($args->{'caller'}) ne 'grades') && |
|
!$res->deeplinkout() && |
&$oldFilterFunc($res);}; |
&$oldFilterFunc($res);}; |
} |
} |
|
|
my $condition = 0; |
my $condition = 0; |
if ($ENV{'form.condition'}) { |
if ($env{'form.condition'}) { |
$condition = 1; |
$condition = 1; |
|
} elsif (($env{'request.deeplink.login'}) && ($env{'request.course.id'}) && (!$userCanSeeHidden)) { |
|
if (!defined($navmap)) { |
|
$navmap = Apache::lonnavmaps::navmap->new(); |
|
} |
|
if (defined($navmap)) { |
|
my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; |
|
my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; |
|
my $symb = &Apache::loncommon::symb_from_tinyurl($env{'request.deeplink.login'},$cnum,$cdom); |
|
if ($symb) { |
|
my $deeplink; |
|
my $res = $navmap->getBySymb($symb); |
|
if ($res->is_map()) { |
|
my $mapname = &Apache::lonnet::declutter($res->src()); |
|
$mapname = &Apache::lonnet::deversion($mapname); |
|
$deeplink = $navmap->get_mapparam(undef,$mapname,"0.deeplink"); |
|
} else { |
|
$deeplink = $res->deeplink(); |
|
} |
|
if ($deeplink ne '') { |
|
if ((split(/,/,$deeplink))[1] eq 'hide') { |
|
if ($res->is_map()) { |
|
map { $filterHash->{$_} = 1 if $_ } split(/,/,$res->map_hierarchy()); |
|
} else { |
|
my $mapurl = (&Apache::lonnet::decode_symb($symb))[0]; |
|
my $map = $navmap->getResourceByUrl($mapurl); |
|
map { $filterHash->{$_} = 1 if $_ } split(/,/,$map->map_hierarchy()); |
|
} |
|
} |
|
} |
|
} |
|
} |
} |
} |
|
|
if (!$ENV{'form.folderManip'} && !defined($args->{'iterator'})) { |
if (!$env{'form.folderManip'} && !defined($args->{'iterator'})) { |
# Step 1: Check to see if we have a navmap |
# Step 1: Check to see if we have a navmap |
if (!defined($navmap)) { |
if (!defined($navmap)) { |
$navmap = Apache::lonnavmaps::navmap->new(); |
$navmap = Apache::lonnavmaps::navmap->new(); |
$mustCloseNavMap = 1; |
if (!defined($navmap)) { |
} |
# no longer in course |
|
return '<span class="LC_error">'.&mt('No course selected').'</span><br /> |
|
<a href="/adm/roles">'.&mt('Select a course').'</a><br />'; |
|
} |
|
} |
|
|
# Step two: Locate what kind of here marker is necessary |
# Step two: Locate what kind of here marker is necessary |
# Determine where the "here" marker is and where the screen jumps to. |
# Determine where the "here" marker is and where the screen jumps to. |
|
|
if ($ENV{'form.postsymb'}) { |
if ($env{'form.postsymb'} ne '') { |
$here = $jump = $ENV{'form.postsymb'}; |
$here = $jump = &Apache::lonnet::symbclean($env{'form.postsymb'}); |
} elsif ($ENV{'form.postdata'}) { |
} elsif ($env{'form.postdata'} ne '') { |
# couldn't find a symb, is there a URL? |
# couldn't find a symb, is there a URL? |
my $currenturl = $ENV{'form.postdata'}; |
my $currenturl = $env{'form.postdata'}; |
#$currenturl=~s/^http\:\/\///; |
#$currenturl=~s/^http\:\/\///; |
#$currenturl=~s/^[^\/]+//; |
#$currenturl=~s/^[^\/]+//; |
|
unless ($args->{'caller'} eq 'sequence') { |
$here = $jump = &Apache::lonnet::symbread($currenturl); |
$here = $jump = &Apache::lonnet::symbread($currenturl); |
} |
} |
|
} |
|
if (($here eq '') && ($args->{'caller'} ne 'sequence')) { |
|
my $last; |
|
if (tie(my %hash,'GDBM_File',$env{'request.course.fn'}.'_symb.db', |
|
&GDBM_READER(),0640)) { |
|
$last=$hash{'last_known'}; |
|
untie(%hash); |
|
} |
|
if ($last) { $here = $jump = $last; } |
|
} |
|
|
# Step three: Ensure the folders are open |
# Step three: Ensure the folders are open |
my $mapIterator = $navmap->getIterator(undef, undef, undef, 1); |
my $mapIterator = $navmap->getIterator(undef, undef, undef, 1); |
Line 1246 sub render {
|
Line 1480 sub render {
|
# We only need to do this if we need to open the maps to show the |
# 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 |
# current position. This will change the counter so we can't count |
# for the jump marker with this loop. |
# for the jump marker with this loop. |
while (($curRes = $mapIterator->next()) && !$found) { |
while ($here && ($curRes = $mapIterator->next()) && !$found) { |
if (ref($curRes) && $curRes->symb() eq $here) { |
if (ref($curRes) && $curRes->symb() eq $here) { |
my $mapStack = $mapIterator->getStack(); |
my $mapStack = $mapIterator->getStack(); |
|
|
Line 1263 sub render {
|
Line 1497 sub render {
|
} |
} |
} |
} |
|
|
if ( !defined($args->{'iterator'}) && $ENV{'form.folderManip'} ) { # we came from a user's manipulation of the nav page |
if ( !defined($args->{'iterator'}) && $env{'form.folderManip'} ) { # we came from a user's manipulation of the nav page |
# If this is a click on a folder or something, we want to preserve the "here" |
# If this is a click on a folder or something, we want to preserve the "here" |
# from the querystring, and get the new "jump" marker |
# from the querystring, and get the new "jump" marker |
$here = $ENV{'form.here'}; |
$here = $env{'form.here'}; |
$jump = $ENV{'form.jump'}; |
$jump = $env{'form.jump'}; |
} |
} |
|
|
my $it = $args->{'iterator'}; |
my $it = $args->{'iterator'}; |
if (!defined($it)) { |
if (!defined($it)) { |
# Construct a default iterator based on $ENV{'form.'} information |
# Construct a default iterator based on $env{'form.'} information |
|
|
# Step 1: Check to see if we have a navmap |
# Step 1: Check to see if we have a navmap |
if (!defined($navmap)) { |
if (!defined($navmap)) { |
$navmap = Apache::lonnavmaps::navmap->new(); |
$navmap = Apache::lonnavmaps::navmap->new(); |
$mustCloseNavMap = 1; |
if (!defined($navmap)) { |
|
# no longer in course |
|
return '<span class="LC_error">'.&mt('No course selected').'</span><br /> |
|
<a href="/adm/roles">'.&mt('Select a course').'</a><br />'; |
|
} |
} |
} |
|
|
# See if we're being passed a specific map |
# See if we're being passed a specific map |
if ($args->{'iterator_map'}) { |
if ($args->{'iterator_map'}) { |
my $map = $args->{'iterator_map'}; |
my $map = $args->{'iterator_map'}; |
$map = $navmap->getResourceByUrl($map); |
$map = $navmap->getResourceByUrl($map); |
my $firstResource = $map->map_start(); |
if (ref($map)) { |
my $finishResource = $map->map_finish(); |
my $firstResource = $map->map_start(); |
|
my $finishResource = $map->map_finish(); |
$args->{'iterator'} = $it = $navmap->getIterator($firstResource, $finishResource, $filterHash, $condition); |
$args->{'iterator'} = $it = $navmap->getIterator($firstResource, $finishResource, $filterHash, $condition); |
|
} else { |
|
return; |
|
} |
} else { |
} else { |
$args->{'iterator'} = $it = $navmap->getIterator(undef, undef, $filterHash, $condition); |
$args->{'iterator'} = $it = $navmap->getIterator(undef, undef, $filterHash, $condition,undef,$args->{'include_top_level_map'}); |
} |
} |
} |
} |
|
|
# (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 |
# Note this does not take filtering or hidden into account... need |
# to be fixed? |
# to be fixed? |
Line 1322 sub render {
|
Line 1563 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; } |
|
|
# Print key? |
# Print key? |
if ($printKey) { |
if ($printKey) { |
$result .= '<table border="0" cellpadding="2" cellspacing="0">'; |
$result .= '<table border="0" cellpadding="2" cellspacing="0">'; |
my $date=localtime; |
|
$result.='<tr><td align="right" valign="bottom">Key: </td>'; |
$result.='<tr><td align="right" valign="bottom">Key: </td>'; |
|
my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc"); |
if ($navmap->{LAST_CHECK}) { |
if ($navmap->{LAST_CHECK}) { |
$result .= |
$result .= |
'<img src="/adm/lonMisc/chat.gif"> '.&mt('New discussion since').' '. |
'<img src="'.$location.'/chat.gif" alt="" /> '.&mt('New discussion since').' '. |
strftime("%A, %b %e at %I:%M %P", localtime($navmap->{LAST_CHECK})). |
strftime("%A, %b %e at %I:%M %P", localtime($navmap->{LAST_CHECK})). |
'</td><td align="center" valign="bottom"> '. |
'</td><td align="center" valign="bottom"> '. |
'<img src="/adm/lonMisc/feedback.gif"> '.&mt('New message (click to open)').'<p>'. |
'<img src="'.$location.'/feedback.gif" alt="" /> '.&mt('New message (click to open)').'<p>'. |
'</td>'; |
'</td>'; |
} else { |
} else { |
$result .= '<td align="center" valign="bottom"> '. |
$result .= '<td align="center" valign="bottom"> '. |
'<img src="/adm/lonMisc/chat.gif"> '.&mt('Discussions').'</td><td align="center" valign="bottom">'. |
'<img src="'.$location.'/chat.gif" alt="" /> '.&mt('Discussions').'</td><td align="center" valign="bottom">'. |
' <img src="/adm/lonMisc/feedback.gif"> '.&mt('New message (click to open)'). |
' <img src="'.$location.'/feedback.gif" alt="" /> '.&mt('New message (click to open)'). |
'</td>'; |
'</td>'; |
} |
} |
|
|
Line 1346 sub render {
|
Line 1587 sub render {
|
} |
} |
|
|
if ($printCloseAll && !$args->{'resource_no_folder_link'}) { |
if ($printCloseAll && !$args->{'resource_no_folder_link'}) { |
|
my ($link,$text); |
if ($condition) { |
if ($condition) { |
$result.="<a href=\"navmaps?condition=0&filter=&$queryString" . |
$link='navmaps?condition=0&filter=&'.$queryString. |
"&here=" . Apache::lonnet::escape($here) . |
'&here='.&escape($here); |
"\">".&mt('Close All Folders')."</a>"; |
$text='Close all folders'; |
} else { |
} else { |
$result.="<a href=\"navmaps?condition=1&filter=&$queryString" . |
$link='navmaps?condition=1&filter=&'.$queryString. |
"&here=" . Apache::lonnet::escape($here) . |
'&here='.&escape($here); |
"\">".&mt('Open All Folders')."</a>"; |
$text='Open all folders'; |
|
} |
|
if ($env{'form.register'}) { |
|
$link .= '&register='.$env{'form.register'}; |
|
} |
|
if ($args->{'caller'} eq 'navmapsdisplay') { |
|
unless ($args->{'notools'}) { |
|
&add_linkitem($args->{'linkitems'},'changefolder', |
|
"location.href='$link'",$text); |
|
} |
|
} else { |
|
$result.= '<a href="'.$link.'">'.&mt($text).'</a>'; |
|
} |
|
$result .= "\n"; |
|
} |
|
|
|
# Check for any unread discussions in all resources. |
|
if (($args->{'caller'} eq 'navmapsdisplay') && (!$args->{'notools'})) { |
|
&add_linkitem($args->{'linkitems'},'clearbubbles', |
|
'document.clearbubbles.submit()', |
|
'Mark all posts read'); |
|
my $time=time; |
|
my $querystr = &HTML::Entities::encode($ENV{'QUERY_STRING'},'<>&"'); |
|
$result .= (<<END); |
|
<form name="clearbubbles" method="post" action="/adm/feedback"> |
|
<input type="hidden" name="navurl" value="$querystr" /> |
|
<input type="hidden" name="navtime" value="$time" /> |
|
END |
|
if ($env{'form.register'}) { |
|
$result .= '<input type="hidden" name="register" value="'.$env{'form.register'}.'" />'; |
|
} |
|
if ($args->{'sort'} eq 'discussion') { |
|
my $totdisc = 0; |
|
my $haveDisc = ''; |
|
my @allres=$navmap->retrieveResources(); |
|
foreach my $resource (@allres) { |
|
if ($resource->hasDiscussion()) { |
|
$haveDisc .= $resource->wrap_symb().':'; |
|
$totdisc ++; |
|
} |
|
} |
|
if ($totdisc > 0) { |
|
$haveDisc =~ s/:$//; |
|
$result .= (<<END); |
|
<input type="hidden" name="navmaps" value="$haveDisc" /> |
|
</form> |
|
END |
|
} |
|
} |
|
$result.='</form>'; |
|
} |
|
if (($args->{'caller'} eq 'navmapsdisplay') && |
|
((&Apache::lonnet::allowed('mdc',$env{'request.course.id'})) || |
|
(&Apache::lonnet::allowed('cev',$env{'request.course.id'})))) { |
|
my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; |
|
my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; |
|
if ($env{'course.'.$env{'request.course.id'}.'.url'} eq |
|
"uploaded/$cdom/$cnum/default.sequence") { |
|
&add_linkitem($args->{'linkitems'},'edittoplevel', |
|
"javascript:gocmd('/adm/coursedocs','editdocs');", |
|
'Content Editor'); |
} |
} |
$result .= "<br /><br />\n"; |
} |
} |
|
|
if ($args->{'caller'} eq 'navmapsdisplay') { |
|
$result .= &show_linkitems_toolbar($args,$condition); |
|
} elsif ($args->{'sort_html'}) { |
|
$result.=$args->{'sort_html'}; |
|
} |
|
|
|
#$result .= "<br />\n"; |
if ($r) { |
if ($r) { |
$r->print($result); |
$r->print($result); |
$r->rflush(); |
$r->rflush(); |
$result = ""; |
$result = ""; |
} |
} |
# End parameter setting |
# End parameter setting |
|
|
|
$result .= "<br />\n"; |
|
|
# Data |
# Data |
$result .= '<table cellspacing="0" cellpadding="3" border="0" bgcolor="#FFFFFF">' ."\n"; |
$result.=&Apache::loncommon::start_data_table("LC_tableOfContent"); |
|
|
my $res = "Apache::lonnavmaps::resource"; |
my $res = "Apache::lonnavmaps::resource"; |
my %condenseStatuses = |
my %condenseStatuses = |
( $res->NETWORK_FAILURE => 1, |
( $res->NETWORK_FAILURE => 1, |
$res->NOTHING_SET => 1, |
$res->NOTHING_SET => 1, |
$res->CORRECT => 1 ); |
$res->CORRECT => 1 ); |
my @backgroundColors = ("#FFFFFF", "#F6F6F6"); |
|
|
|
# Shared variables |
# Shared variables |
$args->{'counter'} = 0; # counts the rows |
$args->{'counter'} = 0; # counts the rows |
$args->{'indentLevel'} = 0; |
$args->{'indentLevel'} = 0; |
$args->{'isNewBranch'} = 0; |
$args->{'isNewBranch'} = 0; |
$args->{'condensed'} = 0; |
$args->{'condensed'} = 0; |
$args->{'indentString'} = setDefault($args->{'indentString'}, "<img src='/adm/lonIcons/whitespace1.gif' width='25' height='1' alt='' border='0' />"); |
|
|
my $location = &Apache::loncommon::lonhttpdurl("/adm/lonIcons/whitespace_21.gif"); |
|
$args->{'indentString'} = setDefault($args->{'indentString'}, "<img src='$location' alt='' />"); |
$args->{'displayedHereMarker'} = 0; |
$args->{'displayedHereMarker'} = 0; |
|
|
# If we're suppressing empty sequences, look for them here. Use DFS for speed, |
# If we're suppressing empty sequences, look for them here. |
# since structure actually doesn't matter, except what map has what resources. |
# We also do this even if $args->{'suppressEmptySequences'} |
if ($args->{'suppressEmptySequences'}) { |
# is not true, so we can hide empty sequences for which the |
my $dfsit = Apache::lonnavmaps::DFSiterator->new($navmap, |
# hiddenresource parameter is set to yes (at map level), or |
$it->{FIRST_RESOURCE}, |
# mark as hidden for users who have $userCanSeeHidden. |
$it->{FINISH_RESOURCE}, |
# Use DFS for speed, since structure actually doesn't matter, |
{}, undef, 1); |
# except what map has what resources. |
my $depth = 0; |
|
$dfsit->next(); |
my $dfsit = Apache::lonnavmaps::DFSiterator->new($navmap, |
my $curRes = $dfsit->next(); |
$it->{FIRST_RESOURCE}, |
while ($depth > -1) { |
$it->{FINISH_RESOURCE}, |
if ($curRes == $dfsit->BEGIN_MAP()) { $depth++; } |
{}, undef, 1); |
if ($curRes == $dfsit->END_MAP()) { $depth--; } |
|
|
my $depth = 0; |
if (ref($curRes)) { |
$dfsit->next(); |
# Parallel pre-processing: Do sequences have non-filtered-out children? |
my $curRes = $dfsit->next(); |
if ($curRes->is_map()) { |
while ($depth > -1) { |
$curRes->{DATA}->{HAS_VISIBLE_CHILDREN} = 0; |
if ($curRes == $dfsit->BEGIN_MAP()) { $depth++; } |
# Sequences themselves do not count as visible children, |
if ($curRes == $dfsit->END_MAP()) { $depth--; } |
# unless those sequences also have visible children. |
|
# This means if a sequence appears, there's a "promise" |
if (ref($curRes)) { |
# that there's something under it if you open it, somewhere. |
# Parallel pre-processing: Do sequences have non-filtered-out children? |
} else { |
if ($curRes->is_map()) { |
# Not a sequence: if it's filtered, ignore it, otherwise |
$curRes->{DATA}->{HAS_VISIBLE_CHILDREN} = 0; |
# rise up the stack and mark the sequences as having children |
# Sequences themselves do not count as visible children, |
if (&$filterFunc($curRes)) { |
# unless those sequences also have visible children. |
for my $sequence (@{$dfsit->getStack()}) { |
# This means if a sequence appears, there's a "promise" |
$sequence->{DATA}->{HAS_VISIBLE_CHILDREN} = 1; |
# that there's something under it if you open it, somewhere. |
} |
} elsif ($curRes->src()) { |
|
# Not a sequence: if it's filtered, ignore it, otherwise |
|
# rise up the stack and mark the sequences as having children |
|
if (&$filterFunc($curRes)) { |
|
for my $sequence (@{$dfsit->getStack()}) { |
|
$sequence->{DATA}->{HAS_VISIBLE_CHILDREN} = 1; |
} |
} |
} |
} |
} |
} |
} continue { |
|
$curRes = $dfsit->next(); |
|
} |
} |
|
} continue { |
|
$curRes = $dfsit->next(); |
} |
} |
|
|
my $displayedJumpMarker = 0; |
my $displayedJumpMarker = 0; |
Line 1429 sub render {
|
Line 1746 sub render {
|
$args->{'here'} = $here; |
$args->{'here'} = $here; |
|
|
$args->{'indentLevel'} = -1; # first BEGIN_MAP takes this to 0 |
$args->{'indentLevel'} = -1; # first BEGIN_MAP takes this to 0 |
while ($curRes = $it->next()) { |
my @resources; |
|
my $code='';# sub { !(shift->is_map();) }; |
|
if ($args->{'sort'} eq 'title') { |
|
my $oldFilterFunc = $filterFunc; |
|
my $filterFunc= |
|
sub { |
|
my ($res)=@_; |
|
if ($res->is_map()) { return 0;} |
|
return &$oldFilterFunc($res); |
|
}; |
|
@resources=$navmap->retrieveResources(undef,$filterFunc); |
|
@resources= sort { &cmp_title($a,$b) } @resources; |
|
} elsif ($args->{'sort'} eq 'duedate') { |
|
my $oldFilterFunc = $filterFunc; |
|
my $filterFunc= |
|
sub { |
|
my ($res)=@_; |
|
if (!$res->is_problem()) { return 0;} |
|
return &$oldFilterFunc($res); |
|
}; |
|
@resources=$navmap->retrieveResources(undef,$filterFunc); |
|
@resources= sort { |
|
if ($a->duedate ne $b->duedate) { |
|
return $a->duedate cmp $b->duedate; |
|
} |
|
my $value=&cmp_title($a,$b); |
|
return $value; |
|
} @resources; |
|
} elsif ($args->{'sort'} eq 'discussion') { |
|
my $oldFilterFunc = $filterFunc; |
|
my $filterFunc= |
|
sub { |
|
my ($res)=@_; |
|
if (!$res->hasDiscussion() && |
|
!$res->getFeedback() && |
|
!$res->getErrors()) { return 0;} |
|
return &$oldFilterFunc($res); |
|
}; |
|
@resources=$navmap->retrieveResources(undef,$filterFunc); |
|
@resources= sort { &cmp_title($a,$b) } @resources; |
|
} else { |
|
#unknow sort mechanism or default |
|
undef($args->{'sort'}); |
|
} |
|
|
|
# Determine if page will be served with https in case |
|
# it contains a syllabus which uses an external URL |
|
# which points at an http site. |
|
|
|
my ($is_ssl,$cdom,$cnum,$hostname); |
|
if ($ENV{'SERVER_PORT'} == 443) { |
|
$is_ssl = 1; |
|
if ($r) { |
|
$hostname = $r->hostname(); |
|
} else { |
|
$hostname = $ENV{'SERVER_NAME'}; |
|
} |
|
} |
|
if ($env{'request.course.id'}) { |
|
$cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; |
|
$cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; |
|
} |
|
|
|
my $inhibitmenu; |
|
if ($args->{'modalLink'}) { |
|
$inhibitmenu = '&inhibitmenu=yes'; |
|
} |
|
|
|
while (1) { |
|
if ($args->{'sort'}) { |
|
$curRes = shift(@resources); |
|
} else { |
|
$curRes = $it->next($closeAllPages); |
|
} |
|
if (!$curRes) { last; } |
|
|
# Maintain indentation level. |
# Maintain indentation level. |
if ($curRes == $it->BEGIN_MAP() || |
if ($curRes == $it->BEGIN_MAP() || |
$curRes == $it->BEGIN_BRANCH() ) { |
$curRes == $it->BEGIN_BRANCH() ) { |
Line 1456 sub render {
|
Line 1848 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_map() && $args->{'suppressEmptySequences'} && |
$args->{'mapHidden'} = 0; |
!$curRes->{DATA}->{HAS_VISIBLE_CHILDREN}) { |
$args->{'mapUnlisted'} = 0; |
next; |
$args->{'mapHiddenDeepLink'} = 0; |
|
if (($curRes->is_map()) && (!$curRes->{DATA}->{HAS_VISIBLE_CHILDREN})) { |
|
if ($args->{'suppressEmptySequences'}) { |
|
next; |
|
} else { |
|
my $mapname = &Apache::lonnet::declutter($curRes->src()); |
|
$mapname = &Apache::lonnet::deversion($mapname); |
|
if (lc($navmap->get_mapparam(undef,$mapname,"0.hiddenresource")) eq 'yes') { |
|
if ($userCanSeeHidden) { |
|
$args->{'mapHidden'} = 1; |
|
} else { |
|
next; |
|
} |
|
} elsif ($curRes->deeplinkout) { |
|
if ($userCanSeeHidden) { |
|
$args->{'mapHiddenDeepLink'} = 1; |
|
} else { |
|
next; |
|
} |
|
} else { |
|
my $deeplink = $navmap->get_mapparam(undef,$mapname,"0.deeplink"); |
|
my ($state,$others,$listed) = split(/,/,$deeplink); |
|
if (($listed eq 'absent') || ($listed eq 'grades')) { |
|
if ($userCanSeeHidden) { |
|
$args->{'mapUnlisted'} = 1; |
|
} else { |
|
next; |
|
} |
|
} |
|
} |
|
} |
} |
} |
|
|
# If we're suppressing navmaps and this is a navmap, continue on |
# If we're suppressing navmaps and this is a navmap, continue on |
Line 1479 sub render {
|
Line 1901 sub render {
|
$args->{'multipart'} = $curRes->multipart(); |
$args->{'multipart'} = $curRes->multipart(); |
|
|
if ($condenseParts) { # do the condensation |
if ($condenseParts) { # do the condensation |
if (!$curRes->opendate("0")) { |
|
@parts = (); |
|
$args->{'condensed'} = 1; |
|
} |
|
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[0]); |
my $status = $curRes->status($parts[0]); |
Line 1523 sub render {
|
Line 1941 sub render {
|
$args->{'condensed'} = 1; |
$args->{'condensed'} = 1; |
} |
} |
} |
} |
} |
} |
|
# If deep-link parameter is set (and is not set to full) suppress link |
|
# unless privileged user, tinyurl used for login resolved to a map, and |
|
# the resource is within the map. |
|
if ((!$curRes->deeplink($args->{'caller'})) || |
|
($curRes->deeplink($args->{'caller'}) eq 'full') || &advancedUser()) { |
|
$args->{'resource_nolink'} = 0; |
|
} else { |
|
$args->{'resource_nolink'} = 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) { |
Line 1532 sub render {
|
Line 1959 sub render {
|
# Add part 0 so we display it correctly. |
# Add part 0 so we display it correctly. |
unshift @parts, '0'; |
unshift @parts, '0'; |
} |
} |
|
|
|
{ |
|
my ($src,$symb,$anchor,$stack); |
|
if ($args->{'sort'}) { |
|
my $it = $navmap->getIterator(undef, undef, undef, 1); |
|
while ( my $res=$it->next()) { |
|
if (ref($res) && |
|
$res->symb() eq $curRes->symb()) { last; } |
|
} |
|
$stack=$it->getStack(); |
|
} else { |
|
$stack=$it->getStack(); |
|
} |
|
($src,$symb,$anchor)=getLinkForResource($stack); |
|
my $srcHasQuestion = $src =~ /\?/; |
|
if ($env{'request.course.id'}) { |
|
if (($is_ssl) && ($src =~ m{^\Q/public/$cdom/$cnum/syllabus\E($|\?)}) && |
|
($env{'course.'.$env{'request.course.id'}.'.externalsyllabus'} =~ m{^http://})) { |
|
unless ((&Apache::lonnet::uses_sts()) || (&Apache::lonnet::waf_allssl($hostname))) { |
|
if ($hostname ne '') { |
|
$src = 'http://'.$hostname.$src; |
|
} |
|
$src .= ($srcHasQuestion? '&' : '?') . 'usehttp=1'; |
|
$srcHasQuestion = 1; |
|
} |
|
} elsif (($is_ssl) && ($src =~ m{^\Q/adm/wrapper/ext/\E(?!https:)})) { |
|
unless ((&Apache::lonnet::uses_sts()) || (&Apache::lonnet::waf_allssl($hostname))) { |
|
if ($hostname ne '') { |
|
$src = 'http://'.$hostname.$src; |
|
} |
|
$src .= ($srcHasQuestion? '&' : '?') . 'usehttp=1'; |
|
$srcHasQuestion = 1; |
|
} |
|
} |
|
} |
|
if (defined($anchor)) { $anchor='#'.$anchor; } |
|
if (($args->{'caller'} eq 'sequence') && ($curRes->is_map())) { |
|
$args->{"resourceLink"} = $src.($srcHasQuestion?'&':'?') .'navmap=1'; |
|
} else { |
|
$args->{"resourceLink"} = $src. |
|
($srcHasQuestion?'&':'?') . |
|
'symb=' . &escape($symb).$inhibitmenu.$anchor; |
|
} |
|
} |
# Now, we've decided what parts to show. Loop through them and |
# Now, we've decided what parts to show. Loop through them and |
# show them. |
# show them. |
foreach my $part (@parts) { |
foreach my $part (@parts) { |
$rownum ++; |
$rownum ++; |
my $backgroundColor = $backgroundColors[$rownum % scalar(@backgroundColors)]; |
|
|
|
$result .= " <tr bgcolor='$backgroundColor'>\n"; |
$result .= &Apache::loncommon::start_data_table_row(); |
|
|
# Set up some data about the parts that the cols might want |
# Set up some data about the parts that the cols might want |
my $filter = $it->{FILTER}; |
my $filter = $it->{FILTER}; |
my $stack = $it->getStack(); |
|
my $src = getLinkForResource($stack); |
|
|
|
my $srcHasQuestion = $src =~ /\?/; |
|
$args->{"resourceLink"} = $src. |
|
($srcHasQuestion?'&':'?') . |
|
'symb=' . &Apache::lonnet::escape($curRes->symb()); |
|
|
|
# Now, display each column. |
# Now, display each column. |
foreach my $col (@$cols) { |
foreach my $col (@$cols) { |
my $colHTML = ''; |
my $colHTML = ''; |
Line 1567 sub render {
|
Line 2029 sub render {
|
$currentJumpDelta) { |
$currentJumpDelta) { |
# Jam the anchor after the <td> tag; |
# Jam the anchor after the <td> tag; |
# necessary for valid HTML (which Mozilla requires) |
# necessary for valid HTML (which Mozilla requires) |
$colHTML =~ s/\>/\>\<a name="curloc" \/\>/; |
$colHTML =~ s/\>/\>\<a name="curloc" \>\<\/a\>/; |
$displayedJumpMarker = 1; |
$displayedJumpMarker = 1; |
} |
} |
$result .= $colHTML . "\n"; |
$result .= $colHTML . "\n"; |
} |
} |
$result .= " </tr>\n"; |
$result .= &Apache::loncommon::end_data_table_row(); |
$args->{'isNewBranch'} = 0; |
$args->{'isNewBranch'} = 0; |
} |
} |
|
|
Line 1591 sub render {
|
Line 2053 sub render {
|
} |
} |
} |
} |
} |
} |
|
|
|
$result.=&Apache::loncommon::end_data_table(); |
|
|
# Print out the part that jumps to #curloc if it exists |
# Print out the part that jumps to #curloc if it exists |
# delay needed because the browser is processing the jump before |
# delay needed because the browser is processing the jump before |
Line 1600 sub render {
|
Line 2064 sub render {
|
# it's quite likely this might fix other browsers, too, and |
# it's quite likely this might fix other browsers, too, and |
# certainly won't hurt anything. |
# certainly won't hurt anything. |
if ($displayedJumpMarker) { |
if ($displayedJumpMarker) { |
$result .= "<script>setTimeout(\"location += '#curloc';\", 0)</script>\n"; |
$result .= &Apache::lonhtmlcommon::scripttag(" |
|
if (location.href.indexOf('#curloc')==-1) { |
|
setTimeout(\"location += '#curloc';\", 0) |
|
} |
|
"); |
} |
} |
|
|
$result .= "</table>"; |
|
|
|
if ($r) { |
if ($r) { |
$r->print($result); |
$r->print($result); |
$result = ""; |
$result = ""; |
$r->rflush(); |
$r->rflush(); |
} |
} |
|
|
if ($mustCloseNavMap) { $navmap->untieHashes(); } |
return $result; |
|
} |
|
|
|
sub add_linkitem { |
|
my ($linkitems,$name,$cmd,$text)=@_; |
|
$$linkitems{$name}{'cmd'}=$cmd; |
|
$$linkitems{$name}{'text'}=&mt($text); |
|
} |
|
|
|
sub show_linkitems_toolbar { |
|
my ($args,$condition) = @_; |
|
my $result; |
|
if (ref($args) eq 'HASH') { |
|
if (ref($args->{'linkitems'}) eq 'HASH') { |
|
my $numlinks = scalar(keys(%{$args->{'linkitems'}})); |
|
if ($numlinks > 1) { |
|
$result = '<td>'. |
|
&Apache::loncommon::help_open_menu('Navigation Screen','Navigation_Screen', |
|
undef,'RAT'). |
|
'</td>'. |
|
'<td> </td>'. |
|
'<td class="LC_middle">'.&mt('Tools:').'</td>'; |
|
} |
|
$result .= '<td align="left">'."\n". |
|
'<ul id="LC_toolbar">'; |
|
my @linkorder = ('firsthomework','everything','uncompleted', |
|
'changefolder','clearbubbles','edittoplevel'); |
|
foreach my $link (@linkorder) { |
|
if (ref($args->{'linkitems'}{$link}) eq 'HASH') { |
|
if ($args->{'linkitems'}{$link}{'text'} ne '') { |
|
$args->{'linkitems'}{$link}{'cmd'}=~s/"/'/g; |
|
if ($args->{'linkitems'}{$link}{'cmd'}) { |
|
my $link_id = 'LC_content_toolbar_'.$link; |
|
if ($link eq 'changefolder') { |
|
if ($condition) { |
|
$link_id='LC_content_toolbar_changefolder_toggled'; |
|
} else { |
|
$link_id='LC_content_toolbar_changefolder'; |
|
} |
|
} |
|
$result .= '<li><a href="#" '. |
|
'onclick="'.$args->{'linkitems'}{$link}{'cmd'}.'" '. |
|
'id="'.$link_id.'" '. |
|
'class="LC_toolbarItem" '. |
|
'title="'.$args->{'linkitems'}{$link}{'text'}.'">'. |
|
'</a></li>'."\n"; |
|
} |
|
} |
|
} |
|
} |
|
$result .= '</ul>'. |
|
'</td>'; |
|
if (($numlinks==1) && (exists($args->{'linkitems'}{'edittoplevel'}))) { |
|
$result .= '<td><a href="'.$args->{'linkitems'}{'edittoplevel'}{'cmd'}.'">'. |
|
&mt('Content Editor').'</a></td>'; |
|
} |
|
} |
|
if ($args->{'sort_html'}) { |
|
$result .= '<td> </td><td> </td><td> </td>'. |
|
'<td align="right">'.$args->{'sort_html'}.'</td>'; |
|
} |
|
} |
|
if ($result) { |
|
$result = "<table><tr>$result</tr></table>"; |
|
} |
return $result; |
return $result; |
} |
} |
|
|
1; |
1; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
package Apache::lonnavmaps::navmap; |
package Apache::lonnavmaps::navmap; |
|
|
=pod |
=pod |
Line 1635 In order of increasing complexity and po
|
Line 2172 In order of increasing complexity and po
|
|
|
=over 4 |
=over 4 |
|
|
=item * C<$navmap-E<gt>getByX>, where X is B<Id>, B<Symb>, B<Url> or B<MapPc>. This provides |
=item * C<$navmap-E<gt>getByX>, where X is B<Id>, B<Symb> or B<MapPc> and getResourceByUrl. This provides |
various ways to obtain resource objects, based on various identifiers. |
various ways to obtain resource objects, based on various identifiers. |
Use this when you want to request information about one object or |
Use this when you want to request information about one object or |
a handful of resources you already know the identities of, from some |
a handful of resources you already know the identities of, from some |
other source. For more about Ids, Symbs, and MapPcs, see the |
other source. For more about Ids, Symbs, and MapPcs, see the |
Resource documentation. Note that Url should be a B<last resort>, |
Resource documentation. Note that Url should be a B<last resort>, |
not your first choice; it only works when there is only one |
not your first choice; it only really works when there is only one |
instance of the resource in the course, which only applies to |
instance of the resource in the course, which only applies to |
maps, and even that may change in the future. |
maps, and even that may change in the future (see the B<getResourceByUrl> |
|
documentation for more details.) |
|
|
=item * C<my @resources = $navmap-E<gt>retrieveResources(args)>. This |
=item * C<my @resources = $navmap-E<gt>retrieveResources(args)>. This |
retrieves resources matching some criterion and returns them |
retrieves resources matching some criterion and returns them |
Line 1673 successful, or B<undef> if not.
|
Line 2211 successful, or B<undef> if not.
|
|
|
=back |
=back |
|
|
When you are done with the $navmap object, you I<must> call |
|
$navmap->untieHashes(), or you'll prevent the current user from using that |
|
course until the web server is restarted. (!) |
|
|
|
=head2 Methods |
=head2 Methods |
|
|
=over 4 |
=over 4 |
Line 1689 See iterator documentation below.
|
Line 2223 See iterator documentation below.
|
|
|
use strict; |
use strict; |
use GDBM_File; |
use GDBM_File; |
|
use Apache::lonnet; |
|
use LONCAPA; |
|
|
sub new { |
sub new { |
# magic invocation to create a class instance |
# magic invocation to create a class instance |
my $proto = shift; |
my $proto = shift; |
my $class = ref($proto) || $proto; |
my $class = ref($proto) || $proto; |
my $self = {}; |
my $self = {}; |
|
bless($self); # So we can call change_user if necessary |
|
|
|
$self->{USERNAME} = shift || $env{'user.name'}; |
|
$self->{DOMAIN} = shift || $env{'user.domain'}; |
|
$self->{CODE} = shift; |
|
$self->{NOHIDE} = shift; |
|
|
|
|
|
|
# Resource cache stores navmap resources as we reference them. We generate |
# 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 1704 sub new {
|
Line 2248 sub new {
|
# failed |
# failed |
$self->{NETWORK_FAILURE} = 0; |
$self->{NETWORK_FAILURE} = 0; |
|
|
# tie the nav hash |
# We can only tie the nav hash as done below if the username/domain |
|
# match the env one. Otherwise change_user does everything we need...since we can't |
|
# assume there are course hashes for the specific requested user:domain |
|
# Note: change_user is also called if we need the nav hash when printing CODEd |
|
# assignments or printing an exam, in which the enclosing folder for the items in |
|
# the exam has hidden set. |
|
# |
|
|
my %navmaphash; |
if (($self->{USERNAME} eq $env{'user.name'}) && ($self->{DOMAIN} eq $env{'user.domain'}) && |
my %parmhash; |
!$self->{CODE} && !$self->{NOHIDE}) { |
my $courseFn = $ENV{"request.course.fn"}; |
|
if (!(tie(%navmaphash, 'GDBM_File', "${courseFn}.db", |
# tie the nav hash |
&GDBM_READER(), 0640))) { |
|
return undef; |
my %navmaphash; |
|
my %parmhash; |
|
my $courseFn = $env{"request.course.fn"}; |
|
if (!(tie(%navmaphash, 'GDBM_File', "${courseFn}.db", |
|
&GDBM_READER(), 0640))) { |
|
return undef; |
|
} |
|
|
|
if (!(tie(%parmhash, 'GDBM_File', "${courseFn}_parms.db", |
|
&GDBM_READER(), 0640))) |
|
{ |
|
untie %{$self->{PARM_HASH}}; |
|
return undef; |
|
} |
|
|
|
$self->{NAV_HASH} = \%navmaphash; |
|
$self->{PARM_HASH} = \%parmhash; |
|
$self->{PARM_CACHE} = {}; |
|
} else { |
|
$self->change_user($self->{USERNAME}, $self->{DOMAIN}, $self->{CODE}, $self->{NOHIDE}); |
} |
} |
|
|
|
return $self; |
|
} |
|
|
|
# |
|
# In some instances it is useful to be able to dynamically change the |
|
# username/domain associated with a navmap (e.g. to navigate for someone |
|
# else besides the current user...if sufficiently privileged. |
|
# Parameters: |
|
# user - New user. |
|
# domain- Domain the user belongs to. |
|
# code - Anonymous CODE in use. |
|
# Implicit inputs: |
|
# |
|
sub change_user { |
|
my $self = shift; |
|
$self->{USERNAME} = shift; |
|
$self->{DOMAIN} = shift; |
|
$self->{CODE} = shift; |
|
$self->{NOHIDE} = shift; |
|
|
|
# If the hashes are already tied make sure to break that bond: |
|
|
|
untie %{$self->{NAV_HASH}}; |
|
untie %{$self->{PARM_HASH}}; |
|
|
|
# The assumption is that we have to |
|
# use lonmap here to re-read the hash and from it reconstruct |
|
# new big and parameter hashes. An implicit assumption at this time |
|
# is that the course file is probably not created locally yet |
|
# an that we will therefore just read without tying. |
|
|
|
my ($cdom, $cnum) = split(/\_/, $env{'request.course.id'}); |
|
|
|
my %big_hash; |
|
&Apache::lonmap::loadmap($cnum, $cdom, $self->{USERNAME}, $self->{DOMAIN}, $self->{CODE}, $self->{NOHIDE}, \%big_hash); |
|
$self->{NAV_HASH} = \%big_hash; |
|
|
|
|
|
|
|
# Now clear the parm cache and reconstruct the parm hash fromt he big_hash |
|
# param.xxxx keys. |
|
|
|
$self->{PARM_CACHE} = {}; |
|
|
if (!(tie(%parmhash, 'GDBM_File', "${courseFn}_parms.db", |
my %parm_hash = {}; |
&GDBM_READER(), 0640))) |
foreach my $key (keys(%big_hash)) { |
{ |
if ($key =~ /^param\./) { |
untie %{$self->{PARM_HASH}}; |
my $param_key = $key; |
return undef; |
$param_key =~ s/^param\.//; |
|
$parm_hash{$param_key} = $big_hash{$key}; |
|
} |
} |
} |
|
|
$self->{NAV_HASH} = \%navmaphash; |
$self->{PARM_HASH} = \%parm_hash; |
$self->{PARM_HASH} = \%parmhash; |
|
$self->{PARM_CACHE} = {}; |
|
|
|
bless($self); |
|
|
|
return $self; |
|
} |
} |
|
|
sub generate_course_user_opt { |
sub generate_course_user_opt { |
my $self = shift; |
my $self = shift; |
if ($self->{COURSE_USER_OPT_GENERATED}) { return; } |
if ($self->{COURSE_USER_OPT_GENERATED}) { return; } |
|
|
my $uname=$ENV{'user.name'}; |
my $uname=$self->{USERNAME}; |
my $udom=$ENV{'user.domain'}; |
my $udom=$self->{DOMAIN}; |
my $uhome=$ENV{'user.home'}; |
|
my $cid=$ENV{'request.course.id'}; |
my $cid=$env{'request.course.id'}; |
my $chome=$ENV{'course.'.$cid.'.home'}; |
my $cdom=$env{'course.'.$cid.'.domain'}; |
my ($cdom,$cnum)=split(/\_/,$cid); |
my $cnum=$env{'course.'.$cid.'.num'}; |
|
|
my $userprefix=$uname.'_'.$udom.'_'; |
|
|
|
my %courserdatas; my %useropt; my %courseopt; my %userrdatas; |
|
unless ($uhome eq 'no_host') { |
|
# ------------------------------------------------- Get coursedata (if present) |
# ------------------------------------------------- Get coursedata (if present) |
unless ((time-$courserdatas{$cid.'.last_cache'})<240) { |
my $courseopt=&Apache::lonnet::get_courseresdata($cnum,$cdom); |
my $reply=&Apache::lonnet::reply('dump:'.$cdom.':'.$cnum. |
# Check for network failure |
':resourcedata',$chome); |
if (!ref($courseopt)) { |
# Check for network failure |
if ( $courseopt =~ /no.such.host/i || $courseopt =~ /con_lost/i) { |
if ( $reply =~ /no.such.host/i || $reply =~ /con_lost/i) { |
$self->{NETWORK_FAILURE} = 1; |
$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); |
|
} |
} |
|
undef($courseopt); |
|
} |
|
|
# --------------------------------------------------- Get userdata (if present) |
# --------------------------------------------------- Get userdata (if present) |
unless ((time-$userrdatas{$uname.'___'.$udom.'.last_cache'})<240) { |
|
my $reply=&Apache::lonnet::reply('dump:'.$udom.':'.$uname.':resourcedata',$uhome); |
my $useropt=&Apache::lonnet::get_userresdata($uname,$udom); |
if ($reply!~/^error\:/) { |
# Check for network failure |
$userrdatas{$uname.'___'.$udom}=$reply; |
if (!ref($useropt)) { |
$userrdatas{$uname.'___'.$udom.'.last_cache'}=time; |
if ( $useropt =~ /no.such.host/i || $useropt =~ /con_lost/i) { |
} |
$self->{NETWORK_FAILURE} = 1; |
# 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; |
undef($useropt); |
$self->{USER_OPT} = \%useropt; |
|
} |
} |
|
|
|
$self->{COURSE_OPT} = $courseopt; |
|
$self->{USER_OPT} = $useropt; |
|
|
$self->{COURSE_USER_OPT_GENERATED} = 1; |
$self->{COURSE_USER_OPT_GENERATED} = 1; |
|
|
return; |
return; |
} |
} |
|
|
|
|
|
|
sub generate_email_discuss_status { |
sub generate_email_discuss_status { |
my $self = shift; |
my $self = shift; |
|
my $symb = shift; |
if ($self->{EMAIL_DISCUSS_GENERATED}) { return; } |
if ($self->{EMAIL_DISCUSS_GENERATED}) { return; } |
|
|
my $cid=$ENV{'request.course.id'}; |
my $cid=$env{'request.course.id'}; |
my ($cdom,$cnum)=split(/\_/,$cid); |
my $cdom=$env{'course.'.$cid.'.domain'}; |
|
my $cnum=$env{'course.'.$cid.'.num'}; |
|
|
my %emailstatus = &Apache::lonnet::dump('email_status'); |
my %emailstatus = &Apache::lonnet::dump('email_status',$self->{DOMAIN},$self->{USERNAME}); |
my $logoutTime = $emailstatus{'logout'}; |
my $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); |
|
my %lastread = &Apache::lonnet::dump('nohist_'.$cid.'_discuss', |
|
$self->{DOMAIN},$self->{USERNAME},'lastread'); |
|
my %lastreadtime = (); |
|
foreach my $key (keys(%lastread)) { |
|
my $shortkey = $key; |
|
$shortkey =~ s/_lastread$//; |
|
$lastreadtime{$shortkey} = $lastread{$key}; |
|
} |
|
|
my %feedback=(); |
my %feedback=(); |
my %error=(); |
my %error=(); |
my $keys = &Apache::lonnet::reply('keys:'. |
my @keys = &Apache::lonnet::getkeys('nohist_email',$self->{DOMAIN}, |
$ENV{'user.domain'}.':'. |
$self->{USERNAME}); |
$ENV{'user.name'}.':nohist_email', |
|
$ENV{'user.home'}); |
|
|
|
foreach my $msgid (split(/\&/, $keys)) { |
foreach my $msgid (@keys) { |
$msgid=&Apache::lonnet::unescape($msgid); |
if ((!$emailstatus{$msgid}) || ($emailstatus{$msgid} eq 'new')) { |
my $plain=&Apache::lonnet::unescape(&Apache::lonnet::unescape($msgid)); |
my ($sendtime,$shortsubj,$fromname,$fromdomain,$status,$fromcid, |
if ($plain=~/(Error|Feedback) \[([^\]]+)\]/) { |
$symb,$error) = &Apache::lonmsg::unpackmsgid(&LONCAPA::escape($msgid)); |
my ($what,$url)=($1,$2); |
&Apache::lonenc::check_decrypt(\$symb); |
my %status= |
if (($fromcid ne '') && ($fromcid ne $cid)) { |
&Apache::lonnet::get('email_status',[$msgid]); |
next; |
if ($status{$msgid}=~/^error\:/) { |
} |
$status{$msgid}=''; |
if (defined($symb)) { |
} |
if (defined($error) && $error == 1) { |
|
$error{$symb}.=','.$msgid; |
if (($status{$msgid} eq 'new') || |
} else { |
(!$status{$msgid})) { |
$feedback{$symb}.=','.$msgid; |
if ($what eq 'Error') { |
} |
$error{$url}.=','.$msgid; |
} else { |
} else { |
my $plain= |
$feedback{$url}.=','.$msgid; |
&LONCAPA::unescape(&LONCAPA::unescape($msgid)); |
} |
if ($plain=~/ \[([^\]]+)\]\:/) { |
} |
my $url=$1; |
|
if ($plain=~/\:Error \[/) { |
|
$error{$url}.=','.$msgid; |
|
} else { |
|
$feedback{$url}.=','.$msgid; |
|
} |
|
} |
|
} |
} |
} |
} |
} |
|
|
|
#symbs of resources that have feedbacks (will be urls pre-2.3) |
$self->{FEEDBACK} = \%feedback; |
$self->{FEEDBACK} = \%feedback; |
$self->{ERROR_MSG} = \%error; # what is this? JB |
#or errors (will be urls pre 2.3) |
|
$self->{ERROR_MSG} = \%error; |
$self->{DISCUSSION_TIME} = \%discussiontime; |
$self->{DISCUSSION_TIME} = \%discussiontime; |
$self->{EMAIL_STATUS} = \%emailstatus; |
$self->{EMAIL_STATUS} = \%emailstatus; |
|
$self->{LAST_READ} = \%lastreadtime; |
|
|
$self->{EMAIL_DISCUSS_GENERATED} = 1; |
$self->{EMAIL_DISCUSS_GENERATED} = 1; |
} |
} |
Line 1845 sub get_user_data {
|
Line 2457 sub get_user_data {
|
if ($self->{RETRIEVED_USER_DATA}) { return; } |
if ($self->{RETRIEVED_USER_DATA}) { return; } |
|
|
# Retrieve performance data on problems |
# Retrieve performance data on problems |
my %student_data = Apache::lonnet::currentdump($ENV{'request.course.id'}, |
my %student_data = Apache::lonnet::currentdump($env{'request.course.id'}, |
$ENV{'user.domain'}, |
$self->{DOMAIN}, |
$ENV{'user.name'}); |
$self->{USERNAME}); |
$self->{STUDENT_DATA} = \%student_data; |
$self->{STUDENT_DATA} = \%student_data; |
|
|
$self->{RETRIEVED_USER_DATA} = 1; |
$self->{RETRIEVED_USER_DATA} = 1; |
} |
} |
|
|
|
sub get_discussion_data { |
|
my $self = shift; |
|
if ($self->{RETRIEVED_DISCUSSION_DATA}) { |
|
return $self->{DISCUSSION_DATA}; |
|
} |
|
|
|
$self->generate_email_discuss_status(); |
|
|
|
my $cid=$env{'request.course.id'}; |
|
my $cdom=$env{'course.'.$cid.'.domain'}; |
|
my $cnum=$env{'course.'.$cid.'.num'}; |
|
# Retrieve discussion data for resources in course |
|
my %discussion_data = &Apache::lonnet::dumpstore($cid,$cdom,$cnum); |
|
|
|
|
|
$self->{DISCUSSION_DATA} = \%discussion_data; |
|
$self->{RETRIEVED_DISCUSSION_DATA} = 1; |
|
return $self->{DISCUSSION_DATA}; |
|
} |
|
|
|
|
# Internal function: Takes a key to look up in the nav hash and implements internal |
# Internal function: Takes a key to look up in the nav hash and implements internal |
# memory caching of that key. |
# memory caching of that key. |
sub navhash { |
sub navhash { |
Line 1872 sub navhash {
|
Line 2505 sub navhash {
|
# Checks to see if coursemap is defined, matching test in old lonnavmaps |
# Checks to see if coursemap is defined, matching test in old lonnavmaps |
sub courseMapDefined { |
sub courseMapDefined { |
my $self = shift; |
my $self = shift; |
my $uri = &Apache::lonnet::clutter($ENV{'request.course.uri'}); |
my $uri = &Apache::lonnet::clutter($env{'request.course.uri'}); |
|
|
my $firstres = $self->navhash("map_start_$uri"); |
my $firstres = $self->navhash("map_start_$uri"); |
my $lastres = $self->navhash("map_finish_$uri"); |
my $lastres = $self->navhash("map_finish_$uri"); |
Line 1882 sub courseMapDefined {
|
Line 2515 sub courseMapDefined {
|
sub getIterator { |
sub getIterator { |
my $self = shift; |
my $self = shift; |
my $iterator = Apache::lonnavmaps::iterator->new($self, shift, shift, |
my $iterator = Apache::lonnavmaps::iterator->new($self, shift, shift, |
shift, undef, shift); |
shift, undef, shift, |
|
shift, shift, shift); |
return $iterator; |
return $iterator; |
} |
} |
|
|
# unties the hash when done |
|
sub untieHashes { |
|
my $self = shift; |
|
untie %{$self->{NAV_HASH}}; |
|
untie %{$self->{PARM_HASH}}; |
|
} |
|
|
|
# Private method: Does the given resource (as a symb string) have |
# Private method: Does the given resource (as a symb string) have |
# current discussion? Returns 0 if chat/mail data not extracted. |
# current discussion? Returns 0 if chat/mail data not extracted. |
sub hasDiscussion { |
sub hasDiscussion { |
my $self = shift; |
my $self = shift; |
my $symb = shift; |
my $symb = shift; |
|
|
$self->generate_email_discuss_status(); |
$self->generate_email_discuss_status(); |
|
|
if (!defined($self->{DISCUSSION_TIME})) { return 0; } |
if (!defined($self->{DISCUSSION_TIME})) { return 0; } |
|
|
#return defined($self->{DISCUSSION_TIME}->{$symb}); |
#return defined($self->{DISCUSSION_TIME}->{$symb}); |
return $self->{DISCUSSION_TIME}->{$symb} > |
|
$self->{LAST_CHECK}; |
# backward compatibility (bulletin boards used to be 'wrapped') |
|
my $ressymb = $self->wrap_symb($symb); |
|
if ( defined ( $self->{LAST_READ}->{$ressymb} ) ) { |
|
return $self->{DISCUSSION_TIME}->{$ressymb} > $self->{LAST_READ}->{$ressymb}; |
|
} else { |
|
# return $self->{DISCUSSION_TIME}->{$ressymb} > $self->{LAST_CHECK}; # v.1.1 behavior |
|
return $self->{DISCUSSION_TIME}->{$ressymb} > 0; # in 1.2 will display speech bubble icons for all items with posts until marked as read (even if read in v 1.1). |
|
} |
|
} |
|
|
|
sub last_post_time { |
|
my $self = shift; |
|
my $symb = shift; |
|
my $ressymb = $self->wrap_symb($symb); |
|
return $self->{DISCUSSION_TIME}->{$ressymb}; |
|
} |
|
|
|
sub discussion_info { |
|
my $self = shift; |
|
my $symb = shift; |
|
my $filter = shift; |
|
|
|
$self->get_discussion_data(); |
|
|
|
my $ressymb = $self->wrap_symb($symb); |
|
# keys used to store bulletinboard postings use 'unwrapped' symb. |
|
my $discsymb = &escape($self->unwrap_symb($ressymb)); |
|
my $version = $self->{DISCUSSION_DATA}{'version:'.$discsymb}; |
|
if (!$version) { return; } |
|
|
|
my $prevread = $self->{LAST_READ}{$ressymb}; |
|
|
|
my $count = 0; |
|
my $hiddenflag = 0; |
|
my $deletedflag = 0; |
|
my ($hidden,$deleted,%info); |
|
|
|
for (my $id=$version; $id>0; $id--) { |
|
my $vkeys=$self->{DISCUSSION_DATA}{$id.':keys:'.$discsymb}; |
|
my @keys=split(/:/,$vkeys); |
|
if (grep(/^hidden$/ ,@keys)) { |
|
if (!$hiddenflag) { |
|
$hidden = $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':hidden'}; |
|
$hiddenflag = 1; |
|
} |
|
} elsif (grep(/^deleted$/,@keys)) { |
|
if (!$deletedflag) { |
|
$deleted = $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':deleted'}; |
|
$deletedflag = 1; |
|
} |
|
} else { |
|
if (($hidden !~/\.$id\./) && ($deleted !~/\.$id\./)) { |
|
if ($filter eq 'unread') { |
|
if ($prevread >= $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':timestamp'}) { |
|
next; |
|
} |
|
} |
|
$count++; |
|
$info{$count}{'subject'} = |
|
$self->{DISCUSSION_DATA}{$id.':'.$discsymb.':subject'}; |
|
$info{$count}{'id'} = $id; |
|
$info{$count}{'timestamp'} = $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':timestamp'}; |
|
} |
|
} |
|
} |
|
if (wantarray) { |
|
return ($count,%info); |
|
} |
|
return $count; |
|
} |
|
|
|
sub wrap_symb { |
|
my $self = shift; |
|
my $symb = shift; |
|
if ($symb =~ m-___(adm/[^/]+/[^/]+/)(\d+)(/bulletinboard)$-) { |
|
unless ($symb =~ m|adm/wrapper/adm|) { |
|
$symb = 'bulletin___'.$2.'___adm/wrapper/'.$1.$2.$3; |
|
} |
|
} |
|
return $symb; |
|
} |
|
|
|
sub unwrap_symb { |
|
my $self = shift; |
|
my $ressymb = shift; |
|
my $discsymb = $ressymb; |
|
if ($ressymb =~ m-^(bulletin___\d+___)adm/wrapper/(adm/[^/]+/[^/]+/\d+/bulletinboard)$-) { |
|
$discsymb = $1.$2; |
|
} |
|
return $discsymb; |
} |
} |
|
|
# Private method: Does the given resource (as a symb string) have |
# Private method: Does the given resource (as a symb string) have |
# current feedback? Returns the string in the feedback hash, which |
# current feedback? Returns the string in the feedback hash, which |
# will be false if it does not exist. |
# will be false if it does not exist. |
|
|
sub getFeedback { |
sub getFeedback { |
my $self = shift; |
my $self = shift; |
my $symb = shift; |
my $symb = shift; |
|
my $source = shift; |
|
|
$self->generate_email_discuss_status(); |
$self->generate_email_discuss_status(); |
|
|
if (!defined($self->{FEEDBACK})) { return ""; } |
if (!defined($self->{FEEDBACK})) { return ""; } |
|
|
return $self->{FEEDBACK}->{$symb}; |
my $feedback; |
|
if ($self->{FEEDBACK}->{$symb}) { |
|
$feedback = $self->{FEEDBACK}->{$symb}; |
|
if ($self->{FEEDBACK}->{$source}) { |
|
$feedback .= ','.$self->{FEEDBACK}->{$source}; |
|
} |
|
} else { |
|
if ($self->{FEEDBACK}->{$source}) { |
|
$feedback = $self->{FEEDBACK}->{$source}; |
|
} |
|
} |
|
return $feedback; |
} |
} |
|
|
# Private method: Get the errors for that resource (by source). |
# Private method: Get the errors for that resource (by source). |
sub getErrors { |
sub getErrors { |
my $self = shift; |
my $self = shift; |
|
my $symb = shift; |
my $src = shift; |
my $src = shift; |
|
|
$self->generate_email_discuss_status(); |
$self->generate_email_discuss_status(); |
|
|
if (!defined($self->{ERROR_MSG})) { return ""; } |
if (!defined($self->{ERROR_MSG})) { return ""; } |
return $self->{ERROR_MSG}->{$src}; |
|
|
my $errors; |
|
if ($self->{ERROR_MSG}->{$symb}) { |
|
$errors = $self->{ERROR_MSG}->{$symb}; |
|
if ($self->{ERROR_MSG}->{$src}) { |
|
$errors .= ','.$self->{ERROR_MSG}->{$src}; |
|
} |
|
} else { |
|
if ($self->{ERROR_MSG}->{$src}) { |
|
$errors = $self->{ERROR_MSG}->{$src}; |
|
} |
|
} |
|
return $errors; |
} |
} |
|
|
=pod |
=pod |
Line 1947 resource object.
|
Line 2688 resource object.
|
Based on the symb of the resource, get a resource object for that |
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. |
resource. This is one of the proper ways to get a resource object. |
|
|
=item * B<getMapByMapPc>(map_pc): |
=item * B<getByMapPc>(map_pc): |
|
|
Based on the map_pc of the resource, get a resource object for |
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. |
the given map. This is one of the proper ways to get a resource object. |
Line 1956 the given map. This is one of the proper
|
Line 2697 the given map. This is one of the proper
|
|
|
# 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 |
# as we use them. The real point is to prevent reading any more from the tied |
# as we use them. The real point is to prevent reading any more from the tied |
# hash then we have to, which should hopefully alleviate speed problems. |
# hash than we have to, which should hopefully alleviate speed problems. |
|
|
sub getById { |
sub getById { |
my $self = shift; |
my $self = shift; |
Line 1976 sub getById {
|
Line 2717 sub getById {
|
sub getBySymb { |
sub getBySymb { |
my $self = shift; |
my $self = shift; |
my $symb = shift; |
my $symb = shift; |
|
|
my ($mapUrl, $id, $filename) = &Apache::lonnet::decode_symb($symb); |
my ($mapUrl, $id, $filename) = &Apache::lonnet::decode_symb($symb); |
my $map = $self->getResourceByUrl($mapUrl); |
my $map = $self->getResourceByUrl($mapUrl); |
return $self->getById($map->map_pc() . '.' . $id); |
my $returnvalue = undef; |
|
if (ref($map)) { |
|
$returnvalue = $self->getById($map->map_pc() .'.'.$id); |
|
} |
|
return $returnvalue; |
} |
} |
|
|
sub getByMapPc { |
sub getByMapPc { |
Line 2001 resource in the navmap.
|
Line 2747 resource in the navmap.
|
sub firstResource { |
sub firstResource { |
my $self = shift; |
my $self = shift; |
my $firstResource = $self->navhash('map_start_' . |
my $firstResource = $self->navhash('map_start_' . |
&Apache::lonnet::clutter($ENV{'request.course.uri'})); |
&Apache::lonnet::clutter($env{'request.course.uri'})); |
return $self->getById($firstResource); |
return $self->getById($firstResource); |
} |
} |
|
|
Line 2017 in the navmap.
|
Line 2763 in the navmap.
|
sub finishResource { |
sub finishResource { |
my $self = shift; |
my $self = shift; |
my $firstResource = $self->navhash('map_finish_' . |
my $firstResource = $self->navhash('map_finish_' . |
&Apache::lonnet::clutter($ENV{'request.course.uri'})); |
&Apache::lonnet::clutter($env{'request.course.uri'})); |
return $self->getById($firstResource); |
return $self->getById($firstResource); |
} |
} |
|
|
Line 2025 sub finishResource {
|
Line 2771 sub finishResource {
|
# the actual lookup; parmval caches the results. |
# the actual lookup; parmval caches the results. |
sub parmval { |
sub parmval { |
my $self = shift; |
my $self = shift; |
my ($what,$symb)=@_; |
my ($what,$symb,$recurse)=@_; |
my $hashkey = $what."|||".$symb; |
my $hashkey = $what."|||".$symb; |
|
my $cache = $self->{PARM_CACHE}; |
if (defined($self->{PARM_CACHE}->{$hashkey})) { |
if (defined($self->{PARM_CACHE}->{$hashkey})) { |
return $self->{PARM_CACHE}->{$hashkey}; |
if (ref($self->{PARM_CACHE}->{$hashkey}) eq 'ARRAY') { |
|
if (defined($self->{PARM_CACHE}->{$hashkey}->[0])) { |
|
if (wantarray) { |
|
return @{$self->{PARM_CACHE}->{$hashkey}}; |
|
} else { |
|
return $self->{PARM_CACHE}->{$hashkey}->[0]; |
|
} |
|
} |
|
} else { |
|
return $self->{PARM_CACHE}->{$hashkey}; |
|
} |
} |
} |
|
my $result = $self->parmval_real($what, $symb, $recurse); |
my $result = $self->parmval_real($what, $symb); |
|
$self->{PARM_CACHE}->{$hashkey} = $result; |
$self->{PARM_CACHE}->{$hashkey} = $result; |
return $result; |
if (wantarray) { |
|
return @{$result}; |
|
} |
|
return $result->[0]; |
} |
} |
|
|
|
|
sub parmval_real { |
sub parmval_real { |
my $self = shift; |
my $self = shift; |
my ($what,$symb,$recurse) = @_; |
my ($what,$symb,$recurse) = @_; |
|
|
|
|
# Make sure the {USER_OPT} and {COURSE_OPT} hashes are populated |
# Make sure the {USER_OPT} and {COURSE_OPT} hashes are populated |
$self->generate_course_user_opt(); |
$self->generate_course_user_opt(); |
|
|
my $cid=$ENV{'request.course.id'}; |
my $cid=$env{'request.course.id'}; |
my $csec=$ENV{'request.course.sec'}; |
my $csec=$env{'request.course.sec'}; |
my $uname=$ENV{'user.name'}; |
my $cgroup=''; |
my $udom=$ENV{'user.domain'}; |
my @cgrps=split(/:/,$env{'request.course.groups'}); |
|
if (@cgrps > 0) { |
|
@cgrps = sort(@cgrps); |
|
$cgroup = $cgrps[0]; |
|
} |
|
my $uname=$self->{USERNAME}; |
|
my $udom=$self->{DOMAIN}; |
|
|
unless ($symb) { return ''; } |
unless ($symb) { return ['']; } |
my $result=''; |
my $result=''; |
|
|
my ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb); |
my ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb); |
|
$mapname = &Apache::lonnet::deversion($mapname); |
# ----------------------------------------------------- Cascading lookup scheme |
# ----------------------------------------------------- Cascading lookup scheme |
my $rwhat=$what; |
my $rwhat=$what; |
$what=~s/^parameter\_//; |
$what=~s/^parameter\_//; |
Line 2061 sub parmval_real {
|
Line 2827 sub parmval_real {
|
|
|
my $symbparm=$symb.'.'.$what; |
my $symbparm=$symb.'.'.$what; |
my $mapparm=$mapname.'___(all).'.$what; |
my $mapparm=$mapname.'___(all).'.$what; |
my $usercourseprefix=$uname.'_'.$udom.'_'.$cid; |
my $usercourseprefix=$cid; |
|
|
|
|
|
|
|
my $grplevel=$usercourseprefix.'.['.$cgroup.'].'.$what; |
|
my $grplevelr=$usercourseprefix.'.['.$cgroup.'].'.$symbparm; |
|
my $grplevelm=$usercourseprefix.'.['.$cgroup.'].'.$mapparm; |
|
|
|
|
my $seclevel= $usercourseprefix.'.['.$csec.'].'.$what; |
my $seclevel= $usercourseprefix.'.['.$csec.'].'.$what; |
my $seclevelr=$usercourseprefix.'.['.$csec.'].'.$symbparm; |
my $seclevelr=$usercourseprefix.'.['.$csec.'].'.$symbparm; |
my $seclevelm=$usercourseprefix.'.['.$csec.'].'.$mapparm; |
my $seclevelm=$usercourseprefix.'.['.$csec.'].'.$mapparm; |
|
|
|
|
my $courselevel= $usercourseprefix.'.'.$what; |
my $courselevel= $usercourseprefix.'.'.$what; |
my $courselevelr=$usercourseprefix.'.'.$symbparm; |
my $courselevelr=$usercourseprefix.'.'.$symbparm; |
my $courselevelm=$usercourseprefix.'.'.$mapparm; |
my $courselevelm=$usercourseprefix.'.'.$mapparm; |
|
|
|
|
my $useropt = $self->{USER_OPT}; |
my $useropt = $self->{USER_OPT}; |
my $courseopt = $self->{COURSE_OPT}; |
my $courseopt = $self->{COURSE_OPT}; |
my $parmhash = $self->{PARM_HASH}; |
my $parmhash = $self->{PARM_HASH}; |
|
|
# ---------------------------------------------------------- first, check user |
# ---------------------------------------------------------- first, check user |
if ($uname and defined($useropt)) { |
if ($uname and defined($useropt)) { |
if (defined($$useropt{$courselevelr})) { return $$useropt{$courselevelr}; } |
if (defined($$useropt{$courselevelr})) { return [$$useropt{$courselevelr},'resource']; } |
if (defined($$useropt{$courselevelm})) { return $$useropt{$courselevelm}; } |
if (defined($$useropt{$courselevelm})) { return [$$useropt{$courselevelm},'map']; } |
if (defined($$useropt{$courselevel})) { return $$useropt{$courselevel}; } |
if (defined($$useropt{$courselevel})) { return [$$useropt{$courselevel},'course']; } |
} |
} |
|
|
# ------------------------------------------------------- second, check course |
# ------------------------------------------------------- second, check course |
|
if ($cgroup ne '' and defined($courseopt)) { |
|
if (defined($$courseopt{$grplevelr})) { return [$$courseopt{$grplevelr},'resource']; } |
|
if (defined($$courseopt{$grplevelm})) { return [$$courseopt{$grplevelm},'map']; } |
|
if (defined($$courseopt{$grplevel})) { return [$$courseopt{$grplevel},'course']; } |
|
} |
|
|
if ($csec and defined($courseopt)) { |
if ($csec and defined($courseopt)) { |
if (defined($$courseopt{$seclevelr})) { return $$courseopt{$seclevelr}; } |
if (defined($$courseopt{$seclevelr})) { return [$$courseopt{$seclevelr},'resource']; } |
if (defined($$courseopt{$seclevelm})) { return $$courseopt{$seclevelm}; } |
if (defined($$courseopt{$seclevelm})) { return [$$courseopt{$seclevelm},'map']; } |
if (defined($$courseopt{$seclevel})) { return $$courseopt{$seclevel}; } |
if (defined($$courseopt{$seclevel})) { return [$$courseopt{$seclevel},'course']; } |
} |
} |
|
|
if (defined($courseopt)) { |
if (defined($courseopt)) { |
if (defined($$courseopt{$courselevelr})) { return $$courseopt{$courselevelr}; } |
if (defined($$courseopt{$courselevelr})) { return [$$courseopt{$courselevelr},'resource']; } |
if (defined($$courseopt{$courselevelm})) { return $$courseopt{$courselevelm}; } |
|
if (defined($$courseopt{$courselevel})) { return $$courseopt{$courselevel}; } |
|
} |
} |
|
|
# ----------------------------------------------------- third, check map parms |
# ----------------------------------------------------- third, check map parms |
|
|
my $thisparm=$$parmhash{$symbparm}; |
my $thisparm=$$parmhash{$symbparm}; |
if (defined($thisparm)) { return $thisparm; } |
if (defined($thisparm)) { return [$thisparm,'map']; } |
|
|
# ----------------------------------------------------- fourth , check default |
# ----------------------------------------------------- fourth , check default |
|
|
my $default=&Apache::lonnet::metadata($fn,$rwhat.'.default'); |
my $meta_rwhat=$rwhat; |
if (defined($default)) { return $default} |
$meta_rwhat=~s/\./_/g; |
|
my $default=&Apache::lonnet::metadata($fn,$meta_rwhat); |
# --------------------------------------------------- fifth , cascade up parts |
if (defined($default)) { return [$default,'resource']} |
|
$default=&Apache::lonnet::metadata($fn,'parameter_'.$meta_rwhat); |
|
if (defined($default)) { return [$default,'resource']} |
|
# --------------------------------------------------- fifth, check more course |
|
if (defined($courseopt)) { |
|
if (defined($$courseopt{$courselevelm})) { return [$$courseopt{$courselevelm},'map']; } |
|
if (defined($$courseopt{$courselevel})) { |
|
my $ret = [$$courseopt{$courselevel},'course']; |
|
return $ret; |
|
} |
|
} |
|
# --------------------------------------------------- sixth , cascade up parts |
|
|
my ($space,@qualifier)=split(/\./,$rwhat); |
my ($space,@qualifier)=split(/\./,$rwhat); |
my $qualifier=join('.',@qualifier); |
my $qualifier=join('.',@qualifier); |
Line 2114 sub parmval_real {
|
Line 2904 sub parmval_real {
|
my $id=pop(@parts); |
my $id=pop(@parts); |
my $part=join('_',@parts); |
my $part=join('_',@parts); |
if ($part eq '') { $part='0'; } |
if ($part eq '') { $part='0'; } |
my $partgeneral=$self->parmval($part.".$qualifier",$symb,1); |
my @partgeneral=$self->parmval($part.".$qualifier",$symb,1); |
if (defined($partgeneral)) { return $partgeneral; } |
if (defined($partgeneral[0])) { return \@partgeneral; } |
|
} |
|
if ($recurse) { return []; } |
|
my $pack_def=&Apache::lonnet::packages_tab_default($fn,'resource.'.$rwhat); |
|
if (defined($pack_def)) { return [$pack_def,'resource']; } |
|
return ['']; |
|
} |
|
|
|
sub recurseup_maps { |
|
my ($self,$mapname) = @_; |
|
my @recurseup; |
|
if ($mapname) { |
|
my $res = $self->getResourceByUrl($mapname); |
|
if (ref($res)) { |
|
my @pcs = split(/,/,$res->map_hierarchy()); |
|
shift(@pcs); |
|
if (@pcs) { |
|
@recurseup = map { &Apache::lonnet::declutter($self->getByMapPc($_)->src()); } reverse(@pcs); |
|
} |
|
} |
|
} |
|
return @recurseup; |
|
} |
|
|
|
sub recursed_crumbs { |
|
my ($self,$mapurl,$restitle) = @_; |
|
my (@revmapinfo,@revmapres); |
|
my $mapres = $self->getResourceByUrl($mapurl); |
|
if (ref($mapres)) { |
|
@revmapres = map { $self->getByMapPc($_); } split(/,/,$mapres->map_breadcrumbs()); |
|
shift(@revmapres); |
|
} |
|
my $allowedlength = 60; |
|
my $minlength = 5; |
|
my $allowedtitle = 30; |
|
if (($env{'environment.icons'} eq 'iconsonly') && (!$env{'browser.mobile'})) { |
|
$allowedlength = 100; |
|
$allowedtitle = 70; |
|
} |
|
if (length($restitle) > $allowedtitle) { |
|
$restitle = &truncate_crumb_text($restitle,$allowedtitle); |
|
} |
|
my $totallength = length($restitle); |
|
my @links; |
|
|
|
foreach my $map (@revmapres) { |
|
my $pc = $map->map_pc(); |
|
next if ((!$pc) || ($pc == 1)); |
|
push(@links,$map); |
|
my $text = $map->title(); |
|
if ($text eq '') { |
|
$text = '...'; |
|
} |
|
push(@revmapinfo,{'href' => $env{'request.use_absolute'}.$map->link().'?navmap=1','text' => $text,'no_mt' => 1,}); |
|
$totallength += length($text); |
|
} |
|
my $numlinks = scalar(@links); |
|
if ($numlinks) { |
|
if ($totallength - $allowedlength > 0) { |
|
my $available = $allowedlength - length($restitle); |
|
my $avg = POSIX::ceil($available/$numlinks); |
|
if ($avg < $minlength) { |
|
$avg = $minlength; |
|
} |
|
@revmapinfo = (); |
|
foreach my $map (@links) { |
|
my $title = $map->title(); |
|
if ($title eq '') { |
|
$title = '...'; |
|
} |
|
my $showntitle = &truncate_crumb_text($title,$avg); |
|
if ($showntitle ne '') { |
|
push(@revmapinfo,{'href' => $env{'request.use_absolute'}.$map->link().'?navmap=1','text' => $showntitle,'no_mt' => 1,}); |
|
} |
|
} |
|
} |
|
} |
|
if ($restitle ne '') { |
|
push(@revmapinfo,{'text' => $restitle, 'no_mt' => 1}); |
|
} |
|
return @revmapinfo; |
|
} |
|
|
|
sub truncate_crumb_text { |
|
my ($title,$limit) = @_; |
|
my $showntitle = ''; |
|
if (length($title) > $limit) { |
|
my @words = split(/\b\s*/,$title); |
|
if (@words == 1) { |
|
$showntitle = substr($title,0,$limit).' ...'; |
|
} else { |
|
my $linklength = 0; |
|
my $num = 0; |
|
foreach my $word (@words) { |
|
$linklength += 1+length($word); |
|
if ($word eq '-') { |
|
$showntitle =~ s/ $//; |
|
$showntitle .= $word; |
|
} elsif ($linklength > $limit) { |
|
if ($num < @words) { |
|
$showntitle .= $word.' ...'; |
|
last; |
|
} else { |
|
$showntitle .= $word; |
|
} |
|
} else { |
|
$showntitle .= $word.' '; |
|
} |
|
} |
|
$showntitle =~ s/ $//; |
|
} |
|
return $showntitle; |
|
} else { |
|
return $title; |
|
} |
|
} |
|
|
|
# |
|
# Determines the open/close dates for printing a map that |
|
# encloses a resource. |
|
# |
|
sub map_printdates { |
|
my ($self, $res, $part) = @_; |
|
|
|
|
|
|
|
|
|
|
|
my $opendate = $self->get_mapparam($res->symb(),'',"$part.printstartdate"); |
|
my $closedate= $self->get_mapparam($res->symb(),'', "$part.printenddate"); |
|
|
|
|
|
return ($opendate, $closedate); |
|
} |
|
|
|
sub get_mapparam { |
|
my ($self, $symb, $mapname, $what) = @_; |
|
|
|
# Ensure the course option hash is populated: |
|
|
|
$self->generate_course_user_opt(); |
|
|
|
# Get the course id and section if there is one. |
|
|
|
my $cid=$env{'request.course.id'}; |
|
my $csec=$env{'request.course.sec'}; |
|
my $cgroup=''; |
|
my @cgrps=split(/:/,$env{'request.course.groups'}); |
|
if (@cgrps > 0) { |
|
@cgrps = sort(@cgrps); |
|
$cgroup = $cgrps[0]; |
|
} |
|
my $uname=$self->{USERNAME}; |
|
my $udom=$self->{DOMAIN}; |
|
|
|
unless ($symb || $mapname) { return; } |
|
my $result=''; |
|
my ($recursed,@recurseup); |
|
|
|
# Figure out which map we are in. |
|
|
|
if ($symb && !$mapname) { |
|
my ($id,$fn); |
|
($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb); |
|
$mapname = &Apache::lonnet::deversion($mapname); |
|
} |
|
|
|
my $rwhat=$what; |
|
$what=~s/^parameter\_//; |
|
$what=~s/\_/\./; |
|
|
|
# Build the hash keys for the lookup: |
|
|
|
my $symbparm=$symb.'.'.$what; |
|
my $mapparm=$mapname.'___(all).'.$what; |
|
my $usercourseprefix=$cid; |
|
|
|
|
|
my $grplevel = "$usercourseprefix.[$cgroup].$mapparm"; |
|
my $seclevel = "$usercourseprefix.[$csec].$mapparm"; |
|
my $courselevel = "$usercourseprefix.$mapparm"; |
|
|
|
|
|
# Get handy references to the hashes we need in $self: |
|
|
|
my $useropt = $self->{USER_OPT}; |
|
my $courseopt = $self->{COURSE_OPT}; |
|
my $parmhash = $self->{PARM_HASH}; |
|
|
|
# Check per user |
|
|
|
|
|
|
|
if ($uname and defined($useropt)) { |
|
if (defined($$useropt{$courselevel})) { |
|
return $$useropt{$courselevel}; |
|
} |
|
if ($what =~ /\.(encrypturl|hiddenresource)$/) { |
|
unless ($recursed) { |
|
@recurseup = $self->recurseup_maps($mapname); |
|
$recursed = 1; |
|
} |
|
foreach my $item (@recurseup) { |
|
my $norecursechk=$usercourseprefix.'.'.$item.'___(all).'.$what; |
|
if (defined($$useropt{$norecursechk})) { |
|
if ($what =~ /\.(encrypturl|hiddenresource)$/) { |
|
return $$useropt{$norecursechk}; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
# Check course -- group |
|
|
|
|
|
|
|
if ($cgroup ne '' and defined ($courseopt)) { |
|
if (defined($$courseopt{$grplevel})) { |
|
return $$courseopt{$grplevel}; |
|
} |
|
if ($what =~ /\.(encrypturl|hiddenresource)$/) { |
|
unless ($recursed) { |
|
@recurseup = $self->recurseup_maps($mapname); |
|
$recursed = 1; |
|
} |
|
foreach my $item (@recurseup) { |
|
my $norecursechk=$usercourseprefix.'.['.$cgroup.'].'.$item.'___(all).'.$what; |
|
if (defined($$courseopt{$norecursechk})) { |
|
if ($what =~ /\.(encrypturl|hiddenresource)$/) { |
|
return $$courseopt{$norecursechk}; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
# Check course -- section |
|
|
|
|
|
|
|
|
|
|
|
if ($csec and defined($courseopt)) { |
|
if (defined($$courseopt{$seclevel})) { |
|
return $$courseopt{$seclevel}; |
|
} |
|
if ($what =~ /\.(encrypturl|hiddenresource)$/) { |
|
unless ($recursed) { |
|
@recurseup = $self->recurseup_maps($mapname); |
|
$recursed = 1; |
|
} |
|
foreach my $item (@recurseup) { |
|
my $norecursechk=$usercourseprefix.'.['.$csec.'].'.$item.'___(all).'.$what; |
|
if (defined($$courseopt{$norecursechk})) { |
|
if ($what =~ /\.(encrypturl|hiddenresource)$/) { |
|
return $$courseopt{$norecursechk}; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
# Check the map parameters themselves: |
|
|
|
if ($symb) { |
|
my $symbparm=$symb.'.'.$what; |
|
my $thisparm = $$parmhash{$symbparm}; |
|
if (defined($thisparm)) { |
|
return $thisparm; |
|
} |
|
} |
|
|
|
|
|
# Additional course parameters: |
|
|
|
if (defined($courseopt)) { |
|
if (defined($$courseopt{$courselevel})) { |
|
return $$courseopt{$courselevel}; |
|
} |
|
if ($what =~ /\.(encrypturl|hiddenresource)$/) { |
|
unless ($recursed) { |
|
@recurseup = $self->recurseup_maps($mapname); |
|
$recursed = 1; |
|
} |
|
foreach my $item (@recurseup) { |
|
my $norecursechk=$usercourseprefix.'.'.$item.'___(all).'.$what; |
|
if (defined($$courseopt{$norecursechk})) { |
|
if ($what =~ /\.(encrypturl|hiddenresource)$/) { |
|
return $$courseopt{$norecursechk}; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
return undef; # Unefined if we got here. |
|
} |
|
|
|
sub course_printdates { |
|
my ($self, $symb, $part) = @_; |
|
|
|
|
|
my $opendate = $self->getcourseparam($symb, $part . '.printstartdate'); |
|
my $closedate = $self->getcourseparam($symb, $part . '.printenddate'); |
|
return ($opendate, $closedate); |
|
|
|
} |
|
|
|
sub getcourseparam { |
|
my ($self, $symb, $what) = @_; |
|
|
|
$self->generate_course_user_opt(); # If necessary populate the hashes. |
|
|
|
my $uname = $self->{USERNAME}; |
|
my $udom = $self->{DOMAIN}; |
|
|
|
# Course, section, group ids come from the env: |
|
|
|
my $cid = $env{'request.course.id'}; |
|
my $csec = $env{'request.course.sec'}; |
|
my $cgroup = ''; # Assume no group |
|
|
|
my @cgroups = split(/:/, $env{'request.course.groups'}); |
|
if(@cgroups > 0) { |
|
@cgroups = sort(@cgroups); |
|
$cgroup = $cgroups[0]; # There is a course group. |
|
} |
|
my ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb); |
|
$mapname = &Apache::lonnet::deversion($mapname); |
|
|
|
# |
|
# Make the various lookup keys: |
|
# |
|
|
|
$what=~s/^parameter\_//; |
|
$what=~s/\_/\./; |
|
|
|
|
|
my $symbparm = $symb . '.' . $what; |
|
my $mapparm=$mapname.'___(all).'.$what; |
|
|
|
# Local refs to the hashes we're going to look at: |
|
|
|
my $useropt = $self->{USER_OPT}; |
|
my $courseopt = $self->{COURSE_OPT}; |
|
|
|
# |
|
# We want the course level stuff from the way |
|
# parmval_real operates |
|
# TODO: Factor some of this stuff out of |
|
# both parmval_real and here |
|
# |
|
my $courselevel = $cid . '.' . $what; |
|
my $grplevel = $cid . '.[' . $cgroup . ']' . $what; |
|
my $seclevel = $cid . '.[' . $csec . ']' . $what; |
|
|
|
|
|
# Try for the user's course level option: |
|
|
|
if ($uname and defined($useropt)) { |
|
if (defined($$useropt{$courselevel})) { |
|
return $$useropt{$courselevel}; |
|
} |
|
} |
|
# Try for the group's course level option: |
|
|
|
if ($cgroup ne '' and defined($courseopt)) { |
|
if (defined($$courseopt{$grplevel})) { |
|
return $$courseopt{$grplevel}; |
|
} |
|
} |
|
|
|
# Try for section level parameters: |
|
|
|
if ($csec ne '' and defined($courseopt)) { |
|
if (defined($$courseopt{$seclevel})) { |
|
return $$courseopt{$seclevel}; |
|
} |
} |
} |
if ($recurse) { return undef; } |
# Try for 'additional' course parameters: |
my $pack_def=&Apache::lonnet::packages_tab_default($fn,'resource.'.$what); |
|
if (defined($pack_def)) { return $pack_def; } |
if (defined($courseopt)) { |
return ''; |
if (defined($$courseopt{$courselevel})) { |
|
return $$courseopt{$courselevel}; |
|
} |
|
} |
|
return undef; |
|
|
} |
} |
|
|
|
|
=pod |
=pod |
|
|
=item * B<getResourceByUrl>(url): |
=item * B<getResourceByUrl>(url,multiple): |
|
|
Retrieves a resource object by URL of the resource. If passed a |
Retrieves a resource object by URL of the resource, unless the optional |
resource object, it will simply return it, so it is safe to use this |
multiple parameter is included in which case an array of resource |
method in code like "$res = $navmap->getResourceByUrl($res)", if |
objects is returned. If passed a resource object, it will simply return |
you're not sure if $res is already an object, or just a URL. If the |
it, so it is safe to use this method in code like |
resource appears multiple times in the course, only the first instance |
"$res = $navmap->getResourceByUrl($res)" |
will be returned. As a result, this is probably useful only for maps. |
if you're not sure if $res is already an object, or just a URL. If the |
|
resource appears multiple times in the course, only the first instance |
|
will be returned (useful for maps), unless the multiple parameter has |
|
been included, in which case all instances are returned in an array. |
|
|
=item * B<retrieveResources>(map, filterFunc, recursive, bailout): |
=item * B<retrieveResources>(map, filterFunc, recursive, bailout, showall, noblockcheck): |
|
|
The map is a specification of a map to retreive the resources from, |
The map is a specification of a map to retreive the resources from, |
either as a url or as an object. The filterFunc is a reference to a |
either as a url or as an object. The filterFunc is a reference to a |
Line 2142 function that takes a resource object as
|
Line 3317 function that takes a resource object as
|
true if the resource should be included, or false if it should not |
true if the resource should be included, or false if it should not |
be. If recursive is true, the map will be recursively examined, |
be. If recursive is true, the map will be recursively examined, |
otherwise it will not be. If bailout is true, the function will return |
otherwise it will not be. If bailout is true, the function will return |
as soon as it finds a resource, if false it will finish. By default, |
as soon as it finds a resource, if false it will finish. If showall is |
the map is the top-level map of the course, filterFunc is a function |
true it will not hide maps that contain nothing but one other map. The |
that always returns 1, recursive is true, bailout is false. The |
noblockcheck arg is propagated to become the sixth arg in the call to |
resources will be returned in a list containing the resource objects |
lonnet::allowed when checking a resource's availability during collection |
for the corresponding resources, with B<no structure information> in |
of resources using the iterator. noblockcheck needs to be true if |
the list; regardless of branching, recursion, etc., it will be a flat |
retrieveResources() was called by a routine that itself was called by |
list. |
lonnet::allowed, in order to avoid recursion. By default the map |
|
is the top-level map of the course, filterFunc is a function that |
|
always returns 1, recursive is true, bailout is false, showall is |
|
false. The resources will be returned in a list containing the |
|
resource objects for the corresponding resources, with B<no structure |
|
information> in the list; regardless of branching, recursion, etc., |
|
it will be a flat list. |
|
|
Thus, this is suitable for cases where you don't want the structure, |
Thus, this is suitable for cases where you don't want the structure, |
just a list of all resources. It is also suitable for finding out how |
just a list of all resources. It is also suitable for finding out how |
Line 2157 want to know is if I<any> resources matc
|
Line 3338 want to know is if I<any> resources matc
|
parameter will allow you to avoid potentially expensive enumeration of |
parameter will allow you to avoid potentially expensive enumeration of |
all matching resources. |
all matching resources. |
|
|
=item * B<hasResources>(map, filterFunc, recursive): |
=item * B<hasResource>(map, filterFunc, recursive, showall): |
|
|
Convience method for |
Convenience method for |
|
|
scalar(retrieveResources($map, $filterFunc, $recursive, 1)) > 0 |
scalar(retrieveResources($map, $filterFunc, $recursive, 1, $showall)) > 0 |
|
|
which will tell whether the map has resources matching the description |
which will tell whether the map has resources matching the description |
in the filter function. |
in the filter function. |
|
|
|
=item * B<usedVersion>(url): |
|
|
|
Retrieves version infomation for a url. Returns the version (a number, or |
|
the string "mostrecent") for resources which have version information in |
|
the big hash. |
|
|
=cut |
=cut |
|
|
|
|
sub getResourceByUrl { |
sub getResourceByUrl { |
my $self = shift; |
my $self = shift; |
my $resUrl = shift; |
my $resUrl = shift; |
|
my $multiple = shift; |
|
|
if (ref($resUrl)) { return $resUrl; } |
if (ref($resUrl)) { return $resUrl; } |
|
|
$resUrl = &Apache::lonnet::clutter($resUrl); |
$resUrl = &Apache::lonnet::clutter($resUrl); |
my $resId = $self->{NAV_HASH}->{'ids_' . $resUrl}; |
my $resId = $self->{NAV_HASH}->{'ids_' . $resUrl}; |
if ($resId =~ /,/) { |
|
$resId = (split (/,/, $resId))[0]; |
|
} |
|
if (!$resId) { return ''; } |
if (!$resId) { return ''; } |
return $self->getById($resId); |
if ($multiple) { |
|
my @resources = (); |
|
my @resIds = split (/,/, $resId); |
|
foreach my $id (@resIds) { |
|
my $resourceId = $self->getById($id); |
|
if ($resourceId) { |
|
push(@resources,$resourceId); |
|
} |
|
} |
|
return @resources; |
|
} else { |
|
if ($resId =~ /,/) { |
|
$resId = (split (/,/, $resId))[0]; |
|
} |
|
return $self->getById($resId); |
|
} |
} |
} |
|
|
sub retrieveResources { |
sub retrieveResources { |
Line 2194 sub retrieveResources {
|
Line 3395 sub retrieveResources {
|
if (!defined($recursive)) { $recursive = 1; } |
if (!defined($recursive)) { $recursive = 1; } |
my $bailout = shift; |
my $bailout = shift; |
if (!defined($bailout)) { $bailout = 0; } |
if (!defined($bailout)) { $bailout = 0; } |
|
my $showall = shift; |
|
my $noblockcheck = shift; |
# Create the necessary iterator. |
# Create the necessary iterator. |
if (!ref($map)) { # assume it's a url of a map. |
if (!ref($map)) { # assume it's a url of a map. |
$map = $self->getResourceByUrl($map); |
$map = $self->getResourceByUrl($map); |
Line 2213 sub retrieveResources {
|
Line 3415 sub retrieveResources {
|
|
|
# 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(), |
undef, $recursive); |
undef, $recursive, $showall); |
|
|
my @resources = (); |
my @resources = (); |
|
|
|
if (&$filterFunc($map)) { |
|
push(@resources, $map); |
|
} |
|
|
# Run down the iterator and collect the resources. |
# Run down the iterator and collect the resources. |
my $curRes; |
my $curRes; |
|
|
while ($curRes = $it->next()) { |
while ($curRes = $it->next(undef,$noblockcheck)) { |
if (ref($curRes)) { |
if (ref($curRes)) { |
if (!&$filterFunc($curRes)) { |
if (!&$filterFunc($curRes)) { |
next; |
next; |
} |
} |
|
|
push @resources, $curRes; |
push(@resources, $curRes); |
|
|
if ($bailout) { |
if ($bailout) { |
return @resources; |
return @resources; |
Line 2243 sub hasResource {
|
Line 3449 sub hasResource {
|
my $map = shift; |
my $map = shift; |
my $filterFunc = shift; |
my $filterFunc = shift; |
my $recursive = shift; |
my $recursive = shift; |
|
my $showall = shift; |
|
|
return scalar($self->retrieveResources($map, $filterFunc, $recursive, 1)) > 0; |
return scalar($self->retrieveResources($map, $filterFunc, $recursive, 1, $showall)) > 0; |
|
} |
|
|
|
sub usedVersion { |
|
my $self = shift; |
|
my $linkurl = shift; |
|
return $self->navhash("version_$linkurl"); |
} |
} |
|
|
1; |
1; |
|
|
package Apache::lonnavmaps::iterator; |
package Apache::lonnavmaps::iterator; |
|
use Scalar::Util qw(weaken); |
|
use Apache::lonnet; |
|
|
=pod |
=pod |
|
|
Line 2275 getIterator behaves as follows:
|
Line 3490 getIterator behaves as follows:
|
|
|
=over 4 |
=over 4 |
|
|
=item * B<getIterator>(firstResource, finishResource, filterHash, condition, forceTop, returnTopMap): |
=item * B<getIterator>(firstResource, finishResource, filterHash, condition, forceTop, returnTopMap, $deeplinklisted): |
|
|
All parameters are optional. firstResource is a resource reference |
All parameters are optional. firstResource is a resource reference |
corresponding to where the iterator should start. It defaults to |
corresponding to where the iterator should start. It defaults to |
Line 2292 that is not just a single, 'redirecting'
|
Line 3507 that is not just a single, 'redirecting'
|
will return all information, starting with the top-level map, |
will return all information, starting with the top-level map, |
regardless of content. returnTopMap, if true (default false), will |
regardless of content. returnTopMap, if true (default false), will |
cause the iterator to return the top-level map object (resource 0.0) |
cause the iterator to return the top-level map object (resource 0.0) |
before anything else. |
before anything else. deeplinklisted if true (default false), will |
|
check "listed" status of a resource with a deeplink, and unless "absent" |
|
will exclude deeplink checking when retrieving the browsePriv from |
|
lonnet::allowed(). |
|
|
Thus, by default, only top-level resources will be shown. Change the |
Thus, by default, only top-level resources will be shown. Change the |
condition to a 1 without changing the hash, and all resources will be |
condition to a 1 without changing the hash, and all resources will be |
Line 2362 Note that inside of the loop, it's frequ
|
Line 3580 Note that inside of the loop, it's frequ
|
resource objects will be references, and any non-references will |
resource objects will be references, and any non-references will |
be the tokens described above. |
be the tokens described above. |
|
|
Also note there is some old code floating around that trys to track |
The next() routine can take two (optional) arguments: |
|
closeAllPages - if true will not recurse down a .page |
|
noblockcheck - passed to browsePriv() for passing as sixth arg to |
|
call to lonnet::allowed. This needs to be set if retrieveResources |
|
was already called from another routine called within lonnet::allowed, |
|
so as to prevent recursion. |
|
|
|
Also note there is some old code floating around that tries to track |
the depth of the iterator to see when it's done; do not copy that |
the depth of the iterator to see when it's done; do not copy that |
code. It is difficult to get right and harder to understand then |
code. It is difficult to get right and harder to understand than |
this. They should be migrated to this new style. |
this. They should be migrated to this new style. |
|
|
=cut |
=cut |
Line 2390 sub new {
|
Line 3615 sub new {
|
my $class = ref($proto) || $proto; |
my $class = ref($proto) || $proto; |
my $self = {}; |
my $self = {}; |
|
|
$self->{NAV_MAP} = shift; |
weaken($self->{NAV_MAP} = shift); |
return undef unless ($self->{NAV_MAP}); |
return undef unless ($self->{NAV_MAP}); |
|
|
|
$self->{USERNAME} = $self->{NAV_MAP}->{USERNAME}; |
|
$self->{DOMAIN} = $self->{NAV_MAP}->{DOMAIN}; |
|
|
# Handle the parameters |
# Handle the parameters |
$self->{FIRST_RESOURCE} = shift || $self->{NAV_MAP}->firstResource(); |
$self->{FIRST_RESOURCE} = shift || $self->{NAV_MAP}->firstResource(); |
$self->{FINISH_RESOURCE} = shift || $self->{NAV_MAP}->finishResource(); |
$self->{FINISH_RESOURCE} = shift || $self->{NAV_MAP}->finishResource(); |
Line 2419 sub new {
|
Line 3647 sub new {
|
# have we done that yet? |
# have we done that yet? |
$self->{HAVE_RETURNED_0} = 0; |
$self->{HAVE_RETURNED_0} = 0; |
|
|
|
# Do we want to check the "listed" status for a resource for which |
|
# deeplinking applies. |
|
$self->{DEEPLINKLISTED} = shift; |
|
|
# Now, we need to pre-process the map, by walking forward and backward |
# Now, we need to pre-process the map, by walking forward and backward |
# over the parts of the map we're going to look at. |
# over the parts of the map we're going to look at. |
|
|
Line 2503 sub new {
|
Line 3735 sub new {
|
} |
} |
|
|
# Check: Was this only one resource, a map? |
# Check: Was this only one resource, a map? |
if ($resourceCount == 1 && $resource->is_map() && !$self->{FORCE_TOP}) { |
if ($resourceCount == 1 && $resource->is_sequence() && !$self->{FORCE_TOP}) { |
my $firstResource = $resource->map_start(); |
my $firstResource = $resource->map_start(); |
my $finishResource = $resource->map_finish(); |
my $finishResource = $resource->map_finish(); |
return |
return Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource, |
Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource, |
$finishResource, $self->{FILTER}, |
$finishResource, $self->{FILTER}, |
$self->{ALREADY_SEEN}, |
$self->{ALREADY_SEEN}, |
$self->{CONDITION}, |
$self->{CONDITION}, 0); |
$self->{FORCE_TOP}, |
|
undef,$self->{DEEPLINKLISTED}); |
} |
} |
|
|
# Set up some bookkeeping information. |
# Set up some bookkeeping information. |
Line 2530 sub new {
|
Line 3762 sub new {
|
$self->{ALREADY_SEEN}->{$self->{FIRST_RESOURCE}->{ID}} = 1; |
$self->{ALREADY_SEEN}->{$self->{FIRST_RESOURCE}->{ID}} = 1; |
|
|
bless ($self); |
bless ($self); |
|
|
return $self; |
return $self; |
} |
} |
|
|
sub next { |
sub next { |
my $self = shift; |
my $self = shift; |
|
my $closeAllPages=shift; |
|
my $noblockcheck = shift; |
if ($self->{FINISHED}) { |
if ($self->{FINISHED}) { |
return END_ITERATOR(); |
return END_ITERATOR(); |
} |
} |
Line 2545 sub next {
|
Line 3777 sub next {
|
# do so. |
# do so. |
if ($self->{RETURN_0} && !$self->{HAVE_RETURNED_0}) { |
if ($self->{RETURN_0} && !$self->{HAVE_RETURNED_0}) { |
$self->{HAVE_RETURNED_0} = 1; |
$self->{HAVE_RETURNED_0} = 1; |
|
my $nextTopLevel = $self->{NAV_MAP}->getById('0.0'); |
return $self->{NAV_MAP}->getById('0.0'); |
return $self->{NAV_MAP}->getById('0.0'); |
} |
} |
|
if ($self->{RETURN_0} && !$self->{HAVE_RETURNED_0_BEGIN_MAP}) { |
|
$self->{HAVE_RETURNED_0_BEGIN_MAP} = 1; |
|
return $self->BEGIN_MAP(); |
|
} |
|
|
if ($self->{RECURSIVE_ITERATOR_FLAG}) { |
if ($self->{RECURSIVE_ITERATOR_FLAG}) { |
# grab the next from the recursive iterator |
# grab the next from the recursive iterator |
my $next = $self->{RECURSIVE_ITERATOR}->next(); |
my $next = $self->{RECURSIVE_ITERATOR}->next($closeAllPages); |
|
|
# is it a begin or end map? If so, update the depth |
# is it a begin or end map? If so, update the depth |
if ($next == BEGIN_MAP() ) { $self->{RECURSIVE_DEPTH}++; } |
if ($next == BEGIN_MAP() ) { $self->{RECURSIVE_DEPTH}++; } |
Line 2560 sub next {
|
Line 3797 sub next {
|
if ($self->{RECURSIVE_DEPTH} == 0) { |
if ($self->{RECURSIVE_DEPTH} == 0) { |
$self->{RECURSIVE_ITERATOR_FLAG} = 0; |
$self->{RECURSIVE_ITERATOR_FLAG} = 0; |
} |
} |
|
|
return $next; |
return $next; |
} |
} |
|
|
Line 2636 sub next {
|
Line 3872 sub next {
|
# So we need to look at all the resources we can get to from here, |
# So we need to look at all the resources we can get to from here, |
# categorize them if we haven't seen them, remember if we have a new |
# categorize them if we haven't seen them, remember if we have a new |
my $nextUnfiltered = $here->getNext(); |
my $nextUnfiltered = $here->getNext(); |
|
|
|
|
my $maxDepthAdded = -1; |
my $maxDepthAdded = -1; |
|
|
for (@$nextUnfiltered) { |
for (@$nextUnfiltered) { |
Line 2648 sub next {
|
Line 3886 sub next {
|
} |
} |
|
|
# Is this the end of a branch? If so, all of the resources examined above |
# Is this the end of a branch? If so, all of the resources examined above |
# led to lower levels then the one we are currently at, so we push a END_BRANCH |
# led to lower levels than the one we are currently at, so we push a END_BRANCH |
# marker onto the stack so we don't forget. |
# marker onto the stack so we don't forget. |
# Example: For the usual A(BC)(DE)F case, when the iterator goes down the |
# Example: For the usual A(BC)(DE)F case, when the iterator goes down the |
# BC branch and gets to C, it will see F as the only next resource, but it's |
# BC branch and gets to C, it will see F as the only next resource, but it's |
Line 2664 sub next {
|
Line 3902 sub next {
|
|
|
# That ends the main iterator logic. Now, do we want to recurse |
# That ends the main iterator logic. Now, do we want to recurse |
# down this map (if this resource is a map)? |
# down this map (if this resource is a map)? |
if ($self->{HERE}->is_map() && |
if ( ($self->{HERE}->is_sequence() || (!$closeAllPages && $self->{HERE}->is_page())) && |
(defined($self->{FILTER}->{$self->{HERE}->map_pc()}) xor $self->{CONDITION})) { |
(defined($self->{FILTER}->{$self->{HERE}->map_pc()}) xor $self->{CONDITION}) && |
|
($env{'request.role.adv'} || !$self->{HERE}->randomout())) { |
$self->{RECURSIVE_ITERATOR_FLAG} = 1; |
$self->{RECURSIVE_ITERATOR_FLAG} = 1; |
my $firstResource = $self->{HERE}->map_start(); |
my $firstResource = $self->{HERE}->map_start(); |
my $finishResource = $self->{HERE}->map_finish(); |
my $finishResource = $self->{HERE}->map_finish(); |
|
|
$self->{RECURSIVE_ITERATOR} = |
$self->{RECURSIVE_ITERATOR} = |
Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource, |
Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource, |
$finishResource, $self->{FILTER}, |
$finishResource, $self->{FILTER}, |
$self->{ALREADY_SEEN}, $self->{CONDITION}); |
$self->{ALREADY_SEEN}, |
|
$self->{CONDITION}, |
|
$self->{FORCE_TOP}, |
|
undef,$self->{DEEPLINKLISTED}); |
} |
} |
|
|
# If this is a blank resource, don't actually return it. |
# If this is a blank resource, don't actually return it. |
# Should you ever find you need it, make sure to add an option to the code |
# Should you ever find you need it, make sure to add an option to the code |
# that you can use; other things depend on this behavior. |
# that you can use; other things depend on this behavior. |
my $browsePriv = $self->{HERE}->browsePriv(); |
my $browsePriv = $self->{HERE}->browsePriv($noblockcheck,$self->{DEEPLINKLISTED}); |
if (!$self->{HERE}->src() || |
if (!$self->{HERE}->src() || |
(!($browsePriv eq 'F') && !($browsePriv eq '2')) ) { |
(!($browsePriv eq 'F') && !($browsePriv eq '2')) ) { |
return $self->next(); |
return $self->next($closeAllPages); |
} |
} |
|
|
return $self->{HERE}; |
return $self->{HERE}; |
Line 2726 sub populateStack {
|
Line 3967 sub populateStack {
|
1; |
1; |
|
|
package Apache::lonnavmaps::DFSiterator; |
package Apache::lonnavmaps::DFSiterator; |
|
use Scalar::Util qw(weaken); |
|
use Apache::lonnet; |
|
|
# Not documented in the perldoc: This is a simple iterator that just walks |
# Not documented in the perldoc: This is a simple iterator that just walks |
# through the nav map and presents the resources in a depth-first search |
# through the nav map and presents the resources in a depth-first search |
Line 2734 package Apache::lonnavmaps::DFSiterator;
|
Line 3977 package Apache::lonnavmaps::DFSiterator;
|
# useful for pre-processing of some kind, and is in fact used by the main |
# useful for pre-processing of some kind, and is in fact used by the main |
# iterator that way, but that's about it. |
# iterator that way, but that's about it. |
# One could imagine merging this into the init routine of the main iterator, |
# One could imagine merging this into the init routine of the main iterator, |
# but this might as well be left seperate, since it is possible some other |
# but this might as well be left separate, since it is possible some other |
# use might be found for it. - Jeremy |
# use might be found for it. - Jeremy |
|
|
# Unlike the main iterator, this DOES return all resources, even blank ones. |
# Unlike the main iterator, this DOES return all resources, even blank ones. |
Line 2754 sub new {
|
Line 3997 sub new {
|
my $class = ref($proto) || $proto; |
my $class = ref($proto) || $proto; |
my $self = {}; |
my $self = {}; |
|
|
$self->{NAV_MAP} = shift; |
weaken($self->{NAV_MAP} = shift); |
return undef unless ($self->{NAV_MAP}); |
return undef unless ($self->{NAV_MAP}); |
|
|
|
$self->{USERNAME} = $self->{NAV_MAP}->{USERNAME}; |
|
$self->{DOMAIN} = $self->{NAV_MAP}->{DOMAIN}; |
|
|
$self->{FIRST_RESOURCE} = shift || $self->{NAV_MAP}->firstResource(); |
$self->{FIRST_RESOURCE} = shift || $self->{NAV_MAP}->firstResource(); |
$self->{FINISH_RESOURCE} = shift || $self->{NAV_MAP}->finishResource(); |
$self->{FINISH_RESOURCE} = shift || $self->{NAV_MAP}->finishResource(); |
|
|
Line 2853 sub next {
|
Line 4099 sub next {
|
|
|
# filter the next possibilities to remove things we've |
# filter the next possibilities to remove things we've |
# already seen. |
# already seen. |
foreach (@$nextUnfiltered) { |
foreach my $item (@$nextUnfiltered) { |
if (!defined($self->{ALREADY_SEEN}->{$_->{ID}})) { |
if (!defined($self->{ALREADY_SEEN}->{$item->{ID}})) { |
push @$next, $_; |
push @$next, $item; |
} |
} |
} |
} |
|
|
Line 2908 sub populateStack {
|
Line 4154 sub populateStack {
|
1; |
1; |
|
|
package Apache::lonnavmaps::resource; |
package Apache::lonnavmaps::resource; |
|
use Scalar::Util qw(weaken); |
use Apache::lonnet; |
use Apache::lonnet; |
|
|
=pod |
=pod |
Line 2980 X<symb> X<resource, symb>
|
Line 4226 X<symb> X<resource, symb>
|
All resources also have B<symb>s, which uniquely identify a resource |
All resources also have B<symb>s, which uniquely identify a resource |
in a course. Many internal LON-CAPA functions expect a symb. A symb |
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 |
carries along with it the URL of the resource, and the map it appears |
in. Symbs are much larger then resource IDs. |
in. Symbs are much larger than resource IDs. |
|
|
=cut |
=cut |
|
|
Line 2990 sub new {
|
Line 4236 sub new {
|
my $class = ref($proto) || $proto; |
my $class = ref($proto) || $proto; |
my $self = {}; |
my $self = {}; |
|
|
$self->{NAV_MAP} = shift; |
weaken($self->{NAV_MAP} = shift); |
$self->{ID} = shift; |
$self->{ID} = shift; |
|
|
|
$self->{USERNAME} = $self->{NAV_MAP}->{USERNAME}; |
|
$self->{DOMAIN} = $self->{NAV_MAP}->{DOMAIN}; |
|
|
# Store this new resource in the parent nav map's cache. |
# Store this new resource in the parent nav map's cache. |
$self->{NAV_MAP}->{RESOURCE_CACHE}->{$self->{ID}} = $self; |
$self->{NAV_MAP}->{RESOURCE_CACHE}->{$self->{ID}} = $self; |
$self->{RESOURCE_ERROR} = 0; |
$self->{RESOURCE_ERROR} = 0; |
|
|
|
$self->{DUEDATE_CACHE} = undef; |
|
|
# A hash that can be used by two-pass algorithms to store data |
# A hash that can be used by two-pass algorithms to store data |
# about this resource in. Not used by the resource object |
# about this resource in. Not used by the resource object |
# directly. |
# directly. |
$self->{DATA} = {}; |
$self->{DATA} = {}; |
|
|
bless($self); |
bless($self); |
|
|
|
# This is a speed optimization, to avoid calling symb() too often. |
|
$self->{SYMB} = $self->symb(); |
|
|
return $self; |
return $self; |
} |
} |
|
|
Line 3014 sub navHash {
|
Line 4268 sub navHash {
|
my $self = shift; |
my $self = shift; |
my $param = shift; |
my $param = shift; |
my $id = shift; |
my $id = shift; |
return $self->{NAV_MAP}->navhash($param . ($id?$self->{ID}:"")); |
my $arg = $param . ($id?$self->{ID}:""); |
|
if (ref($self) && ref($self->{NAV_MAP}) && defined($arg)) { |
|
return $self->{NAV_MAP}->navhash($arg); |
|
} |
|
return; |
} |
} |
|
|
=pod |
=pod |
Line 3056 false.
|
Line 4314 false.
|
|
|
=item * B<randompick>: |
=item * B<randompick>: |
|
|
Returns true for a map if the randompick feature is being used on the |
Returns the number of randomly picked items for a map if the randompick |
map. (?) |
feature is being used on the map. |
|
|
|
=item * B<randomorder>: |
|
|
|
Returns true for a map if the randomorder feature is being used on the |
|
map. |
|
|
=item * B<src>: |
=item * B<src>: |
|
|
Line 3078 Returns the title of the resource.
|
Line 4341 Returns the title of the resource.
|
# These info functions can be used directly, as they don't return |
# These info functions can be used directly, as they don't return |
# resource information. |
# resource information. |
sub comesfrom { my $self=shift; return $self->navHash("comesfrom_", 1); } |
sub comesfrom { my $self=shift; return $self->navHash("comesfrom_", 1); } |
|
sub encrypted { my $self=shift; return $self->navHash("encrypted_", 1); } |
sub ext { my $self=shift; return $self->navHash("ext_", 1) eq 'true:'; } |
sub ext { my $self=shift; return $self->navHash("ext_", 1) eq 'true:'; } |
sub from { my $self=shift; return $self->navHash("from_", 1); } |
sub from { my $self=shift; return $self->navHash("from_", 1); } |
# considered private and undocumented |
# considered private and undocumented |
sub goesto { my $self=shift; return $self->navHash("goesto_", 1); } |
sub goesto { my $self=shift; return $self->navHash("goesto_", 1); } |
sub kind { my $self=shift; return $self->navHash("kind_", 1); } |
sub kind { my $self=shift; return $self->navHash("kind_", 1); } |
sub randomout { my $self=shift; return $self->navHash("randomout_", 1); } |
sub randomout { my $self=shift; return $self->navHash("randomout_", 1); } |
|
sub deeplinkout { my $self=shift; return $self->navHash("deeplinkout_", 1); } |
sub randompick { |
sub randompick { |
my $self = shift; |
my $self = shift; |
return $self->{NAV_MAP}->{PARM_HASH}->{$self->symb . |
my $randompick = $self->parmval('randompick'); |
'.0.parameter_randompick'}; |
return $randompick; |
|
} |
|
sub randomorder { |
|
my $self = shift; |
|
my $randomorder = $self->parmval('randomorder'); |
|
return ($randomorder =~ /^yes$/i); |
|
} |
|
sub link { |
|
my $self=shift; |
|
if ($self->encrypted()) { return &Apache::lonenc::encrypted($self->src); } |
|
return $self->src; |
} |
} |
sub src { |
sub src { |
my $self=shift; |
my $self=shift; |
return $self->navHash("src_", 1); |
return $self->navHash("src_", 1); |
} |
} |
|
sub shown_symb { |
|
my $self=shift; |
|
if ($self->encrypted()) {return &Apache::lonenc::encrypted($self->{SYMB});} |
|
return $self->{SYMB}; |
|
} |
|
sub id { |
|
my $self=shift; |
|
return $self->{ID}; |
|
} |
|
sub enclosing_map_src { |
|
my $self=shift; |
|
(my $first, my $second) = $self->{ID} =~ /(\d+).(\d+)/; |
|
return $self->navHash('map_id_'.$first); |
|
} |
sub symb { |
sub symb { |
my $self=shift; |
my $self=shift; |
|
if (defined($self->{SYMB})) { return $self->{SYMB}; } |
(my $first, my $second) = $self->{ID} =~ /(\d+).(\d+)/; |
(my $first, my $second) = $self->{ID} =~ /(\d+).(\d+)/; |
my $symbSrc = &Apache::lonnet::declutter($self->src()); |
my $symbSrc = &Apache::lonnet::declutter($self->src()); |
my $symb = &Apache::lonnet::declutter($self->navHash('map_id_'.$first)) |
my $symb = &Apache::lonnet::declutter($self->navHash('map_id_'.$first)) |
. '___' . $second . '___' . $symbSrc; |
. '___' . $second . '___' . $symbSrc; |
return &Apache::lonnet::symbclean($symb); |
return &Apache::lonnet::symbclean($symb); |
} |
} |
|
sub wrap_symb { |
|
my $self = shift; |
|
return $self->{NAV_MAP}->wrap_symb($self->{SYMB}); |
|
} |
sub title { |
sub title { |
my $self=shift; |
my $self=shift; |
if ($self->{ID} eq '0.0') { |
if ($self->{ID} eq '0.0') { |
# If this is the top-level map, return the title of the course |
# If this is the top-level map, return the title of the course |
# since this map can not be titled otherwise. |
# since this map can not be titled otherwise. |
return $ENV{'course.'.$ENV{'request.course.id'}.'.description'}; |
return $env{'course.'.$env{'request.course.id'}.'.description'}; |
} |
} |
return $self->navHash("title_", 1); } |
return $self->navHash("title_", 1); } |
# considered private and undocumented |
# considered private and undocumented |
sub to { my $self=shift; return $self->navHash("to_", 1); } |
sub to { my $self=shift; return $self->navHash("to_", 1); } |
|
sub condition { |
|
my $self=shift; |
|
my $undercond=$self->navHash("undercond_", 1); |
|
if (!defined($undercond)) { return 1; }; |
|
my $condid=$self->navHash("condid_$undercond"); |
|
if (!defined($condid)) { return 1; }; |
|
my $condition=&Apache::lonnet::directcondval($condid); |
|
return $condition; |
|
} |
|
sub condval { |
|
my $self=shift; |
|
my ($pathname,$filename) = |
|
&Apache::lonnet::split_uri_for_cond($self->src()); |
|
|
|
my $match=($env{'acc.res.'.$env{'request.course.id'}.'.'.$pathname}=~ |
|
/\&\Q$filename\E\:([\d\|]+)\&/); |
|
if ($match) { |
|
return &Apache::lonnet::condval($1); |
|
} |
|
return 0; |
|
} |
sub compTitle { |
sub compTitle { |
my $self = shift; |
my $self = shift; |
my $title = $self->title(); |
my $title = $self->title(); |
Line 3121 sub compTitle {
|
Line 4436 sub compTitle {
|
} |
} |
return $title; |
return $title; |
} |
} |
|
|
=pod |
=pod |
|
|
B<Predicate Testing the Resource> |
B<Predicate Testing the Resource> |
Line 3151 Returns true if the resource is a sequen
|
Line 4467 Returns true if the resource is a sequen
|
|
|
=cut |
=cut |
|
|
|
sub hasResource { |
|
my $self = shift; |
|
return $self->{NAV_MAP}->hasResource(@_); |
|
} |
|
|
|
sub retrieveResources { |
|
my $self = shift; |
|
return $self->{NAV_MAP}->retrieveResources(@_); |
|
} |
|
|
|
sub is_exam { |
|
my ($self,$part) = @_; |
|
my $type = $self->parmval('type',$part); |
|
if ($type eq 'exam') { |
|
return 1; |
|
} |
|
if ($self->src() =~ /\.(exam)$/) { |
|
return 1; |
|
} |
|
return 0; |
|
} |
sub is_html { |
sub is_html { |
my $self=shift; |
my $self=shift; |
my $src = $self->src(); |
my $src = $self->src(); |
Line 3164 sub is_page {
|
Line 4500 sub is_page {
|
return $self->navHash("is_map_", 1) && |
return $self->navHash("is_map_", 1) && |
$self->navHash("map_type_" . $self->map_pc()) eq 'page'; |
$self->navHash("map_type_" . $self->map_pc()) eq 'page'; |
} |
} |
|
sub is_practice { |
|
my $self=shift; |
|
my ($part) = @_; |
|
my $type = $self->parmval('type',$part); |
|
if ($type eq 'practice') { |
|
return 1; |
|
} |
|
return 0; |
|
} |
sub is_problem { |
sub is_problem { |
my $self=shift; |
my $self=shift; |
my $src = $self->src(); |
my $src = $self->src(); |
return ($src =~ /\.(problem|exam|quiz|assess|survey|form|library)$/); |
if ($src =~ /$LONCAPA::assess_re/) { |
|
return !($self->is_practice()); |
|
} |
|
return 0; |
} |
} |
sub is_sequence { |
# |
|
# The has below is the set of status that are considered 'incomplete' |
|
# |
|
my %incomplete_hash = |
|
( |
|
TRIES_LEFT() => 1, |
|
OPEN() => 1, |
|
ATTEMPTED() => 1 |
|
|
|
); |
|
# |
|
# Return tru if a problem is incomplete... for now incomplete means that |
|
# any part of the problem is incomplete. |
|
# Note that if the resources is not a problem, 0 is returned. |
|
# |
|
sub is_incomplete { |
|
my $self = shift; |
|
if ($self->is_problem()) { |
|
foreach my $part (@{$self->parts()}) { |
|
if (exists($incomplete_hash{$self->status($part)})) { |
|
return 1; |
|
} |
|
} |
|
} |
|
return 0; |
|
|
|
} |
|
sub is_raw_problem { |
my $self=shift; |
my $self=shift; |
my $src = $self->src(); |
my $src = $self->src(); |
|
if ($src =~ /$LONCAPA::assess_re/) { |
|
return 1; |
|
} |
|
return 0; |
|
} |
|
|
|
sub contains_problem { |
|
my $self=shift; |
|
if ($self->is_page()) { |
|
my $hasProblem=$self->hasResource($self,sub { $_[0]->is_problem() },1); |
|
return $hasProblem; |
|
} |
|
return 0; |
|
} |
|
sub map_contains_problem { |
|
my $self=shift; |
|
if ($self->is_map()) { |
|
my $has_problem= |
|
$self->hasResource($self,sub { $_[0]->is_problem() },1); |
|
return $has_problem; |
|
} |
|
return 0; |
|
} |
|
sub is_sequence { |
|
my $self=shift; |
return $self->navHash("is_map_", 1) && |
return $self->navHash("is_map_", 1) && |
$self->navHash("map_type_" . $self->map_pc()) eq 'sequence'; |
$self->navHash("map_type_" . $self->map_pc()) eq 'sequence'; |
|
} |
|
sub is_missing_map { |
|
my $self=shift; |
|
return $self->navHash("is_map_", 1) && |
|
$self->navHash("map_type_" . $self->map_pc()) eq 'none'; |
|
} |
|
sub is_survey { |
|
my $self = shift(); |
|
my $part = shift(); |
|
my $type = $self->parmval('type',$part); |
|
if (($type eq 'survey') || ($type eq 'surveycred')) { |
|
return 1; |
|
} |
|
if ($self->src() =~ /\.(survey)$/) { |
|
return 1; |
|
} |
|
return 0; |
|
} |
|
sub is_anonsurvey { |
|
my $self = shift(); |
|
my $part = shift(); |
|
my $type = $self->parmval('type',$part); |
|
if (($type eq 'anonsurvey') || ($type eq 'anonsurveycred')) { |
|
return 1; |
|
} |
|
return 0; |
|
} |
|
sub is_task { |
|
my $self=shift; |
|
my $src = $self->src(); |
|
return ($src =~ /\.(task)$/) |
|
} |
|
|
|
sub is_empty_sequence { |
|
my $self=shift; |
|
return !$self->is_page() && $self->navHash("is_map_", 1) && !$self->navHash("map_type_" . $self->map_pc()); |
} |
} |
|
|
# Private method: Shells out to the parmval in the nav map, handler parts. |
# Private method: Shells out to the parmval in the nav map, handler parts. |
Line 3184 sub parmval {
|
Line 4620 sub parmval {
|
if (!defined($part)) { |
if (!defined($part)) { |
$part = '0'; |
$part = '0'; |
} |
} |
return $self->{NAV_MAP}->parmval($part.'.'.$what, $self->symb()); |
return $self->{NAV_MAP}->parmval($part.'.'.$what, $self->{SYMB}); |
} |
} |
|
|
=pod |
=pod |
Line 3217 resource of the map.
|
Line 4653 resource of the map.
|
|
|
Returns a string with the type of the map in it. |
Returns a string with the type of the map in it. |
|
|
|
=item * B<map_hierarchy>: |
|
|
|
Returns a string with a comma-separated ordered list of map_pc IDs |
|
for the hierarchy of maps containing a map, with the top level |
|
map first, then descending to deeper levels, with the enclosing map last. |
|
|
|
=item * B<map_breadcrumbs>: |
|
|
|
Same as map_hierarchy, except maps containing only a single itemm if |
|
it's a map, or containing no items are omitted, unless it's the top |
|
level map (map_pc = 1), which is always included. |
|
|
=back |
=back |
|
|
=cut |
=cut |
Line 3224 Returns a string with the type of the ma
|
Line 4672 Returns a string with the type of the ma
|
sub map_finish { |
sub map_finish { |
my $self = shift; |
my $self = shift; |
my $src = $self->src(); |
my $src = $self->src(); |
$src = Apache::lonnet::clutter($src); |
$src = &Apache::lonnet::clutter($src); |
my $res = $self->navHash("map_finish_$src", 0); |
my $res = $self->navHash("map_finish_$src", 0); |
$res = $self->{NAV_MAP}->getById($res); |
$res = $self->{NAV_MAP}->getById($res); |
return $res; |
return $res; |
Line 3237 sub map_pc {
|
Line 4685 sub map_pc {
|
sub map_start { |
sub map_start { |
my $self = shift; |
my $self = shift; |
my $src = $self->src(); |
my $src = $self->src(); |
$src = Apache::lonnet::clutter($src); |
$src = &Apache::lonnet::clutter($src); |
my $res = $self->navHash("map_start_$src", 0); |
my $res = $self->navHash("map_start_$src", 0); |
$res = $self->{NAV_MAP}->getById($res); |
$res = $self->{NAV_MAP}->getById($res); |
return $res; |
return $res; |
Line 3247 sub map_type {
|
Line 4695 sub map_type {
|
my $pc = $self->map_pc(); |
my $pc = $self->map_pc(); |
return $self->navHash("map_type_$pc", 0); |
return $self->navHash("map_type_$pc", 0); |
} |
} |
|
sub map_hierarchy { |
|
my $self = shift; |
|
my $pc = $self->map_pc(); |
|
return $self->navHash("map_hierarchy_$pc", 0); |
|
} |
|
sub map_breadcrumbs { |
|
my $self = shift; |
|
my $pc = $self->map_pc(); |
|
return $self->navHash("map_breadcrumbs_$pc", 0); |
|
} |
|
|
##### |
##### |
# Property queries |
# Property queries |
Line 3254 sub map_type {
|
Line 4712 sub map_type {
|
|
|
# These functions will be responsible for returning the CORRECT |
# These functions will be responsible for returning the CORRECT |
# VALUE for the parameter, no matter what. So while they may look |
# VALUE for the parameter, no matter what. So while they may look |
# like direct calls to parmval, they can be more then that. |
# like direct calls to parmval, they can be more than that. |
# So, for instance, the duedate function should use the "duedatetype" |
# So, for instance, the duedate function should use the "duedatetype" |
# information, rather then the resource object user. |
# information, rather than the resource object user. |
|
|
=pod |
=pod |
|
|
Line 3277 their code.)
|
Line 4735 their code.)
|
|
|
=over 4 |
=over 4 |
|
|
=item * B<acc>: |
|
|
=item * B<printable> |
|
|
|
returns true if the current date is such that the |
|
specified resource part is printable. |
|
|
|
|
|
=item * B<resprintable> |
|
|
|
Returns true if all parts in the resource are printable making the |
|
entire resource printable. |
|
|
|
=item * B<acc> |
|
|
Get the Client IP/Name Access Control information. |
Get the Client IP/Name Access Control information. |
|
|
Line 3330 Get the weight for the problem.
|
Line 4800 Get the weight for the problem.
|
|
|
=cut |
=cut |
|
|
|
|
|
|
|
|
|
sub printable { |
|
|
|
my ($self, $part) = @_; |
|
|
|
# The following cases apply: |
|
# - If a start date is not set, it is replaced by the open date. |
|
# - Ditto for start/open replaced by content open. |
|
# - If neither start nor printdates are set the part is printable. |
|
# - Start date set but no end date: Printable if now >= start date. |
|
# - End date set but no start date: Printable if now <= end date. |
|
# - both defined: printable if start <= now <= end |
|
# |
|
|
|
# Get the print open/close dates for the resource. |
|
|
|
my $start = $self->parmval("printstartdate", $part); |
|
my $end = $self->parmval("printenddate", $part); |
|
|
|
if (!$start) { |
|
$start = $self->parmval("opendate", $part); |
|
} |
|
if (!$start) { |
|
$start = $self->parmval("contentopen", $part); |
|
} |
|
|
|
|
|
my $now = time(); |
|
|
|
|
|
my $startok = 1; |
|
my $endok = 1; |
|
|
|
if ((defined $start) && ($start ne '')) { |
|
$startok = $start <= $now; |
|
} |
|
if ((defined $end) && ($end != '')) { |
|
$endok = $end >= $now; |
|
} |
|
return $startok && $endok; |
|
} |
|
|
|
sub resprintable { |
|
my $self = shift; |
|
|
|
# get parts...or realize there are no parts. |
|
|
|
my $partsref = $self->parts(); |
|
my @parts = @$partsref; |
|
|
|
if (!@parts) { |
|
return $self->printable(0); |
|
} else { |
|
foreach my $part (@parts) { |
|
if (!$self->printable($part)) { |
|
return 0; |
|
} |
|
} |
|
return 1; |
|
} |
|
} |
|
|
sub acc { |
sub acc { |
(my $self, my $part) = @_; |
(my $self, my $part) = @_; |
return $self->parmval("acc", $part); |
my $acc = $self->parmval("acc", $part); |
|
return $acc; |
} |
} |
sub answerdate { |
sub answerdate { |
(my $self, my $part) = @_; |
(my $self, my $part) = @_; |
# Handle intervals |
# Handle intervals |
if ($self->parmval("answerdate.type", $part) eq 'date_interval') { |
my $answerdatetype = $self->parmval("answerdate.type", $part); |
return $self->duedate($part) + |
my $answerdate = $self->parmval("answerdate", $part); |
$self->parmval("answerdate", $part); |
my $duedate = $self->parmval("duedate", $part); |
|
if ($answerdatetype eq 'date_interval') { |
|
$answerdate = $duedate + $answerdate; |
} |
} |
return $self->parmval("answerdate", $part); |
return $answerdate; |
} |
} |
sub awarded { |
sub awarded { |
my $self = shift; my $part = shift; |
my $self = shift; my $part = shift; |
$self->{NAV_MAP}->get_user_data(); |
$self->{NAV_MAP}->get_user_data(); |
if (!defined($part)) { $part = '0'; } |
if (!defined($part)) { $part = '0'; } |
return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.awarded'}; |
return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$part.'.awarded'}; |
|
} |
|
sub taskversion { |
|
my $self = shift; my $part = shift; |
|
$self->{NAV_MAP}->get_user_data(); |
|
if (!defined($part)) { $part = '0'; } |
|
return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$part.'.version'}; |
|
} |
|
sub taskstatus { |
|
my $self = shift; my $part = shift; |
|
$self->{NAV_MAP}->get_user_data(); |
|
if (!defined($part)) { $part = '0'; } |
|
return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$self->taskversion($part).'.'.$part.'.status'}; |
|
} |
|
sub solved { |
|
my $self = shift; my $part = shift; |
|
$self->{NAV_MAP}->get_user_data(); |
|
if (!defined($part)) { $part = '0'; } |
|
return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$part.'.solved'}; |
} |
} |
|
sub checkedin { |
|
my $self = shift; my $part = shift; |
|
$self->{NAV_MAP}->get_user_data(); |
|
if (!defined($part)) { $part = '0'; } |
|
if ($self->is_task()) { |
|
my $version = $self->taskversion($part); |
|
return ($self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$version .'.'.$part.'.checkedin'},$self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$version .'.'.$part.'.checkedin.slot'}); |
|
} else { |
|
return ($self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$part.'.checkedin'},$self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$part.'.checkedin.slot'}); |
|
} |
|
} |
|
# this should work exactly like the copy in lonhomework.pm |
|
# Why is there a copy in lonhomework? Why not centralized? |
|
# |
|
# TODO: Centralize duedate. |
|
# |
|
|
sub duedate { |
sub duedate { |
(my $self, my $part) = @_; |
(my $self, my $part) = @_; |
return $self->parmval("duedate", $part); |
if (defined ($self->{DUEDATE_CACHE}->{$part})) { |
|
return $self->{DUEDATE_CACHE}->{$part}; |
|
} |
|
my $date; |
|
my @interval=$self->parmval("interval", $part); |
|
my $due_date=$self->parmval("duedate", $part); |
|
if ($interval[0] =~ /(\d+)/) { |
|
my $timelimit = $1; |
|
my $first_access=&Apache::lonnet::get_first_access($interval[1], |
|
$self->{SYMB}); |
|
if (defined($first_access)) { |
|
my $interval = $first_access+$timelimit; |
|
$date = (!$due_date || $interval < $due_date) ? $interval |
|
: $due_date; |
|
} else { |
|
$date = $due_date; |
|
} |
|
} else { |
|
$date = $due_date; |
|
} |
|
$self->{DUEDATE_CACHE}->{$part} = $date; |
|
return $date; |
|
} |
|
sub handgrade { |
|
(my $self, my $part) = @_; |
|
my @response_ids = $self->responseIds($part); |
|
if (@response_ids) { |
|
foreach my $response_id (@response_ids) { |
|
my $handgrade = $self->parmval("handgrade",$part.'_'.$response_id); |
|
if (lc($handgrade) eq 'yes') { |
|
return 'yes'; |
|
} |
|
} |
|
} |
|
my $handgrade = $self->parmval("handgrade", $part); |
|
return $handgrade; |
} |
} |
sub maxtries { |
sub maxtries { |
(my $self, my $part) = @_; |
(my $self, my $part) = @_; |
return $self->parmval("maxtries", $part); |
my $maxtries = $self->parmval("maxtries", $part); |
|
return $maxtries; |
} |
} |
sub opendate { |
sub opendate { |
(my $self, my $part) = @_; |
(my $self, my $part) = @_; |
if ($self->parmval("opendate.type", $part) eq 'date_interval') { |
my $opendatetype = $self->parmval("opendate.type", $part); |
return $self->duedate($part) - |
my $opendate = $self->parmval("opendate", $part); |
$self->parmval("opendate", $part); |
if ($opendatetype eq 'date_interval') { |
|
my $duedate = $self->duedate($part); |
|
$opendate = $duedate - $opendate; |
} |
} |
return $self->parmval("opendate"); |
return $opendate; |
} |
} |
sub problemstatus { |
sub problemstatus { |
(my $self, my $part) = @_; |
(my $self, my $part) = @_; |
return lc $self->parmval("problemstatus", $part); |
my $problemstatus = $self->parmval("problemstatus", $part); |
|
return lc($problemstatus); |
} |
} |
sub sig { |
sub sig { |
(my $self, my $part) = @_; |
(my $self, my $part) = @_; |
return $self->parmval("sig", $part); |
my $sig = $self->parmval("sig", $part); |
|
return $sig; |
} |
} |
sub tol { |
sub tol { |
(my $self, my $part) = @_; |
(my $self, my $part) = @_; |
return $self->parmval("tol", $part); |
my $tol = $self->parmval("tol", $part); |
|
return $tol; |
} |
} |
sub tries { |
sub tries { |
my $self = shift; |
my $self = shift; |
my $tries = $self->queryRestoreHash('tries', shift); |
my $tries = $self->queryRestoreHash('tries', shift); |
if (!defined($tries)) { return '0';} |
if (!defined($tries)) { return '0';} |
Line 3385 sub tries {
|
Line 4998 sub tries {
|
} |
} |
sub type { |
sub type { |
(my $self, my $part) = @_; |
(my $self, my $part) = @_; |
return $self->parmval("type", $part); |
my $type = $self->parmval("type", $part); |
|
return $type; |
} |
} |
sub weight { |
sub weight { |
my $self = shift; my $part = shift; |
my $self = shift; my $part = shift; |
if (!defined($part)) { $part = '0'; } |
if (!defined($part)) { $part = '0'; } |
return &Apache::lonnet::EXT('resource.'.$part.'.weight', |
my $weight = &Apache::lonnet::EXT('resource.'.$part.'.weight', |
$self->symb(), $ENV{'user.domain'}, |
$self->{SYMB}, $self->{DOMAIN}, |
$ENV{'user.name'}, |
$self->{USERNAME}, |
$ENV{'request.course.sec'}); |
$env{'request.course.sec'}); |
|
return $weight; |
|
} |
|
sub part_display { |
|
my $self= shift(); my $partID = shift(); |
|
if (! defined($partID)) { $partID = '0'; } |
|
my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display', |
|
$self->{SYMB}); |
|
if (! defined($display) || $display eq '') { |
|
$display = $partID; |
|
} |
|
return $display; |
|
} |
|
sub slot_control { |
|
my $self=shift(); my $part = shift(); |
|
if (!defined($part)) { $part = '0'; } |
|
my $useslots = $self->parmval("useslots", $part); |
|
my $availablestudent = $self->parmval("availablestudent", $part); |
|
my $available = $self->parmval("available", $part); |
|
return ($useslots,$availablestudent,$available); |
|
} |
|
sub deeplink { |
|
my ($self,$caller,$action) = @_; |
|
my $deeplink = $self->parmval("deeplink"); |
|
if ($deeplink) { |
|
my ($state,$others,$listed,$scope) = split(/,/,$deeplink); |
|
if ($action eq 'getlisted') { |
|
return $listed; |
|
} |
|
if ($env{'request.deeplink.login'}) { |
|
my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; |
|
my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; |
|
my $deeplink_symb = &Apache::loncommon::deeplink_login_symb($cnum,$cdom); |
|
if ($deeplink_symb) { |
|
my ($loginmap,$mapname); |
|
if ($deeplink_symb =~ /\.(page|sequence)$/) { |
|
$mapname = $self->enclosing_map_src(); |
|
$loginmap = &Apache::lonnet::clutter((&Apache::lonnet::decode_symb($deeplink_symb))[2]); |
|
return if ($mapname eq $loginmap); |
|
} else { |
|
return if ($deeplink_symb eq $self->symb()); |
|
if (($scope eq 'map') || ($scope eq 'rec')) { |
|
$mapname = $self->enclosing_map_src(); |
|
$loginmap = &Apache::lonnet::clutter((&Apache::lonnet::decode_symb($deeplink_symb))[0]); |
|
return if ($mapname eq $loginmap); |
|
} |
|
} |
|
if ($scope eq 'rec') { |
|
my $map_pc = $self->navHash('map_pc_'.$mapname); |
|
my @recurseup = split(/,/,$self->navHash('map_hierarchy_'.$map_pc)); |
|
my $login_pc = $self->navHash('map_pc_'.$loginmap); |
|
return if (grep(/^\Q$login_pc\E$/,@recurseup)); |
|
} |
|
} |
|
} |
|
unless (($caller eq 'sequence') || ($state eq 'both')) { |
|
return $listed; |
|
} |
|
} |
|
return; |
} |
} |
|
|
# Multiple things need this |
# Multiple things need this |
Line 3402 sub getReturnHash {
|
Line 5074 sub getReturnHash {
|
my $self = shift; |
my $self = shift; |
|
|
if (!defined($self->{RETURN_HASH})) { |
if (!defined($self->{RETURN_HASH})) { |
my %tmpHash = &Apache::lonnet::restore($self->symb()); |
my %tmpHash = &Apache::lonnet::restore($self->{SYMB},undef,$self->{DOMAIN},$self->{USERNAME}); |
$self->{RETURN_HASH} = \%tmpHash; |
$self->{RETURN_HASH} = \%tmpHash; |
} |
} |
} |
} |
Line 3435 Returns a false value if there has been
|
Line 5107 Returns a false value if there has been
|
logged in, true if there has. Always returns false if the discussion |
logged in, true if there has. Always returns false if the discussion |
data was not extracted when the nav map was constructed. |
data was not extracted when the nav map was constructed. |
|
|
|
=item * B<last_post_time>: |
|
|
|
Returns a false value if there hasn't been discussion otherwise returns |
|
unix timestamp of last time a discussion posting (or edit) was made. |
|
|
|
=item * B<discussion_info>: |
|
|
|
optional argument is a filter (currently can be 'unread'); |
|
returns in scalar context the count of the number of discussion postings. |
|
|
|
returns in list context both the count of postings and a hash ref |
|
containing information about the postings (subject, id, timestamp) in a hash. |
|
|
|
Default is to return counts for all postings. However if called with a second argument set to 'unread', will return information about only unread postings. |
|
|
=item * B<getFeedback>: |
=item * B<getFeedback>: |
|
|
Gets the feedback for the resource and returns the raw feedback string |
Gets the feedback for the resource and returns the raw feedback string |
Line 3442 for the resource, or the null string if
|
Line 5129 for the resource, or the null string if
|
email data was not extracted when the nav map was constructed. Usually |
email data was not extracted when the nav map was constructed. Usually |
used like this: |
used like this: |
|
|
for (split(/\,/, $res->getFeedback())) { |
for my $url (split(/\,/, $res->getFeedback())) { |
my $link = &Apache::lonnet::escape($_); |
my $link = &escape($url); |
... |
... |
|
|
and use the link as appropriate. |
and use the link as appropriate. |
Line 3452 and use the link as appropriate.
|
Line 5139 and use the link as appropriate.
|
|
|
sub hasDiscussion { |
sub hasDiscussion { |
my $self = shift; |
my $self = shift; |
return $self->{NAV_MAP}->hasDiscussion($self->symb()); |
return $self->{NAV_MAP}->hasDiscussion($self->{SYMB}); |
|
} |
|
|
|
sub last_post_time { |
|
my $self = shift; |
|
return $self->{NAV_MAP}->last_post_time($self->{SYMB}); |
|
} |
|
|
|
sub discussion_info { |
|
my ($self,$filter) = @_; |
|
return $self->{NAV_MAP}->discussion_info($self->{SYMB},$filter); |
} |
} |
|
|
sub getFeedback { |
sub getFeedback { |
my $self = shift; |
my $self = shift; |
my $source = $self->src(); |
my $source = $self->src(); |
|
my $symb = $self->{SYMB}; |
if ($source =~ /^\/res\//) { $source = substr $source, 5; } |
if ($source =~ /^\/res\//) { $source = substr $source, 5; } |
return $self->{NAV_MAP}->getFeedback($source); |
return $self->{NAV_MAP}->getFeedback($symb,$source); |
} |
} |
|
|
sub getErrors { |
sub getErrors { |
my $self = shift; |
my $self = shift; |
my $source = $self->src(); |
my $source = $self->src(); |
|
my $symb = $self->{SYMB}; |
if ($source =~ /^\/res\//) { $source = substr $source, 5; } |
if ($source =~ /^\/res\//) { $source = substr $source, 5; } |
return $self->{NAV_MAP}->getErrors($source); |
return $self->{NAV_MAP}->getErrors($symb,$source); |
} |
} |
|
|
=pod |
=pod |
Line 3484 Returns the number of parts of the probl
|
Line 5183 Returns the number of parts of the probl
|
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. |
number of parts in the problem, not including psuedo-part 0. |
|
|
|
=item * B<countResponses>(): |
|
|
|
Returns the total number of responses in the problem a student can answer. |
|
|
|
=item * B<responseTypes>(): |
|
|
|
Returns a hash whose keys are the response types. The values are the number |
|
of times each response type is used. This is for the I<entire> problem, not |
|
just a single part. |
|
|
=item * B<multipart>(): |
=item * B<multipart>(): |
|
|
Returns true if the problem is multipart, false otherwise. Use this instead |
Returns true if the problem is multipart, false otherwise. Use this instead |
Line 3530 sub countParts {
|
Line 5239 sub countParts {
|
return scalar(@{$parts}); # + $delta; |
return scalar(@{$parts}); # + $delta; |
} |
} |
|
|
|
sub countResponses { |
|
my $self = shift; |
|
my $count; |
|
foreach my $part (@{$self->parts()}) { |
|
$count+= scalar($self->responseIds($part)); |
|
} |
|
return $count; |
|
} |
|
|
|
sub responseTypes { |
|
my $self = shift; |
|
my %responses; |
|
foreach my $part (@{$self->parts()}) { |
|
foreach my $responsetype ($self->responseType($part)) { |
|
$responses{$responsetype}++ if (defined($responsetype)); |
|
} |
|
} |
|
return %responses; |
|
} |
|
|
sub multipart { |
sub multipart { |
my $self = shift; |
my $self = shift; |
return $self->countParts() > 1; |
return $self->countParts() > 1; |
Line 3584 sub extractParts {
|
Line 5313 sub extractParts {
|
if ($partorder) { |
if ($partorder) { |
my @parts; |
my @parts; |
for my $part (split (/,/,$partorder)) { |
for my $part (split (/,/,$partorder)) { |
if (!Apache::loncommon::check_if_partid_hidden($part, $self->symb())) { |
if (!Apache::loncommon::check_if_partid_hidden($part, $self->{SYMB})) { |
push @parts, $part; |
push @parts, $part; |
$parts{$part} = 1; |
$parts{$part} = 1; |
} |
} |
Line 3597 sub extractParts {
|
Line 5326 sub extractParts {
|
$self->{PART_TYPE} = {}; |
$self->{PART_TYPE} = {}; |
return; |
return; |
} |
} |
foreach (split(/\,/,$metadata)) { |
foreach my $entry (split(/\,/,$metadata)) { |
if ($_ =~ /^part_(.*)$/) { |
if ($entry =~ /^(?:part|Task)_(.*)$/) { |
my $part = $1; |
my $part = $1; |
# This floods the logs if it blows up |
# This floods the logs if it blows up |
if (defined($parts{$part})) { |
if (defined($parts{$part})) { |
&Apache::lonnet::logthis("$part multiply defined in metadata for " . $self->symb()); |
&Apache::lonnet::logthis("$part multiply defined in metadata for " . $self->{SYMB}); |
} |
} |
|
|
# check to see if part is turned off. |
# check to see if part is turned off. |
|
|
if (!Apache::loncommon::check_if_partid_hidden($part, $self->symb())) { |
if (!Apache::loncommon::check_if_partid_hidden($part, $self->{SYMB})) { |
$parts{$part} = 1; |
$parts{$part} = 1; |
} |
} |
} |
} |
} |
} |
my @sortedParts = sort keys %parts; |
my @sortedParts = sort(keys(%parts)); |
$self->{PARTS} = \@sortedParts; |
$self->{PARTS} = \@sortedParts; |
} |
} |
|
|
|
|
|
# These hashes probably do not need names that end with "Hash".... |
my %responseIdHash; |
my %responseIdHash; |
my %responseTypeHash; |
my %responseTypeHash; |
|
|
|
|
# Init the responseIdHash |
# Init the responseIdHash |
foreach (@{$self->{PARTS}}) { |
foreach my $part (@{$self->{PARTS}}) { |
$responseIdHash{$_} = []; |
$responseIdHash{$part} = []; |
} |
} |
|
|
# Now, the unfortunate thing about this is that parts, part name, and |
# Now, the unfortunate thing about this is that parts, part name, and |
Line 3632 sub extractParts {
|
Line 5362 sub extractParts {
|
# So we have to use our knowlege of part names to figure out |
# So we have to use our knowlege of part names to figure out |
# where the part names begin and end, and even then, it is possible |
# where the part names begin and end, and even then, it is possible |
# to construct ambiguous situations. |
# to construct ambiguous situations. |
foreach (split /,/, $metadata) { |
foreach my $data (split(/,/, $metadata)) { |
if ($_ =~ /^([a-zA-Z]+)response_(.*)/) { |
if ($data =~ /^([a-zA-Z]+)response_(.*)/ |
|
|| $data =~ /^(Task)_(.*)/) { |
my $responseType = $1; |
my $responseType = $1; |
my $partStuff = $2; |
my $partStuff = $2; |
my $partIdSoFar = ''; |
my $partIdSoFar = ''; |
my @partChunks = split /_/, $partStuff; |
my @partChunks = split(/_/, $partStuff); |
my $i = 0; |
my $i = 0; |
for ($i = 0; $i < scalar(@partChunks); $i++) { |
for ($i = 0; $i < scalar(@partChunks); $i++) { |
if ($partIdSoFar) { $partIdSoFar .= '_'; } |
if ($partIdSoFar) { $partIdSoFar .= '_'; } |
Line 3645 sub extractParts {
|
Line 5376 sub extractParts {
|
if ($parts{$partIdSoFar}) { |
if ($parts{$partIdSoFar}) { |
my @otherChunks = @partChunks[$i+1..$#partChunks]; |
my @otherChunks = @partChunks[$i+1..$#partChunks]; |
my $responseId = join('_', @otherChunks); |
my $responseId = join('_', @otherChunks); |
push @{$responseIdHash{$partIdSoFar}}, $responseId; |
if ($self->is_task()) { |
push @{$responseTypeHash{$partIdSoFar}}, $responseType; |
push(@{$responseIdHash{$partIdSoFar}}, |
|
$partIdSoFar); |
|
} else { |
|
push(@{$responseIdHash{$partIdSoFar}}, |
|
$responseId); |
|
} |
|
push(@{$responseTypeHash{$partIdSoFar}}, |
|
$responseType); |
} |
} |
} |
} |
} |
} |
} |
} |
|
my $resorder = &Apache::lonnet::metadata($self->src(),'responseorder'); |
|
# |
|
# Reorder the arrays in the %responseIdHash and %responseTypeHash |
|
if ($resorder) { |
|
my @resorder=split(/,/,$resorder); |
|
foreach my $part (keys(%responseIdHash)) { |
|
my $i=0; |
|
my %resids = map { ($_,$i++) } @{ $responseIdHash{$part} }; |
|
my @neworder; |
|
foreach my $possibleid (@resorder) { |
|
if (exists($resids{$possibleid})) { |
|
push(@neworder,$resids{$possibleid}); |
|
} |
|
} |
|
my @ids; |
|
my @type; |
|
foreach my $element (@neworder) { |
|
push (@ids,$responseIdHash{$part}->[$element]); |
|
push (@type,$responseTypeHash{$part}->[$element]); |
|
} |
|
$responseIdHash{$part}=\@ids; |
|
$responseTypeHash{$part}=\@type; |
|
} |
|
} |
$self->{RESPONSE_IDS} = \%responseIdHash; |
$self->{RESPONSE_IDS} = \%responseIdHash; |
$self->{RESPONSE_TYPES} = \%responseTypeHash; |
$self->{RESPONSE_TYPES} = \%responseTypeHash; |
} |
} |
Line 3671 the completion information.
|
Line 5433 the completion information.
|
Idiomatic usage of these two methods would probably look something |
Idiomatic usage of these two methods would probably look something |
like |
like |
|
|
foreach ($resource->parts()) { |
foreach my $part ($resource->parts()) { |
my $dateStatus = $resource->getDateStatus($_); |
my $dateStatus = $resource->getDateStatus($part); |
my $completionStatus = $resource->getCompletionStatus($_); |
my $completionStatus = $resource->getCompletionStatus($part); |
|
|
or |
or |
|
|
my $status = $resource->status($_); |
my $status = $resource->status($part); |
|
|
... use it here ... |
... use it here ... |
} |
} |
Line 3738 sub OPEN { return 1; }
|
Line 5500 sub OPEN { return 1; }
|
sub PAST_DUE_NO_ANSWER { return 2; } |
sub PAST_DUE_NO_ANSWER { return 2; } |
sub PAST_DUE_ANSWER_LATER { return 3; } |
sub PAST_DUE_ANSWER_LATER { return 3; } |
sub ANSWER_OPEN { return 4; } |
sub ANSWER_OPEN { return 4; } |
sub NOTHING_SET { return 5; } |
sub NOTHING_SET { return 5; } |
sub NETWORK_FAILURE { return 100; } |
sub NETWORK_FAILURE { return 100; } |
|
|
# getDateStatus gets the date status for a given problem part. |
# getDateStatus gets the date status for a given problem part. |
Line 3824 Information not available due to network
|
Line 5586 Information not available due to network
|
|
|
Attempted, and not yet graded. |
Attempted, and not yet graded. |
|
|
|
=item * B<CREDIT_ATTEMPTED>: |
|
|
|
Attempted, and credit received for attempt (survey and anonymous survey only). |
|
|
=back |
=back |
|
|
=cut |
=cut |
Line 3835 sub CORRECT { return 13; }
|
Line 5601 sub CORRECT { return 13; }
|
sub CORRECT_BY_OVERRIDE { return 14; } |
sub CORRECT_BY_OVERRIDE { return 14; } |
sub EXCUSED { return 15; } |
sub EXCUSED { return 15; } |
sub ATTEMPTED { return 16; } |
sub ATTEMPTED { return 16; } |
|
sub CREDIT_ATTEMPTED { return 17; } |
|
|
sub getCompletionStatus { |
sub getCompletionStatus { |
my $self = shift; |
my $self = shift; |
|
my $part = shift; |
return $self->NETWORK_FAILURE if ($self->{NAV_MAP}->{NETWORK_FAILURE}); |
return $self->NETWORK_FAILURE if ($self->{NAV_MAP}->{NETWORK_FAILURE}); |
|
|
my $status = $self->queryRestoreHash('solved', shift); |
my $status = $self->queryRestoreHash('solved', $part); |
|
|
# Left as seperate if statements in case we ever do more with this |
# Left as separate if statements in case we ever do more with this |
if ($status eq 'correct_by_student') {return $self->CORRECT;} |
if ($status eq 'correct_by_student') {return $self->CORRECT;} |
if ($status eq 'correct_by_override') {return $self->CORRECT_BY_OVERRIDE; } |
if ($status eq 'correct_by_scantron') {return $self->CORRECT;} |
|
if ($status eq 'correct_by_override') { |
|
return $self->CORRECT_BY_OVERRIDE; |
|
} |
if ($status eq 'incorrect_attempted') {return $self->INCORRECT; } |
if ($status eq 'incorrect_attempted') {return $self->INCORRECT; } |
if ($status eq 'incorrect_by_override') {return $self->INCORRECT_BY_OVERRIDE; } |
if ($status eq 'incorrect_by_override') {return $self->INCORRECT_BY_OVERRIDE; } |
if ($status eq 'excused') {return $self->EXCUSED; } |
if ($status eq 'excused') {return $self->EXCUSED; } |
if ($status eq 'ungraded_attempted') {return $self->ATTEMPTED; } |
if ($status eq 'ungraded_attempted') {return $self->ATTEMPTED; } |
|
if ($status eq 'credit_attempted') { |
|
if ($self->is_anonsurvey($part) || $self->is_survey($part)) { |
|
return $self->CREDIT_ATTEMPTED; |
|
} else { |
|
return $self->ATTEMPTED; |
|
} |
|
} |
return $self->NOT_ATTEMPTED; |
return $self->NOT_ATTEMPTED; |
} |
} |
|
|
Line 3938 The item is open and not yet tried.
|
Line 5716 The item is open and not yet tried.
|
|
|
The problem has been attempted. |
The problem has been attempted. |
|
|
|
=item * B<CREDIT_ATTEMPTED>: |
|
|
|
The problem has been attempted, and credit given for the attempt (survey and anonymous survey only). |
|
|
=item * B<ANSWER_SUBMITTED>: |
=item * B<ANSWER_SUBMITTED>: |
|
|
An answer has been submitted, but the student should not see it. |
An answer has been submitted, but the student should not see it. |
Line 3946 An answer has been submitted, but the st
|
Line 5728 An answer has been submitted, but the st
|
|
|
=cut |
=cut |
|
|
sub TRIES_LEFT { return 20; } |
sub TRIES_LEFT { return 20; } |
sub ANSWER_SUBMITTED { return 21; } |
sub ANSWER_SUBMITTED { return 21; } |
|
sub PARTIALLY_CORRECT { return 22; } |
|
|
|
sub RESERVED_LATER { return 30; } |
|
sub RESERVED { return 31; } |
|
sub RESERVED_LOCATION { return 32; } |
|
sub RESERVABLE { return 33; } |
|
sub RESERVABLE_LATER { return 34; } |
|
sub NOTRESERVABLE { return 35; } |
|
sub NOT_IN_A_SLOT { return 36; } |
|
sub NEEDS_CHECKIN { return 37; } |
|
sub WAITING_FOR_GRADE { return 38; } |
|
sub UNKNOWN { return 39; } |
|
|
sub status { |
sub status { |
my $self = shift; |
my $self = shift; |
Line 3963 sub status {
|
Line 5757 sub status {
|
#if ($self->{RESOURCE_ERROR}) { return NETWORK_FAILURE; } |
#if ($self->{RESOURCE_ERROR}) { return NETWORK_FAILURE; } |
if ($completionStatus == NETWORK_FAILURE) { return NETWORK_FAILURE; } |
if ($completionStatus == NETWORK_FAILURE) { return NETWORK_FAILURE; } |
|
|
my $suppressFeedback = $self->problemstatus($part) eq 'no'; |
my $suppressFeedback = 0; |
|
if (($self->problemstatus($part) eq 'no') || |
|
($self->problemstatus($part) eq 'no_feedback_ever')) { |
|
$suppressFeedback = 1; |
|
} |
# If there's an answer date and we're past it, don't |
# If there's an answer date and we're past it, don't |
# suppress the feedback; student should know |
# suppress the feedback; student should know |
if ($self->answerdate($part) && $self->answerdate($part) < time()) { |
if ($self->duedate($part) && $self->duedate($part) < time() && |
|
$self->answerdate($part) && $self->answerdate($part) < time()) { |
$suppressFeedback = 0; |
$suppressFeedback = 0; |
} |
} |
|
|
# There are a few whole rows we can dispose of: |
# There are a few whole rows we can dispose of: |
if ($completionStatus == CORRECT || |
if ($completionStatus == CORRECT || |
$completionStatus == CORRECT_BY_OVERRIDE ) { |
$completionStatus == CORRECT_BY_OVERRIDE ) { |
return $suppressFeedback? ANSWER_SUBMITTED : CORRECT; |
if ( $suppressFeedback ) { return ANSWER_SUBMITTED } |
|
my $awarded=$self->awarded($part); |
|
if ($awarded < 1 && $awarded > 0) { |
|
return PARTIALLY_CORRECT; |
|
} elsif ($awarded<1) { |
|
return INCORRECT; |
|
} |
|
return CORRECT; |
|
} |
|
|
|
# If it's WRONG... and not open |
|
if ( ($completionStatus == INCORRECT || |
|
$completionStatus == INCORRECT_BY_OVERRIDE) |
|
&& (!$self->opendate($part) || $self->opendate($part) > time()) ) { |
|
return INCORRECT; |
} |
} |
|
|
if ($completionStatus == ATTEMPTED) { |
if ($completionStatus == ATTEMPTED) { |
return ATTEMPTED; |
return ATTEMPTED; |
} |
} |
|
|
|
if ($completionStatus == CREDIT_ATTEMPTED) { |
|
return CREDIT_ATTEMPTED; |
|
} |
|
|
# If it's EXCUSED, then return that no matter what |
# If it's EXCUSED, then return that no matter what |
if ($completionStatus == EXCUSED) { |
if ($completionStatus == EXCUSED) { |
return EXCUSED; |
return EXCUSED; |
Line 3994 sub status {
|
Line 5811 sub status {
|
|
|
if ($dateStatus == PAST_DUE_ANSWER_LATER || |
if ($dateStatus == PAST_DUE_ANSWER_LATER || |
$dateStatus == PAST_DUE_NO_ANSWER ) { |
$dateStatus == PAST_DUE_NO_ANSWER ) { |
return $dateStatus; |
return $suppressFeedback ? ANSWER_SUBMITTED : $dateStatus; |
} |
} |
|
|
if ($dateStatus == ANSWER_OPEN) { |
if ($dateStatus == ANSWER_OPEN) { |
Line 4018 sub status {
|
Line 5835 sub status {
|
} |
} |
|
|
# Otherwise, it's untried and open |
# Otherwise, it's untried and open |
return OPEN; |
return OPEN; |
|
} |
|
|
|
sub check_for_slot { |
|
my $self = shift; |
|
my $part = shift; |
|
my $symb = $self->{SYMB}; |
|
my ($use_slots,$available,$availablestudent) = $self->slot_control($part); |
|
if (($use_slots ne '') && ($use_slots !~ /^\s*no\s*$/i)) { |
|
my @slots = (split(/:/,$availablestudent),split(/:/,$available)); |
|
my $cid=$env{'request.course.id'}; |
|
my $cdom=$env{'course.'.$cid.'.domain'}; |
|
my $cnum=$env{'course.'.$cid.'.num'}; |
|
my $now = time; |
|
my $num_usable_slots = 0; |
|
my ($checkedin,$checkedinslot,%consumed_uniq,%slots); |
|
if (@slots > 0) { |
|
%slots=&Apache::lonnet::get('slots',[@slots],$cdom,$cnum); |
|
if (&Apache::lonnet::error(%slots)) { |
|
return (UNKNOWN); |
|
} |
|
my @sorted_slots = &Apache::loncommon::sorted_slots(\@slots,\%slots,'starttime'); |
|
foreach my $slot_name (@sorted_slots) { |
|
next if (!defined($slots{$slot_name}) || !ref($slots{$slot_name})); |
|
my $end = $slots{$slot_name}->{'endtime'}; |
|
my $start = $slots{$slot_name}->{'starttime'}; |
|
my $ip = $slots{$slot_name}->{'ip'}; |
|
if ($self->simpleStatus() == OPEN) { |
|
if ($end > $now) { |
|
if ($start > $now) { |
|
return (RESERVED_LATER,$start,$slot_name); |
|
} else { |
|
if ($ip ne '') { |
|
if (!&Apache::loncommon::check_ip_acc($ip)) { |
|
return (RESERVED_LOCATION,$end,$slot_name); |
|
} |
|
} |
|
my @proctors; |
|
if ($slots{$slot_name}->{'proctor'} ne '') { |
|
@proctors = split(',',$slots{$slot_name}->{'proctor'}); |
|
} |
|
if (@proctors > 0) { |
|
($checkedin,$checkedinslot) = $self->checkedin(); |
|
unless ((grep(/^\Q$checkedin\E/,@proctors)) && |
|
($checkedinslot eq $slot_name)) { |
|
return (NEEDS_CHECKIN,$end,$slot_name); |
|
} |
|
} |
|
return (RESERVED,$end,$slot_name); |
|
} |
|
} |
|
} elsif ($end > $now) { |
|
$num_usable_slots ++; |
|
} |
|
} |
|
my ($is_correct,$wait_for_grade); |
|
if ($self->is_task()) { |
|
my $taskstatus = $self->taskstatus(); |
|
$is_correct = (($taskstatus eq 'pass') || |
|
($self->solved() =~ /^correct_/)); |
|
unless ($taskstatus =~ /^(?:pass|fail)$/) { |
|
$wait_for_grade = 1; |
|
} |
|
} else { |
|
unless ($self->completable()) { |
|
$wait_for_grade = 1; |
|
} |
|
unless (($self->problemstatus($part) eq 'no') || |
|
($self->problemstatus($part) eq 'no_feedback_ever')) { |
|
$is_correct = ($self->solved($part) =~ /^correct_/); |
|
$wait_for_grade = 0; |
|
} |
|
} |
|
($checkedin,$checkedinslot) = $self->checkedin(); |
|
if ($checkedin) { |
|
if (ref($slots{$checkedinslot}) eq 'HASH') { |
|
$consumed_uniq{$checkedinslot} = $slots{$checkedinslot}{'uniqueperiod'}; |
|
} |
|
if ($wait_for_grade) { |
|
return (WAITING_FOR_GRADE); |
|
} elsif ($is_correct) { |
|
return (CORRECT); |
|
} |
|
} |
|
if ($num_usable_slots) { |
|
return(NOT_IN_A_SLOT); |
|
} |
|
} |
|
my $reservable = &Apache::lonnet::get_reservable_slots($cnum,$cdom,$env{'user.name'}, |
|
$env{'user.domain'}); |
|
if (ref($reservable) eq 'HASH') { |
|
if ((ref($reservable->{'now_order'}) eq 'ARRAY') && (ref($reservable->{'now'}) eq 'HASH')) { |
|
foreach my $slot (reverse (@{$reservable->{'now_order'}})) { |
|
my $canuse; |
|
if (($reservable->{'now'}{$slot}{'symb'} eq '') || |
|
($reservable->{'now'}{$slot}{'symb'} eq $symb)) { |
|
$canuse = 1; |
|
} |
|
if ($canuse) { |
|
if ($checkedin) { |
|
if (ref($consumed_uniq{$checkedinslot}) eq 'ARRAY') { |
|
my ($uniqstart,$uniqend)=@{$consumed_uniq{$checkedinslot}}; |
|
if ($reservable->{'now'}{$slot}{'uniqueperiod'} =~ /^(\d+),(\d+)$/) { |
|
my ($new_uniq_start,$new_uniq_end) = ($1,$2); |
|
next if (! |
|
($uniqstart < $new_uniq_start && $uniqend < $new_uniq_start) || |
|
($uniqstart > $new_uniq_end && $uniqend > $new_uniq_end )); |
|
} |
|
} |
|
} |
|
return(RESERVABLE,$reservable->{'now'}{$slot}{'endreserve'}); |
|
} |
|
} |
|
} |
|
if ((ref($reservable->{'future_order'}) eq 'ARRAY') && (ref($reservable->{'future'}) eq 'HASH')) { |
|
foreach my $slot (@{$reservable->{'future_order'}}) { |
|
my $canuse; |
|
if (($reservable->{'future'}{$slot}{'symb'} eq '') || |
|
($reservable->{'future'}{$slot}{'symb'} eq $symb)) { |
|
$canuse = 1; |
|
} |
|
if ($canuse) { |
|
if ($checkedin) { |
|
if (ref($consumed_uniq{$checkedinslot}) eq 'ARRAY') { |
|
my ($uniqstart,$uniqend)=@{$consumed_uniq{$checkedinslot}}; |
|
if ($reservable->{'future'}{$slot}{'uniqueperiod'} =~ /^(\d+),(\d+)$/) { |
|
my ($new_uniq_start,$new_uniq_end) = ($1,$2); |
|
next if (! |
|
($uniqstart < $new_uniq_start && $uniqend < $new_uniq_start) || |
|
($uniqstart > $new_uniq_end && $uniqend > $new_uniq_end )); |
|
} |
|
} |
|
} |
|
return(RESERVABLE_LATER,$reservable->{'future'}{$slot}{'startreserve'}); |
|
} |
|
} |
|
} |
|
} |
|
return(NOTRESERVABLE); |
|
} |
|
return; |
} |
} |
|
|
sub CLOSED { return 23; } |
sub CLOSED { return 23; } |
Line 4060 my %compositeToSimple =
|
Line 6017 my %compositeToSimple =
|
NETWORK_FAILURE() => ERROR, |
NETWORK_FAILURE() => ERROR, |
NOTHING_SET() => CLOSED, |
NOTHING_SET() => CLOSED, |
CORRECT() => CORRECT, |
CORRECT() => CORRECT, |
|
PARTIALLY_CORRECT() => PARTIALLY_CORRECT, |
EXCUSED() => CORRECT, |
EXCUSED() => CORRECT, |
PAST_DUE_NO_ANSWER() => INCORRECT, |
PAST_DUE_NO_ANSWER() => INCORRECT, |
PAST_DUE_ANSWER_LATER() => INCORRECT, |
PAST_DUE_ANSWER_LATER() => INCORRECT, |
Line 4069 my %compositeToSimple =
|
Line 6027 my %compositeToSimple =
|
INCORRECT() => INCORRECT, |
INCORRECT() => INCORRECT, |
OPEN() => OPEN, |
OPEN() => OPEN, |
ATTEMPTED() => ATTEMPTED, |
ATTEMPTED() => ATTEMPTED, |
|
CREDIT_ATTEMPTED() => CORRECT, |
ANSWER_SUBMITTED() => ATTEMPTED |
ANSWER_SUBMITTED() => ATTEMPTED |
); |
); |
|
|
Line 4143 sub completable {
|
Line 6102 sub completable {
|
# and it is not "attempted" (manually graded problem), it is |
# and it is not "attempted" (manually graded problem), it is |
# not "complete" |
# not "complete" |
if ($self->getCompletionStatus($part) == ATTEMPTED() || |
if ($self->getCompletionStatus($part) == ATTEMPTED() || |
|
$self->getCompletionStatus($part) == CREDIT_ATTEMPTED() || |
$status == ANSWER_SUBMITTED() ) { |
$status == ANSWER_SUBMITTED() ) { |
# did this part already, as well as we can |
# did this part already, as well as we can |
next; |
next; |
Line 4158 sub completable {
|
Line 6118 sub completable {
|
|
|
=pod |
=pod |
|
|
|
B<Answerable> |
|
|
|
The answerable method differs from the completable method in its handling of problem parts |
|
for which feedback on correctness is suppressed, but the student still has tries left, and |
|
the problem part is not past due, (i.e., the student could submit a different answer if |
|
he/she so chose). For that case completable will return 0, whereas answerable will return 1. |
|
|
|
=cut |
|
|
|
sub answerable { |
|
my $self = shift; |
|
if (!$self->is_problem()) { return 0; } |
|
my $partCount = $self->countParts(); |
|
foreach my $part (@{$self->parts()}) { |
|
if ($part eq '0' && $partCount != 1) { next; } |
|
my $status = $self->status($part); |
|
if ($self->getCompletionStatus($part) == ATTEMPTED() || |
|
$self->getCompletionStatus($part) == CREDIT_ATTEMPTED() || |
|
$status == ANSWER_SUBMITTED() ) { |
|
if ($self->tries($part) < $self->maxtries($part) || !$self->maxtries($part)) { |
|
return 1; |
|
} |
|
} |
|
if ($status == OPEN() || $status == TRIES_LEFT() || $status == NETWORK_FAILURE()) { |
|
return 1; |
|
} |
|
} |
|
# None of the parts were answerable, so neither is this problem. |
|
return 0; |
|
} |
|
|
|
=pod |
|
|
=head2 Resource/Nav Map Navigation |
=head2 Resource/Nav Map Navigation |
|
|
=over 4 |
=over 4 |
Line 4180 sub getNext {
|
Line 6173 sub getNext {
|
my $to = $self->to(); |
my $to = $self->to(); |
foreach my $branch ( split(/,/, $to) ) { |
foreach my $branch ( split(/,/, $to) ) { |
my $choice = $self->{NAV_MAP}->getById($branch); |
my $choice = $self->{NAV_MAP}->getById($branch); |
|
#if (!$choice->condition()) { next; } |
my $next = $choice->goesto(); |
my $next = $choice->goesto(); |
$next = $self->{NAV_MAP}->getById($next); |
$next = $self->{NAV_MAP}->getById($next); |
|
|
Line 4192 sub getPrevious {
|
Line 6186 sub getPrevious {
|
my $self = shift; |
my $self = shift; |
my @branches; |
my @branches; |
my $from = $self->from(); |
my $from = $self->from(); |
foreach my $branch ( split /,/, $from) { |
foreach my $branch ( split(/,/, $from)) { |
my $choice = $self->{NAV_MAP}->getById($branch); |
my $choice = $self->{NAV_MAP}->getById($branch); |
my $prev = $choice->comesfrom(); |
my $prev = $choice->comesfrom(); |
$prev = $self->{NAV_MAP}->getById($prev); |
$prev = $self->{NAV_MAP}->getById($prev); |
Line 4204 sub getPrevious {
|
Line 6198 sub getPrevious {
|
|
|
sub browsePriv { |
sub browsePriv { |
my $self = shift; |
my $self = shift; |
|
my $noblockcheck = shift; |
|
my $deeplinklisted = shift; |
if (defined($self->{BROWSE_PRIV})) { |
if (defined($self->{BROWSE_PRIV})) { |
return $self->{BROWSE_PRIV}; |
return $self->{BROWSE_PRIV}; |
} |
} |
|
my ($nodeeplinkcheck,$nodeeplinkout); |
$self->{BROWSE_PRIV} = &Apache::lonnet::allowed('bre', $self->src()); |
if ($deeplinklisted) { |
|
my $deeplink = $self->deeplink(undef,'getlisted'); |
|
if (($deeplink) && ($deeplink ne 'absent')) { |
|
$nodeeplinkcheck = 1; |
|
} |
|
$nodeeplinkout = 1; |
|
} |
|
$self->{BROWSE_PRIV} = &Apache::lonnet::allowed('bre',$self->src(), |
|
$self->{SYMB},undef, |
|
undef,$noblockcheck, |
|
undef,$nodeeplinkcheck, |
|
$nodeeplinkout); |
} |
} |
|
|
=pod |
=pod |