--- loncom/interface/loncommon.pm	2018/11/18 22:50:52	1.1324
+++ loncom/interface/loncommon.pm	2025/01/06 00:22:57	1.1445
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # a pile of common routines
 #
-# $Id: loncommon.pm,v 1.1324 2018/11/18 22:50:52 raeburn Exp $
+# $Id: loncommon.pm,v 1.1445 2025/01/06 00:22:57 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -61,7 +61,7 @@ use POSIX qw(strftime mktime);
 use Apache::lonmenu();
 use Apache::lonenc();
 use Apache::lonlocal;
-use Apache::lonnet();
+use Apache::lonnavmaps();
 use HTML::Entities;
 use Apache::lonhtmlcommon();
 use Apache::loncoursedata();
@@ -71,7 +71,10 @@ use Apache::lonuserutils();
 use Apache::lonuserstate();
 use Apache::courseclassifier();
 use LONCAPA qw(:DEFAULT :match);
+use LONCAPA::ltiutils;
 use LONCAPA::LWPReq;
+use LONCAPA::map();
+use HTTP::Request;
 use DateTime::TimeZone;
 use DateTime::Locale;
 use Encode();
@@ -79,7 +82,6 @@ use Text::Aspell;
 use Authen::Captcha;
 use Captcha::reCAPTCHA;
 use JSON::DWIW;
-use LWP::UserAgent;
 use Crypt::DES;
 use DynaLoader; # for Crypt::DES version
 use MIME::Lite;
