--- loncom/interface/loncommon.pm	2021/06/12 20:51:38	1.1360
+++ loncom/interface/loncommon.pm	2022/05/27 04:35:36	1.1379
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # a pile of common routines
 #
-# $Id: loncommon.pm,v 1.1360 2021/06/12 20:51:38 raeburn Exp $
+# $Id: loncommon.pm,v 1.1379 2022/05/27 04:35:36 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -1310,6 +1310,9 @@ sub help_open_topic {
     if (($env{'request.lti.login'}) && ($env{'request.lti.target'} eq 'iframe')) {
         $target = '';
     }
+    if (($env{'request.deeplink.login'}) && ($env{'request.deeplink.target'} eq '_self')) {
+        $target = ''; 
+    }
     if ($text ne "") {	
 	$template.='<span class="LC_help_open_topic">'
                   .'<a'.$target.' href="'.$link.'">'
@@ -1416,7 +1419,7 @@ sub help_open_menu {
 }
 
 sub top_nav_help {
-    my ($text) = @_;
+    my ($text,$linkattr) = @_;
     $text = &mt($text);
     my $stay_on_page = 1;
 
@@ -1430,7 +1433,7 @@ sub top_nav_help {
     if ($link) {
         return <<"END";
 $banner_link
-<a href="$link" title="$title">$text</a>
+<a href="$link" title="$title" $linkattr>$text</a>
 END
     } else {
         return '&nbsp;'.$text.'&nbsp;';
@@ -1520,6 +1523,9 @@ sub help_open_bug {
     if (($env{'request.lti.login'}) && ($env{'request.lti.target'} eq 'iframe')) {
         $target = '';
     }
+    if (($env{'request.deeplink.login'}) && ($env{'request.deeplink.target'})) {
+        $target = ' target="'.$env{'request.deeplink.target'}.'"';
+    }
     # Add the text
     if ($text ne "")
     {
@@ -3648,6 +3654,155 @@ sub check_passwd_rules {
     return $warning;
 }
 
+sub passwd_validation_js {
+    my ($currpasswdval,$domain,$context,$id) = @_;
+    my (%passwdconf,$alertmsg);
+    if ($context eq 'linkprot') {
+        my %domconfig = &Apache::lonnet::get_dom('configuration',['ltisec'],$domain);
+        if (ref($domconfig{'ltisec'}) eq 'HASH') {
+            if (ref($domconfig{'ltisec'}{'rules'}) eq 'HASH') {
+                %passwdconf = %{$domconfig{'ltisec'}{'rules'}};
+            }
+        }
+        if ($id eq 'add') {
+            $alertmsg = &mt('Secret for added launcher did not satisfy requirement(s):').'\n\n';
+        } elsif ($id =~ /^\d+$/) {
+            my $pos = $id+1;
+            $alertmsg = &mt('Secret for launcher [_1] did not satisfy requirement(s):','#'.$pos).'\n\n';
+        } else {
+            $alertmsg = &mt('A secret did not satisfy requirement(s):').'\n\n';
+        }
+    } else {
+        %passwdconf = &Apache::lonnet::get_passwdconf($domain);
+        $alertmsg = &mt('Initial password did not satisfy requirement(s):').'\n\n';
+    }
+    my ($min,$max,@chars,$numrules,$intargjs,%alert);
+    $numrules = 0;
+    $min = $Apache::lonnet::passwdmin;
+    if (ref($passwdconf{'chars'}) eq 'ARRAY') {
+        if ($passwdconf{'min'} =~ /^\d+$/) {
+            if ($passwdconf{'min'} > $min) {
+                $min = $passwdconf{'min'};
+            }
+        }
+        if ($passwdconf{'max'} =~ /^\d+$/) {
+            $max = $passwdconf{'max'};
+            $numrules ++;
+        }
+        @chars = @{$passwdconf{'chars'}};
+        if (@chars) {
+            $numrules ++;
+        }
+    }
+    if ($min > 0) {
+        $numrules ++;
+    }
+    if (($min > 0) || ($max ne '') || (@chars > 0)) {
+        if ($min) {
+            $alert{'min'} = &mt('minimum [quant,_1,character]',$min).'\n';
+        }
+        if ($max) {
+            $alert{'max'} = &mt('maximum [quant,_1,character]',$max).'\n';
+        }
+        my (@charalerts,@charrules);
+        if (@chars) {
+            if (grep(/^uc$/,@chars)) {
+                push(@charalerts,&mt('contain at least one upper case letter'));
+                push(@charrules,'uc');
+            }
+            if (grep(/^lc$/,@chars)) {
+                push(@charalerts,&mt('contain at least one lower case letter'));
+                push(@charrules,'lc');
+            }
+            if (grep(/^num$/,@chars)) {
+                push(@charalerts,&mt('contain at least one number'));
+                push(@charrules,'num');
+            }
+            if (grep(/^spec$/,@chars)) {
+                push(@charalerts,&mt('contain at least one non-alphanumeric'));
+                push(@charrules,'spec');
+            }
+        }
+        $intargjs = qq|            var rulesmsg = '';\n|.
+                    qq|            var currpwval = $currpasswdval;\n|;
+            if ($min) {
+                $intargjs .= qq|
+            if (currpwval.length < $min) {
+                rulesmsg += ' - $alert{min}';
+            }
+|;
+            }
+            if ($max) {
+                $intargjs .= qq|
+            if (currpwval.length > $max) {
+                rulesmsg += ' - $alert{max}';
+            }
+|;
+            }
+            if (@chars > 0) {
+                my $charrulestr = '"'.join('","',@charrules).'"';
+                my $charalertstr = '"'.join('","',@charalerts).'"';
+                $intargjs .= qq|            var brokerules = new Array();\n|.
+                             qq|            var charrules = new Array($charrulestr);\n|.
+                             qq|            var charalerts = new Array($charalertstr);\n|;
+                my %rules;
+                map { $rules{$_} = 1; } @chars;
+                if ($rules{'uc'}) {
+                    $intargjs .= qq|
+            var ucRegExp = /[A-Z]/;
+            if (!ucRegExp.test(currpwval)) {
+                brokerules.push('uc');
+            }
+|;
+                }
+                if ($rules{'lc'}) {
+                    $intargjs .= qq|
+            var lcRegExp = /[a-z]/;
+            if (!lcRegExp.test(currpwval)) {
+                brokerules.push('lc');
+            }
+|;
+                }
+                if ($rules{'num'}) {
+                     $intargjs .= qq|
+            var numRegExp = /[0-9]/;
+            if (!numRegExp.test(currpwval)) {
+                brokerules.push('num');
+            }
+|;
+                }
+                if ($rules{'spec'}) {
+                     $intargjs .= q|
+            var specRegExp = /[!"#$%&'()*+,\-.\/:;<=>?@[\\^\]_`{\|}~]/;
+            if (!specRegExp.test(currpwval)) {
+                brokerules.push('spec');
+            }
+|;
+                }
+                $intargjs .= qq|
+            if (brokerules.length > 0) {
+                for (var i=0; i<brokerules.length; i++) {
+                    for (var j=0; j<charrules.length; j++) {
+                        if (brokerules[i] == charrules[j]) {
+                            rulesmsg += ' - '+charalerts[j]+'\\n';
+                            break;
+                        }
+                    }
+                }
+            }
+|;
+            }
+            $intargjs .= qq|
+            if (rulesmsg != '') {
+                rulesmsg = '$alertmsg'+rulesmsg;
+                alert(rulesmsg);
+                return false;
+            }
+|;
+    }
+    return ($numrules,$intargjs);
+}
+
 ###############################################################
 ##    Get Kerberos Defaults for Domain                 ##
 ###############################################################
@@ -5201,8 +5356,90 @@ sub findallcourses {
 ###############################################
 
 sub blockcheck {
-    my ($setters,$activity,$uname,$udom,$url,$is_course,$symb,$caller) = @_;
-
+    my ($setters,$activity,$clientip,$uname,$udom,$url,$is_course,$symb,$caller) = @_;
+    unless (($activity eq 'docs') || ($activity eq 'reinit') || ($activity eq 'alert')) {
+        my ($has_evb,$check_ipaccess);
+        my $dom = $env{'user.domain'};
+        if ($env{'request.course.id'}) {
+            my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+            my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
+            my $checkrole = "cm./$cdom/$cnum";
+            my $sec = $env{'request.course.sec'};
+            if ($sec ne '') {
+                $checkrole .= "/$sec";
+            }
+            if ((&Apache::lonnet::allowed('evb',undef,undef,$checkrole)) &&
+                ($env{'request.role'} !~ /^st/)) {
+                $has_evb = 1;
+            }
+            unless ($has_evb) {
+                if (($activity eq 'printout') || ($activity eq 'grades') || ($activity eq 'search') ||
+                    ($activity eq 'boards') || ($activity eq 'groups') || ($activity eq 'chat')) {
+                    if ($udom eq $cdom) {
+                        $check_ipaccess = 1;
+                    }
+                }
+            }
+        } elsif (($activity eq 'com') || ($activity eq 'port') || ($activity eq 'blogs') ||
+                ($activity eq 'about') || ($activity eq 'wishlist') || ($activity eq 'passwd')) {
+            my $checkrole;
+            if ($env{'request.role.domain'} eq '') {
+                $checkrole = "cm./$env{'user.domain'}/";
+            } else {
+                $checkrole = "cm./$env{'request.role.domain'}/";
+            }
+            if (($checkrole) && (&Apache::lonnet::allowed('evb',undef,undef,$checkrole))) {
+                $has_evb = 1;
+            }
+        }
+        unless ($has_evb || $check_ipaccess) {
+            my @machinedoms = &Apache::lonnet::current_machine_domains();
+            if (($dom eq 'public') && ($activity eq 'port')) {
+                $dom = $udom;
+            }
+            if (($dom ne '') && (grep(/^\Q$dom\E$/,@machinedoms))) {
+                $check_ipaccess = 1;
+            } else {
+                my $lonhost = $Apache::lonnet::perlvar{'lonHostID'};
+                my $internet_names = &Apache::lonnet::get_internet_names($lonhost);
+                my $prim = &Apache::lonnet::domain($dom,'primary');
+                my $intdom = &Apache::lonnet::internet_dom($prim);
+                if (($intdom ne '') && (ref($internet_names) eq 'ARRAY')) {
+                    if (grep(/^\Q$intdom\E$/,@{$internet_names})) {
+                        $check_ipaccess = 1;
+                    }
+                }
+            }
+        }
+        if ($check_ipaccess) {
+            my ($ipaccessref,$cached)=&Apache::lonnet::is_cached_new('ipaccess',$dom);
+            unless (defined($cached)) {
+                my %domconfig =
+                    &Apache::lonnet::get_dom('configuration',['ipaccess'],$dom);
+                $ipaccessref = &Apache::lonnet::do_cache_new('ipaccess',$dom,$domconfig{'ipaccess'},1800);
+            }
+            if ((ref($ipaccessref) eq 'HASH') && ($clientip)) {
+                foreach my $id (keys(%{$ipaccessref})) {
+                    if (ref($ipaccessref->{$id}) eq 'HASH') {
+                        my $range = $ipaccessref->{$id}->{'ip'};
+                        if ($range) {
+                            if (&Apache::lonnet::ip_match($clientip,$range)) {
+                                if (ref($ipaccessref->{$id}->{'commblocks'}) eq 'HASH') {
+                                    if ($ipaccessref->{$id}->{'commblocks'}->{$activity} eq 'on') {
+                                        return ('','','',$id,$dom);
+                                        last;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        if (($activity eq 'wishlist') || ($activity eq 'annotate')) {
+            return ();
+        }
+    }
     if (defined($udom) && defined($uname)) {
         # If uname and udom are for a course, check for blocks in the course.
         if (($is_course) || (&Apache::lonnet::is_course($udom,$uname))) {
@@ -5218,7 +5455,10 @@ sub blockcheck {
     my $startblock = 0;
     my $endblock = 0;
     my $triggerblock = '';
-    my %live_courses = &findallcourses(undef,$uname,$udom);
+    my %live_courses;
+    unless (($activity eq 'wishlist') || ($activity eq 'annotate')) {
+        %live_courses = &findallcourses(undef,$uname,$udom);
+    }
 
     # If uname is for a user, and activity is course-specific, i.e.,
     # boards, chat or groups, check for blocking in current course only.
@@ -5503,14 +5743,17 @@ sub parse_block_record {
 }
 
 sub blocking_status {
-    my ($activity,$uname,$udom,$url,$is_course,$symb,$caller) = @_;
+    my ($activity,$clientip,$uname,$udom,$url,$is_course,$symb,$caller) = @_;
     my %setters;
 
 # check for active blocking
-    my ($startblock,$endblock,$triggerblock) = 
-        &blockcheck(\%setters,$activity,$uname,$udom,$url,$is_course,$symb,$caller);
+    if ($clientip eq '') {
+        $clientip = &Apache::lonnet::get_requestor_ip();
+    }
+    my ($startblock,$endblock,$triggerblock,$by_ip,$blockdom) = 
+        &blockcheck(\%setters,$activity,$clientip,$uname,$udom,$url,$is_course,$symb,$caller);
     my $blocked = 0;
-    if ($startblock && $endblock) {
+    if (($startblock && $endblock) || ($by_ip)) {
         $blocked = 1;
     }
 
@@ -5564,6 +5807,10 @@ END_MYBLOCK
         $text = &mt('Checking Course Update Blocked');
     } elsif ($activity eq 'about') {
         $text = &mt('Access to User Information Pages Blocked');
+    } elsif ($activity eq 'wishlist') {
+        $text = &mt('Access to Stored Links Blocked');
+    } elsif ($activity eq 'annotate') {
+        $text = &mt('Access to Annotations Blocked');
     }
     $output .= <<"END_BLOCK";
 <div class='$class'>
@@ -5745,6 +5992,17 @@ sub get_domainconf {
                                     }
                                 }
                             }
+                        } elsif ($key eq 'saml') {
+                            if (ref($domconfig{'login'}{$key}) eq 'HASH') {
+                                foreach my $host (keys(%{$domconfig{'login'}{$key}})) {
+                                    if (ref($domconfig{'login'}{$key}{$host}) eq 'HASH') {
+                                        $designhash{$udom.'.login.'.$key.'_'.$host} = 1;
+                                        foreach my $item ('text','img','alt','url','title','notsso') {
+                                            $designhash{$udom.'.login.'.$key.'_'.$item.'_'.$host} = $domconfig{'login'}{$key}{$host}{$item};
+                                        }
+                                    }
+                                }
+                            }
                         } else {
                             foreach my $img (keys(%{$domconfig{'login'}{$key}})) {
                                 $designhash{$udom.'.login.'.$key.'_'.$img} = 
@@ -5849,8 +6107,12 @@ sub domainlogo {
 		&Apache::lonnet::repcopy($local_name);
 	    }
 	   $imgsrc = &lonhttpdurl($imgsrc);
-        } 
-        return '<img src="'.$imgsrc.'" alt="'.$domain.'" />';
+        }
+        my $alttext = $domain;
+        if ($designhash{$domain.'.login.alttext_domlogo'} ne '') {
+            $alttext = $designhash{$domain.'.login.alttext_domlogo'};
+        }
+        return '<img src="'.$imgsrc.'" alt="'.$alttext.'" id="lclogindomlogo" />';
     } elsif (defined(&Apache::lonnet::domain($domain,'description'))) {
         return &Apache::lonnet::domain($domain,'description');
     } else {
@@ -5968,6 +6230,10 @@ sub head_subbox {
 Input: (optional) filename from which breadcrumb trail is built.
        In most cases no input as needed, as $env{'request.filename'}
        is appropriate for use in building the breadcrumb trail.
+       frameset flag
+       If page header is being requested for use in a frameset, then
+       the second (option) argument -- frameset will be true, and
+       the target attribute set for links should be target="_parent".
 
 Returns: HTML div with CSTR path and recent box
          To be included on Authoring Space pages
@@ -5975,7 +6241,7 @@ Returns: HTML div with CSTR path and rec
 =cut
 
 sub CSTR_pageheader {
-    my ($trailfile) = @_;
+    my ($trailfile,$frameset) = @_;
     if ($trailfile eq '') {
         $trailfile = $env{'request.filename'};
     }
@@ -6008,10 +6274,16 @@ sub CSTR_pageheader {
         $title = &mt('Authoring Space');
     }
 
-    my ($target,$crumbtarget) = (' target="_top"','_top'); #FIXME lonpubdir: target="_parent"
-    if (($env{'request.lti.login'}) && ($env{'request.lti.target'} eq 'iframe')) {
+    my ($target,$crumbtarget) = (' target="_top"','_top');
+    if ($frameset) {
+        $target = ' target="_parent"';
+        $crumbtarget = '_parent';
+    } elsif (($env{'request.lti.login'}) && ($env{'request.lti.target'} eq 'iframe')) {
         $target = '';
         $crumbtarget = '';
+    } elsif (($env{'request.deeplink.login'}) && ($env{'request.deeplink.target'})) {
+        $target = ' target="'.$env{'request.deeplink.target'}.'"';
+        $crumbtarget = $env{'request.deeplink.target'};
     }
 
     my $output =
@@ -6029,14 +6301,14 @@ sub CSTR_pageheader {
     }
 
     if ($crsauthor) {
-        $output .= '</form>'.&Apache::lonmenu::constspaceform();
+        $output .= '</form>'.&Apache::lonmenu::constspaceform($frameset);
     } else {
         $output .=
              '<br />'
             #FIXME lonpubdir: &Apache::lonhtmlcommon::crumbs($uname.$thisdisfn.'/',$crumbtarget,'/priv','','+1',1)."</b></tt><br />"
             .&Apache::lonhtmlcommon::select_recent('construct','recent','this.form.action=this.form.recent.value;this.form.submit()')
             .'</form>'
-            .&Apache::lonmenu::constspaceform();
+            .&Apache::lonmenu::constspaceform($frameset);
     }
     $output .= '</div>';
 
@@ -6255,7 +6527,8 @@ sub bodytag {
             Apache::lonmenu::utilityfunctions($httphost), 'start');
 
         unless ($args->{'no_primary_menu'}) {
-            my ($left,$right) = Apache::lonmenu::primary_menu($crstype,$ltimenu,$menucoll,$menuref);
+            my ($left,$right) = Apache::lonmenu::primary_menu($crstype,$ltimenu,$menucoll,$menuref,
+                                                              $args->{'links_disabled'});
 
             if ($env{'request.noversionuri'} =~ m{^/res/adm/pages/}) {
                 if ($dc_info) {
@@ -6287,7 +6560,8 @@ sub bodytag {
             unless ($args->{'no_inline_menu'}) {
                 $bodytag .= Apache::lonmenu::secondary_menu($httphost,$ltiscope,$ltimenu,
                                                             $args->{'no_primary_menu'},
-                                                            $menucoll,$menuref);
+                                                            $menucoll,$menuref,
+                                                            $args->{'links_disabled'});
             }
             $bodytag .= Apache::lonmenu::serverform();
             $bodytag .= Apache::lonhtmlcommon::scripttag('', 'end');
@@ -8364,6 +8638,18 @@ ul.LC_funclist li {
 		cursor:pointer;
 }
 
+.LCisDisabled {
+  cursor: not-allowed;
+  opacity: 0.5;
+}
+
+a[aria-disabled="true"] {
+  color: currentColor;
+  display: inline-block;  /* For IE11/ MS Edge bug */
+  pointer-events: none;
+  text-decoration: none;
+}
+
 pre.LC_wordwrap {
   white-space: pre-wrap;
   white-space: -moz-pre-wrap;
@@ -8833,7 +9119,8 @@ sub print_suppression {
         }
         my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
         my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
-        my $blocked = &blocking_status('printout',$cnum,$cdom,undef,1);
+        my $clientip = &Apache::lonnet::get_requestor_ip();
+        my $blocked = &blocking_status('printout',$clientip,$cnum,$cdom,undef,1);
         if ($blocked) {
             my $checkrole = "cm./$cdom/$cnum";
             if ($env{'request.course.sec'} ne '') {
@@ -8952,6 +9239,9 @@ $args - additional optional args support
                                will contain https://<hostname> if server uses
                                https (as per hosts.tab), but request is for http
              hostname       -> hostname, originally from $r->hostname(), (optional).
+             links_disabled -> Links in primary and secondary menus are disabled
+                               (Can enable them once page has loaded - see lonroles.pm
+                               for an example).
 
 =back
 
@@ -9142,11 +9432,45 @@ sub menucoll_in_effect {
     my ($menucoll,$deeplinkmenu,%menu);
     if ($env{'request.course.id'}) {
         $menucoll = $env{'course.'.$env{'request.course.id'}.'.menudefault'};
-        if (($env{'request.deeplink.login'}) &&
-            ($env{'request.noversionuri'} =~ m{^/(res|uploaded)/})) {
-            my $deeplink = &Apache::lonnet::EXT('resource.0.deeplink');
+        if ($env{'request.deeplink.login'}) {
+            my ($deeplink_symb,$deeplink,$check_login_symb);
+            my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
+            my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+            if ($env{'request.noversionuri'} =~ m{^/(res|uploaded)/}) {
+                if ($env{'request.noversionuri'} =~ /\.(page|sequence)$/) {
+                    my $navmap = Apache::lonnavmaps::navmap->new();
+                    if (ref($navmap)) {
+                        $deeplink = $navmap->get_mapparam(undef,
+                                                          &Apache::lonnet::declutter($env{'request.noversionuri'}),
+                                                          '0.deeplink');
+                    } else {
+                        $check_login_symb = 1;
+                    }
+                } else {
+                    my $symb = &Apache::lonnet::symbread();
+                    if ($symb) {
+                        $deeplink = &Apache::lonnet::EXT('resource.0.deeplink',$symb);
+                    } else {
+                        $check_login_symb = 1;
+                    }
+                }
+            } else {
+                $check_login_symb = 1;
+            }
+            if ($check_login_symb) {
+                $deeplink_symb = &deeplink_login_symb($cnum,$cdom);
+                if ($deeplink_symb =~ /\.(page|sequence)$/) {
+                    my $mapname = &Apache::lonnet::deversion((&Apache::lonnet::decode_symb($deeplink_symb))[2]);
+                    my $navmap = Apache::lonnavmaps::navmap->new();
+                    if (ref($navmap)) {
+                        $deeplink = $navmap->get_mapparam(undef,$mapname,'0.deeplink');
+                    }
+                } else {
+                    $deeplink = &Apache::lonnet::EXT('resource.0.deeplink',$deeplink_symb);
+                }
+            }
             if ($deeplink ne '') {
-                my ($listed,$scope,$access,$display) = split(/,/,$deeplink);
+                my ($state,$others,$listed,$scope,$protect,$display,$target) = split(/,/,$deeplink);
                 if ($display =~ /^\d+$/) {
                     $deeplinkmenu = 1;
                     $menucoll = $display;
@@ -9160,6 +9484,47 @@ sub menucoll_in_effect {
     return ($menucoll,$deeplinkmenu,\%menu);
 }
 
+sub deeplink_login_symb {
+    my ($cnum,$cdom) = @_;
+    my $login_symb;
+    if ($env{'request.deeplink.login'}) {
+        $login_symb = &symb_from_tinyurl($env{'request.deeplink.login'},$cnum,$cdom);
+    }
+    return $login_symb;
+}
+
+sub symb_from_tinyurl {
+    my ($url,$cnum,$cdom) = @_;
+    if ($url =~ m{^\Q/tiny/$cdom/\E(\w+)$}) {
+        my $key = $1;
+        my ($tinyurl,$login);
+        my ($result,$cached)=&Apache::lonnet::is_cached_new('tiny',$cdom."\0".$key);
+        if (defined($cached)) {
+            $tinyurl = $result;
+        } else {
+            my $configuname = &Apache::lonnet::get_domainconfiguser($cdom);
+            my %currtiny = &Apache::lonnet::get('tiny',[$key],$cdom,$configuname);
+            if ($currtiny{$key} ne '') {
+                $tinyurl = $currtiny{$key};
+                &Apache::lonnet::do_cache_new('tiny',$cdom."\0".$key,$currtiny{$key},600);
+            }
+        }
+        if ($tinyurl ne '') {
+            my ($cnumreq,$symb) = split(/\&/,$tinyurl);
+            if (wantarray) {
+                return ($cnumreq,$symb);
+            } elsif ($cnumreq eq $cnum) {
+                return $symb;
+            }
+        }
+    }
+    if (wantarray) {
+        return ();
+    } else {
+        return;
+    }
+}
+
 sub wishlist_window {
     return(<<'ENDWISHLIST');
 <script type="text/javascript">
@@ -9249,7 +9614,15 @@ ENDLINK
 }
 
 sub modal_adhoc_script {
-    my ($funcname,$width,$height,$content)=@_;
+    my ($funcname,$width,$height,$content,$possmathjax)=@_;
+    my $mathjax;
+    if ($possmathjax) {
+        $mathjax = <<'ENDJAX';
+               if (typeof MathJax == 'object') {
+                   MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
+               }
+ENDJAX
+    }
     return (<<ENDADHOC);
 <script type="text/javascript">
 // <![CDATA[
@@ -9260,6 +9633,7 @@ sub modal_adhoc_script {
                 modalWindow.height = $height;
                 modalWindow.content = '$content';
                 modalWindow.open();
+                $mathjax
         };  
 // ]]>
 </script>
@@ -9267,7 +9641,7 @@ ENDADHOC
 }
 
 sub modal_adhoc_inner {
-    my ($funcname,$width,$height,$content)=@_;
+    my ($funcname,$width,$height,$content,$possmathjax)=@_;
     my $innerwidth=$width-20;
     $content=&js_ready(
                  &start_page('Dialog',undef,{'only_body'=>1,'bgcolor'=>'#FFFFFF'}).
@@ -9276,12 +9650,12 @@ sub modal_adhoc_inner {
                  &end_scrollbox().
                  &end_page()
              );
-    return &modal_adhoc_script($funcname,$width,$height,$content);
+    return &modal_adhoc_script($funcname,$width,$height,$content,$possmathjax);
 }
 
 sub modal_adhoc_window {
-    my ($funcname,$width,$height,$content,$linktext)=@_;
-    return &modal_adhoc_inner($funcname,$width,$height,$content).
+    my ($funcname,$width,$height,$content,$linktext,$possmathjax)=@_;
+    return &modal_adhoc_inner($funcname,$width,$height,$content,$possmathjax).
            "<a href=\"javascript:$funcname();void(0);\">".$linktext."</a>";
 }
 
@@ -11156,11 +11530,15 @@ sub sorted_inst_types {
 }
 
 sub get_institutional_codes {
-    my ($settings,$allcourses,$LC_code) = @_;
+    my ($cdom,$crs,$settings,$allcourses,$LC_code) = @_;
 # Get complete list of course sections to update
     my @currsections = ();
     my @currxlists = ();
+    my (%unclutteredsec,%unclutteredlcsec);
     my $coursecode = $$settings{'internal.coursecode'};
+    my $crskey = $crs.':'.$coursecode;
+    @{$unclutteredsec{$crskey}} = ();
+    @{$unclutteredlcsec{$crskey}} = ();
 
     if ($$settings{'internal.sectionnums'} ne '') {
         @currsections = split(/,/,$$settings{'internal.sectionnums'});
@@ -11171,8 +11549,8 @@ sub get_institutional_codes {
     }
 
     if (@currxlists > 0) {
-        foreach (@currxlists) {
-            if (m/^([^:]+):(\w*)$/) {
+        foreach my $xl (@currxlists) {
+            if ($xl =~ /^([^:]+):(\w*)$/) {
                 unless (grep/^$1$/,@{$allcourses}) {
                     push(@{$allcourses},$1);
                     $$LC_code{$1} = $2;
@@ -11180,15 +11558,28 @@ sub get_institutional_codes {
             }
         }
     }
- 
+
     if (@currsections > 0) {
-        foreach (@currsections) {
-            if (m/^(\w+):(\w*)$/) {
-                my $sec = $coursecode.$1;
+        foreach my $sec (@currsections) {
+            if ($sec =~ m/^(\w+):(\w*)$/ ) {
+                my $instsec = $1;
                 my $lc_sec = $2;
-                unless (grep/^$sec$/,@{$allcourses}) {
+                unless (grep/^\Q$instsec\E$/,@{$unclutteredsec{$crskey}}) {
+                    push(@{$unclutteredsec{$crskey}},$instsec);
+                    push(@{$unclutteredlcsec{$crskey}},$lc_sec);
+                }
+            }
+        }
+    }
+
+    if (@{$unclutteredsec{$crskey}} > 0) {
+        my %formattedsec = &Apache::lonnet::auto_instsec_reformat($cdom,'clutter',\%unclutteredsec);
+        if ((ref($formattedsec{$crskey}) eq 'ARRAY') && (ref($unclutteredlcsec{$crskey}) eq 'ARRAY')) {
+            for (my $i=0; $i<@{$formattedsec{$crskey}}; $i++) {
+                my $sec = $coursecode.$formattedsec{$crskey}[$i];
+                unless (grep/^\Q$sec\E$/,@{$allcourses}) {
                     push(@{$allcourses},$sec);
-                    $$LC_code{$sec} = $lc_sec;
+                    $$LC_code{$sec} = $unclutteredlcsec{$crskey}[$i];
                 }
             }
         }
@@ -16323,6 +16714,9 @@ sub construct_course {
     if ($args->{'crstype'}) {
         $cenv{'type'}=$args->{'crstype'};
     }
+    if ($args->{'lti'}) {
+        $cenv{'internal.lti'}=$args->{'lti'};
+    }
     if ($args->{'crsid'}) {
         $cenv{'courseid'}=$args->{'crsid'};
     }
@@ -17822,7 +18216,7 @@ sub needs_coursereinit {
     }
     if (($now-$env{'request.course.timechecked'})>$interval) {
         &Apache::lonnet::appenv({'request.course.timechecked'=>$now});
-        my $blocked = &blocking_status('reinit',$cnum,$cdom,undef,1);
+        my $blocked = &blocking_status('reinit',undef,$cnum,$cdom,undef,1);
         if ($blocked) {
             return ();
         }
@@ -18194,9 +18588,10 @@ sub create_captcha {
 
         if (-e $Apache::lonnet::perlvar{'lonCaptchaDir'}.'/'.$md5sum.'.png') {
             $output = '<input type="hidden" name="crypt" value="'.$md5sum.'" />'."\n".
+                      '<span class="LC_nobreak">'.
                       &mt('Type in the letters/numbers shown below').'&nbsp;'.
                       '<input type="text" size="5" name="code" value="" autocomplete="off" />'.
-                      '<br />'.
+                      '</span><br />'.
                       '<img src="'.$captcha_params{'www_output_dir'}.'/'.$md5sum.'.png" alt="captcha" />';
             last;
         }
@@ -18242,7 +18637,8 @@ sub check_captcha {
 sub create_recaptcha {
     my ($pubkey,$version) = @_;
     if ($version >= 2) {
-        return '<div class="g-recaptcha" data-sitekey="'.$pubkey.'"></div>';
+        return '<div class="g-recaptcha" data-sitekey="'.$pubkey.'"></div>'.
+               '<div style="padding:0;clear:both;margin:0;border:0"></div>';
     } else {
         my $use_ssl;
         if ($ENV{'SERVER_PORT'} == 443) {
@@ -18347,7 +18743,7 @@ sub critical_redirect {
         if (($env{'request.course.id'}) && (($context eq 'flip') || ($context eq 'contents'))) {
             my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
             my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
-            my $blocked = &blocking_status('alert',$cnum,$cdom,undef,1);
+            my $blocked = &blocking_status('alert',undef,$cnum,$cdom,undef,1);
             if ($blocked) {
                 my $checkrole = "cm./$cdom/$cnum";
                 if ($env{'request.course.sec'} ne '') {
@@ -18735,7 +19131,7 @@ sub page_menu {
             my @entries = split(/\&/,$value);
             foreach my $entry (@entries) {
                 my ($name,$fields) = split(/=/,$entry);
-                if (($name eq 'top') || ($name eq 'inline') || ($name eq 'main')) {
+                if (($name eq 'top') || ($name eq 'inline') || ($name eq 'foot') || ($name eq 'main')) {
                     $menu{$name} = $fields;
                 } else {
                     my @shown;