version 1.98, 2002/11/08 18:35:37
|
version 1.100, 2002/11/08 19:10:58
|
Line 48 use Apache::loncommon();
|
Line 48 use Apache::loncommon();
|
use GDBM_File; |
use GDBM_File; |
use POSIX qw (floor strftime); |
use POSIX qw (floor strftime); |
|
|
# -------------------------------------------------------------- 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; |
|
|
|
# This parameter keeps track of whether obtaining the user's information |
|
# failed, which the colorizer in astatus can use |
|
my $networkFailedFlag = 0; |
|
|
|
# ------------------------------------------------------------------ Euclid gcd |
|
|
|
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; |
|
} |
|
return $b; |
|
} |
|
|
|
# --------------------------------------------------------------------- Parmval |
|
|
|
# -------------------------------------------- Figure out a cascading parameter |
|
# |
|
# For this function to work |
|
# |
|
# * parmhash needs to be tied |
|
# * courseopt and useropt need to be initialized for this user and course |
|
# |
|
|
|
sub parmval { |
|
my ($what,$symb)=@_; |
|
my $cid=$ENV{'request.course.id'}; |
|
my $csec=$ENV{'request.course.sec'}; |
|
my $uname=$ENV{'user.name'}; |
|
my $udom=$ENV{'user.domain'}; |
|
|
|
unless ($symb) { return ''; } |
|
my $result=''; |
|
|
|
my ($mapname,$id,$fn)=split(/\_\_\_/,$symb); |
|
|
|
# ----------------------------------------------------- Cascading lookup scheme |
|
my $rwhat=$what; |
|
$what=~s/^parameter\_//; |
|
$what=~s/\_/\./; |
|
|
|
my $symbparm=$symb.'.'.$what; |
|
my $mapparm=$mapname.'___(all).'.$what; |
|
my $usercourseprefix=$uname.'_'.$udom.'_'.$cid; |
|
|
|
my $seclevel= $usercourseprefix.'.['.$csec.'].'.$what; |
|
my $seclevelr=$usercourseprefix.'.['.$csec.'].'.$symbparm; |
|
my $seclevelm=$usercourseprefix.'.['.$csec.'].'.$mapparm; |
|
|
|
my $courselevel= $usercourseprefix.'.'.$what; |
|
my $courselevelr=$usercourseprefix.'.'.$symbparm; |
|
my $courselevelm=$usercourseprefix.'.'.$mapparm; |
|
|
|
# ---------------------------------------------------------- first, check user |
|
if (defined($uname)) { |
|
if (defined($useropt{$courselevelr})) { return $useropt{$courselevelr}; } |
|
if (defined($useropt{$courselevelm})) { return $useropt{$courselevelm}; } |
|
if (defined($useropt{$courselevel})) { return $useropt{$courselevel}; } |
|
} |
|
|
|
# ------------------------------------------------------- second, check course |
|
if (defined($csec)) { |
|
if (defined($courseopt{$seclevelr})) { return $courseopt{$seclevelr}; } |
|
if (defined($courseopt{$seclevelm})) { return $courseopt{$seclevelm}; } |
|
if (defined($courseopt{$seclevel})) { return $courseopt{$seclevel}; } |
|
} |
|
|
|
if (defined($courseopt{$courselevelr})) { return $courseopt{$courselevelr}; } |
|
if (defined($courseopt{$courselevelm})) { return $courseopt{$courselevelm}; } |
|
if (defined($courseopt{$courselevel})) { return $courseopt{$courselevel}; } |
|
|
|
# ----------------------------------------------------- third, check map parms |
|
|
|
my $thisparm=$parmhash{$symbparm}; |
|
if (defined($thisparm)) { return $thisparm; } |
|
|
|
# ----------------------------------------------------- fourth , check default |
|
|
|
my $default=&Apache::lonnet::metadata($fn,$rwhat.'.default'); |
|
if (defined($default)) { return $default} |
|
|
|
# --------------------------------------------------- fifth , cascade up parts |
|
|
|
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 (defined($partgeneral)) { return $partgeneral; } |
|
} else { |
|
my $resourcegeneral=&parmval("0.$qualifier",$symb); |
|
if (defined($resourcegeneral)) { return $resourcegeneral; } |
|
} |
|
} |
|
return ''; |
|
} |
|
|
|
|
|
|
|
# ------------------------------------------------------------- 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}; |
|
} |
|
} |
|
} |
|
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); |
|
} |
|
} |
|
} 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'; |
|
} else { |
|
if ($code==0) { $code=1; } |
|
} |
|
} |
|
|
|
return 'p'.$code.$tcode.'"'.$ctext.'"'; |
|
} |
|
|
|
|
|
sub addresource { |
|
my ($resource,$sofar,$rid,$showtypes,$indent,$linkid)=@_; |
|
if ($showtypes eq 'problems') { |
|
if ($resource!~/\.(problem|exam|quiz|assess|survey|form)$/) { |
|
return; |
|
} |
|
} |
|
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; |
|
} |
|
} |
|
} |
|
} |
|
|
|
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; } |
|
} |
|
} |
|
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; |
|
} |
|
} |
|
$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 ((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=''; } |
|
} |
|
} else { |
|
&addresource($hash{'src_'.$rid},\$sofar,$rid,$showtypes, |
|
$indent,$linkid); |
|
} |
|
|
|
if (defined($hash{'to_'.$rid})) { |
|
$further=&followlinks($rid,$sofar,$beenhere,$further,$showtypes, |
|
$indent,$linkid); |
|
} |
|
|
|
return $further; |
|
} |
|
|
|
# ================================================================ Main Handler |
|
|
|
sub handler { |
sub handler { |
my $r=shift; |
my $r = shift; |
|
|
&Apache::loncommon::get_unprocessed_cgi($ENV{QUERY_STRING}); |
&Apache::loncommon::get_unprocessed_cgi($ENV{QUERY_STRING}); |
|
|
if ($ENV{'form.jtest'} ne "1") |
|
{ |
|
return new_handle($r); |
|
} |
|
|
|
# ------------------------------------------- Set document type for header only |
|
|
|
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; |
|
} |
|
|
|
# ------------------------------------------------------------------- Hash tied |
|
|
|
if ($ENV{'browser.mathml'}) { |
|
$r->content_type('text/xml'); |
|
} else { |
|
$r->content_type('text/html'); |
|
} |
|
&Apache::loncommon::no_cache($r); |
|
$r->send_http_header; |
|
|
|
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('<html><body>Coursemap undefined.</body></html>'); |
|
} else { |
|
|
|
# ----------------------------------------------------------------- Render page |
|
# -------------------------------------------------------------- Set parameters |
|
|
|
|
|
# ---------------------------- initialize coursedata and userdata for this user |
|
undef %courseopt; |
|
undef %useropt; |
|
|
|
my $uname=$ENV{'user.name'}; |
|
my $udom=$ENV{'user.domain'}; |
|
my $uhome=$ENV{'user.home'}; |
|
my $cid=$ENV{'request.course.id'}; |
|
my $chome=$ENV{'course.'.$cid.'.home'}; |
|
my ($cdom,$cnum)=split(/\_/,$cid); |
|
|
|
my $userprefix=$uname.'_'.$udom.'_'; |
|
|
|
unless ($uhome eq 'no_host') { |
|
# ------------------------------------------------- Get coursedata (if present) |
|
unless ((time-$courserdatas{$cid.'.last_cache'})<240) { |
|
my $reply=&Apache::lonnet::reply('dump:'.$cdom.':'.$cnum. |
|
':resourcedata',$chome); |
|
if ($reply!~/^error\:/) { |
|
$courserdatas{$cid}=$reply; |
|
$courserdatas{$cid.'.last_cache'}=time; |
|
} |
|
# check to see if network failed |
|
elsif ( $reply=~/no.such.host/i || $reply=~/con.*lost/i ) |
|
{ |
|
$networkFailedFlag = 1; |
|
} |
|
} |
|
foreach (split(/\&/,$courserdatas{$cid})) { |
|
my ($name,$value)=split(/\=/,$_); |
|
$courseopt{$userprefix.&Apache::lonnet::unescape($name)}= |
|
&Apache::lonnet::unescape($value); |
|
} |
|
# --------------------------------------------------- Get userdata (if present) |
|
unless ((time-$userrdatas{$uname.'___'.$udom.'.last_cache'})<240) { |
|
my $reply=&Apache::lonnet::reply('dump:'.$udom.':'.$uname.':resourcedata',$uhome); |
|
if ($reply!~/^error\:/) { |
|
$userrdatas{$uname.'___'.$udom}=$reply; |
|
$userrdatas{$uname.'___'.$udom.'.last_cache'}=time; |
|
} |
|
} |
|
foreach (split(/\&/,$userrdatas{$uname.'___'.$udom})) { |
|
my ($name,$value)=split(/\=/,$_); |
|
$useropt{$userprefix.&Apache::lonnet::unescape($name)}= |
|
&Apache::lonnet::unescape($value); |
|
} |
|
} |
|
|
|
@rows=(); |
|
|
|
&tracetable(0,$firstres,'&','',0); |
|
|
|
# ------------------------------------------------------------------ Page parms |
|
|
|
my $j; |
|
my $i; |
|
my $lcm=1; |
|
my $contents=0; |
|
|
|
# ---------------------------------------------- Go through table to get layout |
|
|
|
for ($i=0;$i<=$#rows;$i++) { |
|
if ($rows[$i]) { |
|
&Apache::lonxml::debug("Row $i is:".$rows[$i]); |
|
$contents++; |
|
my @colcont=split(/\&/,$rows[$i]); |
|
$lcm*=($#colcont+1)/euclid($lcm,($#colcont+1)); |
|
} |
|
} |
|
|
|
|
|
unless ($contents) { |
|
$r->print('<html><body>Empty Map.</body></html>'); |
|
} else { |
|
|
|
# ------------------------------------------------------------------ Build page |
|
|
|
my $currenturl=$ENV{'form.postdata'}; |
|
$currenturl=~s/^http\:\/\///; |
|
$currenturl=~s/^[^\/]+//; |
|
|
|
# ---------------------------------------------------------------- Send headers |
|
|
|
my $date=localtime; |
|
my $now=time; |
|
# ----------------------------------------- Get email status and discussiontime |
|
|
|
my %emailstatus=&Apache::lonnet::dump('email_status'); |
|
my $logouttime=$emailstatus{'logout'}; |
|
my $courseleave=$emailstatus{'logout_'.$ENV{'request.course.id'}}; |
|
my $lastcheck=($courseleave>$logouttime?$courseleave:$logouttime); |
|
|
|
my %discussiontimes=&Apache::lonnet::dump('discussiontimes', |
|
$cdom,$cnum); |
|
|
|
my %feedback=(); |
|
my %error=(); |
|
foreach my $msgid (split(/\&/,&Apache::lonnet::reply('keys:'. |
|
$ENV{'user.domain'}.':'. |
|
$ENV{'user.name'}.':nohist_email', |
|
$ENV{'user.home'}))) { |
|
$msgid=&Apache::lonnet::unescape($msgid); |
|
my $plain=&Apache::lonnet::unescape(&Apache::lonnet::unescape($msgid)); |
|
if ($plain=~/(Error|Feedback) \[([^\]]+)\]/) { |
|
my ($what,$url)=($1,$2); |
|
my %status= |
|
&Apache::lonnet::get('email_status',[$msgid]); |
|
if ($status{$msgid}=~/^error\:/) { |
|
$status{$msgid}=''; |
|
} |
|
|
|
if (($status{$msgid} eq 'new') || |
|
(!$status{$msgid})) { |
|
if ($what eq 'Error') { |
|
$error{$url}.=','.$msgid; |
|
} else { |
|
$feedback{$url}.=','.$msgid; |
|
} |
|
} |
|
} |
|
} |
|
# ----------------------------------------------------------- Start Page Output |
|
my $bodytagadd=''; |
|
$r->print( |
|
'<html><head><title>Navigate Course Map</title></head>'); |
|
if (($currenturl=~/^\/res/) && |
|
($currenturl!~/^\/res\/adm/)) { |
|
$bodytagadd='onLoad="window.location.hash='."'curloc'".'"'; |
|
} |
|
$r->print(&Apache::loncommon::bodytag('Navigate Course Map','', |
|
$bodytagadd)); |
|
$r->print('<script>window.focus();</script>'); |
|
my $desc=$ENV{'course.'.$ENV{'request.course.id'}.'.description'}; |
|
if (defined($desc)) { $r->print("<h2>$desc</h2>\n"); } |
|
$r->print("<h3>$date</h3>\n"); |
|
$r->rflush(); |
|
$r->print('<img src="/adm/lonMisc/chat.gif"> New discussion since '. |
|
localtime($lastcheck). |
|
'<br><img src="/adm/lonMisc/feedback.gif"> New message (click to open)<p>'); |
|
if (($currenturl=~/^\/res/) && |
|
($currenturl!~/^\/res\/adm/)) { |
|
$r->print('<a href="#curloc">Current Location</a><p>'); |
|
} |
|
|
|
# Handle a network error |
|
|
|
if ($networkFailedFlag) |
|
{ |
|
$r->print('<H2>LON-CAPA network failure.</H2>'."\n"); |
|
$r->print("<p>LON-CAPA's network is having difficulties, some problem" . |
|
" information, such as due dates, will not be available."); |
|
} |
|
# ----------------------------------------------------- The little content list |
|
for ($i=0;$i<=$#rows;$i++) { |
|
if ($rows[$i]) { |
|
my @colcont=split(/\&/,$rows[$i]); |
|
my $avespan=$lcm/($#colcont+1); |
|
for ($j=0;$j<=$#colcont;$j++) { |
|
my $rid=$colcont[$j]; |
|
if ($rid=~/^h(.+)/) { |
|
$rid=$1; |
|
$r->print(' <a href="#'. |
|
$rid.'">'.$hash{'title_'.$rid}. |
|
'</a><br>'); |
|
} |
|
} |
|
} |
|
} |
|
# ----------------------------------------------------------------- Start table |
|
$r->print('<hr><table cols="'.$lcm.'" border="0">'); |
|
for ($i=0;$i<=$#rows;$i++) { |
|
if ($rows[$i]) { |
|
$r->print("\n<tr>"); |
|
my @colcont=split(/\&/,$rows[$i]); |
|
my $avespan=$lcm/($#colcont+1); |
|
|
|
# for each item I wish to print on this row... |
|
for ($j=0;$j<=$#colcont;$j++) { |
|
my $indent;my $indentstr; |
|
my $linkid; |
|
my $rid=$colcont[$j]; |
|
$rid=~/(\d+)\.(\d+)$/; |
|
my $src= |
|
&Apache::lonnet::declutter($hash{'src_'.$1.'.'.$2}); |
|
my $symb= |
|
&Apache::lonnet::declutter($hash{'map_id_'.$1}).'___'.$2.'___'.$src; |
|
my $add='<td>'; |
|
my $adde='</td>'; |
|
my $hwk='<font color="#223322">'; |
|
my $hwke='</font>'; |
|
if ($rid=~/^l(\d+\.\d+),(.+)/) { $linkid=$1; $rid=$2; } |
|
if ($rid=~/^i(\d+),(.+)/) { $indent=$1; $rid=$2; } |
|
if ($rid=~/^h(.+)/) { |
|
$rid=$1; |
|
$add='<th bgcolor="#AAFF55"><a name="'.$rid.'">'; |
|
$adde='</th>'; |
|
if (($ENV{'user.adv'}) && |
|
($parmhash{$symb.'.0.parameter_randompick'})) { |
|
$adde=' (randomly select '. |
|
$parmhash{$symb.'.0.parameter_randompick'}. |
|
')</th>'; |
|
} |
|
} |
|
if ($rid=~/^j(.+)/) { $rid=$1; } |
|
if ($rid=~/^p(\d)(\d)\"([\w\: \(\)\/\,]*)\"(.+)/) { |
|
# sub astatus describes what code/tcode mean |
|
my $code=$1; |
|
my $tcode=$2; |
|
my $ctext=$3; |
|
$rid=$4; |
|
|
|
# will open later |
|
if ($tcode eq '1') { |
|
$add='<td bgcolor="#AAAAAA">'; |
|
} |
|
|
|
# solved/correct |
|
if ($code eq '3') { |
|
$add='<td bgcolor="#AAFFAA">'; |
|
} elsif ($code eq '4') { # partially correct |
|
$add='<td bgcolor="#E0FFAA">'; |
|
} else { |
|
# not attempted |
|
|
|
# we end up here on network failure, so pick a neutral |
|
# color if the network failed instead of bright red. |
|
if ( $networkFailedFlag ) |
|
{ |
|
$add = '<td bgcolor="#AAAAAA">'; |
|
} |
|
else |
|
{ |
|
$add='<td bgcolor="#FFAAAA">'; |
|
} |
|
|
|
if ($tcode eq '2') { # open, not past due |
|
$add='<td bgcolor="#FFFFAA">'; |
|
} |
|
|
|
if ($tcode eq '4') { # due in next 24 hours |
|
$add='<td bgcolor="#FFFF33">'; |
|
$adde='</td>'; |
|
} |
|
} |
|
$hwk='<font color="#888811"><b>'; |
|
$hwke='</b></font>'; |
|
if ($code eq '1') { |
|
$hwke='</b> ('.$ctext.')</font>'; |
|
} |
|
if ($code eq '2' || $code eq '4') { |
|
$hwk='<font color="#992222"><b>'; |
|
$hwke='</b> ('.$ctext.')</font>'; |
|
} |
|
if ($code eq '3') { |
|
$hwk='<font color="#229922"><b>'; |
|
$hwke='</b> ('.$ctext.')</font>'; |
|
} |
|
if ($networkFailedFlag) |
|
{ |
|
$hwke='</b> (status unavailable)</font>'; |
|
} |
|
} |
|
if ($rid && $hash{'src_'.$rid} eq $currenturl) { |
|
$add=$add.'<a name="curloc"></a>'. |
|
'<font color=red size=+2><b>> </b></font>'; |
|
$adde= |
|
'<font color=red size=+2><b> <</b></font>'.$adde; |
|
} |
|
if ($discussiontimes{$symb}>$lastcheck) { |
|
$adde= |
|
'<img border=0 src="/adm/lonMisc/chat.gif">'. |
|
$adde; |
|
} |
|
if ($error{$src}) { |
|
foreach (split(/\,/,$error{$src})) { |
|
if ($_) { |
|
$adde= |
|
' <a href="/adm/email?display='. |
|
&Apache::lonnet::escape($_). |
|
'"><img src="/adm/lonMisc/bomb.gif" border=0></a>' |
|
.$adde; |
|
} |
|
} |
|
} |
|
if ($feedback{$src}) { |
|
foreach (split(/\,/,$feedback{$src})) { |
|
if ($_) { |
|
$adde= |
|
' <a href="/adm/email?display='. |
|
&Apache::lonnet::escape($_). |
|
'"><img src="/adm/lonMisc/feedback.gif" border=0></a>' |
|
.$adde; |
|
} |
|
} |
|
} |
|
if ($indent) { |
|
my $is=" "; |
|
for(my $i=-1;$i<$indent;$i++) { $indentstr.=$is; } |
|
} |
|
if (!$linkid) { $linkid=$rid; } |
|
if ($hash{'randomout_'.$rid}) { |
|
$adde=' <i>(hidden)</i>'.$adde; |
|
} |
|
$r->print($add.$indentstr); |
|
if ($rid) { |
|
$r->print('<a href="'.$hash{'src_'.$linkid}. |
|
(($hash{'src_'.$linkid}=~/\?/)?'&':'?'). |
|
'symb='.&Apache::lonnet::escape($symb) |
|
.'">'. |
|
$hwk.$hash{'title_'.$rid}.$hwke.'</a>'); |
|
} |
|
$r->print($adde); |
|
} |
|
$r->print('</tr>'); |
|
} |
|
} |
|
$r->print("\n</table>"); |
|
$r->print('</body></html>'); |
|
# -------------------------------------------------------------------- End page |
|
} |
|
# ------------------------------------------------------------- End render page |
|
} |
|
# ------------------------------------------------------------------ Untie hash |
|
unless (untie(%hash)) { |
|
&Apache::lonnet::logthis("<font color=blue>WARNING: ". |
|
"Could not untie coursemap $fn (browse).</font>"); |
|
} |
|
unless (untie(%parmhash)) { |
|
&Apache::lonnet::logthis("<font color=blue>WARNING: ". |
|
"Could not untie parmhash (browse).</font>"); |
|
} |
|
return OK; |
|
} |
|
|
|
sub new_handle { |
|
my $r = shift; |
|
|
|
# Handle header-only request |
# Handle header-only request |
if ($r->header_only) { |
if ($r->header_only) { |
if ($ENV{'browser.mathml'}) { |
if ($ENV{'browser.mathml'}) { |
Line 1669 sub init {
|
Line 922 sub init {
|
|
|
my $userprefix=$uname.'_'.$udom.'_'; |
my $userprefix=$uname.'_'.$udom.'_'; |
|
|
my %courserdatas; my %useropt; my %courseopt; |
my %courserdatas; my %useropt; my %courseopt; my %userrdatas; |
unless ($uhome eq 'no_host') { |
unless ($uhome eq 'no_host') { |
# ------------------------------------------------- Get coursedata (if present) |
# ------------------------------------------------- Get coursedata (if present) |
unless ((time-$courserdatas{$cid.'.last_cache'})<240) { |
unless ((time-$courserdatas{$cid.'.last_cache'})<240) { |
Line 2118 sub new {
|
Line 1371 sub new {
|
} |
} |
} |
} |
} |
} |
if (ref($curRes) && $curRes->is_map() && $direction == FORWARD()) { |
|
my $firstResource = $curRes->map_start(); |
|
my $finishResource = $curRes->map_finish(); |
|
my $newIterator = Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, |
|
$firstResource, |
|
$finishResource, |
|
$self->{FILTER}, |
|
$self->{ALREADY_SEEN}, |
|
$self->{CONDITION}); |
|
} |
|
|
|
# Assign the final val |
# Assign the final val |
if (ref($curRes) && $direction == BACKWARD()) { |
if (ref($curRes) && $direction == BACKWARD()) { |
Line 2296 sub populateStack {
|
Line 1539 sub populateStack {
|
|
|
package Apache::lonnavmaps::DFSiterator; |
package Apache::lonnavmaps::DFSiterator; |
|
|
# UNDOCUMENTED: This is a private library, it should not generally be used |
# Not documented in the perldoc: This is a simple iterator that just walks |
# by the outside world. What it does is walk through the nav map in a |
# through the nav map and presents the resources in a depth-first search |
# depth-first fashion. This is not appropriate for most uses, but it is |
# fashion, ignorant of conditionals, randomized resources, etc. It presents |
# used by the main iterator for pre-processing. It also is able to isolate |
# BEGIN_MAP and END_MAP, but does not understand branches at all. It is |
# much of the complexity of the main iterator, so the main iterator is much |
# useful for pre-processing of some kind, and is in fact used by the main |
# simpler. |
# iterator that way, but that's about it. |
# There is no real benefit in merging the main iterator and this one into one class... |
# One could imagine merging this into the init routine of the main iterator, |
# all the logic in DFSiterator would need to be replicated, you gain no performance, |
# but this might as well be left seperate, since it is possible some other |
# at best, you just make one massively complicated iterator in place of two |
# use might be found for it. - Jeremy |
# somewhat complicated ones. ;-) - Jeremy |
|
|
|
# Here are the tokens for the iterator, replicated from iterator for convenience: |
|
|
|
sub BEGIN_MAP { return 1; } # begining of a new map |
sub BEGIN_MAP { return 1; } # begining of a new map |
sub END_MAP { return 2; } # end of the map |
sub END_MAP { return 2; } # end of the map |
sub BEGIN_BRANCH { return 3; } # beginning of a branch |
|
sub END_BRANCH { return 4; } # end of a branch |
|
sub FORWARD { return 1; } # go forward |
sub FORWARD { return 1; } # go forward |
sub BACKWARD { return 2; } |
sub BACKWARD { return 2; } |
|
|
# Params: nav map, start resource, end resource, filter, condition, |
# Params: Nav map ref, first resource id/ref, finish resource id/ref, |
# already seen hash ref |
# filter hash ref (or undef), already seen hash or undef, condition |
|
# (as in main iterator), direction FORWARD or BACKWARD (undef->forward). |
sub new { |
sub new { |
# magic invocation to create a class instance |
# magic invocation to create a class instance |
my $proto = shift; |
my $proto = shift; |
Line 2328 sub new {
|
Line 1566 sub new {
|
$self->{NAV_MAP} = shift; |
$self->{NAV_MAP} = shift; |
return undef unless ($self->{NAV_MAP}); |
return undef unless ($self->{NAV_MAP}); |
|
|
# 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 2347 sub new {
|
Line 1584 sub new {
|
$self->{CONDITION} = shift; |
$self->{CONDITION} = shift; |
$self->{DIRECTION} = shift || FORWARD(); |
$self->{DIRECTION} = shift || FORWARD(); |
|
|
# Flag: Have we started yet? If not, the first action is to return BEGIN_MAP. |
# Flag: Have we started yet? |
$self->{STARTED} = 0; |
$self->{STARTED} = 0; |
|
|
# Should we continue calling the recursive iterator, if any? |
# Should we continue calling the recursive iterator, if any? |
Line 2361 sub new {
|
Line 1598 sub new {
|
$self->{RECURSIVE_DEPTH} = 0; |
$self->{RECURSIVE_DEPTH} = 0; |
|
|
# For keeping track of our branches, we maintain our own stack |
# For keeping track of our branches, we maintain our own stack |
$self->{BRANCH_STACK} = []; |
$self->{STACK} = []; |
# If the size shrinks, we exhausted a branch |
|
$self->{BRANCH_STACK_SIZE} = 0; |
|
$self->{BRANCH_DEPTH} = 0; |
|
|
|
# For returning two things in a forced sequence |
|
$self->{FORCE_NEXT} = undef; |
|
|
|
# Start with the first resource |
# Start with the first resource |
if ($self->{DIRECTION} == FORWARD) { |
if ($self->{DIRECTION} == FORWARD) { |
push @{$self->{BRANCH_STACK}}, $self->{FIRST_RESOURCE}; |
push @{$self->{STACK}}, $self->{FIRST_RESOURCE}; |
} else { |
} else { |
push @{$self->{BRANCH_STACK}}, $self->{FINISH_RESOURCE}; |
push @{$self->{STACK}}, $self->{FINISH_RESOURCE}; |
} |
} |
$self->{BRANCH_STACK_SIZE} = 1; |
|
|
|
bless($self); |
bless($self); |
return $self; |
return $self; |
} |
} |
|
|
# Note... this function is *touchy*. I strongly recommend tracing |
|
# through it with the debugger a few times on a non-trivial map before |
|
# modifying it. Order is *everything*. |
|
sub next { |
sub next { |
my $self = shift; |
my $self = shift; |
|
|
# Iterator logic goes here |
|
|
|
# Are we using a recursive iterator? If so, pull from that and |
# Are we using a recursive iterator? If so, pull from that and |
# watch the depth; we want to resume our level at the correct time. |
# watch the depth; we want to resume our level at the correct time. |
if ($self->{RECURSIVE_ITERATOR_FLAG}) { |
if ($self->{RECURSIVE_ITERATOR_FLAG}) { |
Line 2407 sub next {
|
Line 1632 sub next {
|
return $next; |
return $next; |
} |
} |
|
|
# Is this return value pre-determined? |
|
if (defined($self->{FORCE_NEXT})) { |
|
my $tmp = $self->{FORCE_NEXT}; |
|
$self->{FORCE_NEXT} = undef; |
|
return $tmp; |
|
} |
|
|
|
# Is there a current resource to grab? If not, then return |
# Is there a current resource to grab? If not, then return |
# END_BRANCH and END_MAP in succession. |
# END_MAP, which will end the iterator. |
if (scalar(@{$self->{BRANCH_STACK}}) == 0) { |
if (scalar(@{$self->{STACK}}) == 0) { |
if ($self->{BRANCH_DEPTH} > 0) { |
return $self->END_MAP(); |
$self->{FORCE_NEXT} = $self->END_MAP(); |
|
$self->{BRANCH_DEPTH}--; |
|
return $self->END_BRANCH(); |
|
} else { |
|
return $self->END_MAP(); |
|
} |
|
} |
} |
|
|
# Have we not yet begun? If not, return BEGIN_MAP and |
# Have we not yet begun? If not, return BEGIN_MAP and |
Line 2433 sub next {
|
Line 1645 sub next {
|
return $self->BEGIN_MAP; |
return $self->BEGIN_MAP; |
} |
} |
|
|
# Did the branch stack shrink since last run? If so, |
|
# we exhausted a branch last time, therefore, we're about |
|
# to start a new one. (We know because we already checked to see |
|
# if the stack was empty.) |
|
if ( scalar (@{$self->{BRANCH_STACK}}) < $self->{BRANCH_STACK_SIZE}) { |
|
$self->{BRANCH_STACK_SIZE} = scalar(@{$self->{BRANCH_STACK}}); |
|
$self->{BRANCH_DEPTH}++; |
|
return $self->BEGIN_BRANCH(); |
|
} |
|
|
|
# Remember the size for comparision next time. |
|
$self->{BRANCH_STACK_SIZE} = scalar(@{$self->{BRANCH_STACK}}); |
|
|
|
# If the next resource we mean to return is going to need |
|
# a lower branch level, terminate branches until we get |
|
# there. |
|
|
|
# Get the next resource in the branch |
# Get the next resource in the branch |
$self->{HERE} = pop @{$self->{BRANCH_STACK}}; |
$self->{HERE} = pop @{$self->{STACK}}; |
|
|
# Are we at the right depth? If not, close a branch and return |
# remember that we've seen this, so we don't return it again later |
# the current resource onto the branch stack |
|
# Note: There seems to be some bugs here, so don't rely |
|
# on this, use the real iterator instead. |
|
if (defined($self->{HERE}->{DATA}->{ITERATOR_DEPTH}) |
|
&& $self->{HERE}->{DATA}->{ITERATOR_DEPTH} < |
|
$self->{BRANCH_DEPTH} ) { |
|
$self->{BRANCH_DEPTH}--; |
|
# return it so we can pick it up eventually |
|
push @{$self->{BRANCH_STACK}}, $self->{HERE}; |
|
return $self->END_BRANCH(); |
|
} |
|
|
|
# We always return it after this point and never before |
|
# (proof: look at just the return statements), so we |
|
# remember that we've seen this. |
|
$self->{ALREADY_SEEN}->{$self->{HERE}->{ID}} = 1; |
$self->{ALREADY_SEEN}->{$self->{HERE}->{ID}} = 1; |
|
|
# Get the next possible resources |
# Get the next possible resources |
Line 2481 sub next {
|
Line 1661 sub next {
|
my $next = []; |
my $next = []; |
|
|
# filter the next possibilities to remove things we've |
# filter the next possibilities to remove things we've |
# already seen. Also, remember what branch depth they should |
# already seen. |
# be displayed at, since there's no other reliable way to tell. |
|
foreach (@$nextUnfiltered) { |
foreach (@$nextUnfiltered) { |
if (!defined($self->{ALREADY_SEEN}->{$_->{ID}})) { |
if (!defined($self->{ALREADY_SEEN}->{$_->{ID}})) { |
push @$next, $_; |
push @$next, $_; |
$_->{DATA}->{ITERATOR_DEPTH} = |
|
$self->{BRANCH_DEPTH} + 1; |
|
} |
} |
} |
} |
|
|
# Handle branch cases: |
|
# Nothing is available next: BRANCH_END |
|
# 1 thing next: standard non-branch |
|
# 2+ things next: have some branches |
|
my $nextCount = scalar(@$next); |
|
if ($nextCount == 0) { |
|
# Return this and on the next run, close the branch up if we're |
|
# in a branch |
|
if ($self->{BRANCH_DEPTH} > 0 ) { |
|
$self->{FORCE_NEXT} = $self->END_BRANCH(); |
|
$self->{BRANCH_DEPTH}--; |
|
} |
|
} |
|
|
|
while (@$next) { |
while (@$next) { |
# copy the next possibilities over to the branch stack |
# copy the next possibilities over to the stack |
# in the right order |
push @{$self->{STACK}}, shift @$next; |
push @{$self->{BRANCH_STACK}}, shift @$next; |
|
} |
|
|
|
if ($nextCount >= 2) { |
|
$self->{FORCE_NEXT} = $self->BEGIN_BRANCH(); |
|
$self->{BRANCH_DEPTH}++; |
|
return $self->{HERE}; |
|
} |
} |
|
|
# If this is a map and we want to recurse down it... (not filtered out) |
# If this is a map and we want to recurse down it... (not filtered out) |
Line 2533 sub next {
|
Line 1689 sub next {
|
return $self->{HERE}; |
return $self->{HERE}; |
} |
} |
|
|
sub getStack { |
|
my $self=shift; |
|
|
|
my @stack; |
|
|
|
$self->populateStack(\@stack); |
|
|
|
return \@stack; |
|
} |
|
|
|
# Private method: Calls the iterators recursively to populate the stack. |
|
sub populateStack { |
|
my $self=shift; |
|
my $stack = shift; |
|
|
|
push @$stack, $self->{HERE} if ($self->{HERE}); |
|
|
|
if ($self->{RECURSIVE_ITERATOR_FLAG}) { |
|
$self->{RECURSIVE_ITERATOR}->populateStack($stack); |
|
} |
|
} |
|
|
|
1; |
1; |
|
|
package Apache::lonnavmaps::resource; |
package Apache::lonnavmaps::resource; |