@@ -435,7 +437,7 @@ sub studentbrowser_javascript {
 <script type="text/javascript" language="Javascript">
 // <![CDATA[
     var stdeditbrowser;
-    function openstdbrowser(formname,uname,udom,clicker,roleflag,ignorefilter,courseadvonly) {
+    function openstdbrowser(formname,uname,udom,clicker,roleflag,ignorefilter,courseadv,uident) {
         var url = '/adm/pickstudent?';
         var filter;
 	if (!ignorefilter) {
@@ -450,7 +452,13 @@ sub studentbrowser_javascript {
                                     '&udomelement='+udom+
                                     '&clicker='+clicker;
 	if (roleflag) { url+="&roles=1"; }
-        if (courseadvonly) { url+="&courseadvonly=1"; }
+        if (courseadv == 'condition') {
+            if (document.getElementById('courseadv')) {
+                courseadv = document.getElementById('courseadv').value;
+            }
+        }
+        if ((courseadv == 'only') || (courseadv == 'none')) { url+="&courseadv="+courseadv; }
+        if (uident !== '') { url+="&identelement="+uident; } 
         var title = 'Student_Browser';
         var options = 'scrollbars=1,resizable=1,menubar=0';
         options += ',width=700,height=600';
@@ -482,7 +490,7 @@ ENDRESBRW
 }
 
 sub selectstudent_link {
-   my ($form,$unameele,$udomele,$courseadvonly,$clickerid)=@_;
+   my ($form,$unameele,$udomele,$courseadv,$clickerid,$identelem)=@_;
    my $callargs = "'".&Apache::lonhtmlcommon::entity_encode($form)."','".
                       &Apache::lonhtmlcommon::entity_encode($unameele)."','".
                       &Apache::lonhtmlcommon::entity_encode($udomele)."'";
@@ -493,8 +501,17 @@ sub selectstudent_link {
 	   return '';
        }
        $callargs.=",'".&Apache::lonhtmlcommon::entity_encode($clickerid)."'";
-       if ($courseadvonly)  {
-           $callargs .= ",'',1,1";
+       if ($courseadv eq 'only') {
+           $callargs .= ",'',1,'$courseadv'";
+       } elsif ($courseadv eq 'none') {
+           $callargs .= ",'','','$courseadv'";
+       } elsif ($courseadv eq 'condition') {
+           $callargs .= ",'','','$courseadv'";
+       } elsif ($identelem ne '') {
+           $callargs .= ",'','',''";
+       }
+       if ($identelem ne '') {
+           $callargs .= ",'".&Apache::lonhtmlcommon::entity_encode($identelem)."'";
        }
        return '<span class="LC_nobreak">'.
               '<a href="javascript:openstdbrowser('.$callargs.');">'.
@@ -948,8 +965,8 @@ ENDSCRT
 }
 
 sub select_timezone {
-   my ($name,$selected,$onchange,$includeempty,$disabled)=@_;
-   my $output='<select name="'.$name.'" '.$onchange.$disabled.'>'."\n";
+   my ($name,$selected,$onchange,$includeempty,$id,$disabled)=@_;
+   my $output='<select name="'.$name.'" '.$id.$onchange.$disabled.'>'."\n";
    if ($includeempty) {
        $output .= '<option value=""';
        if (($selected eq '') || ($selected eq 'local')) {
@@ -1223,7 +1240,12 @@ END
         $result.=">".&mt($hashref->{$value}->{'text'})."</option>\n";
     }
     $result .= "</select>\n";
-    my %select2 = %{$hashref->{$firstdefault}->{'select2'}};
+    my %select2;
+    if (ref($hashref->{$firstdefault}) eq 'HASH') {
+        if (ref($hashref->{$firstdefault}->{'select2'}) eq 'HASH') {
+            %select2 = %{$hashref->{$firstdefault}->{'select2'}};
+        }
+    }
     $result .= $middletext;
     $result .= "<select size=\"1\" name=\"$secondselectname\"";
     if ($onchangesecond) {
@@ -1248,7 +1270,7 @@ END
 
 =pod
 
-=item * &help_open_topic($topic,$text,$stayOnPage,$width,$height,$imgid)
+=item * &help_open_topic($topic,$text,$stayOnPage,$width,$height,$imgid,$links_target)
 
 Returns a string corresponding to an HTML link to the given help
 $topic, where $topic corresponds to the name of a .tex file in
@@ -1272,10 +1294,12 @@ $imgid is the id of the img tag used for
 used in a javascript call to switch the image src.  See 
 lonhtmlcommon::htmlareaselectactive() for an example.
 
+$links_target will optionally be set to a target (_top, _parent or _self).
+
 =cut
 
 sub help_open_topic {
-    my ($topic, $text, $stayOnPage, $width, $height, $imgid) = @_;
+    my ($topic, $text, $stayOnPage, $width, $height, $imgid, $links_target) = @_;
     $text = "" if (not defined $text);
     $stayOnPage = 0 if (not defined $stayOnPage);
     $width = 500 if (not defined $width);
@@ -1298,10 +1322,13 @@ sub help_open_topic {
 
     # Add the text
     my $target = ' target="_top"';
-    if (($env{'request.lti.login'}) && ($env{'request.lti.target'} eq 'iframe')) {
+    if ($links_target) {
+        $target = ' target="'.$links_target.'"';
+    } elsif ((($env{'request.lti.login'}) && ($env{'request.lti.target'} eq 'iframe')) ||
+             (($env{'request.deeplink.login'}) && ($env{'request.deeplink.target'} eq '_self'))) {
         $target = '';
     }
-    if ($text ne "") {	
+    if ($text ne "") {
 	$template.='<span class="LC_help_open_topic">'
                   .'<a'.$target.' href="'.$link.'">'
                   .$text.'</a>';
@@ -1345,7 +1372,7 @@ sub helpLatexCheatsheet {
         $out .= '<span>'
                .&help_open_topic('Authoring_Output_Tags',&mt('Output Tags'),$stayOnPage,undef,600)
                .'</span> <span>'
-               .&help_open_topic('Authoring_Multilingual_Problems',&mt('How to create problems in different languages'),$stayOnPage,undef,600)
+               .&help_open_topic('Authoring_Multilingual_Problems',&mt('Languages'),$stayOnPage,undef,600)
 	       .'</span>';
     }
     $out .= '</span>'; # End cheatsheet
@@ -1386,20 +1413,20 @@ ENDOUTPUT
 
 # now just updates the help link and generates a blue icon
 sub help_open_menu {
-    my ($topic,$component_help,$faq,$bug,$stayOnPage,$width,$height,$text) 
+    my ($topic,$component_help,$faq,$bug,$stayOnPage,$width,$height,$text,$links_target) 
 	= @_;    
     $stayOnPage = 1;
     my $output;
     if ($component_help) {
 	if (!$text) {
 	    $output=&help_open_topic($component_help,undef,$stayOnPage,
-				       $width,$height);
+				       $width,$height,'',$links_target);
 	} else {
 	    my $help_text;
 	    $help_text=&unescape($topic);
 	    $output='<table><tr><td>'.
 		&help_open_topic($component_help,$help_text,$stayOnPage,
-				 $width,$height).'</td></tr></table>';
+				 $width,$height,'',$links_target).'</td></tr></table>';
 	}
     }
     my $banner_link = &update_help_link($topic,$component_help,$faq,$bug,$stayOnPage);
@@ -1407,7 +1434,7 @@ sub help_open_menu {
 }
 
 sub top_nav_help {
-    my ($text) = @_;
+    my ($text,$linkattr) = @_;
     $text = &mt($text);
     my $stay_on_page = 1;
 
@@ -1421,7 +1448,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;';
@@ -1507,23 +1534,25 @@ sub help_open_bug {
 	$link = $url;
     }
 
-    my $target = ' target="_top"';
-    if (($env{'request.lti.login'}) && ($env{'request.lti.target'} eq 'iframe')) {
-        $target = '';
+    my $target = '_top';
+    if ((($env{'request.lti.login'}) && ($env{'request.lti.target'} eq 'iframe')) ||
+        (($env{'request.deeplink.login'}) && ($env{'request.deeplink.target'} eq '_self'))) {
+        $target = '_blank';
     }
+
     # Add the text
     if ($text ne "")
     {
 	$template .= 
   "<table bgcolor='#AA3333' cellspacing='1' cellpadding='1' border='0'><tr>".
-  "<td bgcolor='#FF5555'><a".$target." href=\"$link\"><span style=\"color:#FFFFFF;font-size:10pt;\">$text</span></a>";
+  "<td bgcolor='#FF5555'><a target=\"$target\" href=\"$link\"><span style=\"color:#FFFFFF;font-size:10pt;\">$text</span></a>";
     }
 
     # Add the graphic
     my $title = &mt('Report a Bug');
     my $bugicon=&lonhttpdurl("/adm/lonMisc/smallBug.gif");
     $template .= <<"ENDTEMPLATE";
- <a$target href="$link" title="$title"><img src="$bugicon" border="0" alt="(Bug: $topic)" /></a>
+ <a target="$target" href="$link" title="$title"><img src="$bugicon" border="0" alt="(Bug: $topic)" /></a>
 ENDTEMPLATE
     if ($text ne '') { $template.='</td></tr></table>' };
     return $template;
@@ -1735,8 +1764,6 @@ the id of the element to resize, second
 surrounds everything that comes after the textarea, this routine needs
 to be attached to the <body> for the onload and onresize events.
 
-=back
-
 =cut
 
 sub resize_textarea_js {
@@ -1799,8 +1826,11 @@ sub colorfuleditor_js {
             save => 'Save page to make this permanent',
         );
         &js_escape(\%js_lt);
+        my $showfile_js = &show_crsfiles_js();
         $browse_or_search = <<"END";
 
+    $showfile_js
+
     function toggleChooser(form,element,titleid,only,search) {
         var disp = 'none';
         if (document.getElementById('chooser_'+element)) {
@@ -1815,22 +1845,64 @@ sub colorfuleditor_js {
                 toggleResImport(form,element);
             }
             document.getElementById('chooser_'+element).style.display = disp;
+            var dirsel = '';
+            var filesel = '';
+            if (document.getElementById('chooser_'+element+'_crsres')) {
+                var currcrsres = document.getElementById('chooser_'+element+'_crsres').style.display;
+                if (currcrsres == 'none') {
+                    dirsel = 'coursepath_'+element;
+                    var filesel = 'coursefile_'+element;
+                    var include;
+                    if (document.getElementById('crsres_include_'+element)) {
+                        include = document.getElementById('crsres_include_'+element).value;
+                    }
+                    populateCrsSelects(form,dirsel,filesel,1,include,1,0,1,1,0);
+                }
+            }
+            if (document.getElementById('chooser_'+element+'_upload')) {
+                var currcrsupload = document.getElementById('chooser_'+element+'_upload').style.display;
+                if (currcrsupload == 'none') {
+                    dirsel = 'crsauthorpath_'+element;
+                    filesel = '';
+                    populateCrsSelects(form,dirsel,filesel,0,'',1,0,1,0,1);
+                }
+            }
         }
     }
 
-    function toggleCrsFile(form,element,numdirs) {
+    function toggleCrsFile(form,element) {
         if (document.getElementById('chooser_'+element+'_crsres')) {
             var curr = document.getElementById('chooser_'+element+'_crsres').style.display;
             if (curr == 'none') {
-                if (numdirs) {
+                if (document.getElementById('coursepath_'+element)) {
+                    var numdirs;
+                    if (document.getElementById('coursepath_'+element).length) {
+                        numdirs = document.getElementById('coursepath_'+element).length;
+                    }
+                    if ((document.getElementById('hascrsres_'+element)) &&
+                        (document.getElementById('nocrsres_'+element))) {
+                        if (numdirs) {
+                            document.getElementById('hascrsres_'+element).style.display='inline-block';
+                            document.getElementById('nocrsres_'+element).style.display='none';
+                        } else {
+                            document.getElementById('hascrsres_'+element).style.display='none';
+                            document.getElementById('nocrsres_'+element).style.display='inline-block';
+                        }
+                    }
                     form.elements['coursepath_'+element].selectedIndex = 0;
                     if (numdirs > 1) {
-                        window['select1'+element+'_changed']();
+                        var selelem = form.elements['coursefile_'+element];
+                        var i, len = selelem.options.length -1;
+                        if (len >=0) {
+                            for (i = len; i >= 0; i--) {
+                                selelem.remove(i);
+                            }
+                            selelem.options[0] = new Option('','');
+                        }
                     }
                 }
-            } 
+            }
             document.getElementById('chooser_'+element+'_crsres').style.display = 'block';
-            
         }
         if (document.getElementById('chooser_'+element+'_upload')) {
             document.getElementById('chooser_'+element+'_upload').style.display = 'none';
@@ -1841,20 +1913,20 @@ sub colorfuleditor_js {
         return;
     }
 
-    function toggleCrsUpload(form,element,numcrsdirs) {
+    function toggleCrsUpload(form,element) {
         if (document.getElementById('chooser_'+element+'_crsres')) {
             document.getElementById('chooser_'+element+'_crsres').style.display = 'none';
         }
         if (document.getElementById('chooser_'+element+'_upload')) {
             var curr = document.getElementById('chooser_'+element+'_upload').style.display;
             if (curr == 'none') {
-                if (numcrsdirs) {
-                   form.elements['crsauthorpath_'+element].selectedIndex = 0;
-                   form.elements['newsubdir_'+element][0].checked = true;
-                   toggleNewsubdir(form,element);
+                form.elements['newsubdir_'+element][0].checked = true;
+                toggleNewsubdir(form,element);
+                document.getElementById('chooser_'+element+'_upload').style.display = 'block';
+                if (document.getElementById('uploadcrsres_'+element)) {
+                    document.getElementById('uploadcrsres_'+element).value = '';
                 }
             }
-            document.getElementById('chooser_'+element+'_upload').style.display = 'block';
         }
         return;
     }
@@ -1898,19 +1970,21 @@ sub colorfuleditor_js {
         var filename = form.elements['coursefile_'+element];
         var path = directory.options[directory.selectedIndex].value;
         var file = filename.options[filename.selectedIndex].value;
-        form.elements[element].value = '$respath';
-        if (path == '/') {
-            form.elements[element].value += file;
-        } else {
-            form.elements[element].value += path+'/'+file;
-        }
-        unClean();
-        if (document.getElementById('previewimg_'+element)) {
-            document.getElementById('previewimg_'+element).src = form.elements[element].value;
-            var newsrc = document.getElementById('previewimg_'+element).src; 
-        }
-        if (document.getElementById('showimg_'+element)) {
-            document.getElementById('showimg_'+element).innerHTML = '($js_lt{save})';
+        if (file != '') {
+            form.elements[element].value = '$respath';
+            if (path == '/') {
+                form.elements[element].value += file;
+            } else {
+                form.elements[element].value += path+'/'+file;
+            }
+            unClean();
+            if (document.getElementById('previewimg_'+element)) {
+                document.getElementById('previewimg_'+element).src = form.elements[element].value;
+                var newsrc = document.getElementById('previewimg_'+element).src; 
+            }
+            if (document.getElementById('showimg_'+element)) {
+                document.getElementById('showimg_'+element).innerHTML = '($js_lt{save})';
+            }
         }
         toggleChooser(form,element);
         return;
@@ -2199,111 +2273,330 @@ sub crsauthor_url {
 }
 
 sub import_crsauthor_form {
-    my ($form,$firstselectname,$secondselectname,$onchangefirst,$only,$suffix,$disabled) = @_;
+    my ($firstselectname,$secondselectname,$onchangefirst,$only,$suffix,$disabled) = @_;
     return (0) unless ($env{'request.course.id'});
     my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
     my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
     my $crshome = $env{'course.'.$env{'request.course.id'}.'.home'};
     return (0) unless (($cnum ne '') && ($cdom ne ''));
-    my $londocroot = $Apache::lonnet::perlvar{'lonDocRoot'};
     my @ids=&Apache::lonnet::current_machine_ids();
-    my ($output,$is_home,$relpath,%subdirs,%files,%selimport_menus);
-    
+    my ($output,$is_home,$toppath,%subdirs,%files,%selimport_menus,$include,$exclude);
+
     if (grep(/^\Q$crshome\E$/,@ids)) {
         $is_home = 1;
     }
-    $relpath = "/priv/$cdom/$cnum";
-    &Apache::lonnet::recursedirs($is_home,'priv',$londocroot,$relpath,'',\%subdirs,\%files);
+    $toppath = "/priv/$cdom/$cnum";
+    my $nonemptydir = 1;
+    my $js_only;
+    if ($only) {
+        map { $include->{$_} = 1; } split(/\s*,\s*/,$only);
+        $js_only = join(',',map { &js_escape($_); } sort(keys(%{$include})));
+    }
+    $exclude = &Apache::lonnet::priv_exclude();
+    &Apache::lonnet::recursedirs($is_home,1,$include,$exclude,1,0,$toppath,'',\%subdirs,\%files);
+    my $numdirs = scalar(keys(%files));
     my %lt = &Apache::lonlocal::texthash (
         fnam => 'Filename',
         dire => 'Directory',
+        se   => 'Select',
     );
-    my $numdirs = scalar(keys(%files));
-    my (%possexts,$singledir,@singledirfiles);
-    if ($only) {
-        map { $possexts{$_} = 1; } split(/\s*,\s*/,$only);
-    }
-    my (%nonemptydirs,$possdirs);
-    if ($numdirs > 1) {
-        my @order;
-        foreach my $key (sort { lc($a) cmp lc($b) } (keys(%files))) {
-            if (ref($files{$key}) eq 'HASH') {
-                my $shown = $key;
-                if ($key eq '') {
-                    $shown = '/';
-                }
-                my @ordered = ();
-                foreach my $file (sort { lc($a) cmp lc($b) } (keys(%{$files{$key}}))) {
-                    next if ($file =~ /\.rights$/);
-                    if ($only) {
-                        my ($ext) = ($file =~ /\.([^.]+)$/);
-                        unless ($possexts{lc($ext)}) {
-                            next;
-                        }
+    $output = $lt{'dire'}.':&nbsp;'.
+              '<select id="'.$firstselectname.'" name="'.$firstselectname.'" '.
+              'onchange="populateCrsSelects(this.form,'."'$firstselectname','$secondselectname',1,'$js_only',0,1,0,0,0".');">'.
+              '<option value="" selected="selected">'.$lt{'se'}.'</option>';
+    if ($files{'/'}) {
+        $output .= '<option value="/">/</option>'."\n";
+    }
+    foreach my $key (sort { lc($a) cmp lc($b) } (keys(%files))) {
+        next if ($key eq '/');
+        $output .= '<option value="'.$key.'">'.$key.'</option>'."\n";
+    }
+    $output .= '</select><br />'."\n".
+               $lt{'fnam'}.':&nbsp;<select id="'.$secondselectname.'" name="'.$secondselectname.'">'."\n".
+               '<option value="" selected="selected"></option>'."\n".
+               '</select>'."\n".
+               '<input type="hidden" id="crsres_include_'.$suffix.'" value="'.$only.'" />';
+    return ($numdirs,$output);
+}
+
+sub show_crsfiles_js {
+    my $excluderef = &Apache::lonnet::priv_exclude();
+    my $se = &js_escape(&mt('Select'));
+    my $exclude;
+    if (ref($excluderef) eq 'HASH') {
+        $exclude = join(',', map { &js_escape($_); } sort(keys(%{$excluderef})));
+    }
+    my $js = <<"END";
+
+
+    function populateCrsSelects (form,dirsel,filesel,exc,include,setdir,setfile,recurse,nonemptydir,addtopdir) {
+        var relpath = '';
+        if ((setfile) && (dirsel != null) && (dirsel != 'undefined') && (dirsel != '')) {
+            var currdir = form.elements[dirsel].options[form.elements[dirsel].selectedIndex].value;
+            if (currdir == '') {
+                if ((filesel != null) && (filesel != 'undefined') && (filesel != '')) {
+                    selelem = form.elements[filesel];
+                    var j, numfiles = selelem.options.length -1;
+                    if (numfiles >=0) {
+                        for (j = numfiles; j >= 0; j--) {
+                            selelem.remove(j);
+                        }
+                    }
+                    if (selelem.options.length == 0) {
+                        selelem.options[selelem.options.length] = new Option('','');
+                        selelem.selectedIndex = 0;
                     }
-                    $selimport_menus{$key}->{'select2'}->{$file} = $file;
-                    push(@ordered,$file);
-                }
-                if (@ordered) {
-                    push(@order,$key);
-                    $nonemptydirs{$key} = 1;
-                    $selimport_menus{$key}->{'text'} = $shown;
-                    $selimport_menus{$key}->{'default'} = '';
-                    $selimport_menus{$key}->{'select2'}->{''} = '';
-                    $selimport_menus{$key}->{'order'} = \@ordered;
                 }
+                return;
+            } else {
+                relpath = encodeURIComponent(form.elements[dirsel].options[form.elements[dirsel].selectedIndex].value);
             }
         }
-        $possdirs = scalar(keys(%nonemptydirs));
-        if ($possdirs > 1) {
-            my @order = sort { lc($a) cmp lc($b) } (keys(%nonemptydirs));
-            $output = $lt{'dire'}.
-                      &linked_select_forms($form,'<br />'.
-                                           $lt{'fnam'},'',
-                                           $firstselectname,$secondselectname,
-                                           \%selimport_menus,\@order,
-                                           $onchangefirst,'',$suffix).'<br />';
-        } elsif ($possdirs == 1) {
-            $singledir = (keys(%nonemptydirs))[0];
-            if (ref($selimport_menus{$singledir}->{'order'}) eq 'ARRAY') {
-                @singledirfiles = @{$selimport_menus{$singledir}->{'order'}};
-            }
-            delete($selimport_menus{$singledir});
-        }
-    } elsif ($numdirs == 1) {
-        $singledir = (keys(%files))[0];
-        foreach my $file (sort { lc($a) cmp lc($b) } (keys(%{$files{$singledir}}))) {
-            if ($only) {
-                my ($ext) = ($file =~ /\.([^.]+)$/);
-                unless ($possexts{lc($ext)}) {
-                    next;
+        var http = new XMLHttpRequest();
+        var url = "/adm/courseauthor";
+        var crsrole = "$env{'request.role'}";
+        var exclude = '';
+        if (exc) {
+            exclude = '$exclude';
+        }
+        var params = "role=course&files=1&rec="+recurse+"&nonempty="+nonemptydir+"&exc="+exclude+"&inc="+include+"&addtop="+addtopdir+"&path="+relpath;
+        http.open("POST", url, true);
+        http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
+        http.onreadystatechange = function() {
+            if (http.readyState == 4 && http.status == 200) {
+                var data = JSON.parse(http.responseText);
+                var selelem;
+                if ((setdir) && (dirsel != null) && (dirsel != 'undefined') && (dirsel != '')) {
+                    if (Array.isArray(data.dirs)) {
+                        selelem = form.elements[dirsel];
+                        var i, numdirs = selelem.options.length -1;
+                        if (numdirs >=0) {
+                            for (i = numdirs; i >= 0; i--) {
+                                selelem.remove(i);
+                            }
+                        }
+                        var len = data.dirs.length;
+                        if (len) {
+                            selelem.options[selelem.options.length] = new Option('$se','');
+                            var j;
+                            for (j = 0; j < len; j++) {
+                                selelem.options[selelem.options.length] = new Option(data.dirs[j],data.dirs[j]);
+                            }
+                            selelem.selectedIndex = 0;
+                        }
+                        if (!setfile) {
+                            if ((filesel != null) && (filesel != 'undefined') && (filesel != '')) {
+                                selelem = form.elements[filesel];
+                                var j, numfiles = selelem.options.length -1;
+                                if (numfiles >=0) {
+                                    for (j = numfiles; j >= 0; j--) {
+                                        selelem.remove(j);
+                                    }
+                                }
+                                if (selelem.options.length == 0) {
+                                    selelem.options[selelem.options.length] = new Option('','');
+                                    selelem.selectedIndex = 0;
+                                }
+                            }
+                        }
+                    }
+                }
+                if ((setfile) && (filesel != null) && (filesel != 'undefined') && (filesel != '')) {
+                    selelem = form.elements[filesel];
+                    var i, numfiles = selelem.options.length -1;
+                    if (numfiles >=0) {
+                        for (i = numfiles; i >= 0; i--) {
+                            selelem.remove(i);
+                        }
+                    }
+                    var x;
+                    for (x in data.files) {
+                        if (Array.isArray(data.files[x])) {
+                            if (data.files[x].length > 1) {
+                                selelem.options[selelem.options.length] = new Option('$se','');
+                            }
+                            var len = data.files[x].length;
+                            if (len) {
+                                var k;
+                                for (k = 0; k < len; k++) {
+                                    selelem.options[selelem.options.length] = new Option(data.files[x][k],data.files[x][k]);
+                                }
+                                selelem.selectedIndex = 0;
+                            }
+                        }
+                    }
+                    if (selelem.options.length == 0) {
+                        selelem.options[selelem.options.length] = new Option('','');
+                        selelem.selectedIndex = 0;
+                    }
                 }
-            } else {
-                next if ($file =~ /\.rights$/);
             }
-            push(@singledirfiles,$file);
-        }
-        if (@singledirfiles) {
-            $possdirs = 1;
         }
+        http.send(params);
     }
-    if (($possdirs == 1) && (@singledirfiles)) {
-        my $showdir = $singledir;
-        if ($singledir eq '') {
-            $showdir = '/';
+END
+}
+
+sub crsauthor_rights {
+    my ($rightsfile,$path,$docroot,$cnum,$cdom) = @_;
+    my $sourcerights = "$path/$rightsfile";
+    my $now = time;
+    if (!-e $sourcerights) {
+        my $cid = $cdom.'_'.$cnum;
+        if (!-e "$docroot/priv/$cdom") {
+            mkdir("$docroot/priv/$cdom",0755);
+        }
+        if (!-e "$docroot/priv/$cdom/$cnum") {
+            mkdir("$docroot/priv/$cdom/$cnum",0755);
+        }
+        if (open(my $fh,">$sourcerights")) {
+            print $fh <<END;
+<accessrule effect="deny" realm="" type="course" role="" />
+<accessrule effect="allow" realm="$cid" type="course" role="" />
+END
+            close($fh);
         }
-        $output = $lt{'dire'}.
-                  '<select name="'.$firstselectname.'">'.
-                  '<option value="'.$singledir.'">'.$showdir.'</option>'."\n".
-                  '</select><br />'.
-                  $lt{'fnam'}.'<select name="'.$secondselectname.'">'."\n".
-                  '<option value="" selected="selected">'.$lt{'se'}.'</option>'."\n";
-        foreach my $file (@singledirfiles) {
-            $output .= '<option value="'.$file.'">'.$file.'</option>'."\n";
+    }
+    if (!-e "$sourcerights.meta") {
+        if (open(my $fh,">$sourcerights.meta")) {
+            my $author=$env{'environment.firstname'}.' '.
+                       $env{'environment.middlename'}.' '.
+                       $env{'environment.lastname'}.' '.
+                       $env{'environment.generation'};
+            $author =~ s/\s+$//;
+            print $fh <<"END";
+
+<abstract></abstract>
+<author>$author</author>
+<authorspace>$cnum:$cdom</authorspace>
+<copyright>private</copyright>
+<creationdate>$now</creationdate>
+<customdistributionfile></customdistributionfile>
+<dependencies></dependencies>
+<domain>$cdom</domain>
+<highestgradelevel>0</highestgradelevel>
+<keywords></keywords>
+<language>notset</language>
+<lastrevisiondate>$now</lastrevisiondate>
+<lowestgradelevel>0</lowestgradelevel>
+<mime>rights</mime>
+<modifyinguser>$env{'user.name'}:$env{'user.domain'}</modifyinguser>
+<notes></notes>
+<obsolete></obsolete>
+<obsoletereplacement></obsoletereplacement>
+<owner>$cnum:$cdom</owner>
+<rule>deny:::course,allow:$cid::course</rule>
+<sourceavail></sourceavail>
+<standards></standards>
+<subject></subject>
+<title>Course Authoring Rights</title>
+END
+            close($fh);
         }
-        $output .= '</select><br />'."\n";
     }
-    return ($possdirs,$output);
+    return;
+}
+
+=pod
+
+=item * &iframe_wrapper_headjs()
+
+emits javascript containing two global vars to facilitate handling of resizing
+by code in iframe_wrapper_resizejs() used when an iframe is present in a page
+with standard LON-CAPA menus.
+
+=cut
+
+#
+# Where iframe is in use, if window.onload() executes before the custom resize function
+# has been defined (jQuery), two global javascript vars (LCnotready and LCresizedef)
+# are used to ensure document.ready() triggers a call to resize, so the iframe contents
+# do not obscure the Functions menu.
+#
+
+sub iframe_wrapper_headjs {
+    return <<"ENDJS";
+<script type="text/javascript">
+// <![CDATA[
+var LCnotready = 0;
+var LCresizedef = 0;
+// ]]>
+</script>
+
+ENDJS
+
+}
+
+=pod
+
+=item * &iframe_wrapper_resizejs()
+
+emits javascript used to handle resizing for a page containing
+an iframe, to ensure that the iframe does not obscure any
+standard LON-CAPA menu items.
+
+=back
+
+=cut
+
+#
+# jQuery to use when iframe is in use and a page resize occurs.
+# This script will ensure that the iframe does not obscure any
+# standard LON-CAPA inline menus (primary, secondary, and/or
+# breadcrumbs and Functions menus. Expects javascript from
+# &iframe_wrapper_headjs() to be in head portion of the web page,
+# e.g., by inclusion in second arg passed to &start_page().
+#
+
+sub iframe_wrapper_resizejs {
+    my $offset = 5;
+    &get_unprocessed_cgi($ENV{'QUERY_STRING'},['inhibitmenu']);
+    if (($env{'form.inhibitmenu'} eq 'yes') || ($env{'form.only_body'})) {
+        $offset = 0;
+    }
+    return &Apache::lonhtmlcommon::scripttag(<<SCRIPT);
+    \$(document).ready( function() {
+        \$(window).unbind('resize').resize(function(){
+            var header = null;
+            var offset = $offset;
+            var height = 0;
+            var hdrtop = 0;
+            if (\$('div.LC_menus_content:first').length) {
+                if (\$('div.LC_menus_content:first').hasClass ("shown")) {
+                    header = \$('div.LC_menus_content:first');
+                    offset = 12;
+                }
+            } else if (\$('div.LC_head_subbox:first').length) {
+                header = \$('div.LC_head_subbox:first');
+                offset = 9;
+            } else {
+                if (\$('#LC_breadcrumbs').length) {
+                    header = \$('#LC_breadcrumbs');
+                }
+            }
+            if (header != null && header.length) {
+                height = header.height();
+                hdrtop = header.position().top;
+            }
+            var pos = height + hdrtop + offset;
+            \$('.LC_iframecontainer').css('top', pos);
+        });
+        LCresizedef = 1;
+        if (LCnotready == 1) {
+            LCnotready = 0;
+            \$(window).trigger('resize');
+        }
+    });
+    window.onload = function(){
+         if (LCresizedef) {
+             LCnotready = 0;
+             \$(window).trigger('resize');
+         } else {
+             LCnotready = 1;
+         }
+    };
+SCRIPT
+
 }
 
 =pod
@@ -2655,7 +2948,7 @@ sub display_filter {
     my $onchange = "javascript:toggleHistoryOptions(this,'containingphrase','$context',
                                                     '$secondid','$thirdid')";
     return '<span class="LC_nobreak"><label>'.&mt('Records: [_1]',
-			       &Apache::lonmeta::selectbox('show',$env{'form.show'},undef,
+			       &Apache::lonmeta::selectbox('show',$env{'form.show'},'',undef,
 							   (&mt('all'),10,20,50,100,1000,10000))).
 	   '</label></span> <span class="LC_nobreak">'.
            &mt('Filter: [_1]',
@@ -3566,6 +3859,243 @@ sub get_assignable_auth {
     return ($authnum,%can_assign);
 }
 
+sub check_passwd_rules {
+    my ($domain,$plainpass) = @_;
+    my %passwdconf = &Apache::lonnet::get_passwdconf($domain);
+    my ($min,$max,@chars,@brokerule,$warning);
+    $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'};
+        }
+        @chars = @{$passwdconf{'chars'}};
+    }
+    if (($min) && (length($plainpass) < $min)) {
+        push(@brokerule,'min');
+    }
+    if (($max) && (length($plainpass) > $max)) {
+        push(@brokerule,'max');
+    }
+    if (@chars) {
+        my %rules;
+        map { $rules{$_} = 1; } @chars;
+        if ($rules{'uc'}) {
+            unless ($plainpass =~ /[A-Z]/) {
+                push(@brokerule,'uc');
+            }
+        }
+        if ($rules{'lc'}) {
+            unless ($plainpass =~ /[a-z]/) {
+                push(@brokerule,'lc');
+            }
+        }
+        if ($rules{'num'}) {
+            unless ($plainpass =~ /\d/) {
+                push(@brokerule,'num');
+            }
+        }
+        if ($rules{'spec'}) {
+            unless ($plainpass =~ /[!"#$%&'()*+,\-.\/:;<=>?@[\\\]^_`{|}~]/) {
+                push(@brokerule,'spec');
+            }
+        }
+    }
+    if (@brokerule) {
+        my %rulenames = &Apache::lonlocal::texthash(
+            uc   => 'At least one upper case letter',
+            lc   => 'At least one lower case letter',
+            num  => 'At least one number',
+            spec => 'At least one non-alphanumeric',
+        );
+        $rulenames{'uc'} .= ': ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+        $rulenames{'lc'} .= ': abcdefghijklmnopqrstuvwxyz';
+        $rulenames{'num'} .= ': 0123456789';
+        $rulenames{'spec'} .= ': !&quot;\#$%&amp;\'()*+,-./:;&lt;=&gt;?@[\]^_\`{|}~';
+        $rulenames{'min'} = &mt('Minimum password length: [_1]',$min);
+        $rulenames{'max'} = &mt('Maximum password length: [_1]',$max);
+        $warning = &mt('Password did not satisfy the following:').'<ul>';
+        foreach my $rule ('min','max','uc','lc','num','spec') {
+            if (grep(/^$rule$/,@brokerule)) {
+                $warning .= '<li>'.$rulenames{$rule}.'</li>';
+            }
+        }
+        $warning .= '</ul>';
+    }
+    if (wantarray) {
+        return @brokerule;
+    }
+    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';
+        }
+    } elsif ($context eq 'ltitools') {
+        my %domconfig = &Apache::lonnet::get_dom('configuration',['toolsec'],$domain);
+        if (ref($domconfig{'toolsec'}) eq 'HASH') {
+            if (ref($domconfig{'toolsec'}{'rules'}) eq 'HASH') {
+                %passwdconf = %{$domconfig{'toolsec'}{'rules'}};
+            }
+        }
+        if ($id eq 'add') {
+            $alertmsg = &mt('Secret for added external tool did not satisfy requirement(s):').'\n\n';
+        } elsif ($id =~ /^\d+$/) {
+            my $pos = $id+1;
+            $alertmsg = &mt('Secret for external tool [_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                 ##
 ###############################################################
@@ -4046,6 +4576,30 @@ sub syllabuswrapper {
 
 # -----------------------------------------------------------------------------
 
+sub aboutme_on {
+    my ($uname,$udom)=@_;
+    unless ($uname) { $uname=$env{'user.name'}; }
+    unless ($udom)  { $udom=$env{'user.domain'}; }
+    return if ($udom eq 'public' && $uname eq 'public');
+    my $hashkey=$uname.':'.$udom;
+    my ($aboutme,$cached)=&Apache::lonnet::is_cached_new('aboutme',$hashkey);
+    if ($cached) {
+        return $aboutme;
+    }
+    $aboutme = &Apache::lonnet::usertools_access($uname,$udom,'aboutme');
+    &Apache::lonnet::do_cache_new('aboutme',$hashkey,$aboutme,3600);
+    return $aboutme;
+}
+
+sub devalidate_aboutme_cache {
+    my ($uname,$udom)=@_;
+    if (!$udom)  { $udom =$env{'user.domain'}; }
+    if (!$uname) { $uname=$env{'user.name'};   }
+    return if ($udom eq 'public' && $uname eq 'public');
+    my $id=$uname.':'.$udom;
+    &Apache::lonnet::devalidate_cache_new('aboutme',$id);
+}
+
 sub track_student_link {
     my ($linktext,$sname,$sdom,$target,$start,$only_body) = @_;
     my $link ="/adm/trackstudent?";
@@ -4811,6 +5365,59 @@ sub get_student_view_with_retries {
     }
 }
 
+sub css_links {
+    my ($currsymb,$level) = @_;
+    my ($links,@symbs,%cssrefs,%httpref);
+    if ($level eq 'map') {
+        my $navmap = Apache::lonnavmaps::navmap->new();
+        if (ref($navmap)) {
+            my ($map,undef,$url)=&Apache::lonnet::decode_symb($currsymb);
+            my @resources = $navmap->retrieveResources($map,sub { $_[0]->is_problem() },0,0);
+            foreach my $res (@resources) {
+                if (ref($res) && $res->symb()) {
+                    push(@symbs,$res->symb());
+                }
+            }
+        }
+    } else {
+        @symbs = ($currsymb);
+    }
+    foreach my $symb (@symbs) {
+        my $css_href = &Apache::lonnet::EXT('resource.0.cssfile',$symb);
+        if ($css_href =~ /\S/) {
+            unless ($css_href =~ m{https?://}) {
+                my $url = (&Apache::lonnet::decode_symb($symb))[-1];
+                my $proburl =  &Apache::lonnet::clutter($url);
+                my ($probdir) = ($proburl =~ m{(.+)/[^/]+$});
+                unless ($css_href =~ m{^/}) {
+                    $css_href = &Apache::lonnet::hreflocation($probdir,$css_href);
+                }
+                if ($css_href =~ m{^/(res|uploaded)/}) {
+                    unless (($httpref{'httpref.'.$css_href}) ||
+                            (&Apache::lonnet::is_on_map($css_href))) {
+                        my $thisurl = $proburl;
+                        if ($env{'httpref.'.$proburl}) {
+                            $thisurl = $env{'httpref.'.$proburl};
+                        }
+                        $httpref{'httpref.'.$css_href} = $thisurl;
+                    }
+                }
+            }
+            $cssrefs{$css_href} = 1;
+        }
+    }
+    if (keys(%httpref)) {
+        &Apache::lonnet::appenv(\%httpref);
+    }
+    if (keys(%cssrefs)) {
+        foreach my $css_href (keys(%cssrefs)) {
+            next unless ($css_href =~ m{^(/res/|/uploaded/|https?://)});
+            $links .= '<link rel="stylesheet" type="text/css" href="'.$css_href.'" />'."\n";
+        }
+    }
+    return $links;
+}
+
 =pod
 
 =item * &get_student_answers() 
@@ -5066,13 +5673,96 @@ sub findallcourses {
 ###############################################
 
 sub blockcheck {
-    my ($setters,$activity,$uname,$udom,$url,$is_course) = @_;
-
+    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 'index') || ($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))) {
             my ($startblock,$endblock,$triggerblock) =
-                &get_blocks($setters,$activity,$udom,$uname,$url);
+                &get_blocks($setters,$activity,$udom,$uname,$url,$symb,$caller);
             return ($startblock,$endblock,$triggerblock);
         }
     } else {
@@ -5083,13 +5773,17 @@ 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.
 
     if (($activity eq 'boards' || $activity eq 'chat' ||
          $activity eq 'groups' || $activity eq 'printout' ||
+         $activity eq 'search' || $activity eq 'index' ||
          $activity eq 'reinit' || $activity eq 'alert') &&
         ($env{'request.course.id'})) {
         foreach my $key (keys(%live_courses)) {
@@ -5200,7 +5894,7 @@ sub blockcheck {
         # of specified user, unless user has 'evb' privilege.
 
         my ($start,$end,$trigger) = 
-            &get_blocks($setters,$activity,$cdom,$cnum,$url);
+            &get_blocks($setters,$activity,$cdom,$cnum,$url,$symb,$caller);
         if (($start != 0) && 
             (($startblock == 0) || ($startblock > $start))) {
             $startblock = $start;
@@ -5220,7 +5914,7 @@ sub blockcheck {
 }
 
 sub get_blocks {
-    my ($setters,$activity,$cdom,$cnum,$url) = @_;
+    my ($setters,$activity,$cdom,$cnum,$url,$symb,$caller) = @_;
     my $startblock = 0;
     my $endblock = 0;
     my $triggerblock = '';
@@ -5233,7 +5927,13 @@ sub get_blocks {
     my $now = time;
     my %commblocks = &Apache::lonnet::get_comm_blocks($cdom,$cnum);
     if ($activity eq 'docs') {
-        @blockers = &Apache::lonnet::has_comm_blocking('bre',undef,$url,\%commblocks);
+        my ($blocked,$nosymbcache,$noenccheck);
+        if (($caller eq 'blockedaccess') || ($caller eq 'blockingstatus')) {
+            $blocked = 1;
+            $nosymbcache = 1;
+            $noenccheck = 1;
+        }
+        @blockers = &Apache::lonnet::has_comm_blocking('bre',$symb,$url,$nosymbcache,$noenccheck,$blocked,\%commblocks);
         foreach my $block (@blockers) {
             if ($block =~ /^firstaccess____(.+)$/) {
                 my $item = $1;
@@ -5361,14 +6061,17 @@ sub parse_block_record {
 }
 
 sub blocking_status {
-    my ($activity,$uname,$udom,$url,$is_course) = @_;
+    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);
+    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;
     }
 
@@ -5377,12 +6080,17 @@ sub blocking_status {
 
 # build a link to a popup window containing the details
     my $querystring  = "?activity=$activity";
-# $uname and $udom decide whose portfolio the user is trying to look at
-    if (($activity eq 'port') || ($activity eq 'passwd')) {
+# $uname and $udom decide whose portfolio (or information page) the user is trying to look at
+    if (($activity eq 'port') || ($activity eq 'about') || ($activity eq 'passwd')) {
         $querystring .= "&amp;udom=$udom"      if ($udom =~ /^$match_domain$/); 
         $querystring .= "&amp;uname=$uname"    if ($uname =~ /^$match_username$/);
     } elsif ($activity eq 'docs') {
-        $querystring .= '&amp;url='.&HTML::Entities::encode($url,'&"');
+        my $showurl = &Apache::lonenc::check_encrypt($url);
+        $querystring .= '&amp;url='.&HTML::Entities::encode($showurl,'\'&"<>');
+        if ($symb) {
+            my $showsymb = &Apache::lonenc::check_encrypt($symb);
+            $querystring .= '&amp;symb='.&HTML::Entities::encode($showsymb,'\'&"<>');
+        }
     }
 
     my $output .= <<'END_MYBLOCK';
@@ -5407,10 +6115,22 @@ END_MYBLOCK
         $text = &mt('Printing Blocked');
     } elsif ($activity eq 'passwd') {
         $text = &mt('Password Changing Blocked');
+    } elsif ($activity eq 'grades') {
+        $text = &mt('Gradebook Blocked');
+    } elsif ($activity eq 'search') {
+        $text = &mt('Search Blocked');
+    } elsif ($activity eq 'index') {
+        $text = &mt('Content Index Blocked');
     } elsif ($activity eq 'alert') {
         $text = &mt('Checking Critical Messages Blocked');
     } elsif ($activity eq 'reinit') {
         $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'>
@@ -5434,8 +6154,14 @@ sub check_ip_acc {
     if (!defined($acc) || $acc =~ /^\s*$/ || $acc =~/^\s*no\s*$/i) {
         return 1;
     }
-    my $allowed;
-    my $ip=$ENV{'REMOTE_ADDR'} || $clientip || $env{'request.host'};
+    my ($ip,$allowed);
+    if (($ENV{'REMOTE_ADDR'} eq '127.0.0.1') ||
+        ($ENV{'REMOTE_ADDR'} eq &Apache::lonnet::get_host_ip($Apache::lonnet::perlvar{'lonHostID'}))) {
+        $ip = $env{'request.host'} || $ENV{'REMOTE_ADDR'} || $clientip;
+    } else {
+        my $remote_ip = &Apache::lonnet::get_requestor_ip();
+        $ip = $remote_ip || $env{'request.host'} || $clientip;
+    }
 
     my $name;
     my %access = (
@@ -5586,6 +6312,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','window','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} = 
@@ -5690,8 +6427,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 {
@@ -5809,6 +6550,12 @@ 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".
+       If $title is supplied as the third arg, that will be used to 
+       the left of the breadcrumbs tail for the current path.
 
 Returns: HTML div with CSTR path and recent box
          To be included on Authoring Space pages
@@ -5816,7 +6563,7 @@ Returns: HTML div with CSTR path and rec
 =cut
 
 sub CSTR_pageheader {
-    my ($trailfile) = @_;
+    my ($trailfile,$frameset,$title) = @_;
     if ($trailfile eq '') {
         $trailfile = $env{'request.filename'};
     }
@@ -5839,20 +6586,28 @@ sub CSTR_pageheader {
         $lastitem = $thisdisfn;
     }
 
-    my ($crsauthor,$title);
+    my $crsauthor;
     if (($env{'request.course.id'}) &&
         ($env{'course.'.$env{'request.course.id'}.'.num'} eq $uname) &&
         ($env{'course.'.$env{'request.course.id'}.'.domain'} eq $udom)) {
         $crsauthor = 1;
-        $title = &mt('Course Authoring Space');
-    } else {
+        if ($title eq '') {
+            $title = &mt('Course Authoring Space');
+        }
+    } elsif ($title eq '') {
         $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 =
@@ -5870,20 +6625,161 @@ 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>';
 
     return $output;
 }
 
+##############################################
+=pod
+
+=item * &nocodemirror()
+
+Input: None
+
+Returns: 1 if CodeMirror is deactivated based on
+         user's preference, or domain default,
+         if user indicated use of default.
+
+=cut
+
+sub nocodemirror {
+    my $nocodem = $env{'environment.nocodemirror'};
+    unless ($nocodem) {
+        my %domdefs = &Apache::lonnet::get_domain_defaults($env{'user.domain'});
+        if ($domdefs{'nocodemirror'}) {
+            $nocodem = 'yes';
+        }
+    }
+    if ($nocodem eq 'yes') {
+        return 1;
+    }
+    return;
+}
+
+##############################################
+=pod
+
+=item * &permitted_editors()
+
+Input: $uri (optional)
+
+Returns: %editors hash in which keys are editors
+         permitted in current Authoring Space,
+         or in current course for web pages
+         created in a course.
+
+         Value for each key is 1. Possible keys
+         are: edit, xml, and daxe.
+
+         For a regular Authoring Space, if no specific
+         set of editors has been set for the Author
+         who owns the Authoring Space, then the
+         domain default will be used.  If no domain
+         default has been set, then the keys will be
+         edit and xml.
+
+         For a course author, or for web pages created
+         in a course, if no specific set of editors has
+         been set for the course, then the domain
+         course default will be used. If no domain
+         course default has been set, then the keys
+         will be edit and xml.
+
+=cut
+
+sub permitted_editors {
+    my ($uri) = @_;
+    my ($is_author,$is_coauthor,$is_course,$auname,$audom,%editors);
+    if ($env{'request.role'} =~ m{^au\./}) {
+        $is_author = 1;
+    } elsif ($env{'request.role'} =~ m{^(?:ca|aa)\./($match_domain)/($match_username)}) {
+        ($audom,$auname) = ($1,$2);
+        if (($audom ne '') && ($auname ne '')) {
+            if (($env{'user.domain'} eq $audom) &&
+                ($env{'user.name'} eq $auname)) {
+                $is_author = 1;
+            } else {
+                $is_coauthor = 1;
+            }
+        }
+    } elsif ($env{'request.course.id'}) {
+        my ($cdom,$cnum);
+        $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+        $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
+        if (($env{'request.editurl'} =~ m{^/priv/\Q$cdom/$cnum\E/}) ||
+            ($env{'request.editurl'} =~ m{^/uploaded/\Q$cdom/$cnum\E/(docs|supplemental)/}) ||
+            ($uri =~ m{^/uploaded/\Q$cdom/$cnum\E/(docs|supplemental)/})) {
+            $is_course = 1;
+        } elsif ($env{'request.editurl'} =~ m{^/priv/($match_domain)/($match_username)/}) {
+            ($audom,$auname) = ($1,$2);
+        } elsif ($env{'request.uri'} =~ m{^/priv/($match_domain)/($match_username)/}) {
+            ($audom,$auname) = ($1,$2);
+        } elsif (($uri eq '/daxesave') &&
+                 (($env{'form.path'} =~ m{^/daxeopen/priv/\Q$cdom/$cnum\E/}) ||
+                  ($env{'form.path'} =~ m{^/daxeopen/uploaded/\Q$cdom/$cnum\E/(docs|supplemental)/}))) {
+            $is_course = 1;
+        } elsif (($uri eq '/daxesave') &&
+                 ($env{'form.path'} =~ m{^/daxeopen/priv/($match_domain)/($match_username)/})) {
+            ($audom,$auname) = ($1,$2);
+        }
+        unless ($is_course) {
+            if (($audom ne '') && ($auname ne '')) {
+                if (($env{'user.domain'} eq $audom) &&
+                    ($env{'user.name'} eq $auname)) {
+                    $is_author = 1;
+                } else {
+                    $is_coauthor = 1;
+                }
+            }
+        }
+    }
+    if ($is_author) {
+        if (exists($env{'environment.editors'})) {
+            map { $editors{$_} = 1; } split(/,/,$env{'environment.editors'});
+        } else {
+            %editors = ( edit => 1,
+                         xml => 1,
+                       );
+        }
+    } elsif ($is_coauthor) {
+        if (exists($env{"environment.internal.editors./$audom/$auname"})) {
+            map { $editors{$_} = 1; } split(/,/,$env{"environment.internal.editors./$audom/$auname"});
+        } else {
+            %editors = ( edit => 1,
+                         xml => 1,
+                       );
+        }
+    } elsif ($is_course) {
+        if (exists($env{'course.'.$env{'request.course.id'}.'.internal.crseditors'})) {
+            map { $editors{$_} = 1; } split(/,/,$env{'course.'.$env{'request.course.id'}.'.internal.crseditors'});
+        } else {
+            my %domdefaults = &Apache::lonnet::get_domain_defaults($env{'course.'.$env{'request.course.id'}.'.domain'});
+            if (exists($domdefaults{'crseditors'})) {
+                map { $editors{$_} = 1; } split(/,/,$domdefaults{'crseditors'});
+            } else {
+                %editors = ( edit => 1,
+                             xml => 1,
+                           );
+            }
+        }
+    } else {
+        %editors = ( edit => 1,
+                     xml => 1,
+                   );
+    }
+    return %editors;
+}
+
 ###############################################
 ###############################################
 
@@ -5945,6 +6841,21 @@ Inputs:
             context, this will contain a reference to hash of items
             to be included in the page header and/or inline menu.
 
+=item * $menucoll, optional argument, if specific menu collection is in
+            effect, either set as the default for the course, or set for
+            the deeplink paramater for $env{'request.deeplink.login'}
+            then $menucoll will be the number of that collection. 
+
+=item * $menuref, optional argument, reference to a hash, containing the
+            menu options included for the menu in effect, based on the
+            configuration for the numbered menu collection in use.  
+
+=item * $showncrumbsref, reference to a scalar. Calls to lonmenu::innerregister
+            within &bodytag() can result in calls to lonhtmlcommon::breadcrumbs(),
+            if so, $showncrumbsref is set there to 1, and will propagate back
+            via &bodytag() to &start_page(), to prevent lonhtmlcommon::breadcrumbs()
+            being called a second time.
+
 =back
 
 Returns: A uniform header for LON-CAPA web pages.  
@@ -5956,7 +6867,8 @@ other decorations will be returned.
 
 sub bodytag {
     my ($title,$function,$addentries,$bodyonly,$domain,$forcereg,
-        $no_nav_bar,$bgcolor,$args,$advtoolsref,$ltiscope,$ltiuri,$ltimenu)=@_;
+        $no_nav_bar,$bgcolor,$args,$advtoolsref,$ltiscope,$ltiuri,
+        $ltimenu,$menucoll,$menuref,$showncrumbsref)=@_;
 
     my $public;
     if ((($env{'user.name'} eq 'public') && ($env{'user.domain'} eq 'public'))
@@ -5968,7 +6880,6 @@ sub bodytag {
     my $hostname = $args->{'hostname'};
 
     $function = &get_users_function() if (!$function);
-    my $img =    &designparm($function.'.img',$domain);
     my $font =   &designparm($function.'.font',$domain);
     my $pgbg   = $bgcolor || &designparm($function.'.pgbg',$domain);
 
@@ -5985,12 +6896,24 @@ sub bodytag {
     if ($realm) {
         $realm = '/'.$realm;
     }
-    if ($role  eq 'ca') {
+    if ($role eq 'ca') {
         my ($rdom,$rname) = ($realm =~ m{^/($match_domain)/($match_username)$});
         $realm = &plainname($rname,$rdom);
     } 
 # realm
+    my ($cid,$sec);
     if ($env{'request.course.id'}) {
+        $cid = $env{'request.course.id'};
+        if ($env{'request.course.sec'}) {
+            $sec = $env{'request.course.sec'};
+        }
+    } elsif ($realm =~ m{^/($match_domain)/($match_courseid)(?:|/(\w+))$}) {
+        if (&Apache::lonnet::is_course($1,$2)) {
+            $cid = $1.'_'.$2;
+            $sec = $3;
+        }
+    }
+    if ($cid) {
         if ($env{'request.role'} !~ /^cr/) {
             $role = &Apache::lonnet::plaintext($role,&course_type());
         } elsif ($role =~ m{^cr/($match_domain)/\1-domainconfig/(\w+)$}) {
@@ -6002,10 +6925,10 @@ sub bodytag {
         } else {
             $role = (split(/\//,$role,4))[-1]; 
         }
-        if ($env{'request.course.sec'}) {
-            $role .= ('&nbsp;'x2).'-&nbsp;'.&mt('section:').'&nbsp;'.$env{'request.course.sec'};
+        if ($sec) {
+            $role .= ('&nbsp;'x2).'-&nbsp;'.&mt('section:').'&nbsp;'.$sec;
         }   
-	$realm = $env{'course.'.$env{'request.course.id'}.'.description'};
+	$realm = $env{'course.'.$cid.'.description'};
     } else {
         $role = &Apache::lonnet::plaintext($role);
     }
@@ -6028,13 +6951,25 @@ sub bodytag {
 	undef($role);
     }
 
-    if (($env{'request.course.id'}) && ($env{'request.lti.login'})) {
+    my $showcrstitle = 1;
+    if (($cid) && ($env{'request.lti.login'})) {
         if (ref($ltimenu) eq 'HASH') {
             unless ($ltimenu->{'role'}) {
                 undef($role);
             }
             unless ($ltimenu->{'coursetitle'}) {
                 $realm='&nbsp;';
+                $showcrstitle = 0;
+            }
+        }
+    } elsif (($cid) && ($menucoll)) {
+        if (ref($menuref) eq 'HASH') {
+            unless ($menuref->{'role'}) {
+                undef($role);
+            }
+            unless ($menuref->{'crs'}) {
+                $realm='&nbsp;';
+                $showcrstitle = 0;
             }
         }
     }
@@ -6043,17 +6978,15 @@ sub bodytag {
     #
     # Extra info if you are the DC
     my $dc_info = '';
-    if ($env{'user.adv'} && exists($env{'user.role.dc./'.
-                        $env{'course.'.$env{'request.course.id'}.
-                                 '.domain'}.'/'})) {
-        my $cid = $env{'request.course.id'};
+    if (($env{'user.adv'}) && ($env{'request.course.id'}) && $showcrstitle &&
+        (exists($env{'user.role.dc./'.$env{'course.'.$cid.'.domain'}.'/'}))) {
         $dc_info = $cid.' '.$env{'course.'.$cid.'.internal.coursecode'};
         $dc_info =~ s/\s+$//;
     }
 
     my $crstype;
-    if ($env{'request.course.id'}) {
-        $crstype = $env{'course.'.$env{'request.course.id'}.'.type'};
+    if ($cid) {
+        $crstype = $env{'course.'.$cid.'.type'};
     } elsif ($args->{'crstype'}) {
         $crstype = $args->{'crstype'};
     }
@@ -6069,11 +7002,40 @@ sub bodytag {
         #        $titleinfo = &CSTR_pageheader(); #FIXME: Will be removed once all scripts have their own calls
         #    }
 
-        $bodytag .= Apache::lonhtmlcommon::scripttag(
-            Apache::lonmenu::utilityfunctions($httphost), 'start');
+        my $need_endlcint;
+        unless ($args->{'switchserver'}) {
+            $bodytag .= Apache::lonhtmlcommon::scripttag(
+                Apache::lonmenu::utilityfunctions($httphost), 'start');
+            $need_endlcint = 1;
+        }
 
+        my $collapsible;
+        if ($args->{'collapsible_header'} ne '') {
+            $collapsible = 1;
+            my ($menustate,$tiptext,$divclass);
+            if ($args->{'start_collapsed'}) {
+                $menustate = 'collapsed';
+                $tiptext = 'display';
+                $divclass = 'hidden';
+            } else {
+                $menustate = 'expanded';
+                $tiptext = 'hide';
+                $divclass = 'shown';
+            }
+            my $alttext = &mt('menu state: '.$menustate);
+            my $tooltip = &mt($tiptext.' standard menus');
+            $bodytag .= <<"END";
+<div id="LC_expandingContainer" style="display:inline;">
+<div id="LC_collapsible" class="LC_collapse_trigger" style="position: absolute;top: -5px;left: 0px; z-index:101; display:inline;">
+<a href="#" style="text-decoration:none;"><img class="LC_collapsible_indicator" alt="$alttext" title="$tooltip" src="/res/adm/pages/$menustate.png" style="border:0;margin:0;padding:0;max-width:100%;height:auto" /></a></div>
+<div class="LC_menus_content $divclass">
+END
+        }
         unless ($args->{'no_primary_menu'}) {
-            my ($left,$right) = Apache::lonmenu::primary_menu($crstype,$ltimenu);
+            my ($left,$right) = Apache::lonmenu::primary_menu($crstype,$ltimenu,$menucoll,$menuref,
+                                                              $args->{'links_disabled'},
+                                                              $args->{'links_target'},
+                                                              $collapsible);
 
             if ($env{'request.noversionuri'} =~ m{^/res/adm/pages/}) {
                 if ($dc_info) {
@@ -6081,6 +7043,9 @@ sub bodytag {
                 }
                 $bodytag .= qq|<div id="LC_nav_bar">$left $role<br />
                                <em>$realm</em> $dc_info</div>|;
+                if ($need_endlcint) {
+                    $bodytag .= Apache::lonhtmlcommon::scripttag('', 'end');
+                }
                 return $bodytag;
             }
 
@@ -6098,24 +7063,32 @@ sub bodytag {
 
         #if directed to not display the secondary menu, don't.  
         if ($args->{'no_secondary_menu'}) {
+            if ($need_endlcint) {
+                $bodytag .= Apache::lonhtmlcommon::scripttag('', 'end');
+            }
             return $bodytag;
         }
         #don't show menus for public users
         if (!$public){
             unless ($args->{'no_inline_menu'}) {
                 $bodytag .= Apache::lonmenu::secondary_menu($httphost,$ltiscope,$ltimenu,
-                                                            $args->{'no_primary_menu'});
+                                                            $args->{'no_primary_menu'},
+                                                            $menucoll,$menuref,
+                                                            $args->{'links_disabled'},
+                                                            $args->{'links_target'});
             }
             $bodytag .= Apache::lonmenu::serverform();
-            $bodytag .= Apache::lonhtmlcommon::scripttag('', 'end');
+            if ($need_endlcint) {
+                $bodytag .= Apache::lonhtmlcommon::scripttag('', 'end');
+            }
             if ($env{'request.state'} eq 'construct') {
                 $bodytag .= &Apache::lonmenu::innerregister($forcereg,
-                                $args->{'bread_crumbs'},'','',$hostname,$ltiscope,$ltiuri);
+                                $args->{'bread_crumbs'},'','',$hostname,
+                                $ltiscope,$ltiuri,$showncrumbsref);
             } elsif ($forcereg) {
                 $bodytag .= &Apache::lonmenu::innerregister($forcereg,undef,
-                                                            $args->{'group'},
-                                                            $args->{'hide_buttons'},
-                                                            $hostname,$ltiscope,$ltiuri);
+                                $args->{'group'},$args->{'hide_buttons'},
+                                $hostname,$ltiscope,$ltiuri,$showncrumbsref);
             } else {
                 $bodytag .= 
                     &Apache::lonmenu::prepare_functions($env{'request.noversionuri'},
@@ -6123,13 +7096,19 @@ sub bodytag {
                                                         $args->{'bread_crumbs'},
                                                         $advtoolsref,'',$hostname);
             }
-        }else{
-            # this is to seperate menu from content when there's no secondary
-            # menu. Especially needed for public accessible ressources.
+        } else {
+            # this is to separate menu from content when there's no secondary
+            # menu. Especially needed for publicly accessible resources.
             $bodytag .= '<hr style="clear:both" />';
-            $bodytag .= Apache::lonhtmlcommon::scripttag('', 'end'); 
+            if ($need_endlcint) {
+                $bodytag .= Apache::lonhtmlcommon::scripttag('', 'end');
+            }
+        }
+        if ($args->{'collapsible_header'} ne '') {
+            $bodytag .= $args->{'collapsible_header'}.
+                        '<div id="LC_collapsible_separator"></div>'.
+                        '</div></div>';
         }
-
         return $bodytag;
 }
 
@@ -6199,12 +7178,45 @@ sub endbodytag {
     }
     if ( exists( $env{'internal.head.redirect'} ) ) {
         if (!(ref($args) eq 'HASH' && $args->{'noredirectlink'})) {
+            my ($endbodyjs,$idattr);
+            if ($env{'internal.head.to_opener'}) {
+                my $linkid = 'LC_continue_link';
+                $idattr = ' id="'.$linkid.'"';
+                my $redirect_for_js = &js_escape($env{'internal.head.redirect'});
+                $endbodyjs=<<ENDJS;
+<script type="text/javascript">
+// <![CDATA[
+function ebFunction(evt) {
+    evt.preventDefault();
+    var dest = '$redirect_for_js';
+    if (window.opener != null && !window.opener.closed) {
+        window.opener.location.href=dest;
+        window.close();
+    } else {
+        window.location.href=dest;
+    }
+    return false;
+}
+
+\$(document).ready(function () {
+  if (document.getElementById('$linkid')) {
+    var clickelem = document.getElementById('$linkid');
+    clickelem.addEventListener('click',ebFunction,false);
+  }
+});
+// ]]>
+</script>
+ENDJS
+            }
 	    $endbodytag=
-	        "<br /><a href=\"$env{'internal.head.redirect'}\">".
+	        "$endbodyjs<br /><a href=\"$env{'internal.head.redirect'}\"$idattr>".
 	        &mt('Continue').'</a>'.
 	        $endbodytag;
         }
     }
+    if ((ref($args) eq 'HASH') && ($args->{'dashjs'})) {
+        $endbodytag = &Apache::lonhtmlcommon::dash_to_minus_js().$endbodytag;
+    }
     return $endbodytag;
 }
 
@@ -6225,7 +7237,6 @@ Inputs: (all optional)
 sub standard_css {
     my ($function,$domain,$bgcolor) = @_;
     $function  = &get_users_function() if (!$function);
-    my $img    = &designparm($function.'.img',   $domain);
     my $tabbg  = &designparm($function.'.tabbg', $domain);
     my $font   = &designparm($function.'.font',  $domain);
     my $fontmenu = &designparm($function.'.fontmenu', $domain);
@@ -6278,6 +7289,7 @@ body {
   line-height:130%;
   font-size:0.83em;
   color:$font;
+  background-color: $pgbg_or_bgcolor;
 }
 
 a:focus,
@@ -6289,6 +7301,24 @@ form, .inline {
   display: inline;
 }
 
+.LC_visually_hidden:not(:focus):not(:active) {
+    clip-path: inset(50%);
+    height: 1px;
+    overflow: hidden;
+    position: absolute;
+    white-space: nowrap;
+    width: 1px;
+    display: inline;
+}
+
+.LC_menus_content.shown{
+  display: block;
+}
+
+.LC_menus_content.hidden {
+  display: none;
+}
+
 .LC_right {
   text-align:right;
 }
@@ -6309,6 +7339,12 @@ form, .inline {
   width:400px;
 }
 
+#LC_collapsible_separator {
+    border: 1px solid black;
+    width: 99.9%;
+    height: 0px;
+}
+
 .LC_iframecontainer {
     width: 98%;
     margin: 0;
@@ -7184,7 +8220,8 @@ table.LC_prior_tries td {
   padding: 6px;
 }
 
-.LC_answer_unknown {
+.LC_answer_unknown,
+.LC_answer_warning {
   background: orange;
   color: black;
   padding: 6px;
@@ -7520,6 +8557,11 @@ fieldset {
   /* overflow: hidden; */
 }
 
+fieldset#LC_selectuser {
+    margin: 0;
+    padding: 0;
+}
+
 article.geogebraweb div {
     margin: 0;
 }
@@ -8063,6 +9105,10 @@ a#LC_content_toolbar_edittoplevel {
   background-image:url(/res/adm/pages/edittoplevel.gif);
 }
 
+a#LC_content_toolbar_printout {
+  background-image:url(/res/adm/pages/printout.gif);
+}
+
 ul#LC_toolbar li a:hover {
   background-position: bottom center;
 }
@@ -8180,6 +9226,26 @@ 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;
+  white-space: -pre-wrap;
+  white-space: -o-pre-wrap;
+  word-wrap: break-word;
+}
+
 /*
   styles used for response display
 */
@@ -8345,7 +9411,13 @@ Inputs: $title - optional title for the
                                    3- whether the side effect should occur
                            (side effect of setting 
                                $env{'internal.head.redirect'} to the url 
-                               redirected too)
+                               redirected to)
+                                   4- whether the redirect target should be
+                                      the opener of the current (pop-up)
+                                      window (side effect of setting
+                                      $env{'internal.head.to_opener'} to
+                                      1, if true.
+                                   5- whether encrypt check should be skipped
             domain         -> force to color decorate a page for a specific
                                domain
             function       -> force usage of a specific rolish color scheme
@@ -8379,7 +9451,7 @@ sub headtag {
         $inhibitprint = &print_suppression();
     }
 
-    if (!$args->{'frameset'}) {
+    if (!$args->{'frameset'} && !$args->{'switchserver'}) {
 	$result .= &Apache::lonhtmlcommon::htmlareaheaders();
     }
     if ($args->{'force_register'} && $env{'request.noversionuri'} !~ m{^/res/adm/pages/}) {
@@ -8387,7 +9459,8 @@ sub headtag {
     }
     if (!$args->{'no_nav_bar'} 
 	&& !$args->{'only_body'}
-	&& !$args->{'frameset'}) {
+	&& !$args->{'frameset'}
+	&& !$args->{'switchserver'}) {
 	$result .= &help_menu_js($httphost);
         $result.=&modal_window();
         $result.=&togglebox_script();
@@ -8408,15 +9481,45 @@ sub headtag {
         }
     }
     if (ref($args->{'redirect'})) {
-	my ($time,$url,$inhibit_continue) = @{$args->{'redirect'}};
-	$url = &Apache::lonenc::check_encrypt($url);
+	my ($time,$url,$inhibit_continue,$to_opener,$skip_enc_check) = @{$args->{'redirect'}};
+        if (!$skip_enc_check) {
+            $url = &Apache::lonenc::check_encrypt($url);
+        }
 	if (!$inhibit_continue) {
 	    $env{'internal.head.redirect'} = $url;
 	}
-	$result.=<<ADDMETA
+	$result.=<<"ADDMETA";
 <meta http-equiv="pragma" content="no-cache" />
+ADDMETA
+        if ($to_opener) {
+            $env{'internal.head.to_opener'} = 1;
+            my $dest = &js_escape($url);
+            my $timeout = int($time * 1000);
+            $result .=<<"ENDJS";
+<script type="text/javascript">
+// <![CDATA[
+function LC_To_Opener() {
+    var dest = '$dest';
+    if (dest != '') {
+        if (window.opener != null && !window.opener.closed) {
+            window.opener.location.href=dest;
+            window.close();
+        } else {
+            window.location.href=dest;
+        }
+    }
+}
+\$(document).ready(function () {
+    setTimeout('LC_To_Opener()',$timeout);
+});
+// ]]>
+</script>
+ENDJS
+        } else {
+            $result.=<<"ADDMETA";
 <meta http-equiv="Refresh" content="$time; url=$url" />
 ADDMETA
+        }
     } else {
         unless (($args->{'frameset'}) || ($args->{'js_ready'}) || ($args->{'only_body'}) || ($args->{'no_nav_bar'})) {
             my $requrl = $env{'request.uri'};
@@ -8430,43 +9533,99 @@ ADDMETA
                 my $dom_in_use = $Apache::lonnet::perlvar{'lonDefDomain'};
                 unless (&Apache::lonnet::allowed('mau',$dom_in_use)) {
                     my %domdefs = &Apache::lonnet::get_domain_defaults($dom_in_use);
+                    my $lonhost = $Apache::lonnet::perlvar{'lonHostID'};
+                    my ($offload,$offloadoth);
                     if (ref($domdefs{'offloadnow'}) eq 'HASH') {
-                        my $lonhost = $Apache::lonnet::perlvar{'lonHostID'};
                         if ($domdefs{'offloadnow'}{$lonhost}) {
-                            my $newserver = &Apache::lonnet::spareserver(30000,undef,1,$dom_in_use);
-                            if (($newserver) && ($newserver ne $lonhost)) {
-                                my $numsec = 5;
-                                my $timeout = $numsec * 1000;
-                                my ($newurl,$locknum,%locks,$msg);
-                                if ($env{'request.role.adv'}) {
-                                    ($locknum,%locks) = &Apache::lonnet::get_locks();
+                            $offload = 1;
+                            if (($env{'user.domain'} ne '') && ($env{'user.domain'} ne $dom_in_use) &&
+                                (!(($env{'user.name'} eq 'public') && ($env{'user.domain'} eq 'public')))) {
+                                unless (&Apache::lonnet::shared_institution($env{'user.domain'})) {
+                                    $offloadoth = 1;
+                                    $dom_in_use = $env{'user.domain'};
                                 }
-                                my $disable_submit = 0;
-                                if ($requrl =~ /$LONCAPA::assess_re/) {
-                                    $disable_submit = 1;
+                            }
+                        }
+                    }
+                    unless ($offload) {
+                        if (ref($domdefs{'offloadoth'}) eq 'HASH') {
+                            if ($domdefs{'offloadoth'}{$lonhost}) {
+                                if (($env{'user.domain'} ne '') && ($env{'user.domain'} ne $dom_in_use) &&
+                                    (!(($env{'user.name'} eq 'public') && ($env{'user.domain'} eq 'public')))) {
+                                    unless (&Apache::lonnet::shared_institution($env{'user.domain'})) {
+                                        $offload = 1;
+                                        $offloadoth = 1;
+                                        $dom_in_use = $env{'user.domain'};
+                                    }
                                 }
-                                if ($locknum) {
-                                    my @lockinfo = sort(values(%locks));
-                                    $msg = &mt('Once the following tasks are complete: ')."\\n".
-                                           join(", ",sort(values(%locks)))."\\n".
-                                           &mt('your session will be transferred to a different server, after you click "Roles".');
+                            }
+                        }
+                    }
+                    if ($offload) {
+                        my $newserver = &Apache::lonnet::spareserver(undef,30000,undef,1,$dom_in_use);
+                        if (($newserver eq '') && ($offloadoth)) {
+                            my @domains = &Apache::lonnet::current_machine_domains();
+                            if (($dom_in_use ne '') && (!grep(/^\Q$dom_in_use\E$/,@domains))) { 
+                                ($newserver) = &Apache::lonnet::choose_server($dom_in_use);
+                            }
+                        }
+                        if (($newserver) && ($newserver ne $lonhost)) {
+                            my $numsec = 5;
+                            my $timeout = $numsec * 1000;
+                            my ($newurl,$locknum,%locks,$msg);
+                            if ($env{'request.role.adv'}) {
+                                ($locknum,%locks) = &Apache::lonnet::get_locks();
+                            }
+                            my $disable_submit = 0;
+                            if ($requrl =~ /$LONCAPA::assess_re/) {
+                                $disable_submit = 1;
+                            }
+                            if ($locknum) {
+                                my @lockinfo = sort(values(%locks));
+                                $msg = &mt('Once the following tasks are complete:')." \n".
+                                       join(", ",sort(values(%locks)))."\n";
+                                if (&show_course()) {
+                                    $msg .= &mt('your session will be transferred to a different server, after you click "Courses".');
                                 } else {
-                                    if (($requrl =~ m{^/res/}) && ($env{'form.submitted'} =~ /^part_/)) {
-                                        $msg = &mt('Your LON-CAPA submission has been recorded')."\\n";
-                                    }
-                                    $msg .= &mt('Your current LON-CAPA session will be transferred to a different server in [quant,_1,second].',$numsec);
-                                    $newurl = '/adm/switchserver?otherserver='.$newserver;
-                                    if (($env{'request.role'}) && ($env{'request.role'} ne 'cm')) {
-                                        $newurl .= '&role='.$env{'request.role'};
+                                    $msg .= &mt('your session will be transferred to a different server, after you click "Roles".');
+                                }
+                            } else {
+                                if (($requrl =~ m{^/res/}) && ($env{'form.submitted'} =~ /^part_/)) {
+                                    $msg = &mt('Your LON-CAPA submission has been recorded')."\n";
+                                }
+                                $msg .= &mt('Your current LON-CAPA session will be transferred to a different server in [quant,_1,second].',$numsec);
+                                $newurl = '/adm/switchserver?otherserver='.$newserver;
+                                if (($env{'request.role'}) && ($env{'request.role'} ne 'cm')) {
+                                    $newurl .= '&role='.$env{'request.role'};
+                                }
+                                if ($env{'request.symb'}) {
+                                    my $shownsymb = &Apache::lonenc::check_encrypt($env{'request.symb'});
+                                    if ($shownsymb =~ m{^/enc/}) {
+                                        my $reqdmajor = 2;
+                                        my $reqdminor = 11;
+                                        my $reqdsubminor = 3;
+                                        my $newserverrev = &Apache::lonnet::get_server_loncaparev('',$newserver);
+                                        my $remoterev = &Apache::lonnet::get_server_loncaparev(undef,$newserver);
+                                        my ($major,$minor,$subminor) = ($remoterev =~ /^\'?(\d+)\.(\d+)\.(\d+|)[\w.\-]+\'?$/);
+                                        if (($major eq '' && $minor eq '') ||
+                                            (($reqdmajor > $major) || (($reqdmajor == $major) && ($reqdminor > $minor)) ||
+                                            (($reqdmajor == $major) && ($reqdminor == $minor) && (($subminor eq '') ||
+                                             ($reqdsubminor > $subminor))))) {
+                                            undef($shownsymb);
+                                        }
                                     }
-                                    if ($env{'request.symb'}) {
-                                        $newurl .= '&symb='.$env{'request.symb'};
-                                    } else {
-                                        $newurl .= '&origurl='.$requrl;
+                                    if ($shownsymb) {
+                                        &js_escape(\$shownsymb);
+                                        $newurl .= '&symb='.$shownsymb;
                                     }
+                                } else {
+                                    my $shownurl = &Apache::lonenc::check_encrypt($requrl);
+                                    &js_escape(\$shownurl);
+                                    $newurl .= '&origurl='.$shownurl;
                                 }
-                                &js_escape(\$msg);
-                                $result.=<<OFFLOAD
+                            }
+                            &js_escape(\$msg);
+                            $result.=<<OFFLOAD
 <meta http-equiv="pragma" content="no-cache" />
 <script type="text/javascript">
 // <![CDATA[
@@ -8487,7 +9646,6 @@ function LC_Offload_Now() {
 // ]]>
 </script>
 OFFLOAD
-                            }
                         }
                     }
                 }
@@ -8498,8 +9656,12 @@ OFFLOAD
 	$title = 'The LearningOnline Network with CAPA';
     }
     if (!$args->{'no_auto_mt_title'}) { $title = &mt($title); }
-    $result .= '<title> LON-CAPA '.$title.'</title>'
-	.'<link rel="stylesheet" type="text/css" href="'.$url.'"';
+    if ($title =~ /^LON-CAPA\s+/) {
+        $result .= '<title> '.$title.'</title>';
+    } else {
+        $result .= '<title> LON-CAPA '.$title.'</title>';
+    }
+    $result .= "\n".'<link rel="stylesheet" type="text/css" href="'.$url.'"';
     if (!$args->{'frameset'}) {
         $result .= ' /';
     }
@@ -8514,7 +9676,7 @@ OFFLOAD
     }
     if ($clientmobile) {
         $result .= '
-<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
 <meta name="apple-mobile-web-app-capable" content="yes" />';
     }
     $result .= '<meta name="google" content="notranslate" />'."\n";
@@ -8586,7 +9748,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 '') {
@@ -8697,6 +9860,11 @@ $args - additional optional args support
              no_auto_mt_title -> prevent &mt()ing the title arg
              bread_crumbs ->             Array containing breadcrumbs
              bread_crumbs_component ->  if exists show it as headline else show only the breadcrumbs
+             bread_crumbs_style -> breadcrumbs are contained within <div id="LC_breadcrumbs">,
+                                   and &standard_css() contains CSS for #LC_breadcrumbs, if you want
+                                   to override those values, or add to them, specify the value to
+                                   include in the style attribute to include in the div tag by using
+                                   bread_crumbs_style (e.g., overflow: visible)
              bread_crumbs_nomenu -> if true will pass false as the value of $menulink
                                     to lonhtmlcommon::breadcrumbs
              group          -> includes the current group, if page is for a 
@@ -8705,6 +9873,10 @@ $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).
+             links_target   -> Target for links, e.g., _parent (optional).
 
 =back
 
@@ -8717,7 +9889,7 @@ sub start_page {
     #&Apache::lonnet::logthis("start_page ".join(':',caller(0)));
 
     $env{'internal.start_page'}++;
-    my ($result,@advtools,$ltiscope,$ltiuri,%ltimenu);
+    my ($result,@advtools,$ltiscope,$ltiuri,%ltimenu,$menucoll,%menu);
 
     if (! exists($args->{'skip_phases'}{'head'}) ) {
         $result .= &xml_begin($args->{'frameset'}) . &headtag($title, $head_extra, $args);
@@ -8752,8 +9924,48 @@ sub start_page {
         ($ltiscope,$ltiuri) = &LONCAPA::ltiutils::lti_provider_scope($env{'request.lti.uri'},
                                   $env{'course.'.$env{'request.course.id'}.'.domain'},
                                   $env{'course.'.$env{'request.course.id'}.'.num'});
+    } elsif ($env{'request.course.id'}) {
+        my $expiretime=600;
+        if ((time-$env{'course.'.$env{'request.course.id'}.'.last_cache'}) > $expiretime) {
+            &Apache::lonnet::coursedescription($env{'request.course.id'},{'freshen_cache' => 1});
+        }
+        my ($deeplinkmenu,$menuref);
+        ($menucoll,$deeplinkmenu,$menuref) = &menucoll_in_effect();
+        if ($menucoll) {
+            if (ref($menuref) eq 'HASH') {
+                %menu = %{$menuref};
+            }
+            if ($menu{'top'} eq 'n') {
+                $args->{'no_primary_menu'} = 1;
+            }
+            if ($menu{'inline'} eq 'n') {
+                unless (&Apache::lonnet::allowed('opa')) {
+                    my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+                    my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
+                    my $crstype = &course_type();
+                    my $now = time;
+                    my $ccrole;
+                    if ($crstype eq 'Community') {
+                        $ccrole = 'co';
+                    } else {
+                        $ccrole = 'cc';
+                    }
+                    if ($env{'user.role.'.$ccrole.'./'.$cdom.'/'.$cnum}) {
+                        my ($start,$end) = split(/\./,$env{'user.role.'.$ccrole.'./'.$cdom.'/'.$cnum});
+                        if ((($start) && ($start<0)) ||
+                            (($end) && ($end<$now))  ||
+                            (($start) && ($now<$start))) {
+                            $args->{'no_inline_menu'} = 1;
+                        }
+                    } else {
+                        $args->{'no_inline_menu'} = 1;
+                    }
+                }
+            }
+        }
     }
-    
+
+    my $showncrumbs;
     if (! exists($args->{'skip_phases'}{'body'}) ) {
 	if ($args->{'frameset'}) {
 	    my $attr_string = &make_attr_string($args->{'force_register'},
@@ -8766,7 +9978,8 @@ sub start_page {
                          $args->{'only_body'},      $args->{'domain'},
                          $args->{'force_register'}, $args->{'no_nav_bar'},
                          $args->{'bgcolor'},        $args,
-                         \@advtools,$ltiscope,$ltiuri,\%ltimenu);
+                         \@advtools,$ltiscope,$ltiuri,\%ltimenu,$menucoll,
+                         \%menu,\$showncrumbs);
         }
     }
 
@@ -8788,6 +10001,7 @@ sub start_page {
 
     #Breadcrumbs
     if (exists($args->{'bread_crumbs'}) or exists($args->{'bread_crumbs_component'})) {
+        unless ($showncrumbs) {
 		&Apache::lonhtmlcommon::clear_breadcrumbs();
 		#if any br links exists, add them to the breadcrumbs
 		if (exists($args->{'bread_crumbs'}) and ref($args->{'bread_crumbs'}) eq 'ARRAY') {         
@@ -8810,12 +10024,23 @@ sub start_page {
                 } else {
                     undef($menulink);
                 }
+                my $linkprotout;
+                if ($env{'request.deeplink.login'}) {
+                    my $linkprotout = &Apache::lonmenu::linkprot_exit();
+                    if ($linkprotout) {
+                        &Apache::lonhtmlcommon::add_breadcrumb_tool('tools',$linkprotout);
+                    }
+                }
 		#if bread_crumbs_component exists show it as headline else show only the breadcrumbs
 		if(exists($args->{'bread_crumbs_component'})){
-			$result .= &Apache::lonhtmlcommon::breadcrumbs($args->{'bread_crumbs_component'},'',$menulink);
+			$result .= &Apache::lonhtmlcommon::breadcrumbs($args->{'bread_crumbs_component'},
+                                                                       '',$menulink,'',
+                                                                       $args->{'bread_crumbs_style'});
                 } else {
-			$result .= &Apache::lonhtmlcommon::breadcrumbs('','',$menulink);
+			$result .= &Apache::lonhtmlcommon::breadcrumbs('','',$menulink,'',
+                                                                       $args->{'bread_crumbs_style'});
 		}
+        }
     }
     return $result;
 }
@@ -8852,6 +10077,147 @@ sub end_page {
     return $result;
 }
 
+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'}) {
+            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 ($state,$others,$listed,$scope,$protect,$display,$target) = split(/,/,$deeplink);
+                if ($display =~ /^\d+$/) {
+                    $deeplinkmenu = 1;
+                    $menucoll = $display;
+                }
+            }
+        }
+        if ($menucoll) {
+            %menu = &page_menu($env{'course.'.$env{'request.course.id'}.'.menucollections'},$menucoll);
+        }
+    }
+    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 usable_exttools {
+    my %tooltypes;
+    if ($env{'request.course.id'}) {
+        if ($env{'course.'.$env{'request.course.id'}.'.internal.exttool'}) {
+           if ($env{'course.'.$env{'request.course.id'}.'.internal.exttool'} eq 'both') {
+               %tooltypes = (
+                             crs => 1,
+                             dom => 1,
+                            );
+           } elsif ($env{'course.'.$env{'request.course.id'}.'.internal.exttool'} eq 'crs') {
+               $tooltypes{'crs'} = 1;
+           } elsif ($env{'course.'.$env{'request.course.id'}.'.internal.exttool'} eq 'dom') {
+               $tooltypes{'dom'} = 1;
+           }
+        } else {
+            my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+            my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
+            my $crstype = lc($env{'course.'.$env{'request.course.id'}.'.type'});
+            if ($crstype eq '') {
+                $crstype = 'course';
+            }
+            if ($crstype eq 'course') {
+                if ($env{'course.'.$env{'request.course.id'}.'internal.coursecode'}) {
+                    $crstype = 'official';
+                } elsif ($env{'course.'.$env{'request.course.id'}.'.internal.textbook'}) {
+                    $crstype = 'textbook';
+                } elsif ($env{'course.'.$env{'request.course.id'}.'.internal.lti'}) {
+                    $crstype = 'lti';
+                } else {
+                    $crstype = 'unofficial';
+                }
+            }
+            my %domdefaults = &Apache::lonnet::get_domain_defaults($cdom);
+            if ($domdefaults{$crstype.'domexttool'}) {
+                $tooltypes{'dom'} = 1;
+            }
+            if ($domdefaults{$crstype.'exttool'}) {
+                $tooltypes{'crs'} = 1;
+            }
+        }
+    }
+    return %tooltypes;
+}
+
 sub wishlist_window {
     return(<<'ENDWISHLIST');
 <script type="text/javascript">
@@ -8936,13 +10302,20 @@ sub modal_link {
         $target_attr = 'target="'.$target.'"';
     }
     return <<"ENDLINK";
-<a href="$link" $target_attr title="$title" onclick="javascript:openMyModal('$link',$width,$height,'$scrolling','$transparency','$style'); return false;">
-           $linktext</a>
+<a href="$link" $target_attr title="$title" onclick="javascript:openMyModal('$link',$width,$height,'$scrolling','$transparency','$style'); return false;">$linktext</a>
 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[
@@ -8953,6 +10326,7 @@ sub modal_adhoc_script {
                 modalWindow.height = $height;
                 modalWindow.content = '$content';
                 modalWindow.open();
+                $mathjax
         };  
 // ]]>
 </script>
@@ -8960,7 +10334,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'}).
@@ -8969,12 +10343,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>";
 }
 
@@ -9532,6 +10906,16 @@ Scalar: 1 if 'Course' to be used, 0 othe
 
 ###############################################
 sub show_course {
+    my ($udom,$uname) = @_;
+    if (($udom ne '') && ($uname ne '')) {
+        if (($udom ne $env{'user.domain'}) || ($uname ne $env{'user.name'})) {
+            if (&Apache::lonnet::is_advanced_user($udom,$uname)) {
+                return 0;
+            } else {
+                return 1;
+            }
+        }
+    }
     my $course = !$env{'user.adv'};
     if (!$env{'user.adv'}) {
         foreach my $env (keys(%env)) {
@@ -10849,11 +12233,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'});
@@ -10864,8 +12252,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;
@@ -10873,15 +12261,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];
                 }
             }
         }
@@ -13489,7 +14890,9 @@ sub process_extracted_files {
                             my $url = '/uploaded/'.$docudom.'/'.$docuname.'/'.
                                       $docstype.'/'.$mapinner{$outer}.'/'.$newidx.'/'.
                                       $title;
-                            if (($outer !~ /\D/) && ($mapinner{$outer} !~ /\D/) && ($newidx !~ /\D/)) {
+                            if (($outer !~ /\D/) &&
+                                (($mapinner{$outer} eq 'default') || ($mapinner{$outer} !~ /\D/)) &&
+                                ($newidx !~ /\D/)) {
                                 if (!-e "$prefix$dir/$docstype/$mapinner{$outer}") {
                                     mkdir("$prefix$dir/$docstype/$mapinner{$outer}",0755);
                                 }
@@ -13937,7 +15340,7 @@ sub load_tmp_file {
 
 sub valid_datatoken {
     my ($datatoken) = @_;
-    if ($datatoken =~ /^$match_username\_$match_domain\_enroll_$match_domain\_$match_courseid\_\d+_\d+$/) {
+    if ($datatoken =~ /^$match_username\_$match_domain\_enroll_(|$match_domain\_$match_courseid)\_\d+_\d+$/) {
         return $datatoken;
     }
     return;
@@ -15093,6 +16496,8 @@ Inputs:
 
 from -              Sender's email address
 
+replyto -           Reply-To email address
+
 to -                Email address of recipient
 
 subject -           Subject of email
@@ -15103,8 +16508,6 @@ cc_string -         Carbon copy email ad
 
 bcc -               Blind carbon copy email address
 
-type -              File type of attachment
-
 attachment_path -   Path of file to be attached
 
 file_name -         Name of file to be attached
@@ -15121,8 +16524,9 @@ attachment_text -   The body of an attac
 ############################################################
 
 sub mime_email {
-    my ($from, $to, $subject, $body, $cc_string, $bcc, $attachment_path, 
-        $file_name, $attachment_text) = @_;
+    my ($from,$replyto,$to,$subject,$body,$cc_string,$bcc,$attachment_path, 
+        $file_name,$attachment_text) = @_;
+ 
     my $msg = MIME::Lite->new(
              From    => $from,
              To      => $to,
@@ -15130,6 +16534,9 @@ sub mime_email {
              Type    =>'TEXT',
              Data    => $body,
              );
+    if ($replyto ne '') {
+        $msg->add("Reply-To" => $replyto);
+    }
     if ($cc_string ne '') {
         $msg->add("Cc" => $cc_string);
     }
@@ -15540,27 +16947,33 @@ sub assign_category_rows {
 
 
 sub commit_customrole {
-    my ($udom,$uname,$url,$three,$four,$five,$start,$end,$context) = @_;
+    my ($udom,$uname,$url,$three,$four,$five,$start,$end,$context,$othdomby,$requester) = @_;
+    my $result = &Apache::lonnet::assigncustomrole(
+                     $udom,$uname,$url,$three,$four,$five,$end,$start,undef,undef,
+                     $context,$othdomby,$requester);
     my $output = &mt('Assigning custom role').' "'.$five.'" by '.$four.':'.$three.' in '.$url.
                          ($start?', '.&mt('starting').' '.localtime($start):'').
-                         ($end?', ending '.localtime($end):'').': <b>'.
-              &Apache::lonnet::assigncustomrole(
-                 $udom,$uname,$url,$three,$four,$five,$end,$start,undef,undef,$context).
-                 '</b><br />';
-    return $output;
+                         ($end?', ending '.localtime($end):'').': <b>'.$result.'</b><br />';
+    if (wantarray) {
+        return ($output,$result);
+    } else {
+        return $output;
+    }
 }
 
 sub commit_standardrole {
-    my ($udom,$uname,$url,$three,$start,$end,$one,$two,$sec,$context,$credits) = @_;
-    my ($output,$logmsg,$linefeed);
+    my ($udom,$uname,$url,$three,$start,$end,$one,$two,$sec,$context,$credits,
+        $othdomby,$requester) = @_;
+    my ($output,$logmsg,$linefeed,$result);
     if ($context eq 'auto') {
         $linefeed = "\n";
     } else {
         $linefeed = "<br />\n";
     }  
     if ($three eq 'st') {
-        my $result = &commit_studentrole(\$logmsg,$udom,$uname,$url,$three,$start,$end,
-                                         $one,$two,$sec,$context,$credits);
+        $result = &commit_studentrole(\$logmsg,$udom,$uname,$url,$three,$start,$end,
+                                      $one,$two,$sec,$context,$credits,$othdomby,
+                                      $requester);
         if (($result =~ /^error/) || ($result eq 'not_in_class') || 
             ($result eq 'unknown_course') || ($result eq 'refused')) {
             $output = $logmsg.' '.&mt('Error: ').$result."\n"; 
@@ -15580,19 +16993,24 @@ sub commit_standardrole {
         $output = &mt('Assigning').' '.$three.' in '.$url.
                ($start?', '.&mt('starting').' '.localtime($start):'').
                ($end?', '.&mt('ending').' '.localtime($end):'').': ';
-        my $result = &Apache::lonnet::assignrole($udom,$uname,$url,$three,$end,$start,'','',$context);
+        $result = &Apache::lonnet::assignrole($udom,$uname,$url,$three,$end,$start,
+                                              '','',$context,$othdomby,$requester);
         if ($context eq 'auto') {
             $output .= $result.$linefeed;
         } else {
             $output .= '<b>'.$result.'</b>'.$linefeed;
         }
     }
-    return $output;
+    if (wantarray) {
+        return ($output,$result);
+    } else {
+        return $output;
+    }
 }
 
 sub commit_studentrole {
     my ($logmsg,$udom,$uname,$url,$three,$start,$end,$one,$two,$sec,$context,
-        $credits) = @_;
+        $credits,$othdomby,$requester) = @_;
     my ($result,$linefeed,$oldsecurl,$newsecurl);
     if ($context eq 'auto') {
         $linefeed = "\n";
@@ -15616,8 +17034,9 @@ sub commit_studentrole {
                 }
                 $oldsecurl = $uurl;
                 $expire_role_result = 
-                    &Apache::lonnet::assignrole($udom,$uname,$uurl,'st',$now,'','',$context);
-                if ($env{'request.course.sec'} ne '') { 
+                    &Apache::lonnet::assignrole($udom,$uname,$uurl,'st',$now,
+                                                '','','',$context,$othdomby,$requester);
+                if ($env{'request.course.sec'} ne '') {
                     if ($expire_role_result eq 'refused') {
                         my @roles = ('st');
                         my @statuses = ('previous');
@@ -15643,7 +17062,8 @@ sub commit_studentrole {
                 &Apache::lonnet::modify_student_enrollment($udom,$uname,undef,undef,
                                                            undef,undef,undef,$sec,
                                                            $end,$start,'','',$cid,
-                                                           '',$context,$credits);
+                                                           '',$context,$credits,'',
+                                                           $othdomby,$requester);
             if ($modify_section_result =~ /^ok/) {
                 if ($secchange == 1) {
                     if ($sec eq '') {
@@ -15728,7 +17148,8 @@ sub check_clone {
     my $cloneid='/'.$args->{'clonedomain'}.'/'.$args->{'clonecourse'};
     my ($clonecrsudom,$clonecrsunum)= &LONCAPA::split_courseid($cloneid);
     my $clonehome=&Apache::lonnet::homeserver($clonecrsunum,$clonecrsudom);
-    my $clonemsg;
+    my $clonetitle;
+    my @clonemsg;
     my $can_clone = 0;
     my $lctype = lc($args->{'crstype'});
     if ($lctype ne 'community') {
@@ -15736,16 +17157,38 @@ sub check_clone {
     }
     if ($clonehome eq 'no_host') {
         if ($args->{'crstype'} eq 'Community') {
-            $clonemsg = &mt('No new community created.').$linefeed.&mt('A new community could not be cloned from the specified original - [_1] - because it is a non-existent community.',$args->{'clonecourse'}.':'.$args->{'clonedomain'});
+            push(@clonemsg,({
+                              mt => 'No new community created.',
+                              args => [],
+                            },
+                            {
+                              mt => 'A new community could not be cloned from the specified original - [_1] - because it is a non-existent community.',
+                              args => [$args->{'clonedomain'}.':'.$args->{'clonedomain'}],
+                            }));
         } else {
-            $clonemsg = &mt('No new course created.').$linefeed.&mt('A new course could not be cloned from the specified original - [_1] - because it is a non-existent course.',$args->{'clonecourse'}.':'.$args->{'clonedomain'});
-        }     
+            push(@clonemsg,({
+                              mt => 'No new course created.',
+                              args => [],
+                            },
+                            {
+                              mt => 'A new course could not be cloned from the specified original - [_1] - because it is a non-existent course.',
+                              args => [$args->{'clonecourse'}.':'.$args->{'clonedomain'}],
+                            }));
+        }
     } else {
 	my %clonedesc = &Apache::lonnet::coursedescription($cloneid,{'one_time' => 1});
+        $clonetitle = $clonedesc{'description'};
         if ($args->{'crstype'} eq 'Community') {
             if ($clonedesc{'type'} ne 'Community') {
-                $clonemsg = &mt('No new community created.').$linefeed.&mt('A new community could not be cloned from the specified original - [_1] - because it is a course not a community.',$args->{'clonecourse'}.':'.$args->{'clonedomain'});
-                return ($can_clone, $clonemsg, $cloneid, $clonehome);
+                push(@clonemsg,({
+                                  mt => 'No new community created.',
+                                  args => [],
+                                },
+                                {
+                                  mt => 'A new community could not be cloned from the specified original - [_1] - because it is a course not a community.',
+                                  args => [$args->{'clonecourse'}.':'.$args->{'clonedomain'}],
+                                }));
+                return ($can_clone,\@clonemsg,$cloneid,$clonehome);
             }
         }
 	if (($env{'request.role.domain'} eq $args->{'clonedomain'}) &&
@@ -15834,20 +17277,34 @@ sub check_clone {
             }
             unless ($can_clone) {
                 if ($args->{'crstype'} eq 'Community') {
-                    $clonemsg = &mt('No new community created.').$linefeed.&mt('The new community could not be cloned from the existing community because the new community owner ([_1]) does not have cloning rights in the existing community ([_2]).',$args->{'ccuname'}.':'.$args->{'ccdomain'},$clonedesc{'description'});
+                    push(@clonemsg,({
+                                      mt => 'No new community created.',
+                                      args => [],
+                                    },
+                                    {
+                                      mt => 'The new community could not be cloned from the existing community because the new community owner ([_1]) does not have cloning rights in the existing community ([_2]).',
+                                      args => [$args->{'ccuname'}.':'.$args->{'ccdomain'},$clonedesc{'description'}],
+                                    }));
                 } else {
-                    $clonemsg = &mt('No new course created.').$linefeed.&mt('The new course could not be cloned from the existing course because the new course owner ([_1]) does not have cloning rights in the existing course ([_2]).',$args->{'ccuname'}.':'.$args->{'ccdomain'},$clonedesc{'description'});
+                    push(@clonemsg,({
+                                      mt => 'No new course created.',
+                                      args => [],
+                                    },
+                                    {
+                                      mt => 'The new course could not be cloned from the existing course because the new course owner ([_1]) does not have cloning rights in the existing course ([_2]).',
+                                      args => [$args->{'ccuname'}.':'.$args->{'ccdomain'},$clonedesc{'description'}],
+                                    }));
                 }
 	    }
         }
     }
-    return ($can_clone, $clonemsg, $cloneid, $clonehome);
+    return ($can_clone,\@clonemsg,$cloneid,$clonehome,$clonetitle);
 }
 
 sub construct_course {
     my ($args,$logmsg,$courseid,$crsudom,$crsunum,$udom,$uname,$context,
-        $cnum,$category,$coderef) = @_;
-    my $outcome;
+        $cnum,$category,$coderef,$callercontext,$user_lh) = @_;
+    my ($outcome,$msgref,$clonemsgref);
     my $linefeed =  '<br />'."\n";
     if ($context eq 'auto') {
         $linefeed = "\n";
@@ -15856,18 +17313,11 @@ sub construct_course {
 #
 # Are we cloning?
 #
-    my ($can_clone, $clonemsg, $cloneid, $clonehome);
+    my ($can_clone,$cloneid,$clonehome,$clonetitle);
     if (($args->{'clonecourse'}) && ($args->{'clonedomain'})) {
-	($can_clone, $clonemsg, $cloneid, $clonehome) = &check_clone($args,$linefeed);
-	if ($context ne 'auto') {
-            if ($clonemsg ne '') {
-	        $clonemsg = '<span class="LC_error">'.$clonemsg.'</span>';
-            }
-	}
-	$outcome .= $clonemsg.$linefeed;
-
+	($can_clone,$clonemsgref,$cloneid,$clonehome,$clonetitle) = &check_clone($args,$linefeed);
         if (!$can_clone) {
-	    return (0,$outcome);
+	    return (0,$outcome,$clonemsgref);
 	}
     }
 
@@ -15890,15 +17340,20 @@ sub construct_course {
                                              $args->{'ccuname'}.':'.
                                              $args->{'ccdomain'},
                                              $args->{'crstype'},
-                                             $cnum,$context,$category);
+                                             $cnum,$context,$category,
+                                             $callercontext);
 
     # Note: The testing routines depend on this being output; see 
     # Utils::Course. This needs to at least be output as a comment
     # if anyone ever decides to not show this, and Utils::Course::new
     # will need to be suitably modified.
-    $outcome .= &mt('New LON-CAPA [_1] ID: [_2]',$showncrstype,$$courseid).$linefeed;
+    if (($callercontext eq 'auto') && ($user_lh ne '')) {
+        $outcome .= &mt_user($user_lh,'New LON-CAPA [_1] ID: [_2]',$showncrstype,$$courseid).$linefeed;
+    } else {
+        $outcome .= &mt('New LON-CAPA [_1] ID: [_2]',$showncrstype,$$courseid).$linefeed;
+    }
     if ($$courseid =~ /^error:/) {
-        return (0,$outcome);
+        return (0,$outcome,$clonemsgref);
     }
 
 #
@@ -15907,23 +17362,37 @@ sub construct_course {
     ($$crsudom,$$crsunum)= &LONCAPA::split_courseid($$courseid);
     my $crsuhome=&Apache::lonnet::homeserver($$crsunum,$$crsudom);
     if ($crsuhome eq 'no_host') {
-        $outcome .= &mt('Course creation failed, unrecognized course home server.').$linefeed;
-        return (0,$outcome);
+        if (($callercontext eq 'auto') && ($user_lh ne '')) {
+            $outcome .= &mt_user($user_lh,
+                            'Course creation failed, unrecognized course home server.');
+        } else {
+            $outcome .= &mt('Course creation failed, unrecognized course home server.');
+        }
+        $outcome .= $linefeed;
+        return (0,$outcome,$clonemsgref);
     }
     $outcome .= &mt('Created on').': '.$crsuhome.$linefeed;
 
 #
 # Do the cloning
 #   
+    my @clonemsg;
     if ($can_clone && $cloneid) {
-	$clonemsg = &mt('Cloning [_1] from [_2]',$showncrstype,$clonehome);
-	if ($context ne 'auto') {
-	    $clonemsg = '<span class="LC_success">'.$clonemsg.'</span>';
-	}
-	$outcome .= $clonemsg.$linefeed;
+        push(@clonemsg,
+                      {
+                          mt => 'Created [_1] by cloning from [_2]',
+                          args => [$showncrstype,$clonetitle],
+                      });
 	my %oldcenv=&Apache::lonnet::dump('environment',$$crsudom,$$crsunum);
 # Copy all files
-	&Apache::lonclonecourse::copycoursefiles($cloneid,$$courseid,$args->{'datemode'},$args->{'dateshift'});
+        my @info =
+	    &Apache::lonclonecourse::copycoursefiles($cloneid,$$courseid,$args->{'datemode'},
+	                                             $args->{'dateshift'},$args->{'crscode'},
+                                                     $args->{'ccuname'}.':'.$args->{'ccdomain'},
+                                                     $args->{'tinyurls'});
+        if (@info) {
+            push(@clonemsg,@info);
+        }
 # Restore URL
 	$cenv{'url'}=$oldcenv{'url'};
 # Restore title
@@ -15948,8 +17417,7 @@ sub construct_course {
                    'plc.users.denied',
                    'hidefromcat',
                    'checkforpriv',
-                   'categories',
-                   'internal.uniquecode'],
+                   'categories'],
                    $$crsudom,$$crsunum);
         if ($args->{'textbook'}) {
             $cenv{'internal.textbook'} = $args->{'textbook'};
@@ -15964,6 +17432,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'};
     }
@@ -15985,6 +17456,7 @@ sub construct_course {
         $cenv{'internal.defaultcredits'} = $args->{'defaultcredits'};
     }
     my @badclasses = (); # Used to accumulate sections/crosslistings that did not pass classlist access check for course owner.
+    my @oklcsecs = (); # Used to accumulate LON-CAPA sections for validated institutional sections.
     if ($args->{'crssections'}) {
         $cenv{'internal.sectionnums'} = '';
         if ($args->{'crssections'} =~ m/,/) {
@@ -15998,7 +17470,11 @@ sub construct_course {
                 my $class = $args->{'crscode'}.$sec;
                 my $addcheck = &Apache::lonnet::auto_new_course($$crsunum,$$crsudom,$class,$cenv{'internal.courseowner'});
                 $cenv{'internal.sectionnums'} .= $item.',';
-                unless ($addcheck eq 'ok') {
+                if ($addcheck eq 'ok') {
+                    unless (grep(/^\Q$gp\E$/,@oklcsecs)) {
+                        push(@oklcsecs,$gp);
+                    }
+                } else {
                     push(@badclasses,$class);
                 }
             }
@@ -16026,7 +17502,11 @@ sub construct_course {
                 my ($xl,$gp) = split/:/,$item;
                 my $addcheck =  &Apache::lonnet::auto_new_course($$crsunum,$$crsudom,$xl,$cenv{'internal.courseowner'});
                 $cenv{'internal.crosslistings'} .= $item.',';
-                unless ($addcheck eq 'ok') {
+                if ($addcheck eq 'ok') {
+                    unless (grep(/^\Q$gp\E$/,@oklcsecs)) {
+                        push(@oklcsecs,$gp);
+                    }
+                } else {
                     push(@badclasses,$xl);
                 }
             }
@@ -16089,6 +17569,36 @@ sub construct_course {
     if ($args->{'no_end_date'}) {
         $args->{'endaccess'} = 0;
     }
+#  If an official course with institutional sections is created by cloning 
+#  an existing course, section-specific hiding of course totals in student's
+#  view of grades as copied from cloned course, will be checked for valid 
+#  sections.
+    if (($can_clone && $cloneid) &&
+        ($cenv{'internal.coursecode'} ne '') &&
+        ($cenv{'grading'} eq 'standard') &&
+        ($cenv{'hidetotals'} ne '') &&
+        ($cenv{'hidetotals'} ne 'all')) {
+        my @hidesecs;
+        my $deletehidetotals;
+        if (@oklcsecs) {
+            foreach my $sec (split(/,/,$cenv{'hidetotals'})) {
+                if (grep(/^\Q$sec$/,@oklcsecs)) {
+                    push(@hidesecs,$sec);
+                }
+            }
+            if (@hidesecs) {
+                $cenv{'hidetotals'} = join(',',@hidesecs);
+            } else {
+                $deletehidetotals = 1;
+            }
+        } else {
+            $deletehidetotals = 1;
+        }
+        if ($deletehidetotals) {
+            delete($cenv{'hidetotals'});
+            &Apache::lonnet::del('environment',['hidetotals'],$$crsudom,$$crsunum);
+        }
+    }
     $cenv{'internal.autostart'}=$args->{'enrollstart'};
     $cenv{'internal.autoend'}=$args->{'enrollend'};
     $cenv{'default_enrollment_start_date'}=$args->{'startaccess'};
@@ -16190,19 +17700,23 @@ sub construct_course {
 # Open all assignments
 #
     if ($args->{'openall'}) {
+       my $opendate = time;
+       if ($args->{'openallfrom'} =~ /^\d+$/) {
+           $opendate = $args->{'openallfrom'};
+       }
        my $storeunder=$$crsudom.'_'.$$crsunum.'.0.opendate';
-       my %storecontent = ($storeunder         => time,
+       my %storecontent = ($storeunder         => $opendate,
                            $storeunder.'.type' => 'date_start');
-       
-       $outcome .= &mt('Opening all assignments').': '.&Apache::lonnet::cput
-                 ('resourcedata',\%storecontent,$$crsudom,$$crsunum).$linefeed;
+       $outcome .= &mt('All assignments open starting [_1]',
+                       &Apache::lonlocal::locallocaltime($opendate)).': '.
+                   &Apache::lonnet::cput
+                       ('resourcedata',\%storecontent,$$crsudom,$$crsunum).$linefeed;
    }
 #
 # Set first page
 #
     unless (($args->{'nonstandard'}) || ($args->{'firstres'} eq 'blank')
 	    || ($cloneid)) {
-	use LONCAPA::map;
 	$outcome .= &mt('Setting first resource').': ';
 
 	my $map = '/uploaded/'.$$crsudom.'/'.$$crsunum.'/default.sequence';
@@ -16249,7 +17763,7 @@ sub construct_course {
                  ('resourcedata',\%storecontent,$$crsudom,$$crsunum); 
     }
 
-    return (1,$outcome);
+    return (1,$outcome,\@clonemsg);
 }
 
 sub make_unique_code {
@@ -16445,7 +17959,8 @@ sub init_user_environment {
 
     my $public=($username eq 'public' && $domain eq 'public');
 
-    my ($filename,$cookie,$userroles,$firstaccenv,$timerintenv);
+    my ($filename,$cookie,$userroles,$firstaccenv,$timerintenv,
+        $coauthorenv);
     my $now=time;
 
     if ($public) {
@@ -16511,7 +18026,7 @@ sub init_user_environment {
     
 # Initialize roles
 
-	($userroles,$firstaccenv,$timerintenv) = 
+	($userroles,$firstaccenv,$timerintenv,$coauthorenv) = 
             &Apache::lonnet::rolesinit($domain,$username,$authhost);
     }
 # ------------------------------------ Check browser type and MathML capability
@@ -16538,6 +18053,7 @@ sub init_user_environment {
 # --------------------------------------------------------- Write first profile
 
     {
+        my $ip = &Apache::lonnet::get_requestor_ip($r);
 	my %initial_env = 
 	    ("user.name"          => $username,
 	     "user.domain"        => $domain,
@@ -16556,7 +18072,7 @@ sub init_user_environment {
 	     "request.course.sec" => '',
 	     "request.role"       => 'cm',
 	     "request.role.adv"   => $env{'user.adv'},
-	     "request.host"       => $ENV{'REMOTE_ADDR'},);
+	     "request.host"       => $ip,);
 
         if ($form->{'localpath'}) {
 	    $initial_env{"browser.localpath"}  = $form->{'localpath'};
@@ -16588,8 +18104,8 @@ sub init_user_environment {
             my %is_adv = ( is_adv => $env{'user.adv'} );
             my %domdef = &Apache::lonnet::get_domain_defaults($domain);
 
-            foreach my $tool ('aboutme','blog','webdav','portfolio') {
-                $userenv{'availabletools.'.$tool} = 
+            foreach my $tool ('aboutme','blog','webdav','portfolio','portaccess','timezone') {
+                $userenv{'availabletools.'.$tool} =
                     &Apache::lonnet::usertools_access($username,$domain,$tool,'reload',
                                                       undef,\%userenv,\%domdef,\%is_adv);
             }
@@ -16601,6 +18117,23 @@ sub init_user_environment {
                                                       \%userenv,\%domdef,\%is_adv);
             }
 
+            if ((ref($userroles) eq 'HASH') && ($userroles->{'user.author'}) &&
+                (exists($userroles->{"user.role.au./$domain/"}))) {
+                if ($userenv{'authoreditors'}) {
+                    $userenv{'editors'} = $userenv{'authoreditors'};
+                } elsif ($domdef{'editors'} ne '') {
+                    $userenv{'editors'} = $domdef{'editors'};
+                } else {
+                    $userenv{'editors'} = 'edit,xml';
+                }
+                if ($userenv{'authorarchive'}) {
+                    $userenv{'canarchive'} = 1;
+                } elsif (($userenv{'authorarchive'} eq '') &&
+                         ($domdef{'archive'})) {
+                    $userenv{'canarchive'} = 1;
+                }
+            }
+
             $userenv{'canrequest.author'} =
                 &Apache::lonnet::usertools_access($username,$domain,'requestauthor',
                                                   'reload','requestauthor',
@@ -16657,6 +18190,11 @@ sub init_user_environment {
             if (ref($timerintenv) eq 'HASH') {
                 &_add_to_env(\%disk_env,$timerintenv);
             }
+            if (ref($coauthorenv) eq 'HASH') {
+                if (keys(%{$coauthorenv})) {
+                    &_add_to_env(\%disk_env,$coauthorenv);
+                }
+            }
 	    if (ref($args->{'extra_env'})) {
 		&_add_to_env(\%disk_env,$args->{'extra_env'});
 	    }
@@ -17457,27 +18995,51 @@ 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 ();
         }
-        my $lastchange = &Apache::lonnet::get_coursechange($cdom,$cnum);
-        if ($lastchange > $env{'request.course.tied'}) {
-            my %curr_reqd_hash = &Apache::lonnet::userenvironment($cdom,$cnum,'internal.releaserequired');
-            if ($curr_reqd_hash{'internal.releaserequired'} ne '') {
-                my $required = $env{'course.'.$cdom.'_'.$cnum.'.internal.releaserequired'};
-                if ($curr_reqd_hash{'internal.releaserequired'} ne $required) {
-                    &Apache::lonnet::appenv({'course.'.$cdom.'_'.$cnum.'.internal.releaserequired' =>
-                                             $curr_reqd_hash{'internal.releaserequired'}});
-                    my ($switchserver,$switchwarning) =
-                        &check_release_required($loncaparev,$cdom.'_'.$cnum,$env{'request.role'},
-                                                $curr_reqd_hash{'internal.releaserequired'});
-                    if ($switchwarning ne '' || $switchserver ne '') {
-                        return ('switch',$switchwarning,$switchserver);
-                    }
+        my $update;
+        my $lastmainchange = &Apache::lonnet::get_coursechange($cdom,$cnum);
+        my $lastsuppchange = &Apache::lonnet::get_suppchange($cdom,$cnum);
+        if ($lastmainchange > $env{'request.course.tied'}) {
+            my ($needswitch,$switchwarning,$switchserver) = &switch_for_update($loncaparev,$cdom,$cnum);
+            if ($needswitch) {
+                return ('switch',$switchwarning,$switchserver);
+            }
+            $update = 'main';
+        }
+        if ($lastsuppchange > $env{'request.course.suppupdated'}) {
+            if ($update) {
+                $update = 'both';
+            } else {
+                my ($needswitch,$switchwarning,$switchserver) = &switch_for_update($loncaparev,$cdom,$cnum);
+                if ($needswitch) {
+                    return ('switch',$switchwarning,$switchserver);
+                } else {
+                    $update = 'supp';
                 }
             }
-            return ('update');
+            return ($update);
+        }
+    }
+    return ();
+}
+
+sub switch_for_update {
+    my ($loncaparev,$cdom,$cnum) = @_;
+    my %curr_reqd_hash = &Apache::lonnet::userenvironment($cdom,$cnum,'internal.releaserequired');
+    if ($curr_reqd_hash{'internal.releaserequired'} ne '') {
+        my $required = $env{'course.'.$cdom.'_'.$cnum.'.internal.releaserequired'};
+        if ($curr_reqd_hash{'internal.releaserequired'} ne $required) {
+            &Apache::lonnet::appenv({'course.'.$cdom.'_'.$cnum.'.internal.releaserequired' =>
+                                    $curr_reqd_hash{'internal.releaserequired'}});
+            my ($switchserver,$switchwarning) =
+                &check_release_required($loncaparev,$cdom.'_'.$cnum,$env{'request.role'},
+                                        $curr_reqd_hash{'internal.releaserequired'});
+            if ($switchwarning ne '' || $switchserver ne '') {
+                return ('switch',$switchwarning,$switchserver);
+            }
         }
     }
     return ();
@@ -17532,11 +19094,7 @@ sub update_content_constraints {
         }
         undef($navmap);
     }
-    my $suppmap = 'supplemental.sequence';
-    my ($suppcount,$supptools,$errors) = (0,0,0);
-    ($suppcount,$supptools,$errors) = &recurse_supplemental($cnum,$cdom,$suppmap,
-                                                            $suppcount,$supptools,$errors);
-    if ($supptools) {
+    if (&Apache::lonnet::count_supptools($cnum,$cdom,1)) {
         my ($major,$minor) = split(/\./,$checkcrsrestypes{'exttool'});
         if (($major > $reqdmajor) || ($major == $reqdmajor && $minor > $reqdminor)) {
             ($reqdmajor,$reqdminor) = ($major,$minor);
@@ -17588,8 +19146,10 @@ sub parse_supplemental_title {
         my $name =  &plainname($uname,$udom);
         $name = &HTML::Entities::encode($name,'"<>&\'');
         $renametitle = &HTML::Entities::encode($renametitle,'"<>&\'');
-        $title='<i>'.&Apache::lonlocal::locallocaltime($time).'</i> '.
-            $name.': <br />'.$foldertitle;
+        $title='<i>'.&Apache::lonlocal::locallocaltime($time).'</i> '.$name;
+        if ($foldertitle ne '') {
+            $title .= ': <br />'.$foldertitle;
+        }
     }
     if (wantarray) {
         return ($title,$foldertitle,$renametitle);
@@ -17597,32 +19157,147 @@ sub parse_supplemental_title {
     return $title;
 }
 
+sub get_supplemental {
+    my ($cnum,$cdom,$ignorecache,$possdel)=@_;
+    my $hashid=$cnum.':'.$cdom;
+    my ($supplemental,$cached,$set_httprefs);
+    unless ($ignorecache) {
+        ($supplemental,$cached) = &Apache::lonnet::is_cached_new('supplemental',$hashid);
+    }
+    unless (defined($cached)) {
+        my $chome=&Apache::lonnet::homeserver($cnum,$cdom);
+        unless ($chome eq 'no_host') {
+            my @order = @LONCAPA::map::order;
+            my @resources = @LONCAPA::map::resources;
+            my @resparms = @LONCAPA::map::resparms;
+            my @zombies = @LONCAPA::map::zombies;
+            my ($errors,%ids,%hidden);
+            $errors =
+                &recurse_supplemental($cnum,$cdom,'supplemental.sequence',
+                                      $errors,$possdel,\%ids,\%hidden);
+            @LONCAPA::map::order = @order;
+            @LONCAPA::map::resources = @resources;
+            @LONCAPA::map::resparms = @resparms;
+            @LONCAPA::map::zombies = @zombies;
+            $set_httprefs = 1;
+            if ($env{'request.course.id'} eq $cdom.'_'.$cnum) {
+                &Apache::lonnet::appenv({'request.course.suppupdated' => time});
+            }
+            $supplemental = {
+                               ids => \%ids,
+                               hidden => \%hidden,
+                            };
+            &Apache::lonnet::do_cache_new('supplemental',$hashid,$supplemental,600);
+        }
+    }
+    return ($supplemental,$set_httprefs);
+}
+
 sub recurse_supplemental {
-    my ($cnum,$cdom,$suppmap,$numfiles,$numexttools,$errors) = @_;
-    if ($suppmap) {
+    my ($cnum,$cdom,$suppmap,$errors,$possdel,$suppids,$hiddensupp,$hidden) = @_;
+    if (($suppmap) && (ref($suppids) eq 'HASH') && (ref($hiddensupp) eq 'HASH')) {
+        my $mapnum;
+        if ($suppmap eq 'supplemental.sequence') {
+            $mapnum = 0;
+        } else {
+            ($mapnum) = ($suppmap =~ /^supplemental_(\d+)\.sequence$/);
+        }
         my ($errtext,$fatal) = &LONCAPA::map::mapread('/uploaded/'.$cdom.'/'.$cnum.'/'.$suppmap);
         if ($fatal) {
             $errors ++;
         } else {
-            if ($#LONCAPA::map::resources > 0) {
-                foreach my $res (@LONCAPA::map::resources) {
-                    my ($title,$src,$ext,$type,$status)=split(/\:/,$res);
+            my @order = @LONCAPA::map::order;
+            if (@order > 0) {
+                my @resources = @LONCAPA::map::resources;
+                my @resparms = @LONCAPA::map::resparms;
+                foreach my $idx (@order) {
+                    my ($title,$src,$ext,$type,$status)=split(/\:/,$resources[$idx]);
                     if (($src ne '') && ($status eq 'res')) {
+                        my $id = $mapnum.':'.$idx;
+                        push(@{$suppids->{$src}},$id);
+                        if (($hidden) || (&get_supp_parameter($resparms[$idx],'parameter_hiddenresource') =~ /^yes/i)) {
+                            $hiddensupp->{$id} = 1;
+                        }
                         if ($src =~ m{^\Q/uploaded/$cdom/$cnum/\E(supplemental_\d+\.sequence)$}) {
-                            ($numfiles,$numexttools,$errors) = &recurse_supplemental($cnum,$cdom,$1,
-                                                                   $numfiles,$numexttools,$errors);
+                            $errors = &recurse_supplemental($cnum,$cdom,$1,$errors,$possdel,$suppids,
+                                                            $hiddensupp,$hiddensupp->{$id});
                         } else {
-                            if ($src =~ m{^/adm/$cdom/$cnum/\d+/ext\.tool$}) {
-                                $numexttools ++;
+                            my $allowed;
+                            if (($env{'request.role.adv'}) || (!$hiddensupp->{$id})) {
+                                $allowed = 1;
+                            } elsif ($possdel) {
+                                foreach my $item (@{$suppids->{$src}}) {
+                                    next if ($item eq $id);
+                                    unless ($hiddensupp->{$item}) {
+                                       $allowed = 1;
+                                       last;
+                                    }
+                                }
+                                if ((!$allowed) && (exists($env{'httpref.'.$src}))) {
+                                    &Apache::lonnet::delenv('httpref.'.$src);
+                                }
+                            }
+                            if ($allowed && (!exists($env{'httpref.'.$src}))) {
+                                &Apache::lonnet::allowuploaded('/adm/coursedoc',$src);
                             }
-                            $numfiles ++;
                         }
                     }
                 }
             }
         }
     }
-    return ($numfiles,$numexttools,$errors);
+    return $errors;
+}
+
+sub set_supp_httprefs {
+    my ($cnum,$cdom,$supplemental,$possdel) = @_;
+    if (ref($supplemental) eq 'HASH') {
+        if ((ref($supplemental->{'ids'}) eq 'HASH') && (ref($supplemental->{'hidden'}) eq 'HASH')) {
+            foreach my $src (keys(%{$supplemental->{'ids'}})) {
+                next if ($src =~ /\.sequence$/);
+                if (ref($supplemental->{'ids'}->{$src}) eq 'ARRAY') {
+                    my $allowed;
+                    if ($env{'request.role.adv'}) {
+                        $allowed = 1;
+                    } else {
+                        foreach my $id (@{$supplemental->{'ids'}->{$src}}) {
+                            unless ($supplemental->{'hidden'}->{$id}) {
+                                $allowed = 1;
+                                last;
+                            }
+                        }
+                    }
+                    if (exists($env{'httpref.'.$src})) {
+                        if ($possdel) {
+                            unless ($allowed) {
+                                &Apache::lonnet::delenv('httpref.'.$src);
+                            }
+                        }
+                    } elsif ($allowed) {
+                        &Apache::lonnet::allowuploaded('/adm/coursedoc',$src);
+                    }
+                }
+            }
+            if ($env{'request.course.id'} eq $cdom.'_'.$cnum) {
+                &Apache::lonnet::appenv({'request.course.suppupdated' => time});
+            }
+        }
+    }
+}
+
+sub get_supp_parameter {
+    my ($resparm,$name)=@_;
+    return if ($resparm eq '');
+    my $value=undef;
+    my $ptype=undef;
+    foreach (split('&&&',$resparm)) {
+        my ($thistype,$thisname,$thisvalue)=split('___',$_);
+        if ($thisname eq $name) {
+            $value=$thisvalue;
+            $ptype=$thistype;
+        }
+    }
+    return $value;
 }
 
 sub symb_to_docspath {
@@ -17695,11 +19370,72 @@ sub symb_to_docspath {
     return $path;
 }
 
+sub validate_folderpath {
+    my ($supplementalflag,$allowed,$coursenum,$coursedom) = @_;
+    if ($env{'form.folderpath'} ne '') {
+        my @items = split(/\&/,$env{'form.folderpath'});
+        my ($badpath,$changed,$got_supp,$supppath,%supphidden,%suppids);
+        for (my $i=0; $i<@items; $i++) {
+            my $odd = $i%2;
+            if (($odd) && (!$supplementalflag) && ($items[$i] !~ /^[^:]*:(|\d+):(|1):(|1):(|1):(|1)$/)) {
+                $badpath = 1;
+            } elsif ($odd && $supplementalflag) {
+                my $idx = $i-1;
+                if ($items[$i] =~ /^([^:]*)::(|1):::$/) {
+                    my $esc_name = $1;
+                    if ((!$allowed) || ($items[$idx] eq 'supplemental')) {
+                        $supppath .= '&'.$esc_name;
+                        $changed = 1;
+                    } else {
+                        $supppath .= '&'.$items[$i];
+                    }
+                } elsif (($allowed) && ($items[$idx] ne 'supplemental')) {
+                    $changed = 1;
+                    my $is_hidden;
+                    unless ($got_supp) {
+                        my ($supplemental) = &get_supplemental($coursenum,$coursedom);
+                        if (ref($supplemental) eq 'HASH') {
+                            if (ref($supplemental->{'hidden'}) eq 'HASH') {
+                                %supphidden = %{$supplemental->{'hidden'}};
+                            }
+                            if (ref($supplemental->{'ids'}) eq 'HASH') {
+                                %suppids = %{$supplemental->{'ids'}};
+                            }
+                        }
+                        $got_supp = 1;
+                    }
+                    if (ref($suppids{"/uploaded/$coursedom/$coursenum/$items[$idx].sequence"}) eq 'ARRAY') {
+                        my $mapid = $suppids{"/uploaded/$coursedom/$coursenum/$items[$idx].sequence"}->[0];
+                        if ($supphidden{$mapid}) {
+                            $is_hidden = 1;
+                        }
+                    }
+                    $supppath .= '&'.$items[$i].'::'.$is_hidden.':::';
+                } else {
+                    $supppath .= '&'.$items[$i];
+                }
+            } elsif ((!$odd) && ($items[$i] !~ /^(default|supplemental)(|_\d+)$/)) {
+                $badpath = 1;
+            } elsif ($supplementalflag) {
+                $supppath .= '&'.$items[$i];
+            }
+            last if ($badpath);
+        }
+        if ($badpath) {
+            delete($env{'form.folderpath'});
+        } elsif ($changed && $supplementalflag) {
+            $supppath =~ s/^\&//;
+            $env{'form.folderpath'} = $supppath;
+        }
+    }
+    return;
+}
+
 sub captcha_display {
-    my ($context,$lonhost) = @_;
+    my ($context,$lonhost,$defdom) = @_;
     my ($output,$error);
     my ($captcha,$pubkey,$privkey,$version) = 
-        &get_captcha_config($context,$lonhost);
+        &get_captcha_config($context,$lonhost,$defdom);
     if ($captcha eq 'original') {
         $output = &create_captcha();
         unless ($output) {
@@ -17715,9 +19451,9 @@ sub captcha_display {
 }
 
 sub captcha_response {
-    my ($context,$lonhost) = @_;
+    my ($context,$lonhost,$defdom) = @_;
     my ($captcha_chk,$captcha_error);
-    my ($captcha,$pubkey,$privkey,$version) = &get_captcha_config($context,$lonhost);
+    my ($captcha,$pubkey,$privkey,$version) = &get_captcha_config($context,$lonhost,$defdom);
     if ($captcha eq 'original') {
         ($captcha_chk,$captcha_error) = &check_captcha();
     } elsif ($captcha eq 'recaptcha') {
@@ -17729,7 +19465,7 @@ sub captcha_response {
 }
 
 sub get_captcha_config {
-    my ($context,$lonhost) = @_;
+    my ($context,$lonhost,$dom_in_effect) = @_;
     my ($captcha,$pubkey,$privkey,$version,$hashtocheck);
     my $hostname = &Apache::lonnet::hostname($lonhost);
     my $serverhomeID = &Apache::lonnet::get_server_homeID($hostname);
@@ -17777,7 +19513,28 @@ sub get_captcha_config {
         } elsif ($domconfhash{$serverhomedom.'.login.captcha'} eq 'original') {
             $captcha = 'original';
         }
-    }
+    } elsif ($context eq 'passwords') {
+        if ($dom_in_effect) {
+            my %passwdconf = &Apache::lonnet::get_passwdconf($dom_in_effect);
+            if ($passwdconf{'captcha'} eq 'recaptcha') {
+                if (ref($passwdconf{'recaptchakeys'}) eq 'HASH') {
+                    $pubkey = $passwdconf{'recaptchakeys'}{'public'};
+                    $privkey = $passwdconf{'recaptchakeys'}{'private'};
+                }
+                if ($privkey && $pubkey) {
+                    $captcha = 'recaptcha';
+                    $version = $passwdconf{'recaptchaversion'};
+                    if ($version ne '2') {
+                        $version = 1;
+                    }
+                } else {
+                    $captcha = 'original';
+                }
+            } elsif ($passwdconf{'captcha'} ne 'notused') {
+                $captcha = 'original';
+            }
+        }
+    } 
     return ($captcha,$pubkey,$privkey,$version);
 }
 
@@ -17794,9 +19551,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 />'.
+                      '<input type="text" size="5" name="code" value="" autocomplete="new-password" />'.
+                      '</span><br />'.
                       '<img src="'.$captcha_params{'www_output_dir'}.'/'.$md5sum.'.png" alt="captcha" />';
             last;
         }
@@ -17842,7 +19600,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) {
@@ -17860,11 +19619,12 @@ sub create_recaptcha {
 sub check_recaptcha {
     my ($privkey,$version) = @_;
     my $captcha_chk;
+    my $ip = &Apache::lonnet::get_requestor_ip();
     if ($version >= 2) {
         my %info = (
                      secret   => $privkey, 
                      response => $env{'form.g-recaptcha-response'},
-                     remoteip => $ENV{'REMOTE_ADDR'},
+                     remoteip => $ip,
                    );
         my $request=new HTTP::Request('POST','https://www.google.com/recaptcha/api/siteverify');
         $request->content(join('&',map {
@@ -17887,7 +19647,7 @@ sub check_recaptcha {
         my $captcha_result =
             $captcha->check_answer(
                                     $privkey,
-                                    $ENV{'REMOTE_ADDR'},
+                                    $ip,
                                     $env{'form.recaptcha_challenge_field'},
                                     $env{'form.recaptcha_response_field'},
                                   );
@@ -17939,11 +19699,14 @@ sub cleanup_html {
 # $context is the calling context -- roles, grades, contents, menu or flip. 
 sub critical_redirect {
     my ($interval,$context) = @_;
+    unless (($env{'user.domain'} ne '') && ($env{'user.name'} ne '')) {
+        return ();
+    }
     if ((time-$env{'user.criticalcheck.time'})>$interval) {
         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 '') {
@@ -17960,7 +19723,7 @@ sub critical_redirect {
         &Apache::lonnet::appenv({'user.criticalcheck.time'=>time});
         my $redirecturl;
         if ($what[0]) {
-	    if (($what[0] ne 'con_lost') && ($what[0]!~/^error\:/)) {
+	    if (($what[0] ne 'con_lost') && ($what[0] ne 'no_such_host') && ($what[0]!~/^error\:/)) {
 	        $redirecturl='/adm/email?critical=display';
 	        my $url=&Apache::lonnet::absolute_url().$redirecturl;
                 return (1, $url);
@@ -18020,24 +19783,37 @@ sub des_decrypt {
     return $plaintext;
 }
 
-sub make_short_symbs {
+sub get_requested_shorturls {
     my ($cdom,$cnum,$navmap) = @_;
     return unless (ref($navmap));
-    my ($numnew,@errors);
+    my ($numnew,$errors);
     my @toshorten = &Apache::loncommon::get_env_multiple('form.addtiny');
     if (@toshorten) {
         my (%maps,%resources,%titles);
         &Apache::loncourserespicker::enumerate_course_contents($navmap,\%maps,\%resources,\%titles,
                                                                'shorturls',$cdom,$cnum);
-        my %tocreate;
         if (keys(%resources)) {
+            my %tocreate;
             foreach my $item (sort {$a <=> $b} (@toshorten)) {
                 my $symb = $resources{$item};
                 if ($symb) {
                     $tocreate{$cnum.'&'.$symb} = 1;
                 }
             }
+            if (keys(%tocreate)) {
+                ($numnew,$errors) = &make_short_symbs($cdom,$cnum,
+                                                      \%tocreate);
+            }
         }
+    }
+    return ($numnew,$errors);
+}
+
+sub make_short_symbs {
+    my ($cdom,$cnum,$tocreateref,$lockuser) = @_;
+    my ($numnew,@errors);
+    if (ref($tocreateref) eq 'HASH') {
+        my %tocreate = %{$tocreateref};
         if (keys(%tocreate)) {
             my %coursetiny = &Apache::lonnet::dump('tiny',$cdom,$cnum);
             my $su = Short::URL->new(no_vowels => 1);
@@ -18045,9 +19821,11 @@ sub make_short_symbs {
             my (%newunique,%addcourse,%courseonly,%failed);
             # get lock on tiny db
             my $now = time;
+            if ($lockuser eq '') {
+                $lockuser = $env{'user.name'}.':'.$env{'user.domain'};
+            }
             my $lockhash = {
-                                "lock\0$now" => $env{'user.name'}.
-                                                ':'.$env{'user.domain'},
+                                "lock\0$now" => $lockuser,
                             };
             my $tries = 0;
             my $gotlock = &Apache::lonnet::newput_dom('tiny',$lockhash,$cdom);
@@ -18156,6 +19934,188 @@ sub shorten_symbs {
     return $init;
 }
 
+sub is_nonframeable {
+    my ($url,$absolute,$hostname,$ip,$nocache) = @_;
+    my ($remprotocol,$remhost) = ($url =~ m{^(https?)\://(([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,})}i);
+    return if (($remprotocol eq '') || ($remhost eq ''));
+
+    $remprotocol = lc($remprotocol);
+    $remhost = lc($remhost);
+    my $remport = 80;
+    if ($remprotocol eq 'https') {
+        $remport = 443;
+    }
+    my ($result,$cached) = &Apache::lonnet::is_cached_new('noiframe',$remhost.':'.$remport);
+    if ($cached) {
+        unless ($nocache) {
+            if ($result) {
+                return 1;
+            } else {
+                return 0;
+            }
+        }
+    }
+    my $uselink;
+    my $request = new HTTP::Request('HEAD',$url);
+    my $response = &LONCAPA::LWPReq::makerequest('',$request,'','',5);
+    if ($response->is_success()) {
+        my $secpolicy = lc($response->header('content-security-policy'));
+        my $xframeop = lc($response->header('x-frame-options'));
+        $secpolicy =~ s/^\s+|\s+$//g;
+        $xframeop =~ s/^\s+|\s+$//g;
+        if (($secpolicy ne '') || ($xframeop ne '')) {
+            my $remotehost = $remprotocol.'://'.$remhost;
+            my ($origin,$protocol,$port);
+            if ($ENV{'SERVER_PORT'} =~/^\d+$/) {
+                $port = $ENV{'SERVER_PORT'};
+            } else {
+                $port = 80;
+            }
+            if ($absolute eq '') {
+                $protocol = 'http:';
+                if ($port == 443) {
+                    $protocol = 'https:';
+                }
+                $origin = $protocol.'//'.lc($hostname);
+            } else {
+                $origin = lc($absolute);
+                ($protocol,$hostname) = ($absolute =~ m{^(https?:)//([^/]+)$});
+            }
+            if (($secpolicy) && ($secpolicy =~ /\Qframe-ancestors\E([^;]*)(;|$)/)) {
+                my $framepolicy = $1;
+                $framepolicy =~ s/^\s+|\s+$//g;
+                my @policies = split(/\s+/,$framepolicy);
+                if (@policies) {
+                    if (grep(/^\Q'none'\E$/,@policies)) {
+                        $uselink = 1;
+                    } else {
+                        $uselink = 1;
+                        if ((grep(/^\Q*\E$/,@policies)) || (grep(/^\Q$protocol\E$/,@policies)) ||
+                                (($origin ne '') && (grep(/^\Q$origin\E$/,@policies))) ||
+                                (($ip ne '') && (grep(/^\Q$ip\E$/,@policies)))) {
+                            undef($uselink);
+                        }
+                        if ($uselink) {
+                            if (grep(/^\Q'self'\E$/,@policies)) {
+                                if (($origin ne '') && ($remotehost eq $origin)) {
+                                    undef($uselink);
+                                }
+                            }
+                        }
+                        if ($uselink) {
+                            my @possok;
+                            if ($ip ne '') {
+                                push(@possok,$ip);
+                            }
+                            my $hoststr = '';
+                            foreach my $part (reverse(split(/\./,$hostname))) {
+                                if ($hoststr eq '') {
+                                    $hoststr = $part;
+                                } else {
+                                    $hoststr = "$part.$hoststr";
+                                }
+                                if ($hoststr eq $hostname) {
+                                    push(@possok,$hostname);
+                                } else {
+                                    push(@possok,"*.$hoststr");
+                                }
+                            }
+                            if (@possok) {
+                                foreach my $poss (@possok) {
+                                    last if (!$uselink);
+                                    foreach my $policy (@policies) {
+                                        if ($policy =~ m{^(\Q$protocol\E//|)\Q$poss\E(\Q:$port\E|)$}) {
+                                            undef($uselink);
+                                            last;
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            } elsif ($xframeop ne '') {
+                $uselink = 1;
+                my @policies = split(/\s*,\s*/,$xframeop);
+                if (@policies) {
+                    unless (grep(/^deny$/,@policies)) {
+                        if ($origin ne '') {
+                            if (grep(/^sameorigin$/,@policies)) {
+                                if ($remotehost eq $origin) {
+                                    undef($uselink);
+                                }
+                            }
+                            if ($uselink) {
+                                foreach my $policy (@policies) {
+                                    if ($policy =~ /^allow-from\s*(.+)$/) {
+                                        my $allowfrom = $1;
+                                        if (($allowfrom ne '') && ($allowfrom eq $origin)) {
+                                            undef($uselink);
+                                            last;
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+    if ($nocache) {
+        if ($cached) {
+            my $devalidate;
+            if ($uselink && !$result) {
+                $devalidate = 1;
+            } elsif (!$uselink && $result) {
+                $devalidate = 1;
+            }
+            if ($devalidate) {
+                &Apache::lonnet::devalidate_cache_new('noiframe',$remhost.':'.$remport);
+            }
+        }
+    } else {
+        if ($uselink) {
+            $result = 1;
+        } else {
+            $result = 0;
+        }
+        &Apache::lonnet::do_cache_new('noiframe',$remhost.':'.$remport,$result,3600);
+    }
+    return $uselink;
+}
+
+sub page_menu {
+    my ($menucolls,$menunum) = @_;
+    my %menu;
+    foreach my $item (split(/;/,$menucolls)) {
+        my ($num,$value) = split(/\%/,$item);
+        if ($num eq $menunum) {
+            my @entries = split(/\&/,$value);
+            foreach my $entry (@entries) {
+                my ($name,$fields) = split(/=/,$entry);
+                if (($name eq 'top') || ($name eq 'inline') || ($name eq 'foot') || ($name eq 'main')) {
+                    $menu{$name} = $fields;
+                } else {
+                    my @shown;
+                    if ($fields =~ /,/) {
+                        @shown = split(/,/,$fields);
+                    } else {
+                        @shown = ($fields);
+                    }
+                    if (@shown) {
+                        foreach my $field (@shown) {
+                            next if ($field eq '');
+                            $menu{$field} = 1;
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return %menu;
+}
+
 1;
 __END__;