--- loncom/interface/lonnavmaps.pm 2017/12/18 23:13:53 1.537 +++ loncom/interface/lonnavmaps.pm 2020/04/22 14:53:03 1.550 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Navigate Maps Handler # -# $Id: lonnavmaps.pm,v 1.537 2017/12/18 23:13:53 raeburn Exp $ +# $Id: lonnavmaps.pm,v 1.550 2020/04/22 14:53:03 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 = ".mt('Branch')."; } - # 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 = ""."\"".($nowOpen"; 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; } @@ -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()) { + 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()) { + 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"; @@ -2910,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); @@ -2925,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,}); } } } @@ -3433,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; @@ -3868,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(); @@ -4579,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()); } @@ -4999,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 { @@ -5836,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);