--- loncom/interface/lonmenu.pm	2017/05/08 14:20:20	1.472
+++ loncom/interface/lonmenu.pm	2021/07/16 01:14:14	1.509
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Routines to control the menu
 #
-# $Id: lonmenu.pm,v 1.472 2017/05/08 14:20:20 raeburn Exp $
+# $Id: lonmenu.pm,v 1.509 2021/07/16 01:14:14 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -99,7 +99,7 @@ It gets filled in the BEGIN block of thi
 
 =over
 
-=item prep_menuitems(\@menuitem)
+=item prep_menuitems(\@menuitem,$ltitarget)
 
 This routine wraps a menuitem in proper HTML. It is used by primary_menu() and 
 secondary_menu().
@@ -210,16 +210,17 @@ use Apache::lonenc();
 use Apache::lonlocal;
 use Apache::lonmsg();
 use LONCAPA qw(:DEFAULT :match);
+use LONCAPA::ltiutils;
 use HTML::Entities();
 use Apache::lonwishlist();
 
 use vars qw(@desklines %category_names %category_members %category_positions 
-            $readdesk @primary_menu %primary_submenu @secondary_menu);
+            $readdesk @primary_menu %primary_submenu @secondary_menu %secondary_submenu);
 
 my @inlineremote;
 
 sub prep_menuitem {
-    my ($menuitem) = @_;
+    my ($menuitem,$ltitarget) = @_;
     return '' unless(ref($menuitem) eq 'ARRAY');
     my $link;
     if ($$menuitem[1]) { # graphical Link
@@ -229,10 +230,14 @@ sub prep_menuitem {
     } else {             # textual Link
         $link = &mt($$menuitem[3]);
     }
+    my $target = ' target="_top"';
+    if ($ltitarget eq 'iframe') {
+        $target ='';
+    }
     return '<li><a' 
            # highlighting for new messages
            . ( $$menuitem[4] eq 'newmsg' ? ' class="LC_new_message"' : '') 
-           . qq| href="$$menuitem[0]" target="_top">$link</a></li>|;
+           . qq| href="$$menuitem[0]"$target>$link</a></li>|;
 }
 
 # primary_menu() evaluates @primary_menu and returns a two item array,
@@ -242,8 +247,8 @@ sub prep_menuitem {
 # @primary_menu is filled within the BEGIN block of this module with 
 # entries from mydesk.tab
 sub primary_menu {
-    my ($crstype) = @_;
-    my (%menu);
+    my ($crstype,$ltimenu,$menucoll,$menuref) = @_;
+    my (%menu,%ltiexc,%menuopts);
     # each element of @primary contains following array:
     # (link url, icon path, alt text, link text, condition, position)
     my $public;
@@ -260,6 +265,21 @@ sub primary_menu {
         my %roles_in_env;
         $rolecount = &Apache::lonroles::roles_from_env(\%roles_in_env,$update);
     }
+    my ($lti,$ltitarget);
+    if ($env{'request.lti.login'}) {
+        $lti = 1;
+        $ltitarget = $env{'request.lti.target'};
+        if (ref($ltimenu) eq 'HASH') {
+            foreach my $item ('fullname','logout') {
+                unless ($ltimenu->{$item}) {
+                    $ltiexc{$item} = 1;
+                }
+            }
+        }
+    }
+    if (($menucoll) && (ref($menuref) eq 'HASH')) {
+        %menuopts = %{$menuref};
+    }
     foreach my $menuitem (@primary_menu) {
         # evaluate conditions 
         next if    ref($menuitem)       ne 'ARRAY';    #
@@ -274,9 +294,15 @@ sub primary_menu {
                 && !$public;                           # only visible to public
                                                        # users
         next if    $$menuitem[4]        eq 'roles'     ##show links depending on
-                && &Apache::loncommon::show_course();  ##term 'Courses' or 
-        next if    $$menuitem[4]        eq 'courses'   ##'Roles' wanted
-                && !&Apache::loncommon::show_course(); ##
+                && (&Apache::loncommon::show_course()  ##term 'Courses' or
+                || $lti);                              ##'Roles' wanted
+        next if    $$menuitem[4]        eq 'courses'   ##and not LTI access
+                && (!&Apache::loncommon::show_course()
+                || $lti);
+        next if    $$menuitem[4]        eq 'notlti'
+                && $lti;
+        next if    $$menuitem[4]        eq 'ltiexc'
+                && exists($ltiexc{lc($menuitem->[3])});
         my $title = $menuitem->[3];
         if (($crstype eq 'Placement') && (!$env{'request.role.adv'})) {
             if ($menuitem->[4] eq 'courses') {
@@ -289,11 +315,30 @@ sub primary_menu {
         if ($position eq '') {
             $position = 'right';
         }
+        if ($env{'request.course.id'} && $menucoll) {
+            if (($menuitem->[6]) && (!$menuopts{$menuitem->[6]})) {
+                if ($menuitem->[6] eq 'pers') {
+                    if ($menuopts{'name'} && !$ltiexc{'fullname'} &&
+                        $env{'user.name'} && $env{'user.domain'}) {
+                        $menu{$position} .= '<li><a href="#">'.
+                            &Apache::loncommon::plainname($env{'user.name'},
+                                                          $env{'user.domain'}).'</a></li>';
+                        next;
+                    } else {
+                        next;
+                    }
+                } else {
+                    next;
+                }
+            }
+        }
         if (defined($primary_submenu{$title})) {
             my ($link,$target);
             if ($menuitem->[0] ne '') {
                 $link = $menuitem->[0];
-                $target = '_top';
+                unless ($ltitarget eq 'iframe') {
+                    $target = '_top';
+                }
             } else {
                 $link = '#';
             }
@@ -306,10 +351,19 @@ sub primary_menu {
                              ($item->[2] eq 'blog')) &&
                              (!&Apache::lonnet::usertools_access('','',$item->[2],
                                                            undef,'tools')));
+                    if ($env{'request.course.id'} && $menucoll) {
+                        next if ($item->[3]) && (!$menuopts{$item->[3]});
+                    }
                     push(@primsub,$item);
                 }
-                if ($title eq 'Personal' && $env{'user.name'} && $env{'user.domain'} ) {
-                    $title = &Apache::loncommon::plainname($env{'user.name'},$env{'user.domain'});
+                if ($title eq 'Personal') {
+                    if ($env{'user.name'} && $env{'user.domain'} && !$ltiexc{'fullname'}) {
+                        unless (($env{'request.course.id'}) && ($menucoll) && (!$menuopts{'name'})) {
+                            $title = &Apache::loncommon::plainname($env{'user.name'},$env{'user.domain'});
+                        }
+                    }
+                    next if (($env{'request.course.id'}) && ($menucoll) && ($title eq 'Personal') &&
+                             (!@primsub));
                 } else {
                     $title = &mt($title);
                 }
@@ -328,13 +382,13 @@ sub primary_menu {
                                                                   'helpdeskmail',
                                                                   $defdom,$origmail);
                 if ($to ne '') {
-                    $menu{$position} .= &prep_menuitem($menuitem); 
+                    $menu{$position} .= &prep_menuitem($menuitem,$ltitarget); 
                 }
             } else {
                 $menu{$position} .= '<li>'.&Apache::loncommon::top_nav_help('Help').'</li>';
             }
         } else {
-            $menu{$position} .= prep_menuitem($menuitem);
+            $menu{$position} .= prep_menuitem($menuitem,$ltitarget);
         }
     }
     my @output = ('','');
@@ -373,7 +427,7 @@ sub getauthor{
 }
 
 sub secondary_menu {
-    my ($httphost) = @_;
+    my ($httphost,$ltiscope,$ltimenu,$noprimary,$menucoll,$menuref) = @_;
     my $menu;
 
     my $crstype = &Apache::loncommon::course_type();
@@ -394,17 +448,16 @@ sub secondary_menu {
     my $canmodpara    = &Apache::lonnet::allowed('opa', $crs_sec);
     my $canvgr        = &Apache::lonnet::allowed('vgr', $crs_sec);
     my $canmgr        = &Apache::lonnet::allowed('mgr', $crs_sec); 
+    my $canplc        = &Apache::lonnet::allowed('plc', $crs_sec);
     my $author        = &getauthor();
 
-    my ($cdom,$cnum,$showsyllabus,$showfeeds,$showresv,$grouptools);
+    my ($cdom,$cnum,$showsyllabus,$showfeeds,$showresv,$grouptools,
+        $lti,$ltimapres,%ltiexc,%menuopts);
     $grouptools = 0;
     if ($env{'request.course.id'}) {
         $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
         $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
-        if ($canedit || $canvieweditor)  {
-            $showsyllabus = 1;
-            $showfeeds = 1;
-        } else {
+        unless ($canedit || $canvieweditor)  {
             unless (&Apache::lonnet::is_on_map("public/$cdom/$cnum/syllabus")) {
                 if (($env{'course.'.$env{'request.course.id'}.'.externalsyllabus'}) ||
                     ($env{'course.'.$env{'request.course.id'}.'.uploadedsyllabus'}) ||
@@ -423,10 +476,9 @@ sub secondary_menu {
                 $showresv = 1;
             }
         }
-        my %groups = &Apache::lonnet::get_active_groups(
-                     $env{'user.domain'}, $env{'user.name'},$cdom,$cnum);
-        if (%groups) {
-            foreach my $group (keys(%groups)) {
+        if ($env{'request.course.groups'} ne '') {
+            foreach my $group (split(/:/,$env{'request.course.groups'})) {
+                next unless ($group =~ /^\w+$/);
                 my @privs = split(/:/,$env{"user.priv.$env{'request.role'}./$cdom/$cnum/$group"});
                 shift(@privs);
                 if (@privs) {
@@ -434,6 +486,22 @@ sub secondary_menu {
                 }
             }
         }
+        if ($env{'request.lti.login'}) {
+            $lti = 1;
+            if (ref($ltimenu) eq 'HASH') {
+                foreach my $item ('fullname','coursetitle','role','logout','grades') {
+                    unless ($ltimenu->{$item}) {
+                        $ltiexc{$item} = 1;
+                    }
+                }
+            }
+            if (($ltiscope eq 'map') || ($ltiscope eq 'resource')) {
+                $ltimapres = 1;
+            }
+        }
+    }
+    if (($menucoll) && (ref($menuref) eq 'HASH')) {
+        %menuopts = %{$menuref};
     }
 
     my ($canmodifycoauthor); 
@@ -444,7 +512,11 @@ sub secondary_menu {
             $canmodifycoauthor = 1;
         }
     }
-    my ($roleswitcher_js,$roleswitcher_form);
+
+    my ($roleswitcher_js,$roleswitcher_form,$ltitarget);
+    if ($env{'request.lti.login'}) {
+        $ltitarget = $env{'request.lti.target'};
+    }
 
     foreach my $menuitem (@secondary_menu) {
         # evaluate conditions 
@@ -456,7 +528,7 @@ sub secondary_menu {
         next if    $$menuitem[4]   =~ /^crsedit/
                 && (!$canedit && !$canvieweditor);
         next if    $$menuitem[4]  eq 'nvgr'
-                && $canvgr;
+                && ($canvgr || $ltiexc{'grades'});
         next if    $$menuitem[4]  eq 'vgr'
                 && !$canvgr;
         next if    $$menuitem[4]   eq 'viewusers'
@@ -471,27 +543,94 @@ sub secondary_menu {
                 && !$canviewwnew;
         next if    $$menuitem[4]   eq 'params'
                 && (!$canmodpara && !$canviewpara);
-        next if    $$menuitem[4]   =~ /showgroups$/
-                && !$canviewgrps
-                && !$grouptools;
+        next if    $$menuitem[4]   eq 'showgroups'
+                && ($canviewgrps || !$grouptools);
         next if    $$menuitem[4]   eq 'showsyllabus'
                 && !$showsyllabus;
         next if    $$menuitem[4]   eq 'showfeeds'
                 && !$showfeeds;
+        next if     $$menuitem[4]  eq 'plc'
+                && !$canplc;
         next if    $$menuitem[4]    eq 'author'
                 && !$author;
         next if    $$menuitem[4]    eq 'cca'
                 && !$canmodifycoauthor;
+        next if    $$menuitem[4]    eq 'notltimapres'
+                && $ltimapres;
+        next if    $$menuitem[4]    eq 'notlti'
+                && $lti;
+        next if    $$menuitem[4]    eq 'lti'
+                && (!$lti || !$noprimary);
+        next if    $$menuitem[3]    eq 'Logout'
+                && $ltiexc{'logout'};
 
-        if ($$menuitem[3] eq 'Roles' && $env{'request.course.id'}) {
+        my $title = $menuitem->[3];
+        if ($env{'request.course.id'} && $menucoll) {
+            unless ($$menuitem[5] eq 'roles') {
+                next if (($$menuitem[5]) && (!$menuopts{$$menuitem[5]}));
+            }
+        }
+        if (defined($secondary_submenu{$title})) {
+            my ($link,$target);
+            if ($menuitem->[0] ne '') {
+                $link = $menuitem->[0];
+                unless ($ltitarget eq 'iframe') {
+                    $target = '_top';
+                }
+            } else {
+                $link = '#';
+            }
+            my @scndsub;
+            if (ref($secondary_submenu{$title}) eq 'ARRAY') {
+                foreach my $item (@{$secondary_submenu{$title}}) {
+                    if (ref($item) eq 'ARRAY') {
+                        next if ($item->[2] eq 'vgr' && !$canvgr);
+                        next if ($item->[2] eq 'opa' && !$canmodpara);
+                        next if ($item->[2] eq 'vpa' && !$canviewpara);
+                        next if ($item->[2] eq 'viewusers' && !($canmodifyuser || $canviewusers));
+                        next if ($item->[2] eq 'mgr' && !$canmgr);
+                        next if ($item->[2] eq 'vcg' && !$canviewgrps);
+                        next if ($item->[2] eq 'crsedit' && !$canedit && !$canvieweditor);
+                        next if ($item->[2] eq 'params' && !$canmodpara && !$canviewpara);
+                        next if ($item->[2] eq 'author' && !$author);
+                        next if ($item->[2] eq 'cca' && !$canmodifycoauthor);
+                        next if ($item->[2] eq 'lti' && !$lti);
+                        if ($item->[2] =~ /^lti(portfolio|wishlist|blog)$/) {
+                            my $tool = $1;
+                            next if !$lti;
+                            next if (!&Apache::lonnet::usertools_access('','',$tool,
+                                                                        undef,'tools'));
+                        }
+                        push(@scndsub,$item);
+                    }
+                }
+                if ($title eq 'Personal' && $env{'user.name'} && $env{'user.domain'}) {
+                    unless ($ltiexc{'fullname'}) {
+                        $title = &Apache::loncommon::plainname($env{'user.name'},$env{'user.domain'});
+                    }
+                }
+                if (@scndsub > 0) {
+                    $menu .= &create_submenu($link,$target,&mt($title),\@scndsub,1);
+                } elsif ($link ne '#') {
+                    $menu .= '<li><a href="'.$link.'" target="'.$target.'">'.&mt($title).'</a></li>';
+                }
+            }
+        } elsif ($$menuitem[3] eq 'Roles' && $env{'request.course.id'}) {
             # special treatment for role selector
-            ($roleswitcher_js,$roleswitcher_form,my $switcher) =
+            my ($switcher,$has_opa_priv);
+            ($roleswitcher_js,$roleswitcher_form,$switcher,$has_opa_priv) =
                 &roles_selector(
-                        $env{'course.' . $env{'request.course.id'} . '.domain'},
-                        $env{'course.' . $env{'request.course.id'} . '.num'},
-                        $httphost
+                    $env{'course.' . $env{'request.course.id'} . '.domain'},
+                    $env{'course.' . $env{'request.course.id'} . '.num'},
+                    $httphost,$ltitarget
                 );
+            if (($$menuitem[5]) && (!$menuopts{$$menuitem[5]})) {
+                next unless ($has_opa_priv);
+            }
             $menu .= $switcher;
+        } elsif ($$menuitem[3] eq 'Help') { # special treatment for helplink
+            next if ($crstype eq 'Placement');
+            $menu .= '<li>'.&Apache::loncommon::top_nav_help('Help').'</li>';
         } else {
             if ($$menuitem[3] eq 'Syllabus' && $env{'request.course.id'}) {
                 my $url = $$menuitem[0];
@@ -505,16 +644,19 @@ sub secondary_menu {
                 }
                 if ($env{'course.'.$env{'request.course.id'}.'.externalsyllabus'} =~ m{^http://}) {
                     if (($ENV{'SERVER_PORT'} == 443) || ($env{'request.use_absolute'} =~ m{^https://})) {
-                        unless ($$menuitem[0] =~ m{^https?://}) {
-                            $$menuitem[0] = 'http://'.$ENV{'SERVER_NAME'}.$$menuitem[0];
-                        }
-                        unless ($$menuitem[0] =~ /(\&|\?)usehttp=1/) {
-                            $$menuitem[0] .= (($$menuitem[0]=~/\?/) ? '&' : '?').'usehttp=1';
+                        unless ((&Apache::lonnet::uses_sts()) || (&Apache::lonnet::waf_allssl())) {
+                            unless ($$menuitem[0] =~ m{^https?://}) {
+                                $$menuitem[0] = 'http://'.$ENV{'SERVER_NAME'}.$$menuitem[0];
+                            }
+                            unless ($$menuitem[0] =~ /(\&|\?)usehttp=1/) {
+                                $$menuitem[0] .= (($$menuitem[0]=~/\?/) ? '&' : '?').'usehttp=1';
+                            }
                         }
                     }
                 }
+                $$menuitem[0] = &HTML::Entities::encode($$menuitem[0],'&<>"');
             }
-            $menu .= &prep_menuitem(\@$menuitem);
+            $menu .= &prep_menuitem(\@$menuitem,$ltitarget);
         }
     }
     if ($menu =~ /\[url\].*\[symb\]/) {
@@ -531,13 +673,14 @@ sub secondary_menu {
             my $londocroot = $Apache::lonnet::perlvar{'lonDocRoot'};
             ($escurl = $env{'request.filename'}) =~ s{^\Q$londocroot\E}{};
             $escurl  = &escape($escurl);
-        }    
+        }
         $menu =~ s/\[url\]/$escurl/g;
         $menu =~ s/\[symb\]/$escsymb/g;
     }
     $menu =~ s/\[uname\]/$$author{user}/g;
     $menu =~ s/\[udom\]/$$author{dom}/g;
-    if ($showsyllabus || $showfeeds) {
+    $menu =~ s/\[javascript\]/javascript:/g;
+    if ($env{'request.course.id'}) {
         $menu =~ s/\[cnum\]/$cnum/g;
         $menu =~ s/\[cdom\]/$cdom/g;
     }
@@ -620,9 +763,25 @@ sub build_submenu {
                     next unless (($env{'user.name'} ne '') && ($env{'user.domain'} ne ''));
                     $href =~ s/\[domain\]/$env{'user.domain'}/g;
                     $href =~ s/\[user\]/$env{'user.name'}/g;
+                } elsif (($href =~ m{^/adm/preferences\?}) && ($href =~ /\[returnurl\]/)) {
+                    my $returnurl = $ENV{'REQUEST_URI'};
+                    if ($ENV{'REQUEST_URI'} =~ m{/adm/preferences\?action=(?:changedomcoord|authorsettings)\&returnurl=([^\&]+)$}) {
+                        $returnurl = $1;
+                    }
+                    if (($returnurl =~ m{^/adm/createuser($|\?action=)}) ||
+                        ($returnurl =~ m{^/priv/$match_domain/$match_username}) ||
+                        ($returnurl =~ m{^/res(/?$|/$match_domain/$match_username)})) {
+                        $returnurl =~ s{\?.*$}{};
+                        $returnurl = '&amp;returnurl='.&HTML::Entities::encode($returnurl,'"<>&\'');
+                    } else {
+                        undef($returnurl);
+                    }
+                    $href =~ s/\[returnurl\]/$returnurl/;
                 }
                 unless (($href eq '') || ($href =~ /^\#/)) {
-                    $target = ' target="_top"';
+                    if ($target eq '_top') {
+                        $target = ' target="_top"';
+                    }
                 }
 
                 $menu .= '<li style="margin:0;padding:0;'. $bordertop . $borderbot .'">';
@@ -635,7 +794,7 @@ sub build_submenu {
 }
 
 sub innerregister {
-    my ($forcereg,$bread_crumbs,$group,$pagebuttonshide,$hostname) = @_;
+    my ($forcereg,$bread_crumbs,$group,$pagebuttonshide,$hostname,$ltiscope,$ltiuri) = @_;
     my $const_space = ($env{'request.state'} eq 'construct');
     my $is_const_dir = 0;
 
@@ -645,7 +804,7 @@ sub innerregister {
 
     undef(@inlineremote);
 
-    my ($mapurl,$resurl,$crstype);
+    my ($mapurl,$resurl,$crstype,$navmap);
 
     if ($env{'request.course.id'}) {
 #
@@ -653,31 +812,57 @@ sub innerregister {
 #
         $crstype = &Apache::loncommon::course_type();
         if ($env{'request.symb'}) {
-            ($mapurl, my $rid, $resurl) = &Apache::lonnet::decode_symb(&Apache::lonnet::symbread());
+            my $ignorenull;
+            unless ($env{'request.noversionuri'} eq '/adm/navmaps') {
+                $ignorenull = 1;
+            }
+            my $symb = &Apache::lonnet::symbread('','',$ignorenull);
+            ($mapurl, my $rid, $resurl) = &Apache::lonnet::decode_symb($symb);
             my $coursetitle = $env{'course.'.$env{'request.course.id'}.'.description'};
 
             my $maptitle = &Apache::lonnet::gettitle($mapurl);
-            my $restitle = &Apache::lonnet::gettitle(&Apache::lonnet::symbread());
-            my @crumbs;
+            my $restitle = &Apache::lonnet::gettitle($symb);
+            my (@crumbs,@mapcrumbs);
+            if (($env{'request.noversionuri'} ne '/adm/navmaps') && ($mapurl ne '') &&
+                (!(($crstype eq 'Placement') && !$env{'request.role.adv'}))) {
+                unless ($ltiscope eq 'resource') {
+                    if (($mapurl ne $env{'course.'.$env{'request.course.id'}.'.url'}) &&
+                        !(($ltiscope eq 'map') && (&Apache::lonnet::clutter($resurl) eq $ltiuri))) {
+                        $navmap = Apache::lonnavmaps::navmap->new();
+                        if (ref($navmap)) {
+                            @mapcrumbs = $navmap->recursed_crumbs($mapurl,$restitle);
+                        }
+                    }
+                }
+            }
             unless (($forcereg) &&
                     ($env{'request.noversionuri'} eq '/adm/navmaps') &&
                     ($mapurl eq $env{'course.'.$env{'request.course.id'}.'.url'}) ||
-                    (($crstype eq 'Placement') && (!$env{'request.role.adv'}))) {
+                    (($crstype eq 'Placement') && (!$env{'request.role.adv'})) ||
+                    ($ltiscope eq 'map') || ($ltiscope eq 'resource')) {
                 @crumbs = ({text  => $crstype.' Contents', 
                             href  => "Javascript:gopost('/adm/navmaps','')"});
             }
             if ($mapurl ne $env{'course.'.$env{'request.course.id'}.'.url'}) { 
-                push(@crumbs, {text  => '...',
-                               no_mt => 1});
+                if (@mapcrumbs) {
+                    push(@crumbs,@mapcrumbs);
+                } elsif (!(($crstype eq 'Placement') && (!$env{'request.role.adv'})) &&
+                         ($ltiscope ne 'map') && ($ltiscope ne 'resource')) {
+                    push(@crumbs, {text  => '...',
+                                   no_mt => 1});
+                }
             }
 
-            unless (($crstype eq 'Placement') || (!$env{'request.role.adv'})) {
-                push @crumbs, {text => $maptitle, no_mt => 1} if ($maptitle 
-                                                           && $maptitle ne 'default.sequence' 
-                                                           && $maptitle ne $coursetitle);
+            unless ((($crstype eq 'Placement') && (!$env{'request.role.adv'})) || (@mapcrumbs) ||
+                    (!$maptitle) || ($maptitle eq 'default.sequence') ||
+                    ($mapurl eq $env{'course.'.$env{'request.course.id'}.'.url'}) ||
+                    ($ltiscope eq 'resource')) {
+                push @crumbs, {text => $maptitle, no_mt => 1, 
+                               href => &Apache::lonnet::clutter($mapurl).'?navmap=1'};
+            }
+            if ($restitle && !@mapcrumbs) {
+                push(@crumbs,{text => $restitle, no_mt => 1});
             }
-
-            push @crumbs, {text => $restitle, no_mt => 1} if $restitle; 
             my @tools;
             if ($env{'request.filename'} =~ /\.page$/) {
                 my %breadcrumb_tools = &Apache::lonhtmlcommon::current_breadcrumb_tools();
@@ -759,8 +944,14 @@ sub innerregister {
         $perms{'mdc'} = &Apache::lonnet::allowed('mdc',$env{'request.course.id'});
         $perms{'cev'} = &Apache::lonnet::allowed('cev',$env{'request.course.id'});
         my @privs;
+        my $gradable_exttool;
         if ($env{'request.symb'} ne '') {
-             if ($env{'request.filename'}=~/$LONCAPA::assess_re/) {
+             if ($env{'request.noversionuri'} =~ m{^/adm/$cdom/$cnum/(\d+)/ext\.tool$}) {
+                 if (&Apache::lonnet::EXT('resource.0.gradable') =~ /^yes$/i) {
+                     $gradable_exttool = 1;
+                     push(@privs,('mgr','vgr'));
+                 }
+             } elsif ($env{'request.filename'}=~/$LONCAPA::assess_re/) {
                  push(@privs,('mgr','vgr'));
              }
              push(@privs,('opa','vpa'));
@@ -775,8 +966,8 @@ sub innerregister {
 #
 # Determine whether or not to show Grades and Submissions buttons
 #
-        if ($env{'request.symb'} ne '' &&
-            $env{'request.filename'}=~/$LONCAPA::assess_re/) {
+        if (($env{'request.symb'} ne '') &&
+            (($env{'request.filename'}=~/$LONCAPA::assess_re/) || ($gradable_exttool))) {
             if ($perms{'mgr'}) {
                 &switch('','',7,2,'pgrd.png','Content Grades','grades[_4]',
                         "gocmd('/adm/grades','gradingmenu')",
@@ -860,7 +1051,35 @@ ENDMENUITEMS
 # Should probably be in mydesk.tab
 #
             $menuitems = "c&3&1";
-            if (($crstype ne 'Placement') || ($env{'request.role.adv'})) {
+            if ($ltiscope eq 'resource') {
+# Suppress display of backward arrow for LTI Provider if scope is resource.
+# Suppress display of forward arrow for LTI Provider if scope is resource.
+            } elsif ($ltiscope eq 'map') {
+# Suppress display of backward arrow for LTI Provider if scope is map and this is first resource.
+# Suppress display of forward arrow for LTI Provider if scope is map and this is the last resource.
+                my $showforw = 1;
+                my $showback = 1;
+                my $navmap = Apache::lonnavmaps::navmap->new();
+                if (ref($navmap)) {
+                    my $mapres = $navmap->getResourceByUrl($ltiuri);
+                    if (ref($mapres)) {
+                        if ($navmap->isLastResource($mapres,$env{'request.symb'})) {
+                            $showforw = 0;
+                        }
+                        if ($navmap->isFirstResource($mapres,$env{'request.symb'})) {
+                            $showback = 0;
+                        }
+                    }
+                }
+                if ($showback) {
+                    $menuitems.="
+s&2&1&back.png&&&gopost('/adm/flip','back:'+currentURL)&Previous content resource&&1";
+                }
+                if ($showforw) {
+                    $menuitems.="
+s&2&3&forw.png&&&gopost('/adm/flip','forward:'+currentURL)&Next content resource&&3";
+                }
+            } elsif (($crstype ne 'Placement') || ($env{'request.role.adv'})) {
                 $menuitems.="
 s&2&1&back.png&&&gopost('/adm/flip','back:'+currentURL)&Previous content resource&&1
 s&2&3&forw.png&&&gopost('/adm/flip','forward:'+currentURL)&Next content resource&&3";
@@ -918,14 +1137,17 @@ if ($env{'browser.mobile'}) {
     $is_mobile = 1;
 }
 
-            unless ($env{'request.noversionuri'}=~/\/(bulletinboard|smppg|navmaps|syllabus|aboutme|viewclasslist|portfolio|ext\.tool)(\?|$)/) {
-		if ((!$env{'request.enc'}) && ($env{'request.noversionuri'} !~ m{^/adm/wrapper/ext/}) && ($env{'request.noversionuri'} !~ m{^/uploaded/$match_domain/$match_courseid/docs/})) {
+            unless ($env{'request.noversionuri'}=~/\/(bulletinboard|smppg|navmaps|syllabus|aboutme|viewclasslist|portfolio)(\?|$)/) {
+		if ((!$env{'request.enc'}) && ($env{'request.noversionuri'} !~ m{^/adm/wrapper/ext/}) &&
+                    ($env{'request.noversionuri'} !~ m{^/uploaded/$match_domain/$match_courseid/(docs/|default_\d+\.page$)}) &&
+                    ($env{'request.noversionuri'} !~ m{^/adm/.+/ext\.tool$})) {
 		    $menuitems.=(<<ENDREALRES);
-s&6&3&catalog.png&Info&info[_1]&catalog_info('$is_mobile')&Show Metadata
+s&6&3&catalog.png&Info&info[_1]&catalog_info(currentURL,'$is_mobile')&Show Metadata
 ENDREALRES
                 }
-                unless (($env{'request.noversionuri'} =~ m{^/uploaded/$match_domain/$match_courseid/docs/}) ||
-                        ($env{'request.noversionuri'} =~ m{^\Q/adm/wrapper/\E(ext|uploaded)/})) {  
+                unless (($env{'request.noversionuri'} =~ m{^/uploaded/$match_domain/$match_courseid/(docs/|default_\d+\.page$)}) ||
+                        ($env{'request.noversionuri'} =~ m{^\Q/adm/wrapper/\E(ext|uploaded)/}) ||
+                        ($env{'request.noversionuri'} =~ m{^/adm/.+/ext\.tool$})) {
                     $menuitems.=(<<ENDREALRES);
 s&8&1&eval.png&Evaluate&this[_1]&gopost('/adm/evaluate',currentURL,1)&Provide my evaluation of this resource
 ENDREALRES
@@ -1096,15 +1318,17 @@ sub prepare_functions {
     }
 
     my $editbutton = '';
+    my $viewsrcbutton = '';
 #
-# Determine whether or not to display 'Edit' icon/button
+# Determine whether or not to display 'Edit' or 'View Source' icon/button
 #
     if ($resurl =~ m{^/?adm/($match_domain)/($match_username)/aboutme$}) {
+        my $blocked = &Apache::loncommon::blocking_status('about',$2,$1);
         my $file=&Apache::lonnet::declutter($env{'request.filename'});
         ($cfile,$home,$switchserver,$forceedit,$forceview) =
             &Apache::lonnet::can_edit_resource($file,$cnum,$cdom,
                 &Apache::lonnet::clutter($resurl),$env{'request.symb'},$group);
-        if (($cfile) && ($home ne '') && ($home ne 'no_host')) {
+        if (($cfile) && ($home ne '') && ($home ne 'no_host') && (!$blocked)) {
             $editbutton = &get_editbutton($cfile,$home,$switchserver,
                                           $forceedit,$forceview,$forcereg);
         }
@@ -1129,7 +1353,8 @@ sub prepare_functions {
 #
         if (($perms{'mdc'}) &&
             (($resurl =~ m{^/?public/$cdom/$cnum/syllabus}) ||
-            ($resurl =~ m{^/?uploaded/$cdom/$cnum/portfolio/syllabus/}))) {
+             ($resurl =~ m{^/?uploaded/$cdom/$cnum/portfolio/syllabus/}) ||
+             (($resurl =~ m{^/?uploaded/$cdom/$cnum/default_\d+\.sequence$}) && ($env{'form.navmap'})))) {
             if ($resurl =~ m{^/}) {
                 $cfile = $resurl;
             } else {
@@ -1141,9 +1366,17 @@ sub prepare_functions {
             } else {
                 $forceedit = 1;
             }
-            $editbutton = &get_editbutton($cfile,$home,$switchserver,
-                                          $forceedit,$forceview,$forcereg,
-                                          $hostname);
+            if ($cfile =~ m{^/uploaded/$cdom/$cnum/default_\d+\.sequence$}) {
+                my $text = 'Edit Folder';
+                &switch('','',7,4,'docs-22x22.png','Edit Folder','parms[_2]',
+                        "gocmd('/adm/coursedocs','direct')",
+                        'Folder/Page Content');
+                $editbutton = 1;
+            } else {
+                $editbutton = &get_editbutton($cfile,$home,$switchserver,
+                                              $forceedit,$forceview,$forcereg,
+                                              $hostname);
+            }
         } elsif (($resurl eq '/adm/extresedit') &&
                  (($env{'form.symb'}) || ($env{'form.folderpath'}))) {
             ($cfile,$home,$switchserver,$forceedit,$forceview) =
@@ -1172,6 +1405,32 @@ sub prepare_functions {
                                                   $forceedit,$forceview,$forcereg,
                                                   $hostname);
                 }
+                if ((($cfile eq '') || (!$editbutton)) &&
+                    ($resurl =~ /$LONCAPA::assess_re/)) {
+                    my $showurl = &Apache::lonnet::clutter($resurl);
+                    my $crs_sec = $env{'request.course.id'} . (($env{'request.course.sec'} ne '')
+                                                              ? "/$env{'request.course.sec'}"
+                                                              : '');
+                    if ((&Apache::lonnet::allowed('cre','/')) &&
+                        (&Apache::lonnet::metadata($resurl,'sourceavail') eq 'open')) {
+                        $viewsrcbutton = 1;
+                    } elsif (&Apache::lonnet::allowed('vxc',$crs_sec)) {
+                        if ($showurl =~ m{^\Q/res/$cdom/\E($match_username)/}) {
+                            my $auname = $1;
+                            if (($env{'request.course.adhocsrcaccess'} ne '') &&
+                                (grep(/^\Q$auname\E$/,split(/,/,$env{'request.course.adhocsrcaccess'})))) {
+                                $viewsrcbutton = 1;
+                            } elsif ((&Apache::lonnet::metadata($resurl,'sourceavail') eq 'open') &&
+                                     (&Apache::lonnet::allowed('bre',$crs_sec))) {
+                                $viewsrcbutton = 1;
+                            }
+                        }
+                    }
+                    if ($viewsrcbutton) {
+                        &switch('','',6,1,'pcstr.png','View Source','resource[_2]','open_source()',
+                                'View source code');
+                    }
+                }
             }
         }
     }
@@ -1182,10 +1441,13 @@ sub prepare_functions {
         if ($resurl =~ m{^/?adm/($match_domain)/($match_username)/aboutme$}) {
             my ($sdom,$sname) = ($1,$2);
             unless (&Apache::lonnet::is_course($sdom,$sname)) {
-                &switch('','',6,4,'mail-message-new-22x22.png','Message to user',
-                        '',
-                        "go('/adm/email?compose=individual&amp;recname=$sname&amp;recdom=$sdom')",
-                            'Send message to specific user');
+                my $blocked = &Apache::loncommon::blocking_status('about',$sname,$sdom);
+                unless ($blocked) {
+                    &switch('','',6,4,'mail-message-new-22x22.png','Message to user',
+                            '',
+                            "go('/adm/email?compose=individual&amp;recname=$sname&amp;recdom=$sdom')",
+                                'Send message to specific user');
+                }
             }
             my $hideprivileged = 1;
             if (&Apache::lonnet::in_course($sdom,$sname,$cdom,$cnum,undef,
@@ -1249,7 +1511,7 @@ sub prepare_functions {
         &advtools_crumbs(@inlineremote);
         return $editbutton;
     } elsif ($env{'request.registered'}) {
-        return $editbutton;
+        return $editbutton || $viewsrcbutton;
     } else {
         if (ref($bread_crumbs) eq 'ARRAY') {
             if (@inlineremote > 0) {
@@ -1339,7 +1601,8 @@ sub switch {
         unless ($env{'request.state'} eq 'construct') {
             push(@tools,63);
         }
-        if (($env{'environment.icons'} eq 'iconsonly') && 
+        if ((($env{'environment.icons'} eq 'iconsonly') ||
+             ($env{'environment.icons'} eq '') && ($env{'request.lti.login'})) &&
             (grep(/^$idx$/,@tools))) {
             $inlineremote[$idx] =
         '<a title="'.$desc.'" class="LC_menubuttons_link" href="javascript:'.$act.';">'.$pic.'</a>';
@@ -1690,13 +1953,18 @@ sub done_button_js {
                  key      => 'Key:',
                  nokey    => 'A proctor key is required', 
     );
+    my $shownsymb = &HTML::Entities::encode(&Apache::lonenc::check_encrypt($env{'request.symb'}));
     my $navmap = Apache::lonnavmaps::navmap->new(); 
     my ($missing,$tried) = (0,0);
     if (ref($navmap)) {
         my @resources=();
         if ($type eq 'map') {
             my ($mapurl,$rid,$resurl)=&Apache::lonnet::decode_symb($env{'request.symb'});
-            @resources=$navmap->retrieveResources($mapurl,sub { $_[0]->is_problem() });
+            if ($env{'request.symb'} =~ /\.page$/) {
+                @resources=$navmap->retrieveResources($resurl,sub { $_[0]->is_problem() });
+            } else {
+                @resources=$navmap->retrieveResources($mapurl,sub { $_[0]->is_problem() });
+            }
         } else {
             my $res = $navmap->getBySymb($env{'request.symb'});
             if (ref($res)) {
@@ -1748,6 +2016,7 @@ sub done_button_js {
 <form method="post" name="LCdoneButton" action="">
     <input type="hidden" name="LC_interval_done" value="" />
     <input type="hidden" name="LC_interval_done_proctorpass" value="" />
+    <input type="hidden" name="symb" value="$shownsymb" />
     <button id="LC_done-confirm-opener" type="button">$donebuttontext</button>
 </form>
 
@@ -1829,6 +2098,7 @@ END
 
 <form method="post" name="LCdoneButton" action="">
     <input type="hidden" name="LC_interval_done" value="" />
+    <input type="hidden" name="symb" value="$shownsymb" />
     <button id="LC_done-confirm-opener" type="button">$donebuttontext</button>
 </form>
 
@@ -1874,13 +2144,17 @@ END
 sub utilityfunctions {
     my ($httphost) = @_;
     my $currenturl=&Apache::lonnet::clutter(&Apache::lonnet::fixversion((split(/\?/,$env{'request.noversionuri'}))[0]));
-    if ($currenturl =~ m{^/adm/wrapper/ext/}
-        && $env{'request.external.querystring'} ) {
+    my $currentsymb=&Apache::lonenc::check_encrypt($env{'request.symb'});
+    if ($currenturl =~ m{^/adm/wrapper/ext/}) {
+        if ($env{'request.external.querystring'}) {
             $currenturl .= ($currenturl=~/\?/)?'&':'?'.$env{'request.external.querystring'};
+        }
+        my ($anchor) = ($env{'request.symb'} =~ /(\#[^\#]+)$/);
+        if (($anchor) && ($currenturl !~ /\Q$anchor\E$/)) {
+            $currenturl .= $1;
+        }
     }
     $currenturl=&Apache::lonenc::check_encrypt(&unescape($currenturl));
-    
-    my $currentsymb=&Apache::lonenc::check_encrypt($env{'request.symb'});
 
     my $dc_popup_cid;
     if ($env{'user.adv'} && exists($env{'user.role.dc./'.
@@ -1907,6 +2181,15 @@ sub utilityfunctions {
 
     my $countdown = &countdown_toggle_js();
 
+    my $ltitarget;
+    if ($env{'request.lti.login'}) {
+        $ltitarget = $env{'request.lti.target'};
+    }
+
+    my $annotateurl = '/adm/annotation';
+    if ($httphost) {
+        $annotateurl = '/adm/annotations';
+    }
     my $hostvar = '
 function setLCHost() {
     var lcHostname="";
@@ -2003,17 +2286,22 @@ function golist(url) {
        currentURL = null;
        currentSymb= null;
        var lcHostname = setLCHost();
-       top.location.href=lcHostname+url;
+       var ltitarget = '$ltitarget';
+       if (ltitarget == 'iframe') {
+           document.location.href=lcHostname+url;
+       } else {
+           top.location.href=lcHostname+url;
+       }
    }
 }
 
 
 
-function catalog_info(isMobile) {
+function catalog_info(url,isMobile) {
     if (isMobile == 1) {
-        openMyModal(window.location.pathname+'.meta?modal=1',500,400,'yes');
+        openMyModal(url+'.meta?modal=1',500,400,'yes');
     } else {
-        loncatinfo=window.open(window.location.pathname+'.meta',"LONcatInfo",'height=500,width=400,resizable=yes,scrollbars=yes,location=no,menubar=no,toolbar=no');
+        loncatinfo=window.open(url+'.meta',"LONcatInfo",'height=500,width=400,resizable=yes,scrollbars=yes,location=no,menubar=no,toolbar=no');
     }
 }
 
@@ -2035,7 +2323,7 @@ function annotate() {
    annotator.document.write(
    '$start_page_annotate'
   +"<form name='goannotate' target='Annotator' method='post' "
-  +"action='/adm/annotations'>"
+  +"action='$annotateurl'>"
   +"<input type='hidden' name='symbnew' value='"+currentSymb+"' />"
   +"<\\/form>"
   +'$end_page_annotate');
@@ -2056,6 +2344,23 @@ function open_StoredLinks_Import(rat) {
    newWin.focus();
 }
 
+function open_source() {
+   sourcewin=window.open('/adm/source?inhibitmenu=yes&viewonly=1&filename='+currentURL,'LONsource',
+                         'height=500,width=600,resizable=yes,location=no,menubar=no,toolbar=no,scrollbars=yes');
+}
+
+function open_aboutLC() {
+    var isMobile = "$env{'browser.mobile'}";
+    var url = '/adm/about.html';
+    if (isMobile == 1) {
+        openMyModal(url,600,400,'yes');
+    } else {
+        window.open(url,"aboutLONCAPA","height=400,width=600,scrollbars=1,resizable=1,menubar=0,location=1");
+    }
+    return;
+}
+
+
 (function (\$) {
   \$(document).ready(function () {
     \$.single=function(a){return function(b){a[0]=b;return a}}(\$([1]));
@@ -2076,8 +2381,12 @@ ENDUTILITY
 }
 
 sub serverform {
+    my $target;
+    unless (($env{'request.lti.login'}) && ($env{'request.lti.target'} eq 'iframe')) {
+        $target = ' target="_top"';
+    }
     return(<<ENDSERVERFORM);
-<form name="server" action="/adm/logout" method="post" target="_top">
+<form name="server" action="/adm/logout" method="post"$target>
 <input type="hidden" name="postdata" value="none" />
 <input type="hidden" name="command" value="none" />
 <input type="hidden" name="url" value="none" />
@@ -2087,15 +2396,20 @@ ENDSERVERFORM
 }
 
 sub constspaceform {
+    my ($target,$printtarget);
+    unless (($env{'request.lti.login'}) && ($env{'request.lti.target'} eq 'iframe')) {
+        $target = ' target="_top"';
+        $printtarget = ' target="_parent"';
+    }
     return(<<ENDCONSTSPACEFORM);
-<form name="constspace" action="/adm/logout" method="post" target="_top">
+<form name="constspace" action="/adm/logout" method="post"$target>
 <input type="hidden" name="filename" value="" />
 </form>
-<form name="cstrdelete" action="/adm/cfile" method="post" target="_top">
+<form name="cstrdelete" action="/adm/cfile" method="post"$target>
 <input type="hidden" name="action" value="delete" /> 
 <input type="hidden" name="filename" value="" />
 </form>
-<form name="cstrprint" action="/adm/printout" target="_parent" method="post">
+<form name="cstrprint" action="/adm/printout" method="post"$printtarget>
 <input type="hidden" name="postdata" value="" />
 <input type="hidden" name="curseed" value="" />
 <input type="hidden" name="problemtype" value="" />
@@ -2115,12 +2429,12 @@ sub hidden_button_check {
 }
 
 sub roles_selector {
-    my ($cdom,$cnum,$httphost) = @_;
+    my ($cdom,$cnum,$httphost,$ltitarget) = @_;
     my $crstype = &Apache::loncommon::course_type();
     my $now = time;
     my (%courseroles,%seccount,%courseprivs,%roledesc);
     my $is_cc;
-    my ($js,$form,$switcher);
+    my ($js,$form,$switcher,$has_opa_priv);
     my $ccrole;
     if ($crstype eq 'Community') {
         $ccrole = 'co';
@@ -2241,12 +2555,19 @@ sub roles_selector {
                 if ($env{'request.role'} =~ m{^\Q$role\E}) {
                     if ($seccount{$role} > 1) {
                         $include = 1;
+                    } else {
+                        if ($env{'user.priv.'.$env{'request.role'}."./$cdom/$cnum"} =~/opa\&([^\:]*)/) {
+                            $has_opa_priv = 1;
+                        }
                     }
                 } else {
                     $include = 1;
                 }
             }
             if ($include) {
+                if ($env{"user.priv.$role./$cdom/$cnum./$cdom/$cnum"} =~/opa\&([^\:]*)/) {
+                    $has_opa_priv = 1;
+                }
                 push(@submenu,['javascript:adhocRole('."'$role'".')',
                                &Apache::lonnet::plaintext($role,$crstype)]);
             }
@@ -2271,16 +2592,19 @@ sub roles_selector {
                     } else {
                         $rolename = &Apache::lonnet::plaintext($role);
                     }
+                    if ($env{"user.priv.$role./$cdom/$cnum./$cdom/$cnum"} =~/opa\&([^\:]*)/) {
+                        $has_opa_priv = 1;
+                    }
                     push(@submenu,['javascript:adhocRole('."'$role'".')',
                                    $rolename]);
                 }
             }
         }
         if (@submenu > 0) {
-            $switcher = &create_submenu('','',&mt('Switch role'),\@submenu);
+            $switcher = &create_submenu('','',&mt('Switch role'),\@submenu,'','',$ltitarget);
         }
     }
-    return ($js,$form,$switcher);
+    return ($js,$form,$switcher,$has_opa_priv);
 }
 
 sub get_all_courseroles {
@@ -2615,16 +2939,26 @@ sub required_privs {
 
 sub countdown_timer {
     if (($env{'request.course.id'}) && ($env{'request.symb'} ne '') &&
-        ($env{'request.filename'}=~/$LONCAPA::assess_re/)) {
+        (($env{'request.filename'}=~/$LONCAPA::assess_re/) ||
+         (($env{'request.symb'} =~ /ext\.tool$/) &&
+         (&Apache::lonnet::EXT('resource.0.gradable',$env{'request.symb'}) =~ /^yes$/i)))) {
         my ($type,$hastimeleft,$slothastime);
         my $now = time;
         if ($env{'request.filename'} =~ /\.task$/) {
             $type = 'Task';
+        } elsif ($env{'request.symb'} =~ /ext\.tool$/) {
+            $type = 'tool';
         } else {
             $type = 'problem';
         }
-        my ($status,$accessmsg,$slot_name,$slot) =
-            &Apache::lonhomework::check_slot_access('0',$type);
+        my ($status,$accessmsg,$slot_name,$slot);
+        if ($type eq 'tool') {
+            ($status,$accessmsg,$slot_name,$slot) =
+                &Apache::lonhomework::check_slot_access('0',$type,$env{'request.symb'},['0']);
+        } else {
+            ($status,$accessmsg,$slot_name,$slot) =
+                &Apache::lonhomework::check_slot_access('0',$type);
+        }
         if ($slot_name ne '') {
             if (ref($slot) eq 'HASH') {
                 if (($slot->{'starttime'} < $now) &&
@@ -2718,14 +3052,17 @@ BEGIN {
                         $category_positions{$entries[2]}=$entries[1];
                         $category_names{$entries[2]}=$entries[3];
                     } elsif ($configline=~/^prim\:/) {
-                        my @entries = (split(/\:/, $configline))[1..6];
+                        my @entries = (split(/\:/, $configline))[1..7];
                         push(@primary_menu,\@entries);
                     } elsif ($configline=~/^primsub\:/) {
-                        my ($parent,@entries) = (split(/\:/, $configline))[1..4];
+                        my ($parent,@entries) = (split(/\:/, $configline))[1..5];
                         push(@{$primary_submenu{$parent}},\@entries);
                     } elsif ($configline=~/^scnd\:/) {
-                        my @entries = (split(/\:/, $configline))[1..5];
+                        my @entries = (split(/\:/, $configline))[1..6];
                         push(@secondary_menu,\@entries);
+                    } elsif ($configline=~/^scndsub\:/) {
+                        my ($parent,@entries) = (split(/\:/, $configline))[1..4];
+                        push(@{$secondary_submenu{$parent}},\@entries);
                     } elsif ($configline) {
                         push(@desklines,$configline);
                     }