--- loncom/interface/lonnavmaps.pm 2002/09/24 20:01:05 1.54
+++ loncom/interface/lonnavmaps.pm 2006/03/19 19:50:40 1.373
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# Navigate Maps Handler
#
-# $Id: lonnavmaps.pm,v 1.54 2002/09/24 20:01:05 bowersj2 Exp $
+# $Id: lonnavmaps.pm,v 1.373 2006/03/19 19:50:40 albertel Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -25,1079 +25,2099 @@
#
# 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
-#
+###
package Apache::lonnavmaps;
use strict;
+use GDBM_File;
use Apache::Constants qw(:common :http);
-use Apache::lonnet();
use Apache::loncommon();
-use HTML::TokeParser;
-use GDBM_File;
-
-# -------------------------------------------------------------- Module Globals
-my %hash;
-my @rows;
-
-#
-# These cache hashes need to be independent of user, resource and course
-# (user and course can/should be in the keys)
-#
-
-my %courserdatas;
-my %userrdatas;
-
-#
-# These global hashes are dependent on user, course and resource,
-# and need to be initialized every time when a sheet is calculated
-#
-my %courseopt;
-my %useropt;
-my %parmhash;
+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
+}
-# This parameter keeps track of whether obtaining the user's information
-# failed, which the colorizer in astatus can use
-my $networkFailedFlag = 0;
+sub handler {
+ my $r = shift;
+ real_handler($r);
+}
-# ------------------------------------------------------------------ Euclid gcd
+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;
+ }
-sub euclid {
- my ($e,$f)=@_;
- my $a; my $b; my $r;
- if ($e>$f) { $b=$e; $r=$f; } else { $r=$e; $b=$f; }
- while ($r!=0) {
- $a=$b; $b=$r;
- $r=$a%$b;
+ # 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');
}
- return $b;
-}
+ &Apache::loncommon::no_cache($r);
-# --------------------------------------------------------------------- Parmval
+ 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");
+ }
+
+ my $jumpToFirstHomework = 0;
+ # Check to see if the student is jumping to next open, do-able problem
+ if ($ENV{QUERY_STRING} =~ /^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);
- my $courselevel= $usercourseprefix.'.'.$what;
- my $courselevelr=$usercourseprefix.'.'.$symbparm;
- my $courselevelm=$usercourseprefix.'.'.$mapparm;
+ # Mark as both "here" and "jump"
+ $env{'form.postsymb'} = $curRes->symb();
+ }
+ }
+ }
-# ---------------------------------------------------------- first, check user
- if ($uname) {
- if ($useropt{$courselevelr}) { return $useropt{$courselevelr}; }
- if ($useropt{$courselevelm}) { return $useropt{$courselevelm}; }
- if ($useropt{$courselevel}) { return $useropt{$courselevel}; }
+ # 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 Me My First Homework Problem");
+ }
+
+ my $suppressEmptySequences = 0;
+ my $filterFunc = undef;
+ my $resource_no_folder_link = 0;
+
+ # Display only due homework.
+ my $showOnlyHomework = 0;
+ if ($env{'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 Homework")."
");
+ $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 Homework");
+ }
+
+ 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");
-# ------------------------------------------------------- second, check course
- if ($csec) {
- if ($courseopt{$seclevelr}) { return $courseopt{$seclevelr}; }
- if ($courseopt{$seclevelm}) { return $courseopt{$seclevelm}; }
- if ($courseopt{$seclevel}) { return $courseopt{$seclevel}; }
- }
+ $r->print(&Apache::loncommon::end_page());
+ $r->rflush();
- if ($courseopt{$courselevelr}) { return $courseopt{$courselevelr}; }
- if ($courseopt{$courselevelm}) { return $courseopt{$courselevelm}; }
- if ($courseopt{$courselevel}) { return $courseopt{$courselevel}; }
+ return OK;
+}
-# ----------------------------------------------------- third, check map parms
+# 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;
- my $thisparm=$parmhash{$symbparm};
- if ($thisparm) { return $thisparm; }
+ return join (",", keys(%hash));
+}
-# ----------------------------------------------------- fourth , check default
+sub removeFromFilter {
+ my $hashIn = shift;
+ my $subtraction = shift;
+ my %hash = %$hashIn;
- my $default=&Apache::lonnet::metadata($fn,$rwhat.'.default');
- if ($default) { return $default}
+ delete $hash{$subtraction};
+ return join(",", keys(%hash));
+}
-# --------------------------------------------------- fifth , cascade up parts
+# Convenience function: Given a stack returned from getStack on the iterator,
+# return the correct src() value.
+sub getLinkForResource {
+ my $stack = shift;
+ my $res;
- my ($space,@qualifier)=split(/\./,$rwhat);
- my $qualifier=join('.',@qualifier);
- unless ($space eq '0') {
- my ($part,$id)=split(/\_/,$space);
- if ($id) {
- my $partgeneral=&parmval($part.".$qualifier",$symb);
- if ($partgeneral) { return $partgeneral; }
- } else {
- my $resourcegeneral=&parmval("0.$qualifier",$symb);
- if ($resourcegeneral) { return $resourcegeneral; }
+ # 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);
+ }
}
}
- return '';
+
+ # 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);
-# ------------------------------------------------------------- Find out status
-# return codes
-# tcode (timecode)
-# 1: will open later
-# 2: is open and not past due yet
-# 3: is past due date
-# 4: due in the next 24 hours
-#
-# code (curent solved status)
-# 1: not attempted
-# 2: attempted but wrong, or incorrect by instructor
-# 3: solved or correct by instructor
-# 4: partially correct (one or more parts correct)
-# "excused" needs to be supported, but is not yet.
-sub astatus {
- my $rid=shift;
- my $code=0;
- my $ctext='';
- $rid=~/(\d+)\.(\d+)/;
- my $symb=&Apache::lonnet::declutter($hash{'map_id_'.$1}).'___'.$2.'___'.
- &Apache::lonnet::declutter($hash{'src_'.$rid});
- my %duedate=();
- my %opendate=();
- my %answerdate=();
- # need to always check part 0's open/due/answer status
- foreach (sort(split(/\,/,&Apache::lonnet::metadata($hash{'src_'.$rid},'allpo\ssiblekeys')))) {
- if ($_=~/^parameter\_(.*)\_opendate$/) {
- my $part=$1;
- $duedate{$part}=&parmval($part.'.duedate',$symb);
- $opendate{$part}=&parmval($part.'.opendate',$symb);
- $answerdate{$part}=&parmval($part.'.answerdate',$symb);
- if (&parmval($part.'.opendate.type',$symb) eq 'date_interval') {
- $opendate{$part}=$duedate{$part}-$opendate{$part};
- }
- if (&parmval($part.'.answerdate.type',$symb) eq 'date_interval') {
- $answerdate{$part}=$duedate{$part}+$answerdate{$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");
}
}
- my $now=time;
- my $tcode=0;
-
- my %returnhash=&Apache::lonnet::restore($symb);
-
- foreach (sort(keys(%opendate))) {
- my $duedate=$duedate{$_};
- my $opendate=$opendate{$_};
- my $answerdate=$answerdate{$_};
- my $preface='';
- unless ($_ eq '0') { $preface=' Part: '.$_.' '; }
- if ($opendate) {
- if ($now<$duedate || (!$duedate)) {
- unless ($tcode==4) { $tcode=2; }
- if ($duedate) {
- $ctext.=$preface.'Due: '.localtime($duedate);
- } else {
- $ctext.=$preface.'No Due Date';
- }
- if ($now<$opendate) {
- unless ($tcode) { $tcode=1; }
- $ctext.=$preface.'Open: '.localtime($opendate);
- }
- if ($duedate && $duedate-$now<86400) {
- $tcode=4;
- $ctext.=$preface.'Due: '.localtime($duedate);
- }
- } else {
- unless (($tcode==4) || ($tcode eq 2)) { $tcode=3; }
- if ($now<$answerdate) {
- $ctext.='Answer: '.localtime($duedate);
- }
+ 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";
}
- } else {
- unless (($tcode==2) || ($tcode==4)) { $tcode=1; }
}
-
- my $status=$returnhash{'resource.'.$_.'.solved'};
-
- if ($status eq 'correct_by_student') {
- if ($code==0||$code==3) { $code=3; } else { $code=4; }
- $ctext.=' solved';
- } elsif ($status eq 'correct_by_override') {
- if ($code==0||$code==3) { $code=3; } else { $code=4; }
- $ctext.=' override';
- } elsif ($status eq 'incorrect_attempted') {
- if ($code!=4 && $code!=3) { $code=2; }
- if ($code==3) { $code=4; }
- $ctext.=' ('.
- ($returnhash{'resource.'.$_.'.tries'}?
- $returnhash{'resource.'.$_.'.tries'}:'0');
- my $numtries = &parmval($_.'.maxtries',$symb);
- if ($numtries) { $ctext.='/'.$numtries.' tries'; }
- $ctext.=')';
- } elsif ($status eq 'incorrect_by_override') {
- if ($code!=4 && $code!=3) { $code=2; }
- if ($code==3) { $code=4; }
- $ctext.=' override';
- } elsif ($status eq 'excused') {
- if ($code==0||$code==3) { $code=3; } else { $code=4; }
- $ctext.=' excused';
+ if ($res->duedate($part)) {
+ return &mt("Due")." " . timeToHumanString($res->duedate($part),'end') .
+ " $triesString";
} else {
- if ($code==0) { $code=1; }
+ 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;
- return 'p'.$code.$tcode.'"'.$ctext.'"';
+ 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 addresource {
- my ($resource,$sofar,$rid,$showtypes,$indent,$linkid)=@_;
- if ($showtypes eq 'problems') {
- if ($resource!~/\.(problem|exam|quiz|assess|survey|form)$/) {
- return;
- }
+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";
}
- my $brepriv=&Apache::lonnet::allowed('bre',$resource);
- if ($hash{'src_'.$rid}) {
- if (($brepriv eq '2') || ($brepriv eq 'F')) {
- my $pprefix='';
- if ($resource=~/\.(problem|exam|quiz|assess|survey|form)$/) {
- $pprefix=&astatus($rid);
- }
- $$sofar++;
- if ($indent) { $pprefix='i'.$indent.','.$pprefix; }
- if ($linkid) { $pprefix='l'.$linkid.','.$pprefix; }
- if (defined($rows[$$sofar])) {
- $rows[$$sofar].='&'.$pprefix.$rid;
- } else {
- $rows[$$sofar]=$pprefix.$rid;
- }
- }
+
+ if ($delta < 0) {
+ $inPast = 1;
+ $delta = -$delta;
}
-}
-sub followlinks () {
- my ($rid,$sofar,$beenhere,$further,$showtypes,$indent,$linkid)=@_;
- my $mincond=1;
- my $next='';
- foreach (split(/\,/,$hash{'to_'.$rid})) {
- my $thiscond=
- &Apache::lonnet::directcondval($hash{'condid_'.$hash{'undercond_'.$_}});
- if ($thiscond>=$mincond) {
- if ($next) {
- $next.=','.$_.':'.$thiscond;
- } else {
- $next=$_.':'.$thiscond;
- }
- if ($thiscond>$mincond) { $mincond=$thiscond; }
+ 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";
}
- }
- my $col=0;
- &Apache::lonxml::debug("following links -$next-");
- foreach (split(/\,/,$next)) {
- my ($nextlinkid,$condval)=split(/\:/,$_);
- if ($condval>=$mincond) {
- my $now=&tracetable($sofar,$hash{'goesto_'.$nextlinkid},
- $beenhere,$showtypes,$indent,$linkid);
- if ($now>$further) {
- if ($col>0) {
- my $string;
- for(my $i=0;$i<$col;$i++) { $string.='&'; }
- for(my $i=$further+1;$now-1>$i;$i++) {
- $rows[$i]=$string.$rows[$i];
- }
- }
- $further=$now;
+
+ # 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";
}
- $col++;
- }
- return $further;
-}
-# ------------------------------------------------------------ Build page table
-sub tracetable {
- my ($sofar,$rid,$beenhere,$showtypes,$indent,$linkid)=@_;
- my $newshowtypes=$showtypes;
- my $further=$sofar;
-# $Apache::lonxml::debug=1;
- &Apache::lonxml::debug("$rid ; $linkid ; $sofar ; $beenhere ; ".$hash{'src_'.$rid});
- if ($beenhere=~/\&$rid\&/) { return $further; }
- $beenhere.=$rid.'&';
-
- if (defined($hash{'is_map_'.$rid})) {
- $sofar++;
- my $tprefix='';
- if ($hash{'map_type_'.$hash{'map_pc_'.$hash{'src_'.$rid}}}
- eq 'sequence') {
- $tprefix='h';
- } elsif ($hash{'map_type_'.$hash{'map_pc_'.$hash{'src_'.$rid}}}
- eq 'page') {
- $tprefix='j';
- if ($indent) { $tprefix='i'.$indent.','.$tprefix; }
- if ($linkid) { $tprefix='l'.$linkid.','.$tprefix; }
- $newshowtypes='problems';
- $indent++;
- #if in a .page continue to link the encompising .page
- if (!$linkid) { $linkid=$rid; }
- }
- if (defined($rows[$sofar])) {
- $rows[$sofar].='&'.$tprefix.$rid;
- } else {
- $rows[$sofar]=$tprefix.$rid;
+ # If there's a caller supplied format, use it.
+
+ if($format ne '') {
+ my $timeStr = strftime($format, localtime($time));
+ return $timeStr.&Apache::lonlocal::gettimezone();
+ }
+
+ # 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();
}
- if ((defined($hash{'map_start_'.$hash{'src_'.$rid}})) &&
- (defined($hash{'map_finish_'.$hash{'src_'.$rid}}))) {
- my $frid=$hash{'map_finish_'.$hash{'src_'.$rid}};
- $sofar=&tracetable($sofar,$hash{'map_start_'.$hash{'src_'.$rid}},
- '&'.$frid.'&',$newshowtypes,$indent,$linkid);
- &addresource($hash{'src_'.$frid},\$sofar,$frid,$newshowtypes,
- $indent,$linkid);
- if ($tprefix =~ /j$/) { $indent--; $linkid=''; }
+
+ 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();
}
- } else {
- &addresource($hash{'src_'.$rid},\$sofar,$rid,$showtypes,
- $indent,$linkid);
- }
- if (defined($hash{'to_'.$rid})) {
- $further=&followlinks($rid,$sofar,$beenhere,$further,$showtypes,
- $indent,$linkid);
+ # 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();
}
-
- return $further;
}
-# ================================================================ Main Handler
-sub handler {
- my $r=shift;
+=pod
- &Apache::loncommon::get_unprocessed_cgi($ENV{QUERY_STRING});
+=head1 NAME
- if ($ENV{'form.jtest'} ne "1")
- {
- return new_handle($r);
- }
+Apache::lonnavmap - Subroutines to handle and render the navigation
+ maps
-# ------------------------------------------- Set document type for header only
+=head1 SYNOPSIS
- if ($r->header_only) {
- if ($ENV{'browser.mathml'}) {
- $r->content_type('text/xml');
- } else {
- $r->content_type('text/html');
- }
- $r->send_http_header;
- return OK;
- }
- my $requrl=$r->uri;
- my $hashtied;
-# ----------------------------------------------------------------- Tie db file
- my $fn;
- if ($ENV{'request.course.fn'}) {
- $fn=$ENV{'request.course.fn'};
- if (-e "$fn.db") {
- if ((tie(%hash,'GDBM_File',"$fn.db",&GDBM_READER(),0640)) &&
- (tie(%parmhash,'GDBM_File',
- $ENV{'request.course.fn'}.'_parms.db',
- &GDBM_READER(),0640))) {
- $hashtied=1;
- }
- }
- }
- if (!$hashtied) {
- $ENV{'user.error.msg'}="$requrl:bre:0:0:Course not initialized";
- return HTTP_NOT_ACCEPTABLE;
- }
+The main handler generates the navigational listing for the course,
+the other objects export this information in a usable fashion for
+other modules.
+
+=head1 OVERVIEW
+
+X When a user enters a course, LON-CAPA examines the
+course structure and caches it in what is often referred to as the
+"big hash" X. You can see it if you are logged into
+LON-CAPA, in a course, by going to /adm/test. (You may need to
+tweak the /home/httpd/lonTabs/htpasswd file to view it.) The
+content of the hash will be under the heading "Big Hash".
+
+Big Hash contains, among other things, how resources are related
+to each other (next/previous), what resources are maps, which
+resources are being chosen to not show to the student (for random
+selection), and a lot of other things that can take a lot of time
+to compute due to the amount of data that needs to be collected and
+processed.
+
+Apache::lonnavmaps provides an object model for manipulating this
+information in a higher-level fashion then directly manipulating
+the hash. It also provides access to several auxilary functions
+that aren't necessarily stored in the Big Hash, but are a per-
+resource sort of value, like whether there is any feedback on
+a given resource.
+
+Apache::lonnavmaps also abstracts away branching, and someday,
+conditions, for the times where you don't really care about those
+things.
+
+Apache::lonnavmaps also provides fairly powerful routines for
+rendering navmaps, and last but not least, provides the navmaps
+view for when the user clicks the NAV button.
+
+B: Apache::lonnavmaps I works for the "currently
+logged in user"; if you want things like "due dates for another
+student" lonnavmaps can not directly retrieve information like
+that. You need the EXT function. This module can still help,
+because many things, such as the course structure, are constant
+between users, and Apache::lonnavmaps can help by providing
+symbs for the EXT call.
+
+The rest of this file will cover the provided rendering routines,
+which can often be used without fiddling with the navmap object at
+all, then documents the Apache::lonnavmaps::navmap object, which
+is the key to accessing the Big Hash information, covers the use
+of the Iterator (which provides the logic for traversing the
+somewhat-complicated Big Hash data structure), documents the
+Apache::lonnavmaps::Resource objects that are returned by
+
+=head1 Subroutine: render
+
+The navmap renderer package provides a sophisticated rendering of the
+standard navigation maps interface into HTML. The provided nav map
+handler is actually just a glorified call to this.
+
+Because of the large number of parameters this function accepts,
+instead of passing it arguments as is normal, pass it in an anonymous
+hash with the desired options.
+
+The package provides a function called 'render', called as
+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
+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
+the table.
+
+Any additional generally useful column types should be placed in the
+renderer code here, so anybody can use it anywhere else. Any code
+specific to the current application (such as the addition of
+elements in a column) should be placed in the code of the thing using
+the renderer.
+
+At the core of the renderer is the array reference COLS (see Example
+section below for how to pass this correctly). The COLS array will
+consist of entries of one of two types of things: Either an integer
+representing one of the pre-packaged column types, or a sub reference
+that takes a resource reference, a part number, and a reference to the
+argument hash passed to the renderer, and returns a string that will
+be inserted into the HTML representation as it.
-# ------------------------------------------------------------------- Hash tied
+All other parameters are ways of either changing how the columns
+are printing, or which rows are shown.
- if ($ENV{'browser.mathml'}) {
- $r->content_type('text/xml');
- } else {
- $r->content_type('text/html');
- }
- &Apache::loncommon::no_cache($r);
- $r->send_http_header;
+The pre-packaged column names are refered to by constants in the
+Apache::lonnavmaps namespace. The following currently exist:
- my $firstres=$hash{'map_start_'.
- &Apache::lonnet::clutter($ENV{'request.course.uri'})};
- my $lastres=$hash{'map_finish_'.
- &Apache::lonnet::clutter($ENV{'request.course.uri'})};
- if (!(($firstres) && ($lastres))) {
- $r->print('Coursemap undefined.');
- } else {
+=over 4
-# ----------------------------------------------------------------- Render page
-# -------------------------------------------------------------- Set parameters
+=item * B:
+The general info about the resource: Link, icon for the type, etc. The
+first column in the standard nav map display. This column provides the
+indentation effect seen in the B