--- loncom/interface/lonnavmaps.pm 2017/09/10 00:11:27 1.535
+++ loncom/interface/lonnavmaps.pm 2021/04/29 17:45:22 1.551
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# Navigate Maps Handler
#
-# $Id: lonnavmaps.pm,v 1.535 2017/09/10 00:11:27 raeburn Exp $
+# $Id: lonnavmaps.pm,v 1.551 2021/04/29 17:45:22 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -71,7 +71,7 @@ processed.
Apache::lonnavmaps provides an object model for manipulating this
information in a higher-level fashion than directly manipulating
-the hash. It also provides access to several auxilary functions
+the hash. It also provides access to several auxiliary 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.
@@ -92,7 +92,7 @@ graded for named user(s) or specific COD
domain, or CODE can be passed as arguments when creating a new
navmap object.
-Note if you want things like "due dates for another student,
+Note if you want things like "due dates for another student",
you would use the EXT function instead of lonnavmaps.
That said, the lonnavmaps module can still help, because many
things, such as the course structure, are usually constant
@@ -592,7 +592,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"
@@ -658,6 +662,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'));
@@ -699,7 +706,17 @@ sub getDescription {
}
if (($status == $res->ANSWER_OPEN || $status == $res->PARTIALLY_CORRECT)
&& $res->handgrade($part) ne 'yes') {
- return &Apache::lonhtmlcommon::direct_parm_link(&mt("Answer available"),$res->symb(),'answerdate,duedate',$part);
+ my $msg = &mt('Answer available');
+ my $parmlist = 'answerdate,duedate';
+ if (($res->is_tool) && ($res->is_gradable())) {
+ if (($status == $res->PARTIALLY_CORRECT) && ($res->parmval('retrypartial',$part))) {
+ $msg = &mt('Grade received');
+ $parmlist = 'retrypartial';
+ } else {
+ $msg = &mt('Grade available');
+ }
+ }
+ return &Apache::lonhtmlcommon::direct_parm_link($msg,$res->symb(),$parmlist,$part);
}
if ($status == $res->EXCUSED) {
return &mt("Excused by instructor");
@@ -950,11 +967,32 @@ sub render_resource {
$newBranchText = "";
}
- # 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 = "";
@@ -1002,13 +1040,14 @@ 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 = ""."";
if ($params->{'caller'} eq 'sequence') {
$linkopen = "";
+ $linkclose = '';
} else {
$linkopen = "";
$linkclose = "";
@@ -1027,10 +1066,15 @@ sub render_resource {
}
if ($params->{'mapHidden'} || $resource->randomout()) {
$nonLinkedText .= ' ('.&mt('hidden').') ';
+ } elsif ($params->{'mapUnlisted'}) {
+ $nonLinkedText .= ' ('.&mt('unlisted').') ';
}
} else {
if ($resource->randomout()) {
$nonLinkedText .= ' ('.&mt('hidden').') ';
+ } elsif (($resource->deeplink($params->{caller}) eq 'absent') ||
+ ($resource->deeplink($params->{caller}) eq 'grades')) {
+ $nonLinkedText .= ' ('.&mt('unlisted').') ';
}
}
if (!$resource->condval()) {
@@ -1081,10 +1125,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;
}
@@ -1146,7 +1199,7 @@ sub render_quick_status {
my $linkclose = "";
$result .= '
';
- if ($resource->is_problem() &&
+ if ($resource->is_gradable() &&
!$firstDisplayed) {
my $icon = $statusIconMap{$resource->simpleStatus($part)};
my $alt = $iconAltTags{$icon};
@@ -1171,7 +1224,7 @@ sub render_long_status {
my $color;
my $info = '';
- if ($resource->is_problem() || $resource->is_practice()) {
+ if ($resource->is_gradable() || $resource->is_practice()) {
$color = $colormap{$resource->status};
if (dueInLessThan24Hours($resource, $part)) {
@@ -1185,9 +1238,9 @@ sub render_long_status {
}
}
}
-
- if ($resource->kind() eq "res" &&
- $resource->is_raw_problem() &&
+
+ if (($resource->kind() eq "res") &&
+ ($resource->is_raw_problem() || $resource->is_gradable()) &&
!$firstDisplayed) {
if ($color) {$result .= ''; }
$result .= getDescription($resource, $part);
@@ -1234,7 +1287,7 @@ my @statuses = ($resObj->CORRECT, $resOb
sub render_parts_summary_status {
my ($resource, $part, $params) = @_;
- if (!$resource->is_problem() && !$resource->contains_problem) { return ' | | '; }
+ if (!$resource->is_gradable() && !$resource->contains_problem) { return ' | '; }
if ($params->{showParts}) {
return ' | ';
}
@@ -1345,7 +1398,9 @@ sub render {
# Without renaming the filterfunc, the server seems to go into
# an infinite loop
my $oldFilterFunc = $filterFunc;
- $filterFunc = sub { my $res = shift; return !$res->randomout() &&
+ $filterFunc = sub { my $res = shift; return !$res->randomout() &&
+ ($res->deeplink($args->{'caller'}) ne 'absent') &&
+ ($res->deeplink($args->{'caller'}) ne 'grades') &&
&$oldFilterFunc($res);};
}
@@ -1439,10 +1494,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'});
}
@@ -1722,6 +1780,11 @@ END
$cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
}
+ my $inhibitmenu;
+ if ($args->{'modalLink'}) {
+ $inhibitmenu = '&inhibitmenu=yes';
+ }
+
while (1) {
if ($args->{'sort'}) {
$curRes = shift(@resources);
@@ -1757,6 +1820,7 @@ END
# If this is an empty sequence and we're filtering them, continue on
$args->{'mapHidden'} = 0;
+ $args->{'mapUnlisted'} = 0;
if (($curRes->is_map()) && (!$curRes->{DATA}->{HAS_VISIBLE_CHILDREN})) {
if ($args->{'suppressEmptySequences'}) {
next;
@@ -1769,6 +1833,15 @@ END
} else {
next;
}
+ } else {
+ my $deeplink = $navmap->get_mapparam(undef,$mapname,"0.deeplink");
+ if ($deeplink =~ /^(absent|grades),/) {
+ if ($userCanSeeHidden) {
+ $args->{'mapUnlisted'} = 1;
+ } else {
+ next;
+ }
+ }
}
}
}
@@ -1831,8 +1904,17 @@ END
$args->{'condensed'} = 1;
}
}
- }
-
+ }
+ # If deep-link parameter is set (and is not set to full) suppress link
+ # unless privileged user, or calling context is sequence, and parameter
+ # set at map level
+ if ((!$curRes->deeplink($args->{'caller'})) ||
+ ($curRes->deeplink($args->{'caller'}) =~ /^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) {
$args->{'multipart'} = 0;
@@ -1858,14 +1940,20 @@ END
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://})) {
- if ($hostname ne '') {
- $src = 'http://'.$hostname.$src;
+ unless ((&Apache::lonnet::uses_sts()) || (&Apache::lonnet::waf_allssl($hostname))) {
+ if ($hostname ne '') {
+ $src = 'http://'.$hostname.$src;
+ }
+ $src .= ($srcHasQuestion? '&' : '?') . 'usehttp=1';
+ $srcHasQuestion = 1;
}
- $src .= ($srcHasQuestion? '&' : '?') . 'usehttp=1';
- $srcHasQuestion = 1;
} elsif (($is_ssl) && ($src =~ m{^\Q/adm/wrapper/ext/\E(?!https:)})) {
- if ($hostname ne '') {
- $src = 'http://'.$hostname.$src;
+ unless ((&Apache::lonnet::uses_sts()) || (&Apache::lonnet::waf_allssl($hostname))) {
+ if ($hostname ne '') {
+ $src = 'http://'.$hostname.$src;
+ }
+ $src .= ($srcHasQuestion? '&' : '?') . 'usehttp=1';
+ $srcHasQuestion = 1;
}
}
}
@@ -1875,7 +1963,7 @@ END
} else {
$args->{"resourceLink"} = $src.
($srcHasQuestion?'&':'?') .
- 'symb=' . &escape($symb).$anchor;
+ 'symb=' . &escape($symb).$inhibitmenu.$anchor;
}
}
# Now, we've decided what parts to show. Loop through them and
@@ -1904,7 +1992,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";
@@ -2290,7 +2378,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;
@@ -2696,6 +2784,10 @@ sub parmval_real {
my ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb);
$mapname = &Apache::lonnet::deversion($mapname);
+ my $toolsymb = '';
+ if ($fn =~ /ext\.tool$/) {
+ $toolsymb = $symb;
+ }
my ($recursed,@recurseup);
# ----------------------------------------------------- Cascading lookup scheme
@@ -2816,9 +2908,9 @@ sub parmval_real {
my $meta_rwhat=$rwhat;
$meta_rwhat=~s/\./_/g;
- my $default=&Apache::lonnet::metadata($fn,$meta_rwhat);
+ my $default=&Apache::lonnet::metadata($fn,$meta_rwhat,$toolsymb);
if (defined($default)) { return [$default,'resource']}
- $default=&Apache::lonnet::metadata($fn,'parameter_'.$meta_rwhat);
+ $default=&Apache::lonnet::metadata($fn,'parameter_'.$meta_rwhat,$toolsymb);
if (defined($default)) { return [$default,'resource']}
# --------------------------------------------------- fifth, check more course
if (defined($courseopt)) {
@@ -2860,7 +2952,7 @@ sub parmval_real {
if (defined($partgeneral[0])) { return \@partgeneral; }
}
if ($recurse) { return []; }
- my $pack_def=&Apache::lonnet::packages_tab_default($fn,'resource.'.$rwhat);
+ my $pack_def=&Apache::lonnet::packages_tab_default($fn,'resource.'.$rwhat,$toolsymb);
if (defined($pack_def)) { return [$pack_def,'resource']; }
return [''];
}
@@ -2906,7 +2998,7 @@ sub recursed_crumbs {
my $pc = $map->map_pc();
next if ((!$pc) || ($pc == 1));
push(@links,$map);
- push(@revmapinfo,{'href' => $map->link().'?navmap=1','text' => $map->title(),'no_mt' => 1,});
+ push(@revmapinfo,{'href' => $env{'request.use_absolute'}.$map->link().'?navmap=1','text' => $map->title(),'no_mt' => 1,});
$totallength += length($map->title());
}
my $numlinks = scalar(@links);
@@ -2921,7 +3013,7 @@ sub recursed_crumbs {
foreach my $map (@links) {
my $showntitle = &truncate_crumb_text($map->title(),$avg);
if ($showntitle ne '') {
- push(@revmapinfo,{'href' => $map->link().'?navmap=1','text' => $showntitle,'no_mt' => 1,});
+ push(@revmapinfo,{'href' => $env{'request.use_absolute'}.$map->link().'?navmap=1','text' => $showntitle,'no_mt' => 1,});
}
}
}
@@ -3429,6 +3521,71 @@ sub usedVersion {
return $self->navhash("version_$linkurl");
}
+sub isFirstResource {
+ my $self = shift;
+ my $map = shift;
+ my $symb = shift;
+ return unless (ref($map));
+ my $isfirst;
+ my $firstResource = $map->map_start();
+ if (ref($firstResource)) {
+ if ((!$firstResource->is_map()) && ($firstResource->src() ne '')) {
+ if ($firstResource->symb() eq $symb) {
+ $isfirst = 1;
+ } else {
+ $isfirst = 0;
+ }
+ } else {
+ my $it = $self->getIterator($firstResource,undef,undef,1);
+ while ( my $res=$it->next()) {
+ if ((ref($res)) && ($res->src() ne '') && (!$res->is_map())) {
+ if ($res->symb() eq $symb) {
+ $isfirst = 1;
+ } else {
+ $isfirst = 0;
+ }
+ last;
+ }
+ }
+ }
+ }
+ return $isfirst;
+}
+
+sub isLastResource {
+ my $self = shift;
+ my $map = shift;
+ my $symb = shift;
+ return unless (ref($map));
+ my $islast;
+ my $lastResource = $map->map_finish();
+ if (ref($lastResource)) {
+ if ((!$lastResource->is_map()) && ($lastResource->src() ne '')) {
+ if ($lastResource->symb() eq $symb) {
+ $islast = 1;
+ } else {
+ $islast = 0;
+ }
+ } else {
+ my $currRes = $self->getBySymb($symb);
+ if (ref($currRes)) {
+ my $it = $self->getIterator($currRes,undef,undef,1);
+ while ( my $res=$it->next()) {
+ if ((ref($res)) && ($res->src() ne '') && (!$res->is_map())) {
+ if ($res->symb() eq $symb) {
+ $islast = 1;
+ } else {
+ $islast = 0;
+ }
+ last;
+ }
+ }
+ }
+ }
+ }
+ return $islast;
+}
+
1;
package Apache::lonnavmaps::iterator;
@@ -3864,7 +4021,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();
@@ -4475,6 +4633,19 @@ sub is_problem {
}
return 0;
}
+sub is_tool {
+ my $self=shift;
+ my $src = $self->src();
+ return ($src =~ /ext\.tool$/);
+}
+sub is_gradable {
+ my $self=shift;
+ my $src = $self->src();
+ if (($src =~ /$LONCAPA::assess_re/) ||
+ (($self->is_tool()) && ($self->parmval('gradable',0) =~ /^yes$/i))) {
+ return !($self->is_practice());
+ }
+}
#
# The has below is the set of status that are considered 'incomplete'
#
@@ -4562,7 +4733,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());
}
@@ -4982,6 +5152,20 @@ sub slot_control {
my $available = $self->parmval("available", $part);
return ($useslots,$availablestudent,$available);
}
+sub deeplink {
+ my ($self,$caller) = @_;
+ my $value = $self->parmval("deeplink");
+ if ($value) {
+ my @deeplink = split(/,/,$value);
+ if ($caller eq 'sequence') {
+ if ($deeplink[1] ne 'res') {
+ return;
+ }
+ }
+ return $deeplink[0];
+ }
+ return;
+}
# Multiple things need this
sub getReturnHash {
@@ -5136,6 +5320,8 @@ sub parts {
my $self = shift;
if ($self->ext) { return []; }
+ if (($self->is_tool()) &&
+ ($self->is_gradable())) { return ['0']; }
$self->extractParts();
return $self->{PARTS};
@@ -5510,6 +5696,14 @@ Attempted, and not yet graded.
Attempted, and credit received for attempt (survey and anonymous survey only).
+=item * B:
+
+Attempted, but wrong for LTI Tool Provider by passback of grade
+
+=item * B:
+
+Correct for LTI Tool Provider by passback of grade
+
=back
=cut
@@ -5522,6 +5716,8 @@ sub CORRECT_BY_OVERRIDE { return 14; }
sub EXCUSED { return 15; }
sub ATTEMPTED { return 16; }
sub CREDIT_ATTEMPTED { return 17; }
+sub INCORRECT_BY_PASSBACK { return 18; }
+sub CORRECT_BY_PASSBACK { return 19; }
sub getCompletionStatus {
my $self = shift;
@@ -5536,8 +5732,12 @@ sub getCompletionStatus {
if ($status eq 'correct_by_override') {
return $self->CORRECT_BY_OVERRIDE;
}
+ if ($status eq 'correct_by_passback') {
+ return $self->CORRECT_BY_PASSBACK;
+ }
if ($status eq 'incorrect_attempted') {return $self->INCORRECT; }
if ($status eq 'incorrect_by_override') {return $self->INCORRECT_BY_OVERRIDE; }
+ if ($status eq 'incorrect_by_passback') {return $self->INCORRECT_BY_PASSBACK; }
if ($status eq 'excused') {return $self->EXCUSED; }
if ($status eq 'ungraded_attempted') {return $self->ATTEMPTED; }
if ($status eq 'credit_attempted') {
@@ -5691,7 +5891,8 @@ sub status {
# There are a few whole rows we can dispose of:
if ($completionStatus == CORRECT ||
- $completionStatus == CORRECT_BY_OVERRIDE ) {
+ $completionStatus == CORRECT_BY_OVERRIDE ||
+ $completionStatus == CORRECT_BY_PASSBACK ) {
if ( $suppressFeedback ) { return ANSWER_SUBMITTED }
my $awarded=$self->awarded($part);
if ($awarded < 1 && $awarded > 0) {
@@ -5704,7 +5905,8 @@ sub status {
# If it's WRONG... and not open
if ( ($completionStatus == INCORRECT ||
- $completionStatus == INCORRECT_BY_OVERRIDE)
+ $completionStatus == INCORRECT_BY_OVERRIDE ||
+ $completionStatus == INCORRECT_BY_PASSBACK)
&& (!$self->opendate($part) || $self->opendate($part) > time()) ) {
return INCORRECT;
}
@@ -5746,7 +5948,8 @@ sub status {
}
# If it's WRONG...
- if ($completionStatus == INCORRECT || $completionStatus == INCORRECT_BY_OVERRIDE) {
+ if ($completionStatus == INCORRECT || $completionStatus == INCORRECT_BY_OVERRIDE ||
+ $completionStatus == INCORRECT_BY_PASSBACK) {
# and there are TRIES LEFT:
if ($self->tries($part) < $self->maxtries($part) || !$self->maxtries($part)) {
return $suppressFeedback ? ANSWER_SUBMITTED : TRIES_LEFT;
@@ -5800,7 +6003,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);
|