--- loncom/interface/lonmenu.pm	2022/07/08 16:08:05	1.369.2.83.2.4
+++ loncom/interface/lonmenu.pm	2023/09/02 15:56:34	1.369.2.83.2.9
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Routines to control the menu
 #
-# $Id: lonmenu.pm,v 1.369.2.83.2.4 2022/07/08 16:08:05 raeburn Exp $
+# $Id: lonmenu.pm,v 1.369.2.83.2.9 2023/09/02 15:56:34 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -267,7 +267,7 @@ sub prep_menuitem {
 # entries from mydesk.tab
 sub primary_menu {
     my ($crstype,$ltimenu,$menucoll,$menuref,$links_disabled,$links_target) = @_;
-    my (%menu,%menuopts);
+    my (%menu,%ltiexc,%menuopts);
     # each element of @primary contains following array:
     # (link url, icon path, alt text, link text, condition, position)
     my $public;
@@ -275,6 +275,17 @@ sub primary_menu {
         || (($env{'user.name'} eq '') && ($env{'user.domain'} eq ''))) {
         $public = 1;
     }
+    my $lti;
+    if ($env{'request.lti.login'}) {
+        $lti = 1;
+        if (ref($ltimenu) eq 'HASH') {
+            foreach my $item ('fullname','logout') {
+                unless ($ltimenu->{$item}) {
+                    $ltiexc{$item} = 1;
+                }
+            }
+        }
+    }
     my ($listclass,$linkattr,$target);
     if ($links_disabled) {
         $listclass = 'LCisDisabled';
@@ -283,11 +294,14 @@ sub primary_menu {
     if ($links_target ne '') {
         $target = $links_target;
     } else {    
-        my $deeplinktarget;
+        my ($ltitarget,$deeplinktarget);
+        if ($env{'request.lti.login'}) {
+             $ltitarget = $env{'request.lti.target'};
+        }
         if ($env{'request.deeplink.login'}) {
             $deeplinktarget = $env{'request.deeplink.target'};
         }
-        if ($deeplinktarget eq '_self') {
+        if (($ltitarget eq 'iframe') || ($deeplinktarget eq '_self')) {
             $target = '_self';
         } else {
             $target = '_top';
@@ -310,10 +324,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];
         my $position = $menuitem->[5];
         if ($position eq '') {
@@ -322,7 +341,7 @@ sub primary_menu {
         if ($env{'request.course.id'} && $menucoll) {
             if (($menuitem->[6]) && (!$menuopts{$menuitem->[6]})) {
                 if ($menuitem->[6] eq 'pers') {
-                    if ($menuopts{'name'} &&
+                    if ($menuopts{'name'} && !$ltiexc{'fullname'} &&
                         $env{'user.name'} && $env{'user.domain'}) {
                         $menu{$position} .= '<li><a href="#">'.
                             &Apache::loncommon::plainname($env{'user.name'},
@@ -357,7 +376,7 @@ sub primary_menu {
                     push(@primsub,$item);
                 }
                 if ($title eq 'Personal') {
-                    if ($env{'user.name'} && $env{'user.domain'}) {
+                    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'});
                         }
@@ -464,7 +483,8 @@ sub secondary_menu {
     my $canplc        = &Apache::lonnet::allowed('plc', $crs_sec);
     my $author        = &getauthor();
 
-    my ($cdom,$cnum,$showsyllabus,$showfeeds,$showresv,$grouptools,%menuopts);
+    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'};
@@ -498,6 +518,19 @@ 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};
@@ -522,11 +555,14 @@ sub secondary_menu {
     if ($links_target ne '') {
         $target = $links_target;
     } else {
-        my $deeplinktarget;
+        my ($ltitarget,$deeplinktarget);
+        if ($env{'request.lti.login'}) {
+            $ltitarget = $env{'request.lti.target'};
+        }
         if ($env{'request.deeplink.login'}) {
             $deeplinktarget = $env{'request.deeplink.target'};
         }
-        if ($deeplinktarget eq '_self') {
+        if (($ltitarget eq 'iframe') || ($deeplinktarget eq '_self')) {
             $target = '_self';
         } else {
             $target = '_top';
@@ -546,7 +582,7 @@ sub secondary_menu {
         next if    $$menuitem[4]  eq 'crseditCommunity'
                 && ($crstype eq 'Course');
         next if    $$menuitem[4]  eq 'nvgr'
-                && $canvgr;
+                && ($canvgr || $ltiexc{'grades'});
         next if    $$menuitem[4]  eq 'vgr'
                 && !$canvgr;
         next if    $$menuitem[4]   eq 'viewusers'
@@ -573,6 +609,14 @@ sub secondary_menu {
                 && !$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'};
 
         my $title = $menuitem->[3];
         if ($env{'request.course.id'} && $menucoll) {
@@ -603,9 +647,21 @@ sub secondary_menu {
                         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,undef,
                                              $listclass,$linkattr);
@@ -864,30 +920,33 @@ sub innerregister {
             my $restitle = &Apache::lonnet::gettitle($symb);
 
             my (@crumbs,@mapcrumbs);
-            if (($env{'request.noversionuri'} ne '/adm/navmaps') && ($mapurl ne '') &&
-                ($mapurl ne $env{'course.'.$env{'request.course.id'}.'.url'})) {
-                $navmap = Apache::lonnavmaps::navmap->new();
-                if (ref($navmap)) {
-                    @mapcrumbs = $navmap->recursed_crumbs($mapurl,$restitle);
+            if (($env{'request.noversionuri'} ne '/adm/navmaps') && ($mapurl ne '')) { 
+                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'})) {
+            unless (($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'}) { 
                 if (@mapcrumbs) {
                     push(@crumbs,@mapcrumbs);
-                } else {
+                } elsif (($ltiscope ne 'map') && ($ltiscope ne 'resource')) {
                     push(@crumbs, {text  => '...',
                                    no_mt => 1});
                 }
             }
 
             unless ((@mapcrumbs) || (!$maptitle) || ($maptitle eq 'default.sequence') ||
-                    ($mapurl eq $env{'course.'.$env{'request.course.id'}.'.url'})) {
+                    ($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'};
             }
@@ -916,16 +975,22 @@ sub innerregister {
                 if ($env{'form.title'}) {
                     $title = $env{'form.title'};
                 }
-                my $trail;
+                my ($trail,$cnum,$cdom);
+                if ($env{'form.folderpath'}) {
+                    $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
+                    $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+                    &Apache::loncommon::validate_folderpath(1,'',$cnum,$cdom);
+                }
                 if ($env{'form.folderpath'}) {
                     &prepare_functions($resurl,$forcereg,$group,undef,undef,1,$hostname);
+                    $title = &HTML::Entities::encode($title,'\'"<>&');
                     ($trail) =
                         &Apache::lonhtmlcommon::docs_breadcrumbs(undef,$crstype,undef,$title,1,1);
                 } else {
                     &Apache::lonhtmlcommon::add_breadcrumb(
                     {text  => "Supplemental $crstype Content",
                      href  => "javascript:gopost('/adm/supplemental','')"});
-                    $title = &mt('View Resource');
+                    $title = &HTML::Entities::encode(&mt('View Resource'),'\'"<>&');
                     ($trail) =
                         &Apache::lonhtmlcommon::docs_breadcrumbs(undef,$crstype,undef,$title,1,1);
                 }
@@ -937,7 +1002,7 @@ sub innerregister {
                 &Apache::lonhtmlcommon::clear_breadcrumbs();
                 &prepare_functions('/public'.$courseurl."/syllabus",
                                    $forcereg,$group,undef,undef,1,$hostname);
-                $title = &mt('Syllabus File');
+                $title = &HTML::Entities::encode(&mt('Syllabus File'),'\'"<>&');  
                 my ($trail) =
                     &Apache::lonhtmlcommon::docs_breadcrumbs(undef,$crstype,undef,$title,1,1);
                 if (ref($showncrumbsref)) {
@@ -994,8 +1059,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'));
@@ -1010,8 +1081,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'}) {
                     $hwkadd.= &switch('','',7,2,'pgrd.png','Content Grades',
                                       'grades[_4]',
@@ -1096,13 +1167,42 @@ ENDMENUITEMS
 # We are in a course and looking at a registered URL
 # Should probably be in mydesk.tab
 #
-
-	    $menuitems=(<<ENDMENUITEMS);
-c&3&1
-s&2&1&back.png&$swtext{'back'}&&gopost('/adm/flip','back:'+currentURL)&Previous content resource&&1
-s&2&3&forw.png&$swtext{'forw'}&&gopost('/adm/flip','forward:'+currentURL)&Next content resource&&3
+            $menuitems = "c&3&1";
+            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";
+                }
+            } else {
+                $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";
+            }
+            $menuitems .= (<<ENDMENUITEMS);
 c&6&3
-c&8&1
 c&8&2
 s&8&3&prt.png&$swtext{'prt'}&printout[_1]&gopost('/adm/printout',currentURL)&Prepare a printable document
 ENDMENUITEMS
@@ -1605,8 +1705,12 @@ sub get_editbutton {
         if ($env{'form.folderpath'}) {
             $suppanchor = $env{'form.anchor'};
         }
+        my $shownsymb;
+        if ($env{'request.symb'}) {
+            $shownsymb = &Apache::lonenc::check_encrypt($env{'request.symb'});
+        }
         $jscall = &Apache::lonhtmlcommon::jump_to_editres($cfile,$home,$switchserver,
-                                                $forceedit,$forcereg,$env{'request.symb'},
+                                                $forceedit,$forcereg,$env{'request.symb'},$shownsymb,
                                                 &escape($env{'form.folderpath'}),
                                                 &escape($env{'form.title'}),$hostname,
                                                 $env{'form.idx'},&escape($env{'form.suppurl'}),
@@ -1967,7 +2071,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>';
@@ -2587,7 +2692,10 @@ sub utilityfunctions {
 
     my $countdown = &countdown_toggle_js();
 
-    my $deeplinktarget;
+    my ($ltitarget,$deeplinktarget);
+    if ($env{'request.lti.login'}) {
+        $ltitarget = $env{'request.lti.target'};
+    }
     if ($env{'request.deeplink.login'}) {
         $deeplinktarget = $env{'request.deeplink.target'};
     }
@@ -2692,8 +2800,9 @@ function golist(url) {
        currentURL = null;
        currentSymb= null;
        var lcHostname = setLCHost();
+       var ltitarget = '$ltitarget';
        var deeplinktarget = '$deeplinktarget';
-       if (deeplinktarget == '_self') {
+       if ((ltitarget == 'iframe') || (deeplinktarget == '_self')) {
            document.location.href=lcHostname+url;
        } else {
            top.location.href=lcHostname+url;
@@ -2755,6 +2864,17 @@ function open_source() {
                          '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]));
@@ -2798,7 +2918,8 @@ sub constspaceform {
         $target = ' target="_parent"';
         $printtarget = ' target="_parent"';
     } else {
-        unless (($env{'request.deeplink.login'}) && ($env{'request.deeplink.target'} eq '_self')) {
+        unless ((($env{'request.lti.login'}) && ($env{'request.lti.target'} eq 'iframe')) || 
+                (($env{'request.deeplink.login'}) && ($env{'request.deeplink.target'} eq '_self'))) {
             $target = ' target="_top"';
             $printtarget = ' target="_top"';
         }
@@ -3375,16 +3496,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) &&