--- loncom/interface/lonnavmaps.pm 2006/04/29 17:57:29 1.379
+++ loncom/interface/lonnavmaps.pm 2011/11/29 01:49:00 1.469
@@ -1,7 +1,8 @@
# The LearningOnline Network with CAPA
# Navigate Maps Handler
#
-# $Id: lonnavmaps.pm,v 1.379 2006/04/29 17:57:29 albertel Exp $
+# $Id: lonnavmaps.pm,v 1.469 2011/11/29 01:49:00 www Exp $
+
#
# Copyright Michigan State University Board of Trustees
#
@@ -27,709 +28,25 @@
#
###
-package Apache::lonnavmaps;
-
-use strict;
-use GDBM_File;
-use Apache::Constants qw(:common :http);
-use Apache::loncommon();
-use Apache::lonmenu();
-use Apache::lonenc();
-use Apache::lonlocal;
-use Apache::lonnet;
-use POSIX qw (floor strftime);
-use Data::Dumper; # for debugging, not always
-use Time::HiRes qw( gettimeofday tv_interval );
-
-# 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->PARTIALLY_CORRECT => 'navmap.partial.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 => '',
- $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 then 24 hours
-my $hurryUpColor = "#FF0000";
-
-sub launch_win {
- my ($mode,$script,$toplinkitems,$firsttime)=@_;
- my $result;
- if ($script ne 'no') {
- $result.='';
- }
- if ($mode eq 'link') {
- &add_linkitem($toplinkitems,'launchnav','launch_navmapwin()',
- "Launch navigation window");
- }
- return $result;
-}
-
-sub close {
- if ($env{'environment.remotenavmap'} ne 'on') { return ''; }
- return(<
-window.status='Accessing Nav Control';
-menu=window.open("/adm/rat/empty.html","loncapanav",
- "height=600,width=400,scrollbars=1");
-window.status='Closing Nav Control';
-menu.close();
-window.status='Done.';
-
-ENDCLOSE
-}
-
-sub update {
- if ($env{'environment.remotenavmap'} ne 'on') { return ''; }
- if (!$env{'request.course.id'}) { return ''; }
- if ($ENV{'REQUEST_URI'}=~m|^/adm/navmaps|) { return ''; }
- return(<
-
-ENDUPDATE
-}
-
-sub handler {
- my $r = shift;
- real_handler($r);
-}
-
-sub real_handler {
- my $r = shift;
- #my $t0=[&gettimeofday()];
- # 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);
-
- my %toplinkitems=();
- &add_linkitem(\%toplinkitems,'blank','',"Select Action");
- if ($ENV{QUERY_STRING} eq 'collapseExternal') {
- &Apache::lonnet::put('environment',{'remotenavmap' => 'off'});
- &Apache::lonnet::appenv('environment.remotenavmap' => 'off');
- my $menu=&Apache::lonmenu::reopenmenu();
- my $navstatus=&Apache::lonmenu::get_nav_status();
- if ($menu) {
- $menu=(<');
- $r->print('
- ');
- }
-
- if ($env{'environment.remotenavmap'} ne 'on') {
- $r->print(&launch_win('link','yes',\%toplinkitems));
- }
- if ($env{'environment.remotenavmap'} eq 'on') {
- &add_linkitem(\%toplinkitems,'closenav','collapse()',
- "Close navigation window");
- }
-
-
- # Check to see if the student is jumping to next open, do-able problem
- if ($ENV{QUERY_STRING} =~ /^jumpToFirstHomework/) {
- # Find the next homework problem that they can do.
- my $iterator = $navmap->getIterator(undef, undef, undef, 1);
- my $curRes;
- my $foundDoableProblem = 0;
- my $minimumduedate;
-
- while ($curRes = $iterator->next()) {
- if (ref($curRes) && $curRes->is_problem()) {
- my $status = $curRes->status();
- if ($curRes->completable()) {
- my $thisduedate=$curRes->duedate();
- unless ($foundDoableProblem) {
- $minimumduedate=$thisduedate;
- }
-
- $foundDoableProblem = 1;
-
- if ($thisduedate<=$minimumduedate) {
- # 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();
- $minimumduedate=$thisduedate;
- }
- }
- }
- }
-
- # If we found no problems, print a note to that effect.
- if (!$foundDoableProblem) {
- $r->print("All homework assignments have been completed.
");
- }
- } else {
- &add_linkitem(\%toplinkitems,'firsthomework',
- 'location.href="navmaps?jumpToFirstHomework"',
- "Show my first due problem");
- }
-
- my $suppressEmptySequences = 0;
- my $filterFunc = undef;
- my $resource_no_folder_link = 0;
-
- # Display only due homework.
- my $showOnlyHomework = 0;
- if ($env{'form.showOnlyHomework'} eq "1") {
- $showOnlyHomework = 1;
- $suppressEmptySequences = 1;
- $filterFunc = sub { my $res = shift;
- return $res->completable() || $res->is_map();
- };
- &add_linkitem(\%toplinkitems,'everything',
- 'location.href="navmaps?sort='.$env{'form.sort'}.'"',
- "Show everything");
- $r->print("
".&mt("Uncompleted Problems")."
");
- $env{'form.filter'} = '';
- $env{'form.condition'} = 1;
- $resource_no_folder_link = 1;
- } else {
- &add_linkitem(\%toplinkitems,'uncompleted',
- 'location.href="navmaps?sort='.$env{'form.sort'}.
- '&showOnlyHomework=1"',
- "Show only uncompleted problems");
- }
-
- my %selected=($env{'form.sort'} => 'selected=on');
- my $sort_html=("");
- # renderer call
- my $renderArgs = { 'cols' => [0,1,2,3],
- 'sort' => $env{'form.sort'},
- 'url' => '/adm/navmaps',
- 'navmap' => $navmap,
- 'suppressNavmap' => 1,
- 'suppressEmptySequences' => $suppressEmptySequences,
- 'filterFunc' => $filterFunc,
- 'resource_no_folder_link' => $resource_no_folder_link,
- 'sort_html'=> $sort_html,
- 'r' => $r,
- 'caller' => 'navmapsdisplay',
- 'linkitems' => \%toplinkitems};
- my $render = render($renderArgs);
-
- # 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("
".&mt("All homework is currently completed").".
");
- } else { # both jumpToFirstHomework and normal use the same: course must be empty
- $r->print("
This course is empty.
");
- }
- }
- #my $td=&tv_interval($t0);
- #$r->print(" $td");
-
- $r->print(&Apache::loncommon::end_page());
- $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.
-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 (@$stack) { if (defined($_)) { $anchor = $_; } }
- $anchor=&Apache::lonnet::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=&Apache::lonnet::escape($src->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 (@$stack) {
- if (defined($_)) { $res = $_; }
- }
-
- return ($res->link(),$res->shown_symb());
-}
-
-# 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.
-
-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),'start');
- }
- if ($status == $res->OPEN) {
- if ($res->duedate($part)) {
- return &mt("Due")." " .timeToHumanString($res->duedate($part),'end');
- } else {
- return &mt("Open, no due date");
- }
- }
- if ($status == $res->PAST_DUE_ANSWER_LATER) {
- return &mt("Answer open")." " . timeToHumanString($res->answerdate($part),'start');
- }
- if ($status == $res->PAST_DUE_NO_ANSWER) {
- return &mt("Was due")." " . timeToHumanString($res->duedate($part),'end');
- }
- if (($status == $res->ANSWER_OPEN || $status == $res->PARTIALLY_CORRECT)
- && $res->handgrade($part) ne 'yes') {
- 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 = "($tries of $maxtries tries used)";
- if ($maxtries > 1 && $maxtries - $tries == 1) {
- $triesString = "$triesString";
- }
- }
- if ($res->duedate($part)) {
- return &mt("Due")." " . timeToHumanString($res->duedate($part),'end') .
- " $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 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();
-}
-
-# 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($part) &&
- $res->duedate($part) > 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,$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();
-
- 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";
- }
-
- # If there's a caller supplied format, use it.
-
- if($format ne '') {
- my $timeStr = strftime($format, localtime($time));
- return $timeStr.&Apache::lonlocal::gettimezone($time);
- }
-
- # 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 " : "this ") .
- $timeStr.&Apache::lonlocal::gettimezone($time);
- }
-
- my $conjunction='on';
- if ($type eq 'start') {
- $conjunction='at';
- } elsif ($type eq 'end') {
- $conjunction='by';
- }
- # Is it this year?
- if ( $time[5] == $now[5]) {
- # Return on Month Day, HH:MM meridian
- my $timeStr = strftime("$conjunction %A, %b %e at %I:%M %P", localtime($time));
- $timeStr =~ s/12:00 am/00:00/;
- $timeStr =~ s/12:00 pm/noon/;
- return $timeStr.&Apache::lonlocal::gettimezone($time);
- }
-
- # Not this year, so show the year
- my $timeStr = strftime("$conjunction %A, %b %e %Y at %I:%M %P", localtime($time));
- $timeStr =~ s/12:00 am/00:00/;
- $timeStr =~ s/12:00 pm/noon/;
- return $timeStr.&Apache::lonlocal::gettimezone($time);
- }
-}
-
-
=pod
=head1 NAME
-Apache::lonnavmap - Subroutines to handle and render the navigation
- maps
+Apache::lonnavmaps - Subroutines to handle and render the navigation
=head1 SYNOPSIS
+Handles navigational maps.
+
The main handler generates the navigational listing for the course,
the other objects export this information in a usable fashion for
other modules.
+
+This is part of the LearningOnline Network with CAPA project
+described at http://www.lon-capa.org.
+
+
=head1 OVERVIEW
X When a user enters a course, LON-CAPA examines the
@@ -747,7 +64,7 @@ to compute due to the amount of data tha
processed.
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
that aren't necessarily stored in the Big Hash, but are a per-
resource sort of value, like whether there is any feedback on
@@ -793,7 +110,7 @@ Apache::lonnavmaps::render({}).
=head2 Overview of Columns
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
columns to create and what to place in each column, optionally using
one or more of the prepared columns, and the renderer will assemble
@@ -924,6 +241,11 @@ instruct the renderer to render only a p
the source of the map you want to process, like
'/res/103/jerf/navmap.course.sequence'.
+=item * B: default: false
+
+If you need to include the top level map (meaning the course) in the
+rendered output set this to true
+
=item * B: default: constructs one from %env
A reference to a navmap, used only if an iterator is not passed in. If
@@ -1044,6 +366,530 @@ navmaps screen) uses this to display the
=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()
+
+=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 (floor strftime);
+use Time::HiRes qw( gettimeofday tv_interval );
+use LONCAPA;
+use DateTime();
+
+# 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";
+
+my $future_slots_checked = 0;
+my $future_slots = 0;
+
+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; } }
+ $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));
+ }
+ 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);
+ if ($slot_status == $res->UNKNOWN) {
+ return &mt('Reservation status unknown');
+ } elsif ($slot_status == $res->RESERVED) {
+ return &mt('Reserved - ends [_1]',
+ timeToHumanString($slot_time,'end'));
+ } elsif ($slot_status == $res->RESERVED_LOCATION) {
+ return &mt('Reserved - specific location(s) - ends [_1]',
+ timeToHumanString($slot_time,'end'));
+ } elsif ($slot_status == $res->RESERVED_LATER) {
+ return &mt('Reserved - next open [_1]',
+ timeToHumanString($slot_time,'start'));
+ } elsif ($slot_status == $res->RESERVABLE) {
+ return &mt('Reservable ending [_1]',
+ timeToHumanString($slot_time,'end'));
+ } elsif ($slot_status == $res->RESERVABLE_LATER) {
+ return &mt('Reservable starting [_1]',
+ timeToHumanString($slot_time,'start'));
+ } elsif ($slot_status == $res->NOT_IN_A_SLOT) {
+ return &mt('Reserve a time/place to work');
+ } elsif ($slot_status == $res->NOTRESERVABLE) {
+ return &mt('Reservation not available');
+ } elsif ($slot_status == $res->WAITING_FOR_GRADE) {
+ return &mt('Submission in grading queue');
+ }
+ }
+ }
+ 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));
+ } else {
+ return &mt("Due [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'end'),$res->symb(),'duedate',$part));
+ }
+ } else {
+ return &Apache::lonhtmlcommon::direct_parm_link(&mt("Open, no due date"),$res->symb(),'duedate',$part);
+ }
+ }
+ 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 = '('.&mt('[_1] of [quant,_2,try,tries] used',$tries,$maxtries).')';
+ if ($maxtries > 1 && $maxtries - $tries == 1) {
+ $triesString = "$triesString";
+ }
+ }
+ 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 communication_status { return 1; }
sub quick_status { return 2; }
@@ -1058,11 +904,6 @@ sub render_resource {
my $link = $params->{"resourceLink"};
# The URL part is not escaped at this point, but the symb is...
- # The stuff to the left of the ? must have ' replaced by \' since
- # it will be quoted with ' in the href.
-
- my ($left,$right) = split(/\?/, $link);
- $link = $left.'?'.$right;
my $src = $resource->src();
my $it = $params->{"iterator"};
@@ -1075,28 +916,32 @@ sub render_resource {
my $location=&Apache::loncommon::lonhttpdurl("/adm/lonIcons");
# If this is a new branch, label it so
if ($params->{'isNewBranch'}) {
- $newBranchText = "";
+ $newBranchText = "";
}
# links to open and close the folder
-
- my $linkopen = "";
-
-
+ my $whitespace = $location.'/whitespace_21.gif';
+ my $linkopen = ""."";
my $linkclose = "";
# Default icon: unknown page
- my $icon = "";
+ my $icon = "";
if ($resource->is_problem()) {
if ($part eq '0' || $params->{'condensed'}) {
- $icon ='';
+ $icon = '';
} else {
$icon = $params->{'indentString'};
}
} else {
- $icon = "";
+ $icon = "";
}
# Display the correct map icon to open or shut map
@@ -1106,33 +951,31 @@ sub render_resource {
if ($it->{CONDITION}) {
$nowOpen = !$nowOpen;
}
-
+
my $folderType = $resource->is_sequence() ? 'folder' : 'page';
my $title=$resource->title;
- $title=~s/\"/\"/g;
+ $title=~s/\"/\&qout;/g;
if (!$params->{'resource_no_folder_link'}) {
$icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif';
- $icon = "";
-
+ $icon = ""
+ ."";
$linkopen = "{'url'} . '?' .
- $params->{'queryString'} . '&filter=';
+ $params->{'queryString'} . '&filter=';
$linkopen .= ($nowOpen xor $it->{CONDITION}) ?
addToFilter($filter, $mapId) :
removeFromFilter($filter, $mapId);
- $linkopen .= "&condition=" . $it->{CONDITION} . '&hereType='
- . $params->{'hereType'} . '&here=' .
- &Apache::lonnet::escape($params->{'here'}) .
- '&jump=' .
- &Apache::lonnet::escape($resource->symb()) .
- "&folderManip=1\">";
+ $linkopen .= "&condition=" . $it->{CONDITION} . '&hereType='
+ . $params->{'hereType'} . '&here=' .
+ &escape($params->{'here'}) .
+ '&jump=' .
+ &escape($resource->symb()) .
+ "&folderManip=1\">";
} else {
# Don't allow users to manipulate folder
- $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') .
- '.nomanip.gif';
- $icon = "";
+ $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif';
+ $icon = ""."";
$linkopen = "";
$linkclose = "";
@@ -1140,15 +983,19 @@ sub render_resource {
}
if ($resource->randomout()) {
- $nonLinkedText .= ' ('.&mt('hidden').') ';
+ $nonLinkedText .= ' ('.&mt('hidden').') ';
}
if (!$resource->condval()) {
- $nonLinkedText .= ' ('.&mt('conditionally hidden').') ';
+ $nonLinkedText .= ' ('.&mt('conditionally hidden').') ';
+ }
+ if (($resource->is_practice()) && ($resource->is_raw_problem())) {
+ $nonLinkedText .=' '.&mt('not graded').'';
}
-
- # We're done preparing and finally ready to start the rendering
- my $result = "
";
+ # We're done preparing and finally ready to start the rendering
+ my $result = '
';
+ my $newfolderType = $resource->is_sequence() ? 'folder' : 'page';
+
my $indentLevel = $params->{'indentLevel'};
if ($newBranchText) { $indentLevel--; }
@@ -1158,7 +1005,6 @@ sub render_resource {
}
# Decide what to display
-
$result .= "$newBranchText$linkopen$icon$linkclose";
my $curMarkerBegin = '';
@@ -1167,16 +1013,16 @@ sub render_resource {
# Is this the current resource?
if (!$params->{'displayedHereMarker'} &&
$resource->symb() eq $params->{'here'} ) {
- $curMarkerBegin = '>';
- $curMarkerEnd = '<';
- $params->{'displayedHereMarker'} = 1;
+ $curMarkerBegin = '';
+ $curMarkerEnd = '';
+ $params->{'displayedHereMarker'} = 1;
}
if ($resource->is_problem() && $part ne '0' &&
!$params->{'condensed'}) {
my $displaypart=$resource->part_display($part);
$partLabel = " (".&mt('Part: [_1]', $displaypart).")";
- if ($link!~/\#/) { $link.='#'.&Apache::lonnet::escape($part); }
+ if ($link!~/\#/) { $link.='#'.&escape($part); }
$title = "";
}
@@ -1184,14 +1030,10 @@ sub render_resource {
$nonLinkedText .= ' ('.&mt('[_1] parts', $resource->countParts()).')';
}
- my $target;
- if ($env{'environment.remotenavmap'} eq 'on') {
- $target=' target="loncapaclient" ';
- }
if (!$params->{'resource_nolink'} && !$resource->is_sequence() && !$resource->is_empty_sequence) {
- $result .= " $curMarkerBegin$title$partLabel$curMarkerEnd $nonLinkedText
";
+ $result .= "$curMarkerBegin$title$partLabel$curMarkerEnd$nonLinkedText";
} else {
- $result .= " $curMarkerBegin$title$partLabel$curMarkerEnd $nonLinkedText";
+ $result .= "$curMarkerBegin$linkopen$title$partLabel$curMarkerEnd$nonLinkedText";
}
return $result;
@@ -1202,27 +1044,22 @@ sub render_communication_status {
my $discussionHTML = ""; my $feedbackHTML = ""; my $errorHTML = "";
my $link = $params->{"resourceLink"};
- my $target;
- if ($env{'environment.remotenavmap'} eq 'on') {
- $target=' target="loncapaclient" ';
- }
- my $linkopen = "";
+ my $linkopen = "";
my $linkclose = "";
my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc");
if ($resource->hasDiscussion()) {
$discussionHTML = $linkopen .
- '' .
+ '' .
$linkclose;
}
if ($resource->getFeedback()) {
my $feedback = $resource->getFeedback();
- foreach (split(/\,/, $feedback)) {
- if ($_) {
- $feedbackHTML .= ' '
- . '';
+ foreach my $msgid (split(/\,/, $feedback)) {
+ if ($msgid) {
+ $feedbackHTML .= ' '
+ . '';
}
}
}
@@ -1230,14 +1067,13 @@ sub render_communication_status {
if ($resource->getErrors()) {
my $errors = $resource->getErrors();
my $errorcount = 0;
- foreach (split(/,/, $errors)) {
+ foreach my $msgid (split(/,/, $errors)) {
last if ($errorcount>=10); # Only output 10 bombs maximum
- if ($_) {
+ if ($msgid) {
$errorcount++;
- $errorHTML .= ' '
- . '';
+ $errorHTML .= ' '
+ . '';
}
}
}
@@ -1245,8 +1081,7 @@ sub render_communication_status {
if ($params->{'multipart'} && $part != '0') {
$discussionHTML = $feedbackHTML = $errorHTML = '';
}
-
- return "
$discussionHTML$feedbackHTML$errorHTML
";
+ return "
$discussionHTML$feedbackHTML$errorHTML
";
}
sub render_quick_status {
@@ -1256,39 +1091,35 @@ sub render_quick_status {
$params->{'multipart'} && $part eq "0";
my $link = $params->{"resourceLink"};
- my $target;
- if ($env{'environment.remotenavmap'} eq 'on') {
- $target=' target="loncapaclient" ';
- }
- my $linkopen = "";
+ my $linkopen = "";
my $linkclose = "";
-
+
+ $result .= '
';
if ($resource->is_problem() &&
!$firstDisplayed) {
-
my $icon = $statusIconMap{$resource->simpleStatus($part)};
my $alt = $iconAltTags{$icon};
if ($icon) {
my $location=
&Apache::loncommon::lonhttpdurl("/adm/lonIcons/$icon");
- $result .= "
';
+ $result.=&show_linkitems_toolbar($args->{'linkitems'});
if ($args->{'sort_html'}) {
- if ($env{'environment.remotenavmap'} ne 'on') {
- $result.='
' ."\n";
+ $result.=&Apache::loncommon::start_data_table("LC_tableOfContent");
+
my $res = "Apache::lonnavmaps::resource";
my %condenseStatuses =
( $res->NETWORK_FAILURE => 1,
$res->NOTHING_SET => 1,
$res->CORRECT => 1 );
- my @backgroundColors = ("#FFFFFF", "#F6F6F6");
# Shared variables
$args->{'counter'} = 0; # counts the rows
$args->{'indentLevel'} = 0;
$args->{'isNewBranch'} = 0;
- $args->{'condensed'} = 0;
- my $location=
- &Apache::loncommon::lonhttpdurl("/adm/lonIcons/whitespace1.gif");
- $args->{'indentString'} = setDefault($args->{'indentString'}, "");
+ $args->{'condensed'} = 0;
+
+ my $location = &Apache::loncommon::lonhttpdurl("/adm/lonIcons/whitespace_21.gif");
+ $args->{'indentString'} = setDefault($args->{'indentString'}, "");
$args->{'displayedHereMarker'} = 0;
# If we're suppressing empty sequences, look for them here. Use DFS for speed,
@@ -1922,16 +1767,15 @@ END
if (defined($anchor)) { $anchor='#'.$anchor; }
my $srcHasQuestion = $src =~ /\?/;
$args->{"resourceLink"} = $src.
- ($srcHasQuestion?'&':'?') .
- 'symb=' . &Apache::lonnet::escape($symb).$anchor;
+ ($srcHasQuestion?'&':'?') .
+ 'symb=' . &escape($symb).$anchor;
}
# Now, we've decided what parts to show. Loop through them and
# show them.
foreach my $part (@parts) {
$rownum ++;
- my $backgroundColor = $backgroundColors[$rownum % scalar(@backgroundColors)];
- $result .= "
\n";
+ $result .= &Apache::loncommon::start_data_table_row();
# Set up some data about the parts that the cols might want
my $filter = $it->{FILTER};
@@ -1957,7 +1801,7 @@ END
}
$result .= $colHTML . "\n";
}
- $result .= "
\n";
+ $result .= &Apache::loncommon::end_data_table_row();
$args->{'isNewBranch'} = 0;
}
@@ -1985,16 +1829,15 @@ END
# it's quite likely this might fix other browsers, too, and
# certainly won't hurt anything.
if ($displayedJumpMarker) {
- $result .= "
-";
+");
}
- $result .= "
";
-
+ $result.=&Apache::loncommon::end_data_table();
+
if ($r) {
$r->print($result);
$result = "";
@@ -2010,42 +1853,52 @@ sub add_linkitem {
$$linkitems{$name}{'text'}=&mt($text);
}
-sub show_linkitems {
- my ($linkitems)=@_;
- my @linkorder = ("blank","launchnav","closenav","firsthomework",
- "everything","uncompleted","changefolder","clearbubbles");
-
- my $result .= (<
-
-