--- loncom/interface/lonmenu.pm 2004/11/13 21:07:19 1.129
+++ loncom/interface/lonmenu.pm 2024/01/02 14:14:47 1.369.2.83.2.16
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# Routines to control the menu
#
-# $Id: lonmenu.pm,v 1.129 2004/11/13 21:07:19 albertel Exp $
+# $Id: lonmenu.pm,v 1.369.2.83.2.16 2024/01/02 14:14:47 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -26,481 +26,1563 @@
# http://www.lon-capa.org/
#
#
-# There are two parameters controlling the action of this module:
-#
-# browser.interface - if this is 'textual', it overrides the second parameter
-# and goes to screen reader PDA mode
-#
-# environment.remote - if this is 'on', the routines controll the remote
-# control, otherwise they render the main window controls; ignored it
-# browser.interface is 'textual'
-#
+
+=head1 NAME
+
+Apache::lonmenu
+
+=head1 SYNOPSIS
+
+Loads contents of /home/httpd/lonTabs/mydesk.tab,
+used to generate inline menu, and Main Menu page.
+
+This is part of the LearningOnline Network with CAPA project
+described at http://www.lon-capa.org.
+
+=head1 GLOBAL VARIABLES
+
+=over
+
+=item @desklines
+
+Each element of this array contains a line of mydesk.tab that doesn't start with
+cat, prim or scnd.
+It gets filled in the BEGIN block of this module.
+
+=item %category_names
+
+The keys of this hash are the abbreviations used in mydesk.tab in those lines that
+start with cat, the values are strings representing titles.
+It gets filled in the BEGIN block of this module.
+
+=item %category_members
+
+TODO
+
+=item %category_positions
+
+The keys of this hash are the abbreviations used in mydesk.tab in those lines that
+start with cat, its values are position vectors (column, row).
+It gets filled in the BEGIN block of this module.
+
+=item $readdesk
+
+Indicates that mydesk.tab has been read.
+It is set to 'done' in the BEGIN block of this module.
+
+=item @primary_menu
+
+The elements of this array reference arrays that are made up of the components
+of those lines of mydesk.tab that start with prim:.
+It is used by primary_menu() to generate the corresponding menu.
+It gets filled in the BEGIN block of this module.
+
+=item %primary_sub_menu
+
+The keys of this hash reference are the names of items in the primary_menu array
+which have sub-menus. For each key, the corresponding value is a reference to
+an array containing components extracted from lines in mydesk.tab which begin
+with primsub:.
+This hash, which is used by primary_menu to generate sub-menus, is populated in
+the BEGIN block.
+
+=item @secondary_menu
+
+The elements of this array reference arrays that are made up of the components
+of those lines of mydesk.tab that start with scnd.
+It is used by secondary_menu() to generate the corresponding menu.
+It gets filled in the BEGIN block of this module.
+
+=back
+
+=head1 SUBROUTINES
+
+=over
+
+=item prep_menuitems(\@menuitem,$target,$listclass,$linkattr)
+
+This routine wraps a menuitem in proper HTML. It is used by primary_menu() and
+secondary_menu().
+
+=item primary_menu()
+
+This routine evaluates @primary_menu and returns a two item array,
+with the array elements containing XHTML for the left and right sides of
+the menu that contains the following links: About, Message, Roles, Help, Logout
+@primary_menu is filled within the BEGIN block of this module with
+entries from mydesk.tab
+
+=item secondary_menu()
+
+Same as primary_menu() but operates on @secondary_menu.
+
+=item create_submenu()
+
+Creates XHTML for unordered list of sub-menu items which belong to a
+particular top-level menu item. Uses hover pseudo class in css to display
+dropdown list when mouse hovers over top-level item. Support for IE6
+(no hover psuedo class) via LC_hoverable class for
tag for top-
+level item, which employs jQuery to handle behavior on mouseover.
+
+Inputs: 6 - (a) link and (b) target for anchor href in top level item,
+ (c) title for text wrapped by anchor tag in top level item.
+ (d) reference to array of arrays of sub-menu items.
+ (e) boolean to indicate whether to call &mt() to translate
+ name of menu item,
+ (f) optional class for
element in primary menu, for which
+ sub menu is being generated.
+
+The underlying datastructure used in (d) contains data from mydesk.tab.
+It consists of an array which has an array for each item appearing in
+the menu (e.g. [["link", "title", "condition"]] for a single-item menu).
+create_submenu() supports also the creation of XHTML for nested dropdown
+menus represented by unordered lists. This is done by replacing the
+scalar used for the link with an arrayreference containing the menuitems
+for the nested menu. This can be done recursively so that the next menu
+may also contain nested submenus.
+
+ Example:
+ [ # begin of datastructure
+ ["/home/", "Home", "condition1"], # 1st item of the 1st layer menu
+ [ # 2nd item of the 1st layer menu
+ [ # anon. array for nested menu
+ ["/path1", "Path1", undef], # 1st item of the 2nd layer menu
+ ["/path2", "Path2", undef], # 2nd item of the 2nd layer menu
+ [ # 3rd item of the 2nd layer menu
+ [[...], [...], ..., [...]], # containing another menu layer
+ "Sub-Sub-Menu", # title for this container
+ undef
+ ]
+ ], # end of array/nested menu
+ "Sub-Menu", # title for the container item
+ undef
+ ] # end of 2nd item of the 1st layer menu
+]
+
+
+=item innerregister()
+
+This gets called in order to register a URL in the body of the document
+
+=item loadevents()
+
+=item unloadevents()
+
+=item startupremote()
+
+=item setflags()
+
+=item maincall()
+
+=item load_remote_msg()
+
+=item get_menu_name()
+
+=item reopenmenu()
+
+=item open()
+
+Open the menu
+
+=item clear()
+
+=item switch()
+
+Switch a button or create a link
+Switch acts on the javascript that is executed when a button is clicked.
+The javascript is usually similar to "go('/adm/roles')" or "cstrgo(..)".
+
+=item secondlevel()
+
+=item openmenu()
+
+=item inlinemenu()
+
+=item rawconfig()
+
+=item utilityfunctions()
+
+Output from this routine is a number of javascript functions called by
+items in the inline menu, and in some cases items in the Main Menu page.
+
+=item serverform()
+
+=item constspaceform()
+
+=item get_nav_status()
+
+=item hidden_button_check()
+
+=item roles_selector()
+
+=item jump_to_role()
+
+=back
+
+=cut
package Apache::lonmenu;
use strict;
-use Apache::lonnet();
-use Apache::Constants qw(:common);
+use Apache::lonnet;
use Apache::lonhtmlcommon();
use Apache::loncommon();
use Apache::lonenc();
use Apache::lonlocal;
+use Apache::lonmsg();
+use LONCAPA qw(:DEFAULT :match);
+use HTML::Entities();
+use Apache::lonwishlist();
-use vars qw(@desklines $readdesk);
-
+use vars qw(@desklines %category_names %category_members %category_positions
+ $readdesk @primary_menu %primary_submenu @secondary_menu %secondary_submenu);
my @inlineremote;
-my $font;
-my $tabbg;
-my $pgbg;
-
-# ================================================================ Little texts
-
-sub initlittle {
- return &Apache::lonlocal::texthash('ret' => 'Return to Last Location',
- 'nav' => 'Navigate Contents',
- 'main' => 'Main Menu',
- 'launch' => 'Launch Remote Control');
-}
-
-# ============================= This gets called at the top of the body section
-
-sub menubuttons {
- my $forcereg=shift;
- my $target =shift;
- my $registration=shift;
- &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},
- ['inhibitmenu']);
- if ($ENV{'form.inhibitmenu'} eq 'yes') { return ''; }
-
- my $navmaps='';
- my $reloadlink='';
- my $escurl=&Apache::lonnet::escape(&Apache::lonenc::check_encrypt($ENV{'request.noversionuri'}));
- my $escsymb=&Apache::lonnet::escape(&Apache::lonenc::check_encrypt($ENV{'request.symb'}));
- if ($ENV{'browser.interface'} eq 'textual') {
-# Textual display only
- my %lt=&initlittle();
- $pgbg='#FFFFFF';
- $tabbg='#FFFFFF';
- $font='#000000';
- if ($ENV{'request.course.id'}) {
- $navmaps=(<$lt{'nav'}
-ENDNAV
- if (($ENV{'request.noversionuri'}=~/^\/adm\//) &&
- ($ENV{'request.noversionuri'}!~/^\/adm\/wrapper\//) &&
- ($ENV{'request.noversionuri'}!~/^\/adm\/.*\/(smppg|bulletinboard|aboutme)(\?|$)/)) {
- my $escreload=&Apache::lonnet::escape('return:');
- $reloadlink=(<$lt{'ret'}
-ENDRELOAD
+
+sub prep_menuitem {
+ my ($menuitem,$target,$listclass,$linkattr) = @_;
+ return '' unless(ref($menuitem) eq 'ARRAY');
+ my ($link,$targetattr);
+ if ($$menuitem[1]) { # graphical Link
+ $link = "";
+ } else { # textual Link
+ $link = &mt($$menuitem[3]);
+ }
+ if ($target ne '') {
+ $targetattr = ' target="'.$target.'"';
+ }
+ return ($listclass?'
|;
+}
+
+# primary_menu() evaluates @primary_menu and returns a two item array,
+# with the array elements containing XHTML for the left and right sides of
+# the menu that contains the following links:
+# Personal, About, Message, Roles, Help, Logout
+# @primary_menu is filled within the BEGIN block of this module with
+# entries from mydesk.tab
+sub primary_menu {
+ my ($crstype,$ltimenu,$menucoll,$menuref,$links_disabled,$links_target) = @_;
+ my (%menu,%ltiexc,%menuopts);
+ # each element of @primary contains following array:
+ # (link url, icon path, alt text, link text, condition, position)
+ my $public;
+ if ((($env{'user.name'} eq 'public') && ($env{'user.domain'} eq 'public'))
+ || (($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 $utility=&utilityfunctions();
- my $output=(<
-// BEGIN LON-CAPA Internal
-$utility
-
-$lt{'main'}
-$reloadlink $navmaps
-
-ENDMAINMENU
- if ($registration) { $output.=&innerregister($forcereg,$target); }
- return $output."";
- } elsif ($ENV{'environment.remote'} eq 'off') {
-# Remote Control is switched off
-# figure out colors
- my %lt=&initlittle();
- my $function='student';
- if ($ENV{'request.role'}=~/^(cc|in|ta|ep)/) {
- $function='coordinator';
- }
- if ($ENV{'request.role'}=~/^(su|dc|ad|li)/) {
- $function='admin';
- }
- if (($ENV{'request.role'}=~/^(au|ca)/) ||
- ($ENV{'request.noversionuri'}=~/^(\/priv|\~)/)) {
- $function='author';
- }
- my $domain=&Apache::loncommon::determinedomain();
- $pgbg=&Apache::loncommon::designparm($function.'.pgbg',$domain);
- $tabbg=&Apache::loncommon::designparm($function.'.tabbg',$domain);
- $font=&Apache::loncommon::designparm($function.'.font',$domain);
- my $link=&Apache::loncommon::designparm($function.'.link',$domain);
- my $alink=&Apache::loncommon::designparm($function.'.alink',$domain);
- my $vlink=&Apache::loncommon::designparm($function.'.vlink',$domain);
- my $sidebg=&Apache::loncommon::designparm($function.'.sidebg',$domain);
-# Do we have a NAV link?
- if ($ENV{'request.course.id'}) {
- $navmaps=(<
-$lt{'nav'}
-ENDNAV
- if (($ENV{'request.noversionuri'}=~/^\/adm\//) &&
- ($ENV{'request.noversionuri'}!~/^\/adm\/wrapper\//) &&
- ($ENV{'request.noversionuri'}!~/^\/adm\/.*\/(smppg|bulletinboard|aboutme)(\?|$)/)) {
- my $escreload=&Apache::lonnet::escape('return:');
- $reloadlink=(<
-$lt{'ret'}
-ENDRELOAD
- }
- }
- my $reg='';
- if ($registration) {
- $reg=&innerregister($forcereg,$target);
+ }
+ my ($listclass,$linkattr,$target);
+ if ($links_disabled) {
+ $listclass = 'LCisDisabled';
+ $linkattr = 'aria-disabled="true"';
+ }
+ if ($links_target ne '') {
+ $target = $links_target;
+ } else {
+ my ($ltitarget,$deeplinktarget);
+ if ($env{'request.lti.login'}) {
+ $ltitarget = $env{'request.lti.target'};
}
- my $utility=&utilityfunctions();
- return (<
-// BEGIN LON-CAPA Internal
-$utility
-
-
';
+
+ # $link and $title are only used in the initial string written in $menu
+ # as seen above, not needed for nested submenus
+ $menu .= &build_submenu($target, $submenu, $translate, '1', $listclass, $linkattr);
+ $menu .= '
';
+
+ return $menu;
+}
+
+# helper routine for create_submenu
+# build the dropdown (and nested submenus) recursively
+# see perldoc create_submenu documentation for further information
+sub build_submenu {
+ my ($target, $submenu, $translate, $first_level, $listclass, $linkattr) = @_;
+ unless (@{$submenu}) {
+ return '';
+ }
+
+ my $menu = '';
+ my $count = 0;
+ my $numsub = scalar(@{$submenu});
+ foreach my $item (@{$submenu}) {
+ $count ++;
+ if (ref($item) eq 'ARRAY') {
+ my $href = $item->[0];
+ my $bordertop;
+ my $borderbot;
+ my $title;
+
+ if ($translate) {
+ $title = &mt($item->[1]);
+ } else {
+ $title = $item->[1];
+ }
+
+ if ($count == 1 && !$first_level) {
+ $bordertop = 'border-top: 1px solid black;';
+ }
+ if ($count == $numsub) {
+ $borderbot = 'border-bottom: 1px solid black;';
+ }
+
+ # href is a reference to another submenu
+ if (ref($href) eq 'ARRAY') {
+ $menu .= '
';
+ }
+ }
+ }
+ return $menu;
+}
sub registerurl {
- my $forcereg=shift;
- my $target = shift;
+ my ($forcereg) = @_;
my $result = '';
- if ($ENV{'request.noversionuri'} eq '/res/adm/pages/menu.html') { return ''; }
+ if ($env{'request.noversionuri'} =~ m{^/res/adm/pages/}) { return ''; }
my $force_title='';
- if ($ENV{'request.state'} eq 'construct') {
- $force_title=&Apache::lonxml::display_title();
+ if ($env{'request.state'} eq 'construct') {
+ $force_title=&Apache::lonxml::display_title();
}
- if ($target eq 'edit') {
- $result .="\n";
- }
- if (($ENV{'browser.interface'} eq 'textual') ||
- ($ENV{'environment.remote'} eq 'off') ||
- ((($ENV{'request.publicaccess'}) ||
+ if (($env{'environment.remote'} ne 'on') ||
+ ((($env{'request.publicaccess'}) ||
(!&Apache::lonnet::is_on_map(
- &Apache::lonnet::unescape($ENV{'request.noversionuri'})))) &&
+ &unescape($env{'request.noversionuri'})))) &&
(!$forcereg))) {
- return $result.
- ''.$force_title;
+ return
+ $result
+ .''
+ .$force_title;
}
# Graphical display after login only
- if ($Apache::lonxml::registered && !$forcereg) { return ''; }
- if ($target ne 'edit') {
- $result.=&innerregister($forcereg,$target);
- }
+ if ($env{'request.registered'} && !$forcereg) { return ''; }
+ $result.=&innerregister($forcereg);
return $result.$force_title;
}
-# =========== This gets called in order to register a URL, both with the Remote
-# =========== and in the body of the document
-
sub innerregister {
- my $forcereg=shift;
- my $target = shift;
- my $result = '';
- my ($uname,$thisdisfn);
- my $const_space = ($ENV{'request.state'} eq 'construct');
+ my ($forcereg,$bread_crumbs,$group,$pagebuttonshide,$hostname,
+ $ltiscope,$ltiuri,$showncrumbsref) = @_;
+ my $const_space = ($env{'request.state'} eq 'construct');
+ my $is_const_dir = 0;
- if ($ENV{'request.noversionuri'} eq '/res/adm/pages/menu.html') { return ''; }
+ if ($env{'request.noversionuri'} =~ m{^/res/adm/pages/}) { return ''; }
- $Apache::lonxml::registered=1;
+ $env{'request.registered'} = 1;
- my $textinter=($ENV{'browser.interface'} eq 'textual');
- my $noremote=($ENV{'environment.remote'} eq 'off');
-
- my $textual=($textinter || $noremote);
+ my $noremote = ($env{'environment.remote'} ne 'on');
- @inlineremote=();
- undef @inlineremote;
+ undef(@inlineremote);
my $reopen=&Apache::lonmenu::reopenmenu();
my $newmail='';
- if ($noremote) {
- $newmail='
';
+
+ if (&Apache::lonmsg::newmail() && !$noremote) {
+ # We have new mail and remote is up
+ $newmail= 'swmenu.setstatus("you have","messages");';
}
- if (($textual) && ($ENV{'request.symb'}) && ($ENV{'request.course.id'})) {
- my ($mapurl,$rid,$resurl)=&Apache::lonnet::decode_symb($ENV{'request.symb'});
- $newmail.=$ENV{'course.'.$ENV{'request.course.id'}.'.description'};
- my $maptitle=&Apache::lonnet::gettitle($mapurl);
- my $restitle=&Apache::lonnet::gettitle($resurl);
- if ($maptitle) {
- $newmail.=', '.$maptitle;
- }
- if ($restitle) {
- $newmail.=': '.$restitle;
- }
- $newmail.=' ';
- }
- if (&Apache::lonmsg::newmail()) {
- $newmail=($textual?
- 'You have new messages ':
- 'swmenu.setstatus("you have","messages");');
- }
- if ($noremote) {
- $newmail.='
';
- }
- my $timesync=($textual?'':'swmenu.syncclock(1000*'.time.');');
- my $tablestart=($noremote?'