--- loncom/interface/lonnavmaps.pm 2015/09/25 17:39:36 1.509
+++ loncom/interface/lonnavmaps.pm 2022/10/06 16:32:04 1.509.2.14.2.4
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# Navigate Maps Handler
#
-# $Id: lonnavmaps.pm,v 1.509 2015/09/25 17:39:36 raeburn Exp $
+# $Id: lonnavmaps.pm,v 1.509.2.14.2.4 2022/10/06 16:32:04 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
@@ -486,7 +486,7 @@ use Apache::lonlocal;
use Apache::lonnet;
use Apache::lonmap;
-use POSIX qw (floor strftime);
+use POSIX qw (ceil floor strftime);
use Time::HiRes qw( gettimeofday tv_interval );
use LONCAPA;
use DateTime();
@@ -577,7 +577,11 @@ sub getLinkForResource {
my $anchor;
if ($res->is_page()) {
foreach my $item (@$stack) { if (defined($item)) { $anchor = $item; } }
- $anchor=&escape($anchor->shown_symb());
+ if ($anchor->encrypted() && !&advancedUser()) {
+ $anchor='LC_'.$anchor->id();
+ } else {
+ $anchor=&escape($anchor->shown_symb());
+ }
return ($res->link(),$res->shown_symb(),$anchor);
}
# in case folder was skipped over as "only sequence"
@@ -643,6 +647,9 @@ sub getDescription {
} elsif ($slot_status == $res->RESERVABLE) {
$slotmsg = &mt('Reservable, reservations close [_1]',
timeToHumanString($slot_time,'end'));
+ } elsif ($slot_status == $res->NEEDS_CHECKIN) {
+ $slotmsg = &mt('Reserved, check-in needed - ends [_1]',
+ timeToHumanString($slot_time,'end'));
} elsif ($slot_status == $res->RESERVABLE_LATER) {
$slotmsg = &mt('Reservable, reservations open [_1]',
timeToHumanString($slot_time,'start'));
@@ -915,6 +922,9 @@ sub render_resource {
my $nonLinkedText = ''; # stuff after resource title not in link
my $link = $params->{"resourceLink"};
+ if ($resource->ext()) {
+ $link =~ s/\#.+(\?)/$1/g;
+ }
# The URL part is not escaped at this point, but the symb is...
@@ -935,8 +945,31 @@ sub render_resource {
# links to open and close the folder
my $whitespace = $location.'/whitespace_21.gif';
- my $linkopen = ""."";
- my $linkclose = "";
+ my ($nomodal,$linkopen,$linkclose);
+ unless ($resource->is_map() || $params->{'resource_nolink'}) {
+ $linkopen = "";
+ $linkclose = "";
+ if (($params->{'modalLink'}) && (!$resource->is_sequence())) {
+ if ($link =~m{^(?:|/adm/wrapper)/ext/([^#]+)}) {
+ my $exturl = $1;
+ if (($ENV{'SERVER_PORT'} == 443) && ($exturl !~ /^https:/)) {
+ $nomodal = 1;
+ }
+ } elsif (($link eq "/public/$LONCAPA::match_domain/$LONCAPA::match_courseid/syllabus") &&
+ ($env{'request.course.id'}) && ($ENV{'SERVER_PORT'} == 443) &&
+ ($env{'course.'.$env{'request.course.id'}.'.externalsyllabus'} =~ m{^http://})) {
+ $nomodal = 1;
+ }
+ my $esclink = &js_escape($link);
+ if ($nomodal) {
+ $linkopen .= "";
+ } else {
+ $linkopen .= "";
+ }
+ } else {
+ $linkopen .= "";
+ }
+ }
# Default icon: unknown page
my $icon = "";
@@ -964,8 +997,12 @@ sub render_resource {
if ($it->{CONDITION}) {
$nowOpen = !$nowOpen;
}
-
- my $folderType = $resource->is_sequence() ? 'folder' : 'page';
+ my $folderType;
+ if (&advancedUser() && $resource->is_missing_map()) {
+ $folderType = 'none';
+ } else {
+ $folderType = $resource->is_sequence() ? 'folder' : 'page';
+ }
my $title=$resource->title;
$title=~s/\"/\&qout;/g;
if (!$params->{'resource_no_folder_link'}) {
@@ -984,16 +1021,21 @@ sub render_resource {
'&jump=' .
&escape($resource->symb()) .
"&folderManip=1\">";
-
+ $linkclose = '';
} else {
# Don't allow users to manipulate folder
$icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif';
$icon = ""."";
-
- $linkopen = "";
- $linkclose = "";
+ if ($params->{'caller'} eq 'sequence') {
+ $linkopen = "";
+ $linkclose = '';
+ } else {
+ $linkopen = "";
+ $linkclose = "";
+ }
}
- if ((&Apache::lonnet::allowed('mdc',$env{'request.course.id'})) &&
+ if (((&Apache::lonnet::allowed('mdc',$env{'request.course.id'})) ||
+ (&Apache::lonnet::allowed('cev',$env{'request.course.id'}))) &&
($resource->symb=~/\_\_\_[^\_]+\_\_\_uploaded/)) {
if (!$params->{'map_no_edit_link'}) {
my $icon = &Apache::loncommon::lonhttpdurl('/res/adm/pages').'/editmap.png';
@@ -1003,10 +1045,33 @@ sub render_resource {
'';
}
}
- }
-
- if ($resource->randomout()) {
- $nonLinkedText .= ' ('.&mt('hidden').') ';
+ if ($params->{'mapHidden'} || $resource->randomout()) {
+ $nonLinkedText .= ' ('.&mt('hidden').') ';
+ } elsif ($params->{'mapUnlisted'}) {
+ $nonLinkedText .= ' ('.&mt('unlisted').') ';
+ } elsif ($params->{'mapHiddenDeepLink'} || $resource->deeplinkout()) {
+ $nonLinkedText .= ' ('.&mt('not shown').') ';
+ }
+ } else {
+ if ($resource->randomout()) {
+ $nonLinkedText .= ' ('.&mt('hidden').') ';
+ } elsif ($resource->deeplinkout()) {
+ $nonLinkedText .= ' ('.&mt('not shown').') ';
+ } else {
+ my $deeplink = $resource->deeplink($params->{caller});
+ if ((($deeplink eq 'absent') || ($deeplink eq 'grades')) &&
+ &advancedUser()) {
+ $nonLinkedText .= ' ('.&mt('unlisted').') ';
+ } elsif (($deeplink) && ($deeplink) ne 'full') {
+ if (&advancedUser()) {
+ $nonLinkedText .= ' ('.&mt('deep-link access').
+ ') ';
+ } else {
+ $nonLinkedText .= ' ('.&mt('access via external site').
+ ') ';
+ }
+ }
+ }
}
if (!$resource->condval()) {
$nonLinkedText .= ' ('.&mt('conditionally hidden').') ';
@@ -1056,10 +1121,19 @@ sub render_resource {
}
if (!$params->{'resource_nolink'} && !$resource->is_sequence() && !$resource->is_empty_sequence) {
- $result .= "$curMarkerBegin$title$partLabel$curMarkerEnd$editmapLink$nonLinkedText";
- } else {
- $result .= "$curMarkerBegin$linkopen$title$partLabel$curMarkerEnd$editmapLink$nonLinkedText";
+ $linkclose = '';
+ if ($params->{'modalLink'}) {
+ my $esclink = &js_escape($link);
+ if ($nomodal) {
+ $linkopen = "";
+ } else {
+ $linkopen = "";
+ }
+ } else {
+ $linkopen = "";
+ }
}
+ $result .= "$curMarkerBegin$linkopen$title$partLabel$linkclose$curMarkerEnd$editmapLink$nonLinkedText";
return $result;
}
@@ -1321,12 +1395,46 @@ sub render {
# an infinite loop
my $oldFilterFunc = $filterFunc;
$filterFunc = sub { my $res = shift; return !$res->randomout() &&
+ ($res->deeplink($args->{'caller'}) ne 'absent') &&
+ ($res->deeplink($args->{'caller'}) ne 'grades') &&
+ !$res->deeplinkout() &&
&$oldFilterFunc($res);};
}
my $condition = 0;
if ($env{'form.condition'}) {
$condition = 1;
+ } elsif (($env{'request.deeplink.login'}) && ($env{'request.course.id'}) && (!$userCanSeeHidden)) {
+ if (!defined($navmap)) {
+ $navmap = Apache::lonnavmaps::navmap->new();
+ }
+ if (defined($navmap)) {
+ my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+ my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
+ my $symb = &Apache::loncommon::symb_from_tinyurl($env{'request.deeplink.login'},$cnum,$cdom);
+ if ($symb) {
+ my $deeplink;
+ my $res = $navmap->getBySymb($symb);
+ if ($res->is_map()) {
+ my $mapname = &Apache::lonnet::declutter($res->src());
+ $mapname = &Apache::lonnet::deversion($mapname);
+ $deeplink = $navmap->get_mapparam(undef,$mapname,"0.deeplink");
+ } else {
+ $deeplink = $res->deeplink();
+ }
+ if ($deeplink ne '') {
+ if ((split(/,/,$deeplink))[1] eq 'hide') {
+ if ($res->is_map()) {
+ map { $filterHash->{$_} = 1 if $_ } split(/,/,$res->map_hierarchy());
+ } else {
+ my $mapurl = (&Apache::lonnet::decode_symb($symb))[0];
+ my $map = $navmap->getResourceByUrl($mapurl);
+ map { $filterHash->{$_} = 1 if $_ } split(/,/,$map->map_hierarchy());
+ }
+ }
+ }
+ }
+ }
}
if (!$env{'form.folderManip'} && !defined($args->{'iterator'})) {
@@ -1350,10 +1458,11 @@ sub render {
my $currenturl = $env{'form.postdata'};
#$currenturl=~s/^http\:\/\///;
#$currenturl=~s/^[^\/]+//;
-
- $here = $jump = &Apache::lonnet::symbread($currenturl);
+ unless ($args->{'caller'} eq 'sequence') {
+ $here = $jump = &Apache::lonnet::symbread($currenturl);
+ }
}
- if ($here eq '') {
+ if (($here eq '') && ($args->{'caller'} ne 'sequence')) {
my $last;
if (tie(my %hash,'GDBM_File',$env{'request.course.fn'}.'_symb.db',
&GDBM_READER(),0640)) {
@@ -1413,10 +1522,13 @@ sub render {
if ($args->{'iterator_map'}) {
my $map = $args->{'iterator_map'};
$map = $navmap->getResourceByUrl($map);
- my $firstResource = $map->map_start();
- my $finishResource = $map->map_finish();
-
- $args->{'iterator'} = $it = $navmap->getIterator($firstResource, $finishResource, $filterHash, $condition);
+ if (ref($map)) {
+ my $firstResource = $map->map_start();
+ my $finishResource = $map->map_finish();
+ $args->{'iterator'} = $it = $navmap->getIterator($firstResource, $finishResource, $filterHash, $condition);
+ } else {
+ return;
+ }
} else {
$args->{'iterator'} = $it = $navmap->getIterator(undef, undef, $filterHash, $condition,undef,$args->{'include_top_level_map'});
}
@@ -1535,7 +1647,8 @@ END
$result.='';
}
if (($args->{'caller'} eq 'navmapsdisplay') &&
- (&Apache::lonnet::allowed('mdc',$env{'request.course.id'}))) {
+ ((&Apache::lonnet::allowed('mdc',$env{'request.course.id'})) ||
+ (&Apache::lonnet::allowed('cev',$env{'request.course.id'})))) {
my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
if ($env{'course.'.$env{'request.course.id'}.'.url'} eq
@@ -1581,41 +1694,46 @@ END
$args->{'indentString'} = setDefault($args->{'indentString'}, "");
$args->{'displayedHereMarker'} = 0;
- # If we're suppressing empty sequences, look for them here. Use DFS for speed,
- # since structure actually doesn't matter, except what map has what resources.
- if ($args->{'suppressEmptySequences'}) {
- my $dfsit = Apache::lonnavmaps::DFSiterator->new($navmap,
- $it->{FIRST_RESOURCE},
- $it->{FINISH_RESOURCE},
- {}, undef, 1);
- my $depth = 0;
- $dfsit->next();
- my $curRes = $dfsit->next();
- while ($depth > -1) {
- if ($curRes == $dfsit->BEGIN_MAP()) { $depth++; }
- if ($curRes == $dfsit->END_MAP()) { $depth--; }
-
- if (ref($curRes)) {
- # Parallel pre-processing: Do sequences have non-filtered-out children?
- if ($curRes->is_map()) {
- $curRes->{DATA}->{HAS_VISIBLE_CHILDREN} = 0;
- # Sequences themselves do not count as visible children,
- # unless those sequences also have visible children.
- # This means if a sequence appears, there's a "promise"
- # that there's something under it if you open it, somewhere.
- } else {
- # Not a sequence: if it's filtered, ignore it, otherwise
- # rise up the stack and mark the sequences as having children
- if (&$filterFunc($curRes)) {
- for my $sequence (@{$dfsit->getStack()}) {
- $sequence->{DATA}->{HAS_VISIBLE_CHILDREN} = 1;
- }
+ # If we're suppressing empty sequences, look for them here.
+ # We also do this even if $args->{'suppressEmptySequences'}
+ # is not true, so we can hide empty sequences for which the
+ # hiddenresource parameter is set to yes (at map level), or
+ # mark as hidden for users who have $userCanSeeHidden.
+ # Use DFS for speed, since structure actually doesn't matter,
+ # except what map has what resources.
+
+ my $dfsit = Apache::lonnavmaps::DFSiterator->new($navmap,
+ $it->{FIRST_RESOURCE},
+ $it->{FINISH_RESOURCE},
+ {}, undef, 1);
+
+ my $depth = 0;
+ $dfsit->next();
+ my $curRes = $dfsit->next();
+ while ($depth > -1) {
+ if ($curRes == $dfsit->BEGIN_MAP()) { $depth++; }
+ if ($curRes == $dfsit->END_MAP()) { $depth--; }
+
+ if (ref($curRes)) {
+ # Parallel pre-processing: Do sequences have non-filtered-out children?
+ if ($curRes->is_map()) {
+ $curRes->{DATA}->{HAS_VISIBLE_CHILDREN} = 0;
+ # Sequences themselves do not count as visible children,
+ # unless those sequences also have visible children.
+ # This means if a sequence appears, there's a "promise"
+ # that there's something under it if you open it, somewhere.
+ } elsif ($curRes->src()) {
+ # Not a sequence: if it's filtered, ignore it, otherwise
+ # rise up the stack and mark the sequences as having children
+ if (&$filterFunc($curRes)) {
+ for my $sequence (@{$dfsit->getStack()}) {
+ $sequence->{DATA}->{HAS_VISIBLE_CHILDREN} = 1;
}
}
}
- } continue {
- $curRes = $dfsit->next();
}
+ } continue {
+ $curRes = $dfsit->next();
}
my $displayedJumpMarker = 0;
@@ -1673,6 +1791,28 @@ END
undef($args->{'sort'});
}
+ # Determine if page will be served with https in case
+ # it contains a syllabus which uses an external URL
+ # which points at an http site.
+
+ my ($is_ssl,$cdom,$cnum,$hostname);
+ if ($ENV{'SERVER_PORT'} == 443) {
+ $is_ssl = 1;
+ if ($r) {
+ $hostname = $r->hostname();
+ } else {
+ $hostname = $ENV{'SERVER_NAME'};
+ }
+ }
+ if ($env{'request.course.id'}) {
+ $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+ $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
+ }
+
+ my $inhibitmenu;
+ if ($args->{'modalLink'}) {
+ $inhibitmenu = '&inhibitmenu=yes';
+ }
while (1) {
if ($args->{'sort'}) {
@@ -1708,9 +1848,39 @@ END
}
# If this is an empty sequence and we're filtering them, continue on
- if ($curRes->is_map() && $args->{'suppressEmptySequences'} &&
- !$curRes->{DATA}->{HAS_VISIBLE_CHILDREN}) {
- next;
+ $args->{'mapHidden'} = 0;
+ $args->{'mapUnlisted'} = 0;
+ $args->{'mapHiddenDeepLink'} = 0;
+ if (($curRes->is_map()) && (!$curRes->{DATA}->{HAS_VISIBLE_CHILDREN})) {
+ if ($args->{'suppressEmptySequences'}) {
+ next;
+ } else {
+ my $mapname = &Apache::lonnet::declutter($curRes->src());
+ $mapname = &Apache::lonnet::deversion($mapname);
+ if (lc($navmap->get_mapparam(undef,$mapname,"0.hiddenresource")) eq 'yes') {
+ if ($userCanSeeHidden) {
+ $args->{'mapHidden'} = 1;
+ } else {
+ next;
+ }
+ } elsif ($curRes->deeplinkout) {
+ if ($userCanSeeHidden) {
+ $args->{'mapHiddenDeepLink'} = 1;
+ } else {
+ next;
+ }
+ } else {
+ my $deeplink = $navmap->get_mapparam(undef,$mapname,"0.deeplink");
+ my ($state,$others,$listed) = split(/,/,$deeplink);
+ if (($listed eq 'absent') || ($listed eq 'grades')) {
+ if ($userCanSeeHidden) {
+ $args->{'mapUnlisted'} = 1;
+ } else {
+ next;
+ }
+ }
+ }
+ }
}
# If we're suppressing navmaps and this is a navmap, continue on
@@ -1771,7 +1941,16 @@ END
$args->{'condensed'} = 1;
}
}
- }
+ }
+ # If deep-link parameter is set (and is not set to full) suppress link
+ # unless privileged user, tinyurl used for login resolved to a map, and
+ # the resource is within the map.
+ if ((!$curRes->deeplink($args->{'caller'})) ||
+ ($curRes->deeplink($args->{'caller'}) eq 'full') || &advancedUser()) {
+ $args->{'resource_nolink'} = 0;
+ } else {
+ $args->{'resource_nolink'} = 1;
+ }
# If the multipart problem was condensed, "forget" it was multipart
if (scalar(@parts) == 1) {
@@ -1794,11 +1973,35 @@ END
$stack=$it->getStack();
}
($src,$symb,$anchor)=getLinkForResource($stack);
+ my $srcHasQuestion = $src =~ /\?/;
+ if ($env{'request.course.id'}) {
+ if (($is_ssl) && ($src =~ m{^\Q/public/$cdom/$cnum/syllabus\E($|\?)}) &&
+ ($env{'course.'.$env{'request.course.id'}.'.externalsyllabus'} =~ m{^http://})) {
+ unless ((&Apache::lonnet::uses_sts()) || (&Apache::lonnet::waf_allssl($hostname))) {
+ if ($hostname ne '') {
+ $src = 'http://'.$hostname.$src;
+ }
+ $src .= ($srcHasQuestion? '&' : '?') . 'usehttp=1';
+ $srcHasQuestion = 1;
+ }
+ } elsif (($is_ssl) && ($src =~ m{^\Q/adm/wrapper/ext/\E(?!https:)})) {
+ unless ((&Apache::lonnet::uses_sts()) || (&Apache::lonnet::waf_allssl($hostname))) {
+ if ($hostname ne '') {
+ $src = 'http://'.$hostname.$src;
+ }
+ $src .= ($srcHasQuestion? '&' : '?') . 'usehttp=1';
+ $srcHasQuestion = 1;
+ }
+ }
+ }
if (defined($anchor)) { $anchor='#'.$anchor; }
- my $srcHasQuestion = $src =~ /\?/;
- $args->{"resourceLink"} = $src.
- ($srcHasQuestion?'&':'?') .
- 'symb=' . &escape($symb).$anchor;
+ if (($args->{'caller'} eq 'sequence') && ($curRes->is_map())) {
+ $args->{"resourceLink"} = $src.($srcHasQuestion?'&':'?') .'navmap=1';
+ } else {
+ $args->{"resourceLink"} = $src.
+ ($srcHasQuestion?'&':'?') .
+ 'symb=' . &escape($symb).$inhibitmenu.$anchor;
+ }
}
# Now, we've decided what parts to show. Loop through them and
# show them.
@@ -1826,7 +2029,7 @@ END
$currentJumpDelta) {
# Jam the anchor after the
tag;
# necessary for valid HTML (which Mozilla requires)
- $colHTML =~ s/\>/\>\/;
+ $colHTML =~ s/\>/\>\\<\/a\>/;
$displayedJumpMarker = 1;
}
$result .= $colHTML . "\n";
@@ -2212,7 +2415,7 @@ sub generate_email_discuss_status {
foreach my $msgid (@keys) {
if ((!$emailstatus{$msgid}) || ($emailstatus{$msgid} eq 'new')) {
my ($sendtime,$shortsubj,$fromname,$fromdomain,$status,$fromcid,
- $symb,$error) = &Apache::lonmsg::unpackmsgid($msgid);
+ $symb,$error) = &Apache::lonmsg::unpackmsgid(&LONCAPA::escape($msgid));
&Apache::lonenc::check_decrypt(\$symb);
if (($fromcid ne '') && ($fromcid ne $cid)) {
next;
@@ -2313,7 +2516,7 @@ sub getIterator {
my $self = shift;
my $iterator = Apache::lonnavmaps::iterator->new($self, shift, shift,
shift, undef, shift,
- shift, shift);
+ shift, shift, shift);
return $iterator;
}
@@ -2709,6 +2912,116 @@ sub parmval_real {
if (defined($pack_def)) { return [$pack_def,'resource']; }
return [''];
}
+
+sub recurseup_maps {
+ my ($self,$mapname) = @_;
+ my @recurseup;
+ if ($mapname) {
+ my $res = $self->getResourceByUrl($mapname);
+ if (ref($res)) {
+ my @pcs = split(/,/,$res->map_hierarchy());
+ shift(@pcs);
+ if (@pcs) {
+ @recurseup = map { &Apache::lonnet::declutter($self->getByMapPc($_)->src()); } reverse(@pcs);
+ }
+ }
+ }
+ return @recurseup;
+}
+
+sub recursed_crumbs {
+ my ($self,$mapurl,$restitle) = @_;
+ my (@revmapinfo,@revmapres);
+ my $mapres = $self->getResourceByUrl($mapurl);
+ if (ref($mapres)) {
+ @revmapres = map { $self->getByMapPc($_); } split(/,/,$mapres->map_breadcrumbs());
+ shift(@revmapres);
+ }
+ my $allowedlength = 60;
+ my $minlength = 5;
+ my $allowedtitle = 30;
+ if (($env{'environment.icons'} eq 'iconsonly') && (!$env{'browser.mobile'})) {
+ $allowedlength = 100;
+ $allowedtitle = 70;
+ }
+ if (length($restitle) > $allowedtitle) {
+ $restitle = &truncate_crumb_text($restitle,$allowedtitle);
+ }
+ my $totallength = length($restitle);
+ my @links;
+
+ foreach my $map (@revmapres) {
+ my $pc = $map->map_pc();
+ next if ((!$pc) || ($pc == 1));
+ push(@links,$map);
+ my $text = $map->title();
+ if ($text eq '') {
+ $text = '...';
+ }
+ push(@revmapinfo,{'href' => $env{'request.use_absolute'}.$map->link().'?navmap=1','text' => $text,'no_mt' => 1,});
+ $totallength += length($text);
+ }
+ my $numlinks = scalar(@links);
+ if ($numlinks) {
+ if ($totallength - $allowedlength > 0) {
+ my $available = $allowedlength - length($restitle);
+ my $avg = POSIX::ceil($available/$numlinks);
+ if ($avg < $minlength) {
+ $avg = $minlength;
+ }
+ @revmapinfo = ();
+ foreach my $map (@links) {
+ my $title = $map->title();
+ if ($title eq '') {
+ $title = '...';
+ }
+ my $showntitle = &truncate_crumb_text($title,$avg);
+ if ($showntitle ne '') {
+ push(@revmapinfo,{'href' => $env{'request.use_absolute'}.$map->link().'?navmap=1','text' => $showntitle,'no_mt' => 1,});
+ }
+ }
+ }
+ }
+ if ($restitle ne '') {
+ push(@revmapinfo,{'text' => $restitle, 'no_mt' => 1});
+ }
+ return @revmapinfo;
+}
+
+sub truncate_crumb_text {
+ my ($title,$limit) = @_;
+ my $showntitle = '';
+ if (length($title) > $limit) {
+ my @words = split(/\b\s*/,$title);
+ if (@words == 1) {
+ $showntitle = substr($title,0,$limit).' ...';
+ } else {
+ my $linklength = 0;
+ my $num = 0;
+ foreach my $word (@words) {
+ $linklength += 1+length($word);
+ if ($word eq '-') {
+ $showntitle =~ s/ $//;
+ $showntitle .= $word;
+ } elsif ($linklength > $limit) {
+ if ($num < @words) {
+ $showntitle .= $word.' ...';
+ last;
+ } else {
+ $showntitle .= $word;
+ }
+ } else {
+ $showntitle .= $word.' ';
+ }
+ }
+ $showntitle =~ s/ $//;
+ }
+ return $showntitle;
+ } else {
+ return $title;
+ }
+}
+
#
# Determines the open/close dates for printing a map that
# encloses a resource.
@@ -2720,15 +3033,15 @@ sub map_printdates {
- my $opendate = $self->get_mapparam($res->symb(), "$part.printstartdate");
- my $closedate= $self->get_mapparam($res->symb(), "$part.printenddate");
+ my $opendate = $self->get_mapparam($res->symb(),'',"$part.printstartdate");
+ my $closedate= $self->get_mapparam($res->symb(),'', "$part.printenddate");
return ($opendate, $closedate);
}
sub get_mapparam {
- my ($self, $symb, $what) = @_;
+ my ($self, $symb, $mapname, $what) = @_;
# Ensure the course option hash is populated:
@@ -2747,15 +3060,17 @@ sub get_mapparam {
my $uname=$self->{USERNAME};
my $udom=$self->{DOMAIN};
- unless ($symb) { return ['']; }
+ unless ($symb || $mapname) { return; }
my $result='';
-
+ my ($recursed,@recurseup);
# Figure out which map we are in.
- my ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb);
- $mapname = &Apache::lonnet::deversion($mapname);
-
+ if ($symb && !$mapname) {
+ my ($id,$fn);
+ ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb);
+ $mapname = &Apache::lonnet::deversion($mapname);
+ }
my $rwhat=$what;
$what=~s/^parameter\_//;
@@ -2787,6 +3102,20 @@ sub get_mapparam {
if (defined($$useropt{$courselevel})) {
return $$useropt{$courselevel};
}
+ if ($what =~ /\.(encrypturl|hiddenresource)$/) {
+ unless ($recursed) {
+ @recurseup = $self->recurseup_maps($mapname);
+ $recursed = 1;
+ }
+ foreach my $item (@recurseup) {
+ my $norecursechk=$usercourseprefix.'.'.$item.'___(all).'.$what;
+ if (defined($$useropt{$norecursechk})) {
+ if ($what =~ /\.(encrypturl|hiddenresource)$/) {
+ return $$useropt{$norecursechk};
+ }
+ }
+ }
+ }
}
# Check course -- group
@@ -2797,6 +3126,20 @@ sub get_mapparam {
if (defined($$courseopt{$grplevel})) {
return $$courseopt{$grplevel};
}
+ if ($what =~ /\.(encrypturl|hiddenresource)$/) {
+ unless ($recursed) {
+ @recurseup = $self->recurseup_maps($mapname);
+ $recursed = 1;
+ }
+ foreach my $item (@recurseup) {
+ my $norecursechk=$usercourseprefix.'.['.$cgroup.'].'.$item.'___(all).'.$what;
+ if (defined($$courseopt{$norecursechk})) {
+ if ($what =~ /\.(encrypturl|hiddenresource)$/) {
+ return $$courseopt{$norecursechk};
+ }
+ }
+ }
+ }
}
# Check course -- section
@@ -2809,12 +3152,29 @@ sub get_mapparam {
if (defined($$courseopt{$seclevel})) {
return $$courseopt{$seclevel};
}
+ if ($what =~ /\.(encrypturl|hiddenresource)$/) {
+ unless ($recursed) {
+ @recurseup = $self->recurseup_maps($mapname);
+ $recursed = 1;
+ }
+ foreach my $item (@recurseup) {
+ my $norecursechk=$usercourseprefix.'.['.$csec.'].'.$item.'___(all).'.$what;
+ if (defined($$courseopt{$norecursechk})) {
+ if ($what =~ /\.(encrypturl|hiddenresource)$/) {
+ return $$courseopt{$norecursechk};
+ }
+ }
+ }
+ }
}
# Check the map parameters themselves:
- my $thisparm = $$parmhash{$symbparm};
- if (defined($thisparm)) {
- return $thisparm;
+ if ($symb) {
+ my $symbparm=$symb.'.'.$what;
+ my $thisparm = $$parmhash{$symbparm};
+ if (defined($thisparm)) {
+ return $thisparm;
+ }
}
@@ -2824,6 +3184,20 @@ sub get_mapparam {
if (defined($$courseopt{$courselevel})) {
return $$courseopt{$courselevel};
}
+ if ($what =~ /\.(encrypturl|hiddenresource)$/) {
+ unless ($recursed) {
+ @recurseup = $self->recurseup_maps($mapname);
+ $recursed = 1;
+ }
+ foreach my $item (@recurseup) {
+ my $norecursechk=$usercourseprefix.'.'.$item.'___(all).'.$what;
+ if (defined($$courseopt{$norecursechk})) {
+ if ($what =~ /\.(encrypturl|hiddenresource)$/) {
+ return $$courseopt{$norecursechk};
+ }
+ }
+ }
+ }
}
return undef; # Unefined if we got here.
}
@@ -2879,7 +3253,7 @@ sub getcourseparam {
#
# We want the course level stuff from the way
# parmval_real operates
- # TODO: Fator some of this stuff out of
+ # TODO: Factor some of this stuff out of
# both parmval_real and here
#
my $courselevel = $cid . '.' . $what;
@@ -2896,7 +3270,7 @@ sub getcourseparam {
}
# Try for the group's course level option:
- if ($uname ne '' and defined($courseopt)) {
+ if ($cgroup ne '' and defined($courseopt)) {
if (defined($$courseopt{$grplevel})) {
return $$courseopt{$grplevel};
}
@@ -2904,12 +3278,12 @@ sub getcourseparam {
# Try for section level parameters:
- if ($csec and defined($courseopt)) {
+ if ($csec ne '' and defined($courseopt)) {
if (defined($$courseopt{$seclevel})) {
return $$courseopt{$seclevel};
}
}
- # Try for 'additional' course parameterse:
+ # Try for 'additional' course parameters:
if (defined($courseopt)) {
if (defined($$courseopt{$courselevel})) {
@@ -3116,7 +3490,7 @@ getIterator behaves as follows:
=over 4
-=item * B(firstResource, finishResource, filterHash, condition, forceTop, returnTopMap):
+=item * B(firstResource, finishResource, filterHash, condition, forceTop, returnTopMap, $deeplinklisted):
All parameters are optional. firstResource is a resource reference
corresponding to where the iterator should start. It defaults to
@@ -3133,7 +3507,10 @@ that is not just a single, 'redirecting'
will return all information, starting with the top-level map,
regardless of content. returnTopMap, if true (default false), will
cause the iterator to return the top-level map object (resource 0.0)
-before anything else.
+before anything else. deeplinklisted if true (default false), will
+check "listed" status of a resource with a deeplink, and unless "absent"
+will exclude deeplink checking when retrieving the browsePriv from
+lonnet::allowed().
Thus, by default, only top-level resources will be shown. Change the
condition to a 1 without changing the hash, and all resources will be
@@ -3270,6 +3647,10 @@ sub new {
# have we done that yet?
$self->{HAVE_RETURNED_0} = 0;
+ # Do we want to check the "listed" status for a resource for which
+ # deeplinking applies.
+ $self->{DEEPLINKLISTED} = shift;
+
# Now, we need to pre-process the map, by walking forward and backward
# over the parts of the map we're going to look at.
@@ -3361,7 +3742,8 @@ sub new {
$finishResource, $self->{FILTER},
$self->{ALREADY_SEEN},
$self->{CONDITION},
- $self->{FORCE_TOP});
+ $self->{FORCE_TOP},
+ undef,$self->{DEEPLINKLISTED});
}
# Set up some bookkeeping information.
@@ -3521,7 +3903,8 @@ sub next {
# That ends the main iterator logic. Now, do we want to recurse
# down this map (if this resource is a map)?
if ( ($self->{HERE}->is_sequence() || (!$closeAllPages && $self->{HERE}->is_page())) &&
- (defined($self->{FILTER}->{$self->{HERE}->map_pc()}) xor $self->{CONDITION})) {
+ (defined($self->{FILTER}->{$self->{HERE}->map_pc()}) xor $self->{CONDITION}) &&
+ ($env{'request.role.adv'} || !$self->{HERE}->randomout())) {
$self->{RECURSIVE_ITERATOR_FLAG} = 1;
my $firstResource = $self->{HERE}->map_start();
my $finishResource = $self->{HERE}->map_finish();
@@ -3530,13 +3913,14 @@ sub next {
$finishResource, $self->{FILTER},
$self->{ALREADY_SEEN},
$self->{CONDITION},
- $self->{FORCE_TOP});
+ $self->{FORCE_TOP},
+ undef,$self->{DEEPLINKLISTED});
}
# If this is a blank resource, don't actually return it.
# Should you ever find you need it, make sure to add an option to the code
# that you can use; other things depend on this behavior.
- my $browsePriv = $self->{HERE}->browsePriv($noblockcheck);
+ my $browsePriv = $self->{HERE}->browsePriv($noblockcheck,$self->{DEEPLINKLISTED});
if (!$self->{HERE}->src() ||
(!($browsePriv eq 'F') && !($browsePriv eq '2')) ) {
return $self->next($closeAllPages);
@@ -3964,6 +4348,7 @@ sub from { my $self=shift; return $self-
sub goesto { my $self=shift; return $self->navHash("goesto_", 1); }
sub kind { my $self=shift; return $self->navHash("kind_", 1); }
sub randomout { my $self=shift; return $self->navHash("randomout_", 1); }
+sub deeplinkout { my $self=shift; return $self->navHash("deeplinkout_", 1); }
sub randompick {
my $self = shift;
my $randompick = $self->parmval('randompick');
@@ -3999,6 +4384,7 @@ sub enclosing_map_src {
}
sub symb {
my $self=shift;
+ if (defined($self->{SYMB})) { return $self->{SYMB}; }
(my $first, my $second) = $self->{ID} =~ /(\d+).(\d+)/;
my $symbSrc = &Apache::lonnet::declutter($self->src());
my $symb = &Apache::lonnet::declutter($self->navHash('map_id_'.$first))
@@ -4189,6 +4575,11 @@ sub is_sequence {
return $self->navHash("is_map_", 1) &&
$self->navHash("map_type_" . $self->map_pc()) eq 'sequence';
}
+sub is_missing_map {
+ my $self=shift;
+ return $self->navHash("is_map_", 1) &&
+ $self->navHash("map_type_" . $self->map_pc()) eq 'none';
+}
sub is_survey {
my $self = shift();
my $part = shift();
@@ -4218,7 +4609,6 @@ sub is_task {
sub is_empty_sequence {
my $self=shift;
- my $src = $self->src();
return !$self->is_page() && $self->navHash("is_map_", 1) && !$self->navHash("map_type_" . $self->map_pc());
}
@@ -4269,6 +4659,12 @@ Returns a string with a comma-separated
for the hierarchy of maps containing a map, with the top level
map first, then descending to deeper levels, with the enclosing map last.
+=item * B:
+
+Same as map_hierarchy, except maps containing only a single itemm if
+it's a map, or containing no items are omitted, unless it's the top
+level map (map_pc = 1), which is always included.
+
=back
=cut
@@ -4304,6 +4700,11 @@ sub map_hierarchy {
my $pc = $self->map_pc();
return $self->navHash("map_hierarchy_$pc", 0);
}
+sub map_breadcrumbs {
+ my $self = shift;
+ my $pc = $self->map_pc();
+ return $self->navHash("map_breadcrumbs_$pc", 0);
+}
#####
# Property queries
@@ -4528,11 +4929,12 @@ sub duedate {
my $date;
my @interval=$self->parmval("interval", $part);
my $due_date=$self->parmval("duedate", $part);
- if ($interval[0] =~ /\d+/) {
- my $first_access=&Apache::lonnet::get_first_access($interval[1],
- $self->symb);
+ if ($interval[0] =~ /(\d+)/) {
+ my $timelimit = $1;
+ my $first_access=&Apache::lonnet::get_first_access($interval[1],
+ $self->{SYMB});
if (defined($first_access)) {
- my $interval = $first_access+$interval[0];
+ my $interval = $first_access+$timelimit;
$date = (!$due_date || $interval < $due_date) ? $interval
: $due_date;
} else {
@@ -4612,7 +5014,7 @@ sub part_display {
my $self= shift(); my $partID = shift();
if (! defined($partID)) { $partID = '0'; }
my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display',
- $self->symb);
+ $self->{SYMB});
if (! defined($display) || $display eq '') {
$display = $partID;
}
@@ -4626,6 +5028,46 @@ sub slot_control {
my $available = $self->parmval("available", $part);
return ($useslots,$availablestudent,$available);
}
+sub deeplink {
+ my ($self,$caller,$action) = @_;
+ my $deeplink = $self->parmval("deeplink");
+ if ($deeplink) {
+ my ($state,$others,$listed,$scope) = split(/,/,$deeplink);
+ if ($action eq 'getlisted') {
+ return $listed;
+ }
+ if ($env{'request.deeplink.login'}) {
+ my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
+ my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+ my $deeplink_symb = &Apache::loncommon::deeplink_login_symb($cnum,$cdom);
+ if ($deeplink_symb) {
+ my ($loginmap,$mapname);
+ if ($deeplink_symb =~ /\.(page|sequence)$/) {
+ $mapname = $self->enclosing_map_src();
+ $loginmap = &Apache::lonnet::clutter((&Apache::lonnet::decode_symb($deeplink_symb))[2]);
+ return if ($mapname eq $loginmap);
+ } else {
+ return if ($deeplink_symb eq $self->symb());
+ if (($scope eq 'map') || ($scope eq 'rec')) {
+ $mapname = $self->enclosing_map_src();
+ $loginmap = &Apache::lonnet::clutter((&Apache::lonnet::decode_symb($deeplink_symb))[0]);
+ return if ($mapname eq $loginmap);
+ }
+ }
+ if ($scope eq 'rec') {
+ my $map_pc = $self->navHash('map_pc_'.$mapname);
+ my @recurseup = split(/,/,$self->navHash('map_hierarchy_'.$map_pc));
+ my $login_pc = $self->navHash('map_pc_'.$loginmap);
+ return if (grep(/^\Q$login_pc\E$/,@recurseup));
+ }
+ }
+ }
+ unless (($caller eq 'sequence') || ($state eq 'both')) {
+ return $listed;
+ }
+ }
+ return;
+}
# Multiple things need this
sub getReturnHash {
@@ -5408,13 +5850,13 @@ sub check_for_slot {
my $cnum=$env{'course.'.$cid.'.num'};
my $now = time;
my $num_usable_slots = 0;
+ my ($checkedin,$checkedinslot,%consumed_uniq,%slots);
if (@slots > 0) {
- my %slots=&Apache::lonnet::get('slots',[@slots],$cdom,$cnum);
+ %slots=&Apache::lonnet::get('slots',[@slots],$cdom,$cnum);
if (&Apache::lonnet::error(%slots)) {
return (UNKNOWN);
}
my @sorted_slots = &Apache::loncommon::sorted_slots(\@slots,\%slots,'starttime');
- my ($checkedin,$checkedinslot);
foreach my $slot_name (@sorted_slots) {
next if (!defined($slots{$slot_name}) || !ref($slots{$slot_name}));
my $end = $slots{$slot_name}->{'endtime'};
@@ -5438,7 +5880,7 @@ sub check_for_slot {
($checkedin,$checkedinslot) = $self->checkedin();
unless ((grep(/^\Q$checkedin\E/,@proctors)) &&
($checkedinslot eq $slot_name)) {
- return (NEEDS_CHECKIN,undef,$slot_name);
+ return (NEEDS_CHECKIN,$end,$slot_name);
}
}
return (RESERVED,$end,$slot_name);
@@ -5448,19 +5890,30 @@ sub check_for_slot {
$num_usable_slots ++;
}
}
- my ($is_correct,$got_grade);
+ my ($is_correct,$wait_for_grade);
if ($self->is_task()) {
my $taskstatus = $self->taskstatus();
$is_correct = (($taskstatus eq 'pass') ||
($self->solved() =~ /^correct_/));
- $got_grade = ($taskstatus =~ /^(?:pass|fail)$/);
+ unless ($taskstatus =~ /^(?:pass|fail)$/) {
+ $wait_for_grade = 1;
+ }
} else {
- $got_grade = 1;
- $is_correct = ($self->solved() =~ /^correct_/);
+ unless ($self->completable()) {
+ $wait_for_grade = 1;
+ }
+ unless (($self->problemstatus($part) eq 'no') ||
+ ($self->problemstatus($part) eq 'no_feedback_ever')) {
+ $is_correct = ($self->solved($part) =~ /^correct_/);
+ $wait_for_grade = 0;
+ }
}
($checkedin,$checkedinslot) = $self->checkedin();
if ($checkedin) {
- if (!$got_grade) {
+ if (ref($slots{$checkedinslot}) eq 'HASH') {
+ $consumed_uniq{$checkedinslot} = $slots{$checkedinslot}{'uniqueperiod'};
+ }
+ if ($wait_for_grade) {
return (WAITING_FOR_GRADE);
} elsif ($is_correct) {
return (CORRECT);
@@ -5475,16 +5928,46 @@ sub check_for_slot {
if (ref($reservable) eq 'HASH') {
if ((ref($reservable->{'now_order'}) eq 'ARRAY') && (ref($reservable->{'now'}) eq 'HASH')) {
foreach my $slot (reverse (@{$reservable->{'now_order'}})) {
+ my $canuse;
if (($reservable->{'now'}{$slot}{'symb'} eq '') ||
($reservable->{'now'}{$slot}{'symb'} eq $symb)) {
+ $canuse = 1;
+ }
+ if ($canuse) {
+ if ($checkedin) {
+ if (ref($consumed_uniq{$checkedinslot}) eq 'ARRAY') {
+ my ($uniqstart,$uniqend)=@{$consumed_uniq{$checkedinslot}};
+ if ($reservable->{'now'}{$slot}{'uniqueperiod'} =~ /^(\d+),(\d+)$/) {
+ my ($new_uniq_start,$new_uniq_end) = ($1,$2);
+ next if (!
+ ($uniqstart < $new_uniq_start && $uniqend < $new_uniq_start) ||
+ ($uniqstart > $new_uniq_end && $uniqend > $new_uniq_end ));
+ }
+ }
+ }
return(RESERVABLE,$reservable->{'now'}{$slot}{'endreserve'});
}
}
}
if ((ref($reservable->{'future_order'}) eq 'ARRAY') && (ref($reservable->{'future'}) eq 'HASH')) {
foreach my $slot (@{$reservable->{'future_order'}}) {
+ my $canuse;
if (($reservable->{'future'}{$slot}{'symb'} eq '') ||
($reservable->{'future'}{$slot}{'symb'} eq $symb)) {
+ $canuse = 1;
+ }
+ if ($canuse) {
+ if ($checkedin) {
+ if (ref($consumed_uniq{$checkedinslot}) eq 'ARRAY') {
+ my ($uniqstart,$uniqend)=@{$consumed_uniq{$checkedinslot}};
+ if ($reservable->{'future'}{$slot}{'uniqueperiod'} =~ /^(\d+),(\d+)$/) {
+ my ($new_uniq_start,$new_uniq_end) = ($1,$2);
+ next if (!
+ ($uniqstart < $new_uniq_start && $uniqend < $new_uniq_start) ||
+ ($uniqstart > $new_uniq_end && $uniqend > $new_uniq_end ));
+ }
+ }
+ }
return(RESERVABLE_LATER,$reservable->{'future'}{$slot}{'startreserve'});
}
}
@@ -5716,13 +6199,23 @@ sub getPrevious {
sub browsePriv {
my $self = shift;
my $noblockcheck = shift;
+ my $deeplinklisted = shift;
if (defined($self->{BROWSE_PRIV})) {
return $self->{BROWSE_PRIV};
}
-
+ my ($nodeeplinkcheck,$nodeeplinkout);
+ if ($deeplinklisted) {
+ my $deeplink = $self->deeplink(undef,'getlisted');
+ if (($deeplink) && ($deeplink ne 'absent')) {
+ $nodeeplinkcheck = 1;
+ }
+ $nodeeplinkout = 1;
+ }
$self->{BROWSE_PRIV} = &Apache::lonnet::allowed('bre',$self->src(),
$self->{SYMB},undef,
- undef,$noblockcheck);
+ undef,$noblockcheck,
+ undef,$nodeeplinkcheck,
+ $nodeeplinkout);
}
=pod
|