--- loncom/interface/loncommon.pm	2014/02/23 20:52:58	1.1178
+++ 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.1178 2014/02/23 20:52:58 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();
@@ -69,14 +69,27 @@ use Apache::lontexconvert();
 use Apache::lonclonecourse();
 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::Catalog;
+use DateTime::Locale;
+use Encode();
 use Text::Aspell;
 use Authen::Captcha;
 use Captcha::reCAPTCHA;
+use JSON::DWIW;
 use Crypt::DES;
 use DynaLoader; # for Crypt::DES version
+use MIME::Lite;
+use MIME::Types;
+use File::Copy();
+use File::Path();
+use String::CRC32();
+use Short::URL();
 
 # ---------------------------------------------- Designs
 use vars qw(%defaultdesign);
@@ -192,7 +205,7 @@ BEGIN {
     {
         my $langtabfile = $Apache::lonnet::perlvar{'lonTabDir'}.
                                    '/language.tab';
-        if ( open(my $fh,"<$langtabfile") ) {
+        if ( open(my $fh,'<',$langtabfile) ) {
             while (my $line = <$fh>) {
                 next if ($line=~/^\#/);
                 chomp($line);
@@ -214,7 +227,7 @@ BEGIN {
     {
         my $copyrightfile = $Apache::lonnet::perlvar{'lonIncludes'}.
                                   '/copyright.tab';
-        if ( open (my $fh,"<$copyrightfile") ) {
+        if ( open (my $fh,'<',$copyrightfile) ) {
             while (my $line = <$fh>) {
                 next if ($line=~/^\#/);
                 chomp($line);
@@ -228,7 +241,7 @@ BEGIN {
     {
         my $sourcecopyrightfile = $Apache::lonnet::perlvar{'lonIncludes'}.
                                   '/source_copyright.tab';
-        if ( open (my $fh,"<$sourcecopyrightfile") ) {
+        if ( open (my $fh,'<',$sourcecopyrightfile) ) {
             while (my $line = <$fh>) {
                 next if ($line =~ /^\#/);
                 chomp($line);
@@ -242,7 +255,7 @@ BEGIN {
 # -------------------------------------------------------------- default domain designs
     my $designdir=$Apache::lonnet::perlvar{'lonTabDir'}.'/lonDomColors';
     my $designfile = $designdir.'/default.tab';
-    if ( open (my $fh,"<$designfile") ) {
+    if ( open (my $fh,'<',$designfile) ) {
         while (my $line = <$fh>) {
             next if ($line =~ /^\#/);
             chomp($line);
@@ -256,12 +269,12 @@ BEGIN {
     {
         my $categoryfile = $Apache::lonnet::perlvar{'lonTabDir'}.
                                   '/filecategories.tab';
-        if ( open (my $fh,"<$categoryfile") ) {
+        if ( open (my $fh,'<',$categoryfile) ) {
 	    while (my $line = <$fh>) {
 		next if ($line =~ /^\#/);
 		chomp($line);
                 my ($extension,$category)=(split(/\s+/,$line,2));
-                push @{$category_extensions{lc($category)}},$extension;
+                push(@{$category_extensions{lc($category)}},$extension);
             }
             close($fh);
         }
@@ -271,7 +284,7 @@ BEGIN {
     {
         my $typesfile = $Apache::lonnet::perlvar{'lonTabDir'}.
                '/filetypes.tab';
-        if ( open (my $fh,"<$typesfile") ) {
+        if ( open (my $fh,'<',$typesfile) ) {
             while (my $line = <$fh>) {
 		next if ($line =~ /^\#/);
 		chomp($line);
@@ -424,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) {
@@ -439,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';
@@ -471,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)."'";
@@ -482,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.');">'.
@@ -534,7 +562,7 @@ ENDAUTHORBRW
 
 sub coursebrowser_javascript {
     my ($domainfilter,$sec_element,$formname,$role_element,$crstype,
-        $credits_element) = @_;
+        $credits_element,$instcode) = @_;
     my $wintitle = 'Course_Browser';
     if ($crstype eq 'Community') {
         $wintitle = 'Community_Browser';
@@ -585,6 +613,12 @@ sub coursebrowser_javascript {
             var ownername = document.forms[formid].ccuname.value;
             var ownerdom =  document.forms[formid].ccdomain.options[document.forms[formid].ccdomain.selectedIndex].value;
             url += '&cloner='+ownername+':'+ownerdom;
+            if (type == 'Course') {
+                url += '&crscode='+document.forms[formid].crscode.value;
+            }
+        }
+        if (formname == 'requestcrs') {
+            url += '&crsdom=$domainfilter&crscode=$instcode';
         }
         if (multflag !=null && multflag != '') {
             url += '&multiple='+multflag;
@@ -869,6 +903,8 @@ sub selectcourse_link {
    my $linktext = &mt('Select Course');
    if ($selecttype eq 'Community') {
        $linktext = &mt('Select Community');
+   } elsif ($selecttype eq 'Placement') {
+       $linktext = &mt('Select Placement Test'); 
    } elsif ($selecttype eq 'Course/Community') {
        $linktext = &mt('Select Course/Community');
        $type = '';
@@ -929,8 +965,8 @@ ENDSCRT
 }
 
 sub select_timezone {
-   my ($name,$selected,$onchange,$includeempty)=@_;
-   my $output='<select name="'.$name.'" '.$onchange.'>'."\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')) {
@@ -951,8 +987,8 @@ sub select_timezone {
 }
 
 sub select_datelocale {
-    my ($name,$selected,$onchange,$includeempty)=@_;
-    my $output='<select name="'.$name.'" '.$onchange.'>'."\n";
+    my ($name,$selected,$onchange,$includeempty,$disabled)=@_;
+    my $output='<select name="'.$name.'" '.$onchange.$disabled.'>'."\n";
     if ($includeempty) {
         $output .= '<option value=""';
         if ($selected eq '') {
@@ -960,15 +996,16 @@ sub select_datelocale {
         }
         $output .= '> </option>';
     }
+    my @languages = &Apache::lonlocal::preferred_languages();
     my (@possibles,%locale_names);
-    my @locales = DateTime::Locale::Catalog::Locales;
-    foreach my $locale (@locales) {
-        if (ref($locale) eq 'HASH') {
-            my $id = $locale->{'id'};
-            if ($id ne '') {
-                my $en_terr = $locale->{'en_territory'};
-                my $native_terr = $locale->{'native_territory'};
-                my @languages = &Apache::lonlocal::preferred_languages();
+    my @locales = DateTime::Locale->ids();
+    foreach my $id (@locales) {
+        if ($id ne '') {
+            my ($en_terr,$native_terr);
+            my $loc = DateTime::Locale->load($id);
+            if (ref($loc)) {
+                $en_terr = $loc->name();
+                $native_terr = $loc->native_name();
                 if (grep(/^en$/,@languages) || !@languages) {
                     if ($en_terr ne '') {
                         $locale_names{$id} = '('.$en_terr.')';
@@ -982,8 +1019,9 @@ sub select_datelocale {
                         $locale_names{$id} = '('.$en_terr.')';
                     }
                 }
-                push (@possibles,$id);
-            }
+                $locale_names{$id} = Encode::encode('UTF-8',$locale_names{$id});
+                push(@possibles,$id);
+            } 
         }
     }
     foreach my $item (sort(@possibles)) {
@@ -993,7 +1031,7 @@ sub select_datelocale {
         }
         $output.=">$item";
         if ($locale_names{$item} ne '') {
-            $output.="  $locale_names{$item}</option>\n";
+            $output.='  '.$locale_names{$item};
         }
         $output.="</option>\n";
     }
@@ -1002,7 +1040,7 @@ sub select_datelocale {
 }
 
 sub select_language {
-    my ($name,$selected,$includeempty) = @_;
+    my ($name,$selected,$includeempty,$noedit) = @_;
     my %langchoices;
     if ($includeempty) {
         %langchoices = ('' => 'No language preference');
@@ -1014,7 +1052,7 @@ sub select_language {
         }
     }
     %langchoices = &Apache::lonlocal::texthash(%langchoices);
-    return &select_form($selected,$name,\%langchoices);
+    return &select_form($selected,$name,\%langchoices,undef,$noedit);
 }
 
 =pod
@@ -1038,7 +1076,7 @@ sub list_languages {
 	if ($code) {
 	    my $selector    = $supported_codes{$id};
 	    my $description = &plainlanguagedescription($id);
-	    push (@lang_choices, [$selector, $description]);
+	    push(@lang_choices, [$selector, $description]);
 	}
     }
     return \@lang_choices;
@@ -1078,6 +1116,9 @@ linked_select_forms takes the following
 =item * $onchangesecond, additional javascript call to execute for an onchange
         event for the second <select> tag
 
+=item * $suffix, to differentiate separate uses of select2data javascript
+        objects in a page.
+
 =back 
 
 Below is an example of such a hash.  Only the 'text', 'default', and 
@@ -1132,7 +1173,8 @@ sub linked_select_forms {
         $hashref,
         $menuorder,
         $onchangefirst,
-        $onchangesecond
+        $onchangesecond,
+        $suffix
         ) = @_;
     my $second = "document.$formname.$secondselectname";
     my $first = "document.$formname.$firstselectname";
@@ -1140,42 +1182,40 @@ sub linked_select_forms {
     my $result = '';
     $result.='<script type="text/javascript" language="JavaScript">'."\n";
     $result.="// <![CDATA[\n";
-    $result.="var select2data = new Object();\n";
+    $result.="var select2data${suffix} = new Object();\n";
     $" = '","';
     my $debug = '';
     foreach my $s1 (sort(keys(%$hashref))) {
-        $result.="select2data.d_$s1 = new Object();\n";        
-        $result.="select2data.d_$s1.def = new String('".
+        $result.="select2data${suffix}['d_$s1'] = new Object();\n";        
+        $result.="select2data${suffix}['d_$s1'].def = new String('".
             $hashref->{$s1}->{'default'}."');\n";
-        $result.="select2data.d_$s1.values = new Array(";
+        $result.="select2data${suffix}['d_$s1'].values = new Array(";
         my @s2values = sort(keys( %{ $hashref->{$s1}->{'select2'} } ));
         if (ref($hashref->{$s1}->{'order'}) eq 'ARRAY') {
             @s2values = @{$hashref->{$s1}->{'order'}};
         }
         $result.="\"@s2values\");\n";
-        $result.="select2data.d_$s1.texts = new Array(";        
+        $result.="select2data${suffix}['d_$s1'].texts = new Array(";        
         my @s2texts;
         foreach my $value (@s2values) {
-            push @s2texts, $hashref->{$s1}->{'select2'}->{$value};
+            push(@s2texts, $hashref->{$s1}->{'select2'}->{$value});
         }
         $result.="\"@s2texts\");\n";
     }
     $"=' ';
     $result.= <<"END";
 
-function select1_changed() {
+function select1${suffix}_changed() {
     // Determine new choice
-    var newvalue = "d_" + $first.value;
+    var newvalue = "d_" + $first.options[$first.selectedIndex].value;
     // update select2
-    var values     = select2data[newvalue].values;
-    var texts      = select2data[newvalue].texts;
-    var select2def = select2data[newvalue].def;
+    var values     = select2data${suffix}[newvalue].values;
+    var texts      = select2data${suffix}[newvalue].texts;
+    var select2def = select2data${suffix}[newvalue].def;
     var i;
     // out with the old
-    for (i = 0; i < $second.options.length; i++) {
-        $second.options[i] = null;
-    }
-    // in with the nuclear
+    $second.options.length = 0;
+    // in with the new
     for (i=0;i<values.length; i++) {
         $second.options[i] = new Option(values[i]);
         $second.options[i].value = values[i];
@@ -1189,7 +1229,7 @@ function select1_changed() {
 </script>
 END
     # output the initial values for the selection lists
-    $result .= "<select size=\"1\" name=\"$firstselectname\" onchange=\"select1_changed();$onchangefirst\">\n";
+    $result .= "<select size=\"1\" name=\"$firstselectname\" onchange=\"select1${suffix}_changed();$onchangefirst\">\n";
     my @order = sort(keys(%{$hashref}));
     if (ref($menuorder) eq 'ARRAY') {
         @order = @{$menuorder};
@@ -1200,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) {
@@ -1225,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
@@ -1249,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);
@@ -1274,9 +1321,16 @@ sub help_open_topic {
     }
 
     # Add the text
-    if ($text ne "") {	
+    my $target = ' target="_top"';
+    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 "") {
 	$template.='<span class="LC_help_open_topic">'
-                  .'<a target="_top" href="'.$link.'">'
+                  .'<a'.$target.' href="'.$link.'">'
                   .$text.'</a>';
     }
 
@@ -1286,7 +1340,7 @@ sub help_open_topic {
     if ($imgid ne '') {
         $imgid = ' id="'.$imgid.'"';
     }
-    $template.=' <a target="_top" href="'.$link.'" title="'.$title.'">'
+    $template.=' <a'.$target.' href="'.$link.'" title="'.$title.'">'
               .'<img src="'.$helpicon.'" border="0"'
               .' alt="'.&mt('Help: [_1]',$topic).'"'
               .' title="'.$title.'" style="vertical-align:middle;"'.$imgid 
@@ -1315,8 +1369,10 @@ sub helpLatexCheatsheet {
 	  .&help_open_topic('Other_Symbols',&mt('Other Symbols'),$stayOnPage,undef,600)
 	  .'</span>';
     unless ($not_author) {
-        $out .= ' <span>'
-	       .&help_open_topic('Authoring_Output_Tags',&mt('Output Tags'),$stayOnPage,undef,600)
+        $out .= '<span>'
+               .&help_open_topic('Authoring_Output_Tags',&mt('Output Tags'),$stayOnPage,undef,600)
+               .'</span> <span>'
+               .&help_open_topic('Authoring_Multilingual_Problems',&mt('Languages'),$stayOnPage,undef,600)
 	       .'</span>';
     }
     $out .= '</span>'; # End cheatsheet
@@ -1357,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);
@@ -1378,7 +1434,7 @@ sub help_open_menu {
 }
 
 sub top_nav_help {
-    my ($text) = @_;
+    my ($text,$linkattr) = @_;
     $text = &mt($text);
     my $stay_on_page = 1;
 
@@ -1392,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;';
@@ -1477,19 +1533,26 @@ sub help_open_bug {
     {
 	$link = $url;
     }
+
+    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=\"_top\" 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="_top" 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;
@@ -1701,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 {
@@ -1754,6 +1815,790 @@ RESIZE
 
 }
 
+sub colorfuleditor_js {
+    my $browse_or_search;
+    my $respath;
+    my ($cnum,$cdom) = &crsauthor_url();
+    if ($cnum) {
+        $respath = "/res/$cdom/$cnum/";
+        my %js_lt = &Apache::lonlocal::texthash(
+            sunm => 'Sub-directory name',
+            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)) {
+            var curr = document.getElementById('chooser_'+element).style.display;
+            if (curr == 'none') {
+                disp='inline';
+                if (form.elements['chooser_'+element].length) {
+                    for (var i=0; i<form.elements['chooser_'+element].length; i++) {
+                        form.elements['chooser_'+element][i].checked = false;
+                    }
+                }
+                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) {
+        if (document.getElementById('chooser_'+element+'_crsres')) {
+            var curr = document.getElementById('chooser_'+element+'_crsres').style.display;
+            if (curr == 'none') {
+                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) {
+                        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';
+            if (document.getElementById('uploadcrsres_'+element)) {
+                document.getElementById('uploadcrsres_'+element).value = '';
+            }
+        }
+        return;
+    }
+
+    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') {
+                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 = '';
+                }
+            }
+        }
+        return;
+    }
+
+    function toggleResImport(form,element) {
+        var choices = new Array('crsres','upload');
+        for (var i=0; i<choices.length; i++) {
+            if (document.getElementById('chooser_'+element+'_'+choices[i])) {
+                document.getElementById('chooser_'+element+'_'+choices[i]).style.display = 'none';
+            }
+        }
+    }
+
+    function toggleNewsubdir(form,element) {
+        var newsub = form.elements['newsubdir_'+element];
+        if (newsub) {
+            if (newsub.length) {
+                for (var j=0; j<newsub.length; j++) {
+                    if (newsub[j].checked) {
+                        if (document.getElementById('newsubdirname_'+element)) {
+                            if (newsub[j].value == '1') {
+                                document.getElementById('newsubdirname_'+element).type = "text";
+                                if (document.getElementById('newsubdir_'+element)) {
+                                    document.getElementById('newsubdir_'+element).innerHTML = '<br />$js_lt{sunm}';
+                                }
+                            } else {
+                                document.getElementById('newsubdirname_'+element).type = "hidden";
+                                document.getElementById('newsubdirname_'+element).value = "";
+                                document.getElementById('newsubdir_'+element).innerHTML = "";
+                            }
+                        }
+                        break; 
+                    }
+                }
+            }
+        }
+    }
+
+    function updateCrsFile(form,element) {
+        var directory = form.elements['coursepath_'+element];
+        var filename = form.elements['coursefile_'+element];
+        var path = directory.options[directory.selectedIndex].value;
+        var file = filename.options[filename.selectedIndex].value;
+        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;
+    }
+
+    function uploadDone(suffix,name) {
+        if (name) {
+	    document.forms["lonhomework"].elements[suffix].value = name;
+            unClean();
+            toggleChooser(document.forms["lonhomework"],suffix);
+        }
+    }
+
+\$(document).ready(function(){
+
+    \$(document).delegate('form :submit', 'click', function( event ) {
+        if ( \$( this ).hasClass( "LC_uploadcrsres" ) ) {
+            var buttonId = this.id;
+            var suffix = buttonId.toString();
+            suffix = suffix.replace(/^crsupload_/,'');
+            event.preventDefault();
+            document.lonhomework.target = 'crsupload_target_'+suffix;
+            document.lonhomework.action = '/adm/coursepub?LC_uploadcrsres='+suffix;
+            \$(this.form).submit();
+            document.lonhomework.target = '';
+            if (document.getElementById('crsuploadto_'+suffix)) {
+                document.lonhomework.action = document.getElementById('crsuploadto_'+suffix).value;
+            }
+            return false;
+        }
+    });
+});
+END
+    }
+    return <<"COLORFULEDIT"
+<script type="text/javascript">
+// <![CDATA[>
+    function fold_box(curDepth, lastresource){
+
+    // we need a list because there can be several blocks you need to fold in one tag
+        var block = document.getElementsByName('foldblock_'+curDepth);
+    // but there is only one folding button per tag
+        var foldbutton = document.getElementById('folding_btn_'+curDepth);
+
+        if(block.item(0).style.display == 'none'){
+
+            foldbutton.value = '@{[&mt("Hide")]}';
+            for (i = 0; i < block.length; i++){
+                block.item(i).style.display = '';
+            }
+        }else{
+
+            foldbutton.value = '@{[&mt("Show")]}';
+            for (i = 0; i < block.length; i++){
+                // block.item(i).style.visibility = 'collapse';
+                block.item(i).style.display = 'none';
+            }
+        };
+        saveState(lastresource);
+    }
+
+    function saveState (lastresource) {
+
+        var tag_list = getTagList();
+        if(tag_list != null){
+            var timestamp = new Date().getTime();
+            var key = lastresource;
+
+            // the value pattern is: 'time;key1,value1;key2,value2; ... '
+            // starting with timestamp
+            var value = timestamp+';';
+
+            // building the list of key-value pairs
+            for(var i = 0; i < tag_list.length; i++){
+                value += tag_list[i]+',';
+                value += document.getElementsByName(tag_list[i])[0].style.display+';';
+            }
+
+            // only iterate whole storage if nothing to override
+            if(localStorage.getItem(key) == null){        
+
+                // prevent storage from growing large
+                if(localStorage.length > 50){
+                    var regex_getTimestamp = /^(?:\d)+;/;
+                    var oldest_timestamp = regex_getTimestamp.exec(localStorage.key(0));
+                    var oldest_key;
+                    
+                    for(var i = 1; i < localStorage.length; i++){
+                        if (regex_getTimestamp.exec(localStorage.key(i)) < oldest_timestamp) {
+                            oldest_key = localStorage.key(i);
+                            oldest_timestamp = regex_getTimestamp.exec(oldest_key);
+                        }
+                    }
+                    localStorage.removeItem(oldest_key);
+                }
+            }
+            localStorage.setItem(key,value);
+        }
+    }
+
+    // restore folding status of blocks (on page load)
+    function restoreState (lastresource) {
+        if(localStorage.getItem(lastresource) != null){
+            var key = lastresource;
+            var value = localStorage.getItem(key);
+            var regex_delTimestamp = /^\d+;/;
+
+            value.replace(regex_delTimestamp, '');
+
+            var valueArr = value.split(';');
+            var pairs;
+            var elements;
+            for (var i = 0; i < valueArr.length; i++){
+                pairs = valueArr[i].split(',');
+                elements = document.getElementsByName(pairs[0]);
+
+                for (var j = 0; j < elements.length; j++){  
+                    elements[j].style.display = pairs[1];
+                    if (pairs[1] == "none"){
+                        var regex_id = /([_\\d]+)\$/;
+                        regex_id.exec(pairs[0]);
+                        document.getElementById("folding_btn"+RegExp.\$1).value = "Show";
+                    }
+                }
+            }
+        }
+    }
+
+    function getTagList () {
+        
+        var stringToSearch = document.lonhomework.innerHTML;
+
+        var ret = new Array();
+        var regex_findBlock = /(foldblock_.*?)"/g;
+        var tag_list = stringToSearch.match(regex_findBlock);
+
+        if(tag_list != null){
+            for(var i = 0; i < tag_list.length; i++){            
+                ret.push(tag_list[i].replace(/"/, ''));
+            }
+        }
+        return ret;
+    }
+
+    function saveScrollPosition (resource) {
+        var tag_list = getTagList();
+
+        // we dont always want to jump to the first block
+        // 170 is roughly above the "Problem Editing" header. we just want to save if the user scrolled down further than this
+        if(\$(window).scrollTop() > 170){
+            if(tag_list != null){
+                var result;
+                for(var i = 0; i < tag_list.length; i++){
+                    if(isElementInViewport(tag_list[i])){
+                        result += tag_list[i]+';';
+                    }
+                }
+                sessionStorage.setItem('anchor_'+resource, result);
+            }
+        } else {
+            // we dont need to save zero, just delete the item to leave everything tidy
+            sessionStorage.removeItem('anchor_'+resource);
+        }
+    }
+
+    function restoreScrollPosition(resource){
+
+        var elem = sessionStorage.getItem('anchor_'+resource);
+        if(elem != null){
+            var tag_list = elem.split(';');
+            var elem_list;
+
+            for(var i = 0; i < tag_list.length; i++){
+                elem_list = document.getElementsByName(tag_list[i]);
+                
+                if(elem_list.length > 0){
+                    elem = elem_list[0];
+                    break;
+                }
+            }
+            elem.scrollIntoView();
+        }
+    }
+
+    function isElementInViewport(el) {
+
+        // change to last element instead of first
+        var elem = document.getElementsByName(el);
+        var rect = elem[0].getBoundingClientRect();
+
+        return (
+            rect.top >= 0 &&
+            rect.left >= 0 &&
+            rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */
+            rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */
+        );
+    }
+    
+    function autosize(depth){
+        var cmInst = window['cm'+depth];
+        var fitsizeButton = document.getElementById('fitsize'+depth);
+
+        // is fixed size, switching to dynamic
+        if (sessionStorage.getItem("autosized_"+depth) == null) {
+            cmInst.setSize("","auto");
+            fitsizeButton.value = "@{[&mt('Fixed size')]}";
+            sessionStorage.setItem("autosized_"+depth, "yes");
+
+        // is dynamic size, switching to fixed
+        } else {
+            cmInst.setSize("","300px");
+            fitsizeButton.value = "@{[&mt('Dynamic size')]}";
+            sessionStorage.removeItem("autosized_"+depth);
+        }
+    }
+
+$browse_or_search
+
+// ]]>
+</script>
+COLORFULEDIT
+}
+
+sub xmleditor_js {
+    return <<XMLEDIT
+<script type="text/javascript" src="/adm/jQuery/addons/jquery-scrolltofixed.js"></script>
+<script type="text/javascript">
+// <![CDATA[>
+
+    function saveScrollPosition (resource) {
+
+        var scrollPos = \$(window).scrollTop();
+        sessionStorage.setItem(resource,scrollPos);
+    }
+
+    function restoreScrollPosition(resource){
+
+        var scrollPos = sessionStorage.getItem(resource);
+        \$(window).scrollTop(scrollPos);
+    }
+
+    // unless internet explorer
+    if (!(window.navigator.appName == "Microsoft Internet Explorer" && (document.documentMode || document.compatMode))){
+
+        \$(document).ready(function() {
+             \$(".LC_edit_actionbar").scrollToFixed(\{zIndex: 100\});
+        });
+    }
+
+    // inserts text at cursor position into codemirror (xml editor only)
+    function insertText(text){
+        cm.focus();
+        var curPos = cm.getCursor();
+        cm.replaceRange(text.replace(/ESCAPEDSCRIPT/g,'script'), {line: curPos.line,ch: curPos.ch});
+    }
+// ]]>
+</script>
+XMLEDIT
+}
+
+sub insert_folding_button {
+    my $curDepth = $Apache::lonxml::curdepth;
+    my $lastresource = $env{'request.ambiguous'};
+
+    return "<input type=\"button\" id=\"folding_btn_$curDepth\" 
+            value=\"".&mt('Hide')."\" onclick=\"fold_box('$curDepth','$lastresource')\">";
+}
+
+sub crsauthor_url {
+    my ($url) = @_;
+    if ($url eq '') {
+        $url = $ENV{'REQUEST_URI'};
+    }
+    my ($cnum,$cdom);
+    if ($env{'request.course.id'}) {
+        my ($audom,$auname) = ($url =~ m{^/priv/($match_domain)/($match_name)/});
+        if ($audom ne '' && $auname ne '') {
+            if (($env{'course.'.$env{'request.course.id'}.'.num'} eq $auname) &&
+                ($env{'course.'.$env{'request.course.id'}.'.domain'} eq $audom)) {
+                $cnum = $auname;
+                $cdom = $audom;
+            }
+        }
+    }
+    return ($cnum,$cdom);
+}
+
+sub import_crsauthor_form {
+    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 @ids=&Apache::lonnet::current_machine_ids();
+    my ($output,$is_home,$toppath,%subdirs,%files,%selimport_menus,$include,$exclude);
+
+    if (grep(/^\Q$crshome\E$/,@ids)) {
+        $is_home = 1;
+    }
+    $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',
+    );
+    $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;
+                    }
+                }
+                return;
+            } else {
+                relpath = encodeURIComponent(form.elements[dirsel].options[form.elements[dirsel].selectedIndex].value);
+            }
+        }
+        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;
+                    }
+                }
+            }
+        }
+        http.send(params);
+    }
+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);
+        }
+    }
+    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);
+        }
+    }
+    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
 
 =head1 Excel and CSV file utility routines
@@ -1941,10 +2786,24 @@ sub create_text_file {
 # ------------------------------------------
 
 sub domain_select {
-    my ($name,$value,$multiple)=@_;
+    my ($name,$value,$multiple,$incdoms,$excdoms)=@_;
+    my @possdoms;
+    if (ref($incdoms) eq 'ARRAY') {
+        @possdoms = @{$incdoms};
+    } else {
+        @possdoms = &Apache::lonnet::all_domains();
+    }
+
     my %domains=map { 
 	$_ => $_.' '. &Apache::lonnet::domain($_,'description') 
-    } &Apache::lonnet::all_domains();
+    } @possdoms;
+
+    if ((ref($excdoms) eq 'ARRAY') && (@{$excdoms} > 0)) {
+        foreach my $dom (@{$excdoms}) {
+            delete($domains{$dom});
+        }
+    }
+
     if ($multiple) {
 	$domains{''}=&mt('Any domain');
 	$domains{'select_form_order'} = [sort {lc($a) cmp lc($b) } (keys(%domains))];
@@ -2013,12 +2872,15 @@ sub multiple_select_form {
 
 =pod
 
-=item * &select_form($defdom,$name,$hashref,$onchange)
+=item * &select_form($defdom,$name,$hashref,$onchange,$readonly)
 
 Returns a string containing a <select name='$name' size='1'> form to 
 allow a user to select options from a ref to a hash containing:
 option_name => displayed text. An optional $onchange can include
-a javascript onchange item, e.g., onchange="this.form.submit();"  
+a javascript onchange item, e.g., onchange="this.form.submit();".
+An optional arg -- $readonly -- if true will cause the select form
+to be disabled, e.g., for the case where an instructor has a section-
+specific role, and is viewing/modifying parameters. 
 
 See lonrights.pm for an example invocation and use.
 
@@ -2026,12 +2888,16 @@ See lonrights.pm for an example invocati
 
 #-------------------------------------------
 sub select_form {
-    my ($def,$name,$hashref,$onchange) = @_;
+    my ($def,$name,$hashref,$onchange,$readonly) = @_;
     return unless (ref($hashref) eq 'HASH');
     if ($onchange) {
         $onchange = ' onchange="'.$onchange.'"';
     }
-    my $selectform = "<select name=\"$name\" size=\"1\"$onchange>\n";
+    my $disabled;
+    if ($readonly) {
+        $disabled = ' disabled="disabled"';
+    }
+    my $selectform = "<select name=\"$name\" size=\"1\"$onchange$disabled>\n";
     my @keys;
     if (exists($hashref->{'select_form_order'})) {
 	@keys=@{$hashref->{'select_form_order'}};
@@ -2082,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]',
@@ -2200,7 +3066,7 @@ sub select_level_form {
 
 =pod
 
-=item * &select_dom_form($defdom,$name,$includeempty,$showdomdesc,$onchange,$incdoms,$excdoms)
+=item * &select_dom_form($defdom,$name,$includeempty,$showdomdesc,$onchange,$incdoms,$excdoms,$disabled)
 
 Returns a string containing a <select name='$name' size='1'> form to 
 allow a user to select the domain to preform an operation in.  
@@ -2217,14 +3083,19 @@ The optional $incdoms is a reference to
 
 The optional $excdoms is a reference to an array of domains which will be excluded from the available options.
 
+The optional $disabled argument, if true, adds the disabled attribute to the select tag.
+
 =cut
 
 #-------------------------------------------
 sub select_dom_form {
-    my ($defdom,$name,$includeempty,$showdomdesc,$onchange,$incdoms,$excdoms) = @_;
+    my ($defdom,$name,$includeempty,$showdomdesc,$onchange,$incdoms,$excdoms,$disabled) = @_;
     if ($onchange) {
         $onchange = ' onchange="'.$onchange.'"';
     }
+    if ($disabled) {
+        $disabled = ' disabled="disabled"';
+    }
     my (@domains,%exclude);
     if (ref($incdoms) eq 'ARRAY') {
         @domains = sort {lc($a) cmp lc($b)} (@{$incdoms});
@@ -2235,7 +3106,7 @@ sub select_dom_form {
     if (ref($excdoms) eq 'ARRAY') {
         map { $exclude{$_} = 1; } @{$excdoms}; 
     }
-    my $selectdomain = "<select name=\"$name\" size=\"1\"$onchange>\n";
+    my $selectdomain = "<select name=\"$name\" size=\"1\"$onchange$disabled>\n";
     foreach my $dom (@domains) {
         next if ($exclude{$dom});
         $selectdomain.="<option value=\"$dom\" ".
@@ -2362,6 +3233,8 @@ Outputs:
 
 =item * $clientinfo
 
+=item * $clientosversion
+
 =back
 
 =back 
@@ -2381,8 +3254,9 @@ sub decode_user_agent {
     my $clientmathml='';
     my $clientunicode='0';
     my $clientmobile=0;
+    my $clientosversion='';
     for (my $i=0;$i<=$#browsertype;$i++) {
-        my ($bname,$match,$notmatch,$vreg,$minv,$univ)=split(/\:/,$browsertype[$i]);
+        my ($bname,$match,$notmatch,$vreg,$minv,$univ)=split(/\%/,$browsertype[$i]);
 	if (($httpbrowser=~/$match/i)  && ($httpbrowser!~/$notmatch/i)) {
 	    $clientbrowser=$bname;
             $httpbrowser=~/$vreg/i;
@@ -2402,7 +3276,12 @@ sub decode_user_agent {
     if ($httpbrowser=~/next/i) { $clientos='next'; }
     if (($httpbrowser=~/mac/i) ||
         ($httpbrowser=~/powerpc/i)) { $clientos='mac'; }
-    if ($httpbrowser=~/win/i) { $clientos='win'; }
+    if ($httpbrowser=~/win/i) {
+        $clientos='win';
+        if ($httpbrowser =~/Windows\s+NT\s+(\d+\.\d+)/i) {
+            $clientosversion = $1;
+        }
+    }
     if ($httpbrowser=~/embed/i) { $clientos='pda'; }
     if ($httpbrowser=~/(Android|iPod|iPad|iPhone|webOS|Blackberry|Windows Phone|Opera m(?:ob|in)|Fennec)/i) {
         $clientmobile=lc($1);
@@ -2413,7 +3292,8 @@ sub decode_user_agent {
         $clientinfo = 'chromeframe-'.$1;
     }
     return ($httpbrowser,$clientbrowser,$clientversion,$clientmathml,
-            $clientunicode,$clientos,$clientmobile,$clientinfo);
+            $clientunicode,$clientos,$clientmobile,$clientinfo,
+            $clientosversion);
 }
 
 ###############################################################
@@ -2452,6 +3332,8 @@ This is not an optimal method, but it wo
 
 =item * authform_filesystem
 
+=item * authform_lti
+
 =back
 
 See loncreateuser.pm for invocation and use examples.
@@ -2602,13 +3484,16 @@ sub authform_kerberos {
               @_,
               );
     my ($check4,$check5,$krbcheck,$krbarg,$krbver,$result,$authtype,
-        $autharg,$jscall);
+        $autharg,$jscall,$disabled);
     my ($authnum,%can_assign) = &get_assignable_auth($in{'domain'});
     if ($in{'kerb_def_auth'} eq 'krb5') {
        $check5 = ' checked="checked"';
     } else {
        $check4 = ' checked="checked"';
     }
+    if ($in{'readonly'}) {
+        $disabled = ' disabled="disabled"';
+    }
     $krbarg = $in{'kerb_def_dom'};
     if (defined($in{'curr_authtype'})) {
         if ($in{'curr_authtype'} eq 'krb') {
@@ -2653,7 +3538,7 @@ sub authform_kerberos {
         if (defined($in{'mode'})) {
             if ($in{'mode'} eq 'modifycourse') {
                 if ($authnum == 1) {
-                    $authtype = '<input type="radio" name="login" value="krb" />';
+                    $authtype = '<input type="radio" name="login" value="krb"'.$disabled.' />';
                 }
             }
         }
@@ -2662,7 +3547,7 @@ sub authform_kerberos {
     if ($authtype eq '') {
         $authtype = '<input type="radio" name="login" value="krb" '.
                     'onclick="'.$jscall.'" onchange="'.$jscall.'"'.
-                    $krbcheck.' />';
+                    $krbcheck.$disabled.' />';
     }
     if (($can_assign{'krb4'} && $can_assign{'krb5'}) ||
         ($can_assign{'krb4'} && !$can_assign{'krb5'} &&
@@ -2675,9 +3560,9 @@ sub authform_kerberos {
          '<label>'.$authtype,
          '</label><input type="text" size="10" name="krbarg" '.
              'value="'.$krbarg.'" '.
-             'onchange="'.$jscall.'" />',
-         '<label><input type="radio" name="krbver" value="4" '.$check4.' />',
-         '</label><label><input type="radio" name="krbver" value="5" '.$check5.' />',
+             'onchange="'.$jscall.'"'.$disabled.' />',
+         '<label><input type="radio" name="krbver" value="4" '.$check4.$disabled.' />',
+         '</label><label><input type="radio" name="krbver" value="5" '.$check5.$disabled.' />',
 	 '</label>');
     } elsif ($can_assign{'krb4'}) {
         $result .= &mt
@@ -2686,7 +3571,7 @@ sub authform_kerberos {
          '<label>'.$authtype,
          '</label><input type="text" size="10" name="krbarg" '.
              'value="'.$krbarg.'" '.
-             'onchange="'.$jscall.'" />',
+             'onchange="'.$jscall.'"'.$disabled.' />',
          '<label><input type="hidden" name="krbver" value="4" />',
          '</label>');
     } elsif ($can_assign{'krb5'}) {
@@ -2696,7 +3581,7 @@ sub authform_kerberos {
          '<label>'.$authtype,
          '</label><input type="text" size="10" name="krbarg" '.
              'value="'.$krbarg.'" '.
-             'onchange="'.$jscall.'" />',
+             'onchange="'.$jscall.'"'.$disabled.' />',
          '<label><input type="hidden" name="krbver" value="5" />',
          '</label>');
     }
@@ -2709,8 +3594,11 @@ sub authform_internal {
                 kerb_def_dom => 'MSU.EDU',
                 @_,
                 );
-    my ($intcheck,$intarg,$result,$authtype,$autharg,$jscall);
+    my ($intcheck,$intarg,$result,$authtype,$autharg,$jscall,$disabled);
     my ($authnum,%can_assign) = &get_assignable_auth($in{'domain'});
+    if ($in{'readonly'}) {
+        $disabled = ' disabled="disabled"';
+    }
     if (defined($in{'curr_authtype'})) {
         if ($in{'curr_authtype'} eq 'int') {
             if ($can_assign{'int'}) {
@@ -2739,7 +3627,7 @@ sub authform_internal {
         if (defined($in{'mode'})) {
             if ($in{'mode'} eq 'modifycourse') {
                 if ($authnum == 1) {
-                    $authtype = '<input type="radio" name="login" value="int" />';
+                    $authtype = '<input type="radio" name="login" value="int"'.$disabled.' />';
                 }
             }
         }
@@ -2747,14 +3635,14 @@ sub authform_internal {
     $jscall = "javascript:changed_radio('int',$in{'formname'});";
     if ($authtype eq '') {
         $authtype = '<input type="radio" name="login" value="int" '.$intcheck.
-                    ' onchange="'.$jscall.'" onclick="'.$jscall.'" />';
+                    ' onchange="'.$jscall.'" onclick="'.$jscall.'"'.$disabled.' />';
     }
     $autharg = '<input type="password" size="10" name="intarg" value="'.
-               $intarg.'" onchange="'.$jscall.'" />';
+               $intarg.'" onchange="'.$jscall.'"'.$disabled.' />';
     $result = &mt
         ('[_1] Internally authenticated (with initial password [_2])',
          '<label>'.$authtype,'</label>'.$autharg);
-    $result.="<label><input type=\"checkbox\" name=\"visible\" onclick='if (this.checked) { this.form.intarg.type=\"text\" } else { this.form.intarg.type=\"password\" }' />".&mt('Visible input').'</label>';
+    $result.='<label><input type="checkbox" name="visible" onclick="if (this.checked) { this.form.intarg.type='."'text'".' } else { this.form.intarg.type='."'password'".' }"'.$disabled.' />'.&mt('Visible input').'</label>';
     return $result;
 }
 
@@ -2764,8 +3652,11 @@ sub authform_local {
               kerb_def_dom => 'MSU.EDU',
               @_,
               );
-    my ($loccheck,$locarg,$result,$authtype,$autharg,$jscall);
+    my ($loccheck,$locarg,$result,$authtype,$autharg,$jscall,$disabled);
     my ($authnum,%can_assign) = &get_assignable_auth($in{'domain'});
+    if ($in{'readonly'}) {
+        $disabled = ' disabled="disabled"';
+    } 
     if (defined($in{'curr_authtype'})) {
         if ($in{'curr_authtype'} eq 'loc') {
             if ($can_assign{'loc'}) {
@@ -2794,7 +3685,7 @@ sub authform_local {
         if (defined($in{'mode'})) {
             if ($in{'mode'} eq 'modifycourse') {
                 if ($authnum == 1) {
-                    $authtype = '<input type="radio" name="login" value="loc" />';
+                    $authtype = '<input type="radio" name="login" value="loc"'.$disabled.' />';
                 }
             }
         }
@@ -2803,10 +3694,10 @@ sub authform_local {
     if ($authtype eq '') {
         $authtype = '<input type="radio" name="login" value="loc" '.
                     $loccheck.' onchange="'.$jscall.'" onclick="'.
-                    $jscall.'" />';
+                    $jscall.'"'.$disabled.' />';
     }
     $autharg = '<input type="text" size="10" name="locarg" value="'.
-               $locarg.'" onchange="'.$jscall.'" />';
+               $locarg.'" onchange="'.$jscall.'"'.$disabled.' />';
     $result = &mt('[_1] Local Authentication with argument [_2]',
                   '<label>'.$authtype,'</label>'.$autharg);
     return $result;
@@ -2818,8 +3709,11 @@ sub authform_filesystem {
               kerb_def_dom => 'MSU.EDU',
               @_,
               );
-    my ($fsyscheck,$result,$authtype,$autharg,$jscall);
+    my ($fsyscheck,$result,$authtype,$autharg,$jscall,$disabled);
     my ($authnum,%can_assign) = &get_assignable_auth($in{'domain'});
+    if ($in{'readonly'}) {
+        $disabled = ' disabled="disabled"';
+    }
     if (defined($in{'curr_authtype'})) {
         if ($in{'curr_authtype'} eq 'fsys') {
             if ($can_assign{'fsys'}) {
@@ -2832,7 +3726,7 @@ sub authform_filesystem {
             } else {
                 $result = &mt('Currently Filesystem Authenticated.');
                 return $result;
-            }           
+            }
         }
     } else {
         if ($authnum == 1) {
@@ -2845,7 +3739,7 @@ sub authform_filesystem {
         if (defined($in{'mode'})) {
             if ($in{'mode'} eq 'modifycourse') {
                 if ($authnum == 1) {
-                    $authtype = '<input type="radio" name="login" value="fsys" />';
+                    $authtype = '<input type="radio" name="login" value="fsys"'.$disabled.' />';
                 }
             }
         }
@@ -2854,16 +3748,71 @@ sub authform_filesystem {
     if ($authtype eq '') {
         $authtype = '<input type="radio" name="login" value="fsys" '.
                     $fsyscheck.' onchange="'.$jscall.'" onclick="'.
-                    $jscall.'" />';
+                    $jscall.'"'.$disabled.' />';
     }
-    $autharg = '<input type="text" size="10" name="fsysarg" value=""'.
-               ' onchange="'.$jscall.'" />';
+    $autharg = '<input type="password" size="10" name="fsysarg" value=""'.
+               ' onchange="'.$jscall.'"'.$disabled.' />';
     $result = &mt
         ('[_1] Filesystem Authenticated (with initial password [_2])',
-         '<label><input type="radio" name="login" value="fsys" '.
-         $fsyscheck.'onchange="'.$jscall.'" onclick="'.$jscall.'" />',
-         '</label><input type="password" size="10" name="fsysarg" value="" '.
-                  'onchange="'.$jscall.'" />');
+         '<label>'.$authtype,'</label>'.$autharg);
+    return $result;
+}
+
+sub authform_lti {
+    my %in = (
+              formname => 'document.cu',
+              kerb_def_dom => 'MSU.EDU',
+              @_,
+              );
+    my ($lticheck,$result,$authtype,$autharg,$jscall,$disabled);
+    my ($authnum,%can_assign) = &get_assignable_auth($in{'domain'});
+    if ($in{'readonly'}) {
+        $disabled = ' disabled="disabled"';
+    }
+    if (defined($in{'curr_authtype'})) {
+        if ($in{'curr_authtype'} eq 'lti') {
+            if ($can_assign{'lti'}) {
+                $lticheck = 'checked="checked" ';
+                if (defined($in{'mode'})) {
+                    if ($in{'mode'} eq 'modifyuser') {
+                        $lticheck = '';
+                    }
+                }
+            } else {
+                $result = &mt('Currently LTI Authenticated.');
+                return $result;
+            }
+        }
+    } else {
+        if ($authnum == 1) {
+            $authtype = '<input type="hidden" name="login" value="lti" />';
+        }
+    }
+    if (!$can_assign{'lti'}) {
+        return;
+    } elsif ($authtype eq '') {
+        if (defined($in{'mode'})) {
+            if ($in{'mode'} eq 'modifycourse') {
+                if ($authnum == 1) {
+                    $authtype = '<input type="radio" name="login" value="lti"'.$disabled.' />';
+                }
+            }
+        }
+    }
+    $jscall = "javascript:changed_radio('lti',$in{'formname'});";
+    if (($authtype eq '') && (($in{'mode'} eq 'modifycourse') || ($in{'curr_authtype'} ne 'lti'))) {
+        $authtype = '<input type="radio" name="login" value="lti" '.
+                    $lticheck.' onchange="'.$jscall.'" onclick="'.
+                    $jscall.'"'.$disabled.' />';
+    }
+    $autharg = '<input type="hidden" name="ltiarg" value="" />';
+    if ($authtype) {
+        $result = &mt('[_1] LTI Authenticated',
+                      '<label>'.$authtype.'</label>'.$autharg);
+    } else {
+        $result = '<b>'.&mt('LTI Authenticated').'</b>'.
+                  $autharg;
+    }
     return $result;
 }
 
@@ -2877,6 +3826,7 @@ sub get_assignable_auth {
                           krb5 => 1,
                           int  => 1,
                           loc  => 1,
+                          lti  => 1,
                      );
     my %domconfig = &Apache::lonnet::get_dom('configuration',['usercreation'],$dom);
     if (ref($domconfig{'usercreation'}) eq 'HASH') {
@@ -2885,7 +3835,7 @@ sub get_assignable_auth {
             my $context;
             if ($env{'request.role'} =~ /^au/) {
                 $context = 'author';
-            } elsif ($env{'request.role'} =~ /^dc/) {
+            } elsif ($env{'request.role'} =~ /^(dc|dh)/) {
                 $context = 'domain';
             } elsif ($env{'request.course.id'}) {
                 $context = 'course';
@@ -2909,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                 ##
 ###############################################################
@@ -3389,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?";
@@ -3606,7 +4817,11 @@ category
 
 sub filecategorytypes {
     my ($cat) = @_;
-    return @{$category_extensions{lc($cat)}};
+    if (ref($category_extensions{lc($cat)}) eq 'ARRAY') { 
+        return @{$category_extensions{lc($cat)}};
+    } else {
+        return ();
+    }
 }
 
 =pod
@@ -3749,7 +4964,7 @@ sub user_lang {
 =over 4
 
 =item * &get_previous_attempt($symb, $username, $domain, $course,
-    $getattempt, $regexp, $gradesub)
+    $getattempt, $regexp, $gradesub, $usec, $identifier)
 
 Return string with previous attempt on problem. Arguments:
 
@@ -3771,6 +4986,11 @@ Return string with previous attempt on p
 
 =item * $gradesub: routine that processes the string if it matches $regexp
 
+=item * $usec: section of the desired student
+
+=item * $identifier: counter for student (multiple students one problem) or 
+    problem (one student; whole sequence).
+
 =back
 
 The output string is a table containing all desired attempts, if any.
@@ -3778,7 +4998,7 @@ The output string is a table containing
 =cut
 
 sub get_previous_attempt {
-  my ($symb,$username,$domain,$course,$getattempt,$regexp,$gradesub)=@_;
+  my ($symb,$username,$domain,$course,$getattempt,$regexp,$gradesub,$usec,$identifier)=@_;
   my $prevattempts='';
   no strict 'refs';
   if ($symb) {
@@ -3788,13 +5008,18 @@ sub get_previous_attempt {
       my %lasthash=();
       my $version;
       for ($version=1;$version<=$returnhash{'version'};$version++) {
-        foreach my $key (sort(split(/\:/,$returnhash{$version.':keys'}))) {
-	  $lasthash{$key}=$returnhash{$version.':'.$key};
+        foreach my $key (reverse(sort(split(/\:/,$returnhash{$version.':keys'})))) {
+            if ($key =~ /\.rawrndseed$/) {
+                my ($id) = ($key =~ /^(.+)\.rawrndseed$/);
+                $lasthash{$id.'.rndseed'} = $returnhash{$version.':'.$key};
+            } else {
+                $lasthash{$key}=$returnhash{$version.':'.$key};
+            }
         }
       }
       $prevattempts=&start_data_table().&start_data_table_header_row();
       $prevattempts.='<th>'.&mt('History').'</th>';
-      my (%typeparts,%lasthidden);
+      my (%typeparts,%lasthidden,%regraded,%hidestatus);
       my $showsurv=&Apache::lonnet::allowed('vas',$env{'request.course.id'});
       foreach my $key (sort(keys(%lasthash))) {
 	my ($ign,@parts) = split(/\./,$key);
@@ -3811,6 +5036,18 @@ sub get_previous_attempt {
                       $lasthidden{$ign.'.'.$id} = 1;
                   }
               }
+              if ($identifier ne '') {
+                  my $id = join(',',@parts);
+                  if (&Apache::lonnet::EXT("resource.$id.problemstatus",$symb,
+                                               $domain,$username,$usec,undef,$course) =~ /^no/) {
+                      $hidestatus{$ign.'.'.$id} = 1;
+                  }
+              }
+          } elsif ($data eq 'regrader') {
+              if (($identifier ne '') && (@parts)) {
+                  my $id = join(',',@parts);
+                  $regraded{$ign.'.'.$id} = 1;
+              }
           } 
 	} else {
 	  if ($#parts == 0) {
@@ -3822,17 +5059,60 @@ sub get_previous_attempt {
       }
       $prevattempts.=&end_data_table_header_row();
       if ($getattempt eq '') {
+        my (%solved,%resets,%probstatus);
+        if (($identifier ne '') && (keys(%regraded) > 0)) {
+            for ($version=1;$version<=$returnhash{'version'};$version++) {
+                foreach my $id (keys(%regraded)) {
+                    if (($returnhash{$version.':'.$id.'.regrader'}) &&
+                        ($returnhash{$version.':'.$id.'.tries'} eq '') &&
+                        ($returnhash{$version.':'.$id.'.award'} eq '')) {
+                        push(@{$resets{$id}},$version);
+                    }
+                }
+            }
+        }
 	for ($version=1;$version<=$returnhash{'version'};$version++) {
-            my @hidden;
+            my (@hidden,@unsolved);
             if (%typeparts) {
                 foreach my $id (keys(%typeparts)) {
-                    if (($returnhash{$version.':'.$id.'.type'} eq 'anonsurvey') || ($returnhash{$version.':'.$id.'.type'} eq 'anonsurveycred')) {
+                    if (($returnhash{$version.':'.$id.'.type'} eq 'anonsurvey') || 
+                        ($returnhash{$version.':'.$id.'.type'} eq 'anonsurveycred')) {
                         push(@hidden,$id);
+                    } elsif ($identifier ne '') {
+                        unless (($returnhash{$version.':'.$id.'.type'} eq 'survey') ||
+                                ($returnhash{$version.':'.$id.'.type'} eq 'surveycred') ||
+                                ($hidestatus{$id})) {
+                            next if ((ref($resets{$id}) eq 'ARRAY') && grep(/^\Q$version\E$/,@{$resets{$id}}));
+                            if ($returnhash{$version.':'.$id.'.solved'} eq 'correct_by_student') {
+                                push(@{$solved{$id}},$version);
+                            } elsif (($returnhash{$version.':'.$id.'.solved'} ne '') &&
+                                     (ref($solved{$id}) eq 'ARRAY')) {
+                                my $skip;
+                                if (ref($resets{$id}) eq 'ARRAY') {
+                                    foreach my $reset (@{$resets{$id}}) {
+                                        if ($reset > $solved{$id}[-1]) {
+                                            $skip=1;
+                                            last;
+                                        }
+                                    }
+                                }
+                                unless ($skip) {
+                                    my ($ign,$partslist) = split(/\./,$id,2);
+                                    push(@unsolved,$partslist);
+                                }
+                            }
+                        }
                     }
                 }
             }
             $prevattempts.=&start_data_table_row().
-                           '<td>'.&mt('Transaction [_1]',$version).'</td>';
+                           '<td>'.&mt('Transaction [_1]',$version);
+            if (@unsolved) {
+                $prevattempts .= '<span class="LC_nobreak"><label>'.
+                                 '<input type="checkbox" name="HIDE'.$identifier.'" value="'.$version.':'.join('_',@unsolved).'" />'.
+                                 &mt('Hide').'</label></span>';
+            }
+            $prevattempts .= '</td>';
             if (@hidden) {
                 foreach my $key (sort(keys(%lasthash))) {
                     next if ($key =~ /\.foilorder$/);
@@ -3854,9 +5134,15 @@ sub get_previous_attempt {
                         }
                     } else {
                         if ($key =~ /\./) {
-                            my $value = &format_previous_attempt_value($key,
-                                              $returnhash{$version.':'.$key});
-                            $prevattempts.='<td>'.$value.'&nbsp;</td>';
+                            my $value = $returnhash{$version.':'.$key};
+                            if ($key =~ /\.rndseed$/) {
+                                my ($id) = ($key =~ /^(.+)\.[^.]+$/);
+                                if (exists($returnhash{$version.':'.$id.'.rawrndseed'})) {
+                                    $value = $returnhash{$version.':'.$id.'.rawrndseed'};
+                                }
+                            }
+                            $prevattempts.='<td>'.&format_previous_attempt_value($key,$value).
+                                           '&nbsp;</td>';
                         } else {
                             $prevattempts.='<td>&nbsp;</td>';
                         }
@@ -3865,9 +5151,15 @@ sub get_previous_attempt {
             } else {
 	        foreach my $key (sort(keys(%lasthash))) {
                     next if ($key =~ /\.foilorder$/);
-		    my $value = &format_previous_attempt_value($key,
-			            $returnhash{$version.':'.$key});
-		    $prevattempts.='<td>'.$value.'&nbsp;</td>';
+                    my $value = $returnhash{$version.':'.$key};
+                    if ($key =~ /\.rndseed$/) {
+                        my ($id) = ($key =~ /^(.+)\.[^.]+$/);
+                        if (exists($returnhash{$version.':'.$id.'.rawrndseed'})) {
+                            $value = $returnhash{$version.':'.$id.'.rawrndseed'};
+                        }
+                    }
+                    $prevattempts.='<td>'.&format_previous_attempt_value($key,$value).
+                                   '&nbsp;</td>';
 	        }
             }
 	    $prevattempts.=&end_data_table_row();
@@ -3913,9 +5205,15 @@ sub get_previous_attempt {
       }
       $prevattempts.= &end_data_table_row().&end_data_table();
     } else {
+      my $msg;
+      if ($symb =~ /ext\.tool$/) {
+          $msg = &mt('No grade passed back.');
+      } else {
+          $msg = &mt('Nothing submitted - no attempts.');
+      }
       $prevattempts=
 	  &start_data_table().&start_data_table_row().
-	  '<td>'.&mt('Nothing submitted - no attempts.').'</td>'.
+	  '<td>'.$msg.'</td>'.
 	  &end_data_table_row().&end_data_table();
     }
   } else {
@@ -4020,6 +5318,9 @@ sub get_student_view {
   }
   if (defined($target)) { $form{'grade_target'} = $target; }
   $feedurl=&Apache::lonnet::clutter($feedurl);
+  if (($feedurl =~ /ext\.tool$/) && ($target eq 'tex')) {
+      $feedurl =~ s{^/adm/wrapper}{};
+  }
   my ($userview,$response)=&Apache::lonnet::ssi_body($feedurl,%form);
   $userview=~s/\<body[^\>]*\>//gi;
   $userview=~s/\<\/body\>//gi;
@@ -4064,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() 
@@ -4319,33 +5673,119 @@ sub findallcourses {
 ###############################################
 
 sub blockcheck {
-    my ($setters,$activity,$uname,$udom,$url) = @_;
-
-    if (!defined($udom)) {
-        $udom = $env{'user.domain'};
+    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($uname)) {
+    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,$symb,$caller);
+            return ($startblock,$endblock,$triggerblock);
+        }
+    } else {
+        $udom = $env{'user.domain'};
         $uname = $env{'user.name'};
     }
 
-    # If uname and udom are for a course, check for blocks in the course.
-
-    if (&Apache::lonnet::is_course($udom,$uname)) {
-        my ($startblock,$endblock,$triggerblock) = 
-            &get_blocks($setters,$activity,$udom,$uname,$url);
-        return ($startblock,$endblock,$triggerblock);
-    }
-
     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') && ($env{'request.course.id'})) {
+         $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)) {
             if ($key ne $env{'request.course.id'}) {
                 delete($live_courses{$key});
@@ -4428,7 +5868,7 @@ sub blockcheck {
                                                                 $tdom,$spec,$trest,$area);
                         }
                     }
-                    my ($author,$adv) = &Apache::lonnet::set_userprivs(\%userroles,\%allroles);
+                    my ($author,$adv,$rar) = &Apache::lonnet::set_userprivs(\%userroles,\%allroles);
                     if ($userroles{'user.priv.'.$checkrole} =~ /evb\&([^\:]*)/) {
                         if ($1) {
                             $no_userblock = 1;
@@ -4450,11 +5890,11 @@ sub blockcheck {
                  ($env{'request.role'} !~ m{^st\./\Q$cdom\E/\Q$cnum\E}));
         next if ($no_userblock);
 
-        # Retrieve blocking times and identity of locker for course
+        # Retrieve blocking times and identity of blocker for course
         # 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;
@@ -4474,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 = '';
@@ -4487,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;
@@ -4539,13 +5985,19 @@ sub get_blocks {
                 my $end = $start + $env{'course.'.$cdom.'_'.$cnum.'.timerinterval.'.$timersymb}; 
                 if ($start && $end) {
                     if (($start <= time) && ($end >= time)) {
-                        unless (grep(/^\Q$block\E$/,@blockers)) {
-                            push(@blockers,$block);
-                            $triggered{$block} = {
-                                                   start => $start,
-                                                   end   => $end,
-                                                   type  => $type,
-                                                 };
+                        if (ref($commblocks{$block}) eq 'HASH') {
+                            if (ref($commblocks{$block}{'blocks'}) eq 'HASH') {
+                                if ($commblocks{$block}{'blocks'}{$activity} eq 'on') {
+                                    unless(grep(/^\Q$block\E$/,@blockers)) {
+                                        push(@blockers,$block);
+                                        $triggered{$block} = {
+                                                               start => $start,
+                                                               end   => $end,
+                                                               type  => $type,
+                                                             };
+                                    }
+                                }
+                            }
                         }
                     }
                 }
@@ -4609,14 +6061,17 @@ sub parse_block_record {
 }
 
 sub blocking_status {
-    my ($activity,$uname,$udom,$url) = @_;
+    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);
+    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;
     }
 
@@ -4625,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') {
-        $querystring .= "&amp;udom=$udom"      if $udom;
-        $querystring .= "&amp;uname=$uname"    if $uname;
+# $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';
@@ -4647,13 +6107,33 @@ END_MYBLOCK
   
     my $popupUrl = "/adm/blockingstatus/$querystring";
     my $text = &mt('Communication Blocked');
+    my $class = 'LC_comblock';
     if ($activity eq 'docs') {
         $text = &mt('Content Access Blocked');
+        $class = '';
     } elsif ($activity eq 'printout') {
         $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='LC_comblock'>
+<div class='$class'>
   <a onclick='openWindow("$popupUrl","Blocking Table",600,300,"no","no");return false;' href='/adm/blockingstatus/$querystring'
   title='$text'>
   <img class='LC_noBorder LC_middle' title='$text' src='/res/adm/pages/comblock.png' alt='$text'/></a>
@@ -4669,22 +6149,50 @@ END_BLOCK
 ###############################################
 
 sub check_ip_acc {
-    my ($acc)=@_;
+    my ($acc,$clientip)=@_;
     &Apache::lonxml::debug("acc is $acc");
     if (!defined($acc) || $acc =~ /^\s*$/ || $acc =~/^\s*no\s*$/i) {
         return 1;
     }
-    my $allowed=0;
-    my $ip=$env{'request.host'} || $ENV{'REMOTE_ADDR'};
+    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;
-    foreach my $pattern (split(',',$acc)) {
-        $pattern =~ s/^\s*//;
-        $pattern =~ s/\s*$//;
+    my %access = (
+                     allowfrom => 1,
+                     denyfrom  => 0,
+                 );
+    my @allows;
+    my @denies;
+    foreach my $item (split(',',$acc)) {
+        $item =~ s/^\s*//;
+        $item =~ s/\s*$//;
+        my $pattern;
+        if ($item =~ /^\!(.+)$/) {
+            push(@denies,$1);
+        } else {
+            push(@allows,$item);
+        }
+   }
+   my $numdenies = scalar(@denies);
+   my $numallows = scalar(@allows);
+   my $count = 0;
+   foreach my $pattern (@denies,@allows) {
+        $count ++; 
+        my $acctype = 'allowfrom';
+        if ($count <= $numdenies) {
+            $acctype = 'denyfrom';
+        }
         if ($pattern =~ /\*$/) {
             #35.8.*
             $pattern=~s/\*//;
-            if ($ip =~ /^\Q$pattern\E/) { $allowed=1; }
+            if ($ip =~ /^\Q$pattern\E/) { $allowed=$access{$acctype}; }
         } elsif ($pattern =~ /(\d+\.\d+\.\d+)\.\[(\d+)-(\d+)\]$/) {
             #35.8.3.[34-56]
             my $low=$2;
@@ -4692,7 +6200,7 @@ sub check_ip_acc {
             $pattern=$1;
             if ($ip =~ /^\Q$pattern\E/) {
                 my $last=(split(/\./,$ip))[3];
-                if ($last <=$high && $last >=$low) { $allowed=1; }
+                if ($last <=$high && $last >=$low) { $allowed=$access{$acctype}; }
             }
         } elsif ($pattern =~ /^\*/) {
             #*.msu.edu
@@ -4702,10 +6210,10 @@ sub check_ip_acc {
                 my $netaddr=inet_aton($ip);
                 ($name)=gethostbyaddr($netaddr,AF_INET);
             }
-            if ($name =~ /\Q$pattern\E$/i) { $allowed=1; }
+            if ($name =~ /\Q$pattern\E$/i) { $allowed=$access{$acctype}; }
         } elsif ($pattern =~ /\d+\.\d+\.\d+\.\d+/) {
             #127.0.0.1
-            if ($ip =~ /^\Q$pattern\E/) { $allowed=1; }
+            if ($ip =~ /^\Q$pattern\E/) { $allowed=$access{$acctype}; }
         } else {
             #some.name.com
             if (!defined($name)) {
@@ -4713,9 +6221,16 @@ sub check_ip_acc {
                 my $netaddr=inet_aton($ip);
                 ($name)=gethostbyaddr($netaddr,AF_INET);
             }
-            if ($name =~ /\Q$pattern\E$/i) { $allowed=1; }
+            if ($name =~ /\Q$pattern\E$/i) { $allowed=$access{$acctype}; }
+        }
+        if ($allowed =~ /^(0|1)$/) { last; }
+    }
+    if ($allowed eq '') {
+        if ($numdenies && !$numallows) {
+            $allowed = 1;
+        } else {
+            $allowed = 0;
         }
-        if ($allowed) { last; }
     }
     return $allowed;
 }
@@ -4771,23 +6286,40 @@ sub get_domainconf {
             if (keys(%{$domconfig{'login'}})) {
                 foreach my $key (keys(%{$domconfig{'login'}})) {
                     if (ref($domconfig{'login'}{$key}) eq 'HASH') {
-                        if ($key eq 'loginvia') {
-                            if (ref($domconfig{'login'}{'loginvia'}) eq 'HASH') {
-                                foreach my $hostname (keys(%{$domconfig{'login'}{'loginvia'}})) {
-                                    if (ref($domconfig{'login'}{'loginvia'}{$hostname}) eq 'HASH') {
-                                        if ($domconfig{'login'}{'loginvia'}{$hostname}{'server'}) {
-                                            my $server = $domconfig{'login'}{'loginvia'}{$hostname}{'server'};
-                                            $designhash{$udom.'.login.loginvia'} = $server;
-                                            if ($domconfig{'login'}{'loginvia'}{$hostname}{'serverpath'} eq 'custom') {
-
-                                                $designhash{$udom.'.login.loginvia_'.$hostname} = $server.':'.$domconfig{'login'}{'loginvia'}{$hostname}{'custompath'};
-                                            } else {
-                                                $designhash{$udom.'.login.loginvia_'.$hostname} = $server.':'.$domconfig{'login'}{'loginvia'}{$hostname}{'serverpath'};
+                        if (($key eq 'loginvia') || ($key eq 'headtag')) {
+                            if (ref($domconfig{'login'}{$key}) eq 'HASH') {
+                                foreach my $hostname (keys(%{$domconfig{'login'}{$key}})) {
+                                    if (ref($domconfig{'login'}{$key}{$hostname}) eq 'HASH') {
+                                        if ($key eq 'loginvia') {
+                                            if ($domconfig{'login'}{'loginvia'}{$hostname}{'server'}) {
+                                                my $server = $domconfig{'login'}{'loginvia'}{$hostname}{'server'};
+                                                $designhash{$udom.'.login.loginvia'} = $server;
+                                                if ($domconfig{'login'}{'loginvia'}{$hostname}{'serverpath'} eq 'custom') {
+
+                                                    $designhash{$udom.'.login.loginvia_'.$hostname} = $server.':'.$domconfig{'login'}{'loginvia'}{$hostname}{'custompath'};
+                                                } else {
+                                                    $designhash{$udom.'.login.loginvia_'.$hostname} = $server.':'.$domconfig{'login'}{'loginvia'}{$hostname}{'serverpath'};
+                                                }
                                             }
-                                            if ($domconfig{'login'}{'loginvia'}{$hostname}{'exempt'}) {
-                                                $designhash{$udom.'.login.loginvia_exempt_'.$hostname} = $domconfig{'login'}{'loginvia'}{$hostname}{'exempt'};
+                                        } elsif ($key eq 'headtag') {
+                                            if ($domconfig{'login'}{'headtag'}{$hostname}{'url'}) {
+                                                $designhash{$udom.'.login.headtag_'.$hostname} = $domconfig{'login'}{'headtag'}{$hostname}{'url'};
                                             }
                                         }
+                                        if ($domconfig{'login'}{$key}{$hostname}{'exempt'}) {
+                                            $designhash{$udom.'.login.'.$key.'_exempt_'.$hostname} = $domconfig{'login'}{$key}{$hostname}{'exempt'};
+                                        }
+                                    }
+                                }
+                            }
+                        } 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};
+                                        }
                                     }
                                 }
                             }
@@ -4855,7 +6387,7 @@ sub get_legacy_domconf {
     my $designdir=$Apache::lonnet::perlvar{'lonTabDir'}.'/lonDomColors';
     my $designfile =  $designdir.'/'.$udom.'.tab';
     if (-e $designfile) {
-        if ( open (my $fh,"<$designfile") ) {
+        if ( open (my $fh,'<',$designfile) ) {
             while (my $line = <$fh>) {
                 next if ($line =~ /^\#/);
                 chomp($line);
@@ -4895,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 {
@@ -5014,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
@@ -5021,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'};
     }
@@ -5044,13 +6586,36 @@ sub CSTR_pageheader {
         $lastitem = $thisdisfn;
     }
 
+    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;
+        if ($title eq '') {
+            $title = &mt('Course Authoring Space');
+        }
+    } elsif ($title eq '') {
+        $title = &mt('Authoring Space');
+    }
+
+    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 =
          '<div>'
         .&Apache::loncommon::help_open_menu('','',3,'Authoring') #FIXME: Broken? Where is it?
-        .'<b>'.&mt('Authoring Space:').'</b> '
-        .'<form name="dirs" method="post" action="'.$formaction
-        .'" target="_top">' #FIXME lonpubdir: target="_parent"
-        .&Apache::lonhtmlcommon::crumbs($uname.'/'.$parentpath,'_top','/priv/'.$udom,undef,undef);
+        .'<b>'.$title.'</b> '
+        .'<form name="dirs" method="post" action="'.$formaction.'"'.$target.'>'
+        .&Apache::lonhtmlcommon::crumbs($uname.'/'.$parentpath,$crumbtarget,'/priv/'.$udom,undef,undef);
 
     if ($lastitem) {
         $output .=
@@ -5058,17 +6623,163 @@ sub CSTR_pageheader {
             .$lastitem
             .'</span>';
     }
-    $output .=
-         '<br />'
-        #FIXME lonpubdir: &Apache::lonhtmlcommon::crumbs($uname.$thisdisfn.'/','_top','/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()
-        .'</div>';
+
+    if ($crsauthor) {
+        $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($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;
+}
+
 ###############################################
 ###############################################
 
@@ -5108,14 +6819,43 @@ Inputs:
 
 =item * $args, optional argument valid values are
             no_auto_mt_title -> prevents &mt()ing the title arg
-            inherit_jsmath -> when creating popup window in a page,
-                              should it have jsmath forced on by the
-                              current page
+            use_absolute     -> for external resource or syllabus, this will
+                                contain https://<hostname> if server uses
+                                https (as per hosts.tab), but request is for http
+            hostname         -> hostname, from $r->hostname().
 
 =item * $advtoolsref, optional argument, ref to an array containing
             inlineremote items to be added in "Functions" menu below
             breadcrumbs.
 
+=item * $ltiscope, optional argument, will be one of: resource, map or
+            course, if LON-CAPA is in LTI Provider context. Value is
+            the scope of use, i.e., launch was for access to a single, a map
+            or the entire course.
+
+=item * $ltiuri, optional argument, if LON-CAPA is in LTI Provider
+            context, this will contain the URL for the landing item in
+            the course, after launch from an LTI Consumer
+
+=item * $ltimenu, optional argument, if LON-CAPA is in LTI Provider
+            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.  
@@ -5127,7 +6867,8 @@ other decorations will be returned.
 
 sub bodytag {
     my ($title,$function,$addentries,$bodyonly,$domain,$forcereg,
-        $no_nav_bar,$bgcolor,$args,$advtoolsref)=@_;
+        $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'))
@@ -5136,9 +6877,9 @@ sub bodytag {
     }
     if (!$args->{'no_auto_mt_title'}) { $title = &mt($title); }
     my $httphost = $args->{'use_absolute'};
+    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);
 
@@ -5155,19 +6896,39 @@ 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+)$}) {
+            if ($env{'request.role.desc'}) {
+                $role = $env{'request.role.desc'};
+            } else {
+                $role = &mt('Helpdesk[_1]','&nbsp;'.$2);
+            }
+        } 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);
     }
@@ -5178,7 +6939,7 @@ sub bodytag {
 
 # construct main body tag
     my $bodytag = "<body $extra_body_attr>".
-	&Apache::lontexconvert::init_math_support($args->{'inherit_jsmath'});
+	&Apache::lontexconvert::init_math_support();
 
     &get_unprocessed_cgi($ENV{'QUERY_STRING'}, ['inhibitmenu']);
 
@@ -5189,20 +6950,51 @@ sub bodytag {
     if ($public) {
 	undef($role);
     }
-    
+
+    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;
+            }
+        }
+    }
+
     my $titleinfo = '<h1>'.$title.'</h1>';
     #
     # 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+$//;
     }
 
-    $role = '<span class="LC_nobreak">('.$role.')</span>' if $role;
+    my $crstype;
+    if ($cid) {
+        $crstype = $env{'course.'.$cid.'.type'};
+    } elsif ($args->{'crstype'}) {
+        $crstype = $args->{'crstype'};
+    }
+    if (($crstype eq 'Placement') && (!$env{'request.role.adv'})) {
+        undef($role);
+    } else {
+        $role = '<span class="LC_nobreak">('.$role.')</span>' if ($role && !$env{'browser.mobile'});
+    }
 
         if ($env{'request.state'} eq 'construct') { $forcereg=1; }
 
@@ -5210,60 +7002,113 @@ 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 ($left,$right) = Apache::lonmenu::primary_menu();
-
-        if ($env{'request.noversionuri'} =~ m{^/res/adm/pages/}) {
-             if ($dc_info) {
-                 $dc_info = qq|<span class="LC_cusr_subheading">$dc_info</span>|;
-             }
-             $bodytag .= qq|<div id="LC_nav_bar">$left $role<br />
-                <em>$realm</em> $dc_info</div>|;
-            return $bodytag;
+        my $need_endlcint;
+        unless ($args->{'switchserver'}) {
+            $bodytag .= Apache::lonhtmlcommon::scripttag(
+                Apache::lonmenu::utilityfunctions($httphost), 'start');
+            $need_endlcint = 1;
         }
 
-        unless ($env{'request.symb'} =~ m/\.page___\d+___/) {
-            $bodytag .= qq|<div id="LC_nav_bar">$left $role</div>|;
+        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,$menucoll,$menuref,
+                                                              $args->{'links_disabled'},
+                                                              $args->{'links_target'},
+                                                              $collapsible);
+
+            if ($env{'request.noversionuri'} =~ m{^/res/adm/pages/}) {
+                if ($dc_info) {
+                    $dc_info = qq|<span class="LC_cusr_subheading">$dc_info</span>|;
+                }
+                $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;
+            }
+
+            unless ($env{'request.symb'} =~ m/\.page___\d+___/) {
+                $bodytag .= qq|<div id="LC_nav_bar">$left $role</div>|;
+            }
 
-        $bodytag .= $right;
+            $bodytag .= $right;
 
-        if ($dc_info) {
-            $dc_info = &dc_courseid_toggle($dc_info);
+            if ($dc_info) {
+                $dc_info = &dc_courseid_toggle($dc_info);
+            }
+            $bodytag .= qq|<div id="LC_realm">$realm $dc_info</div>|;
         }
-        $bodytag .= qq|<div id="LC_realm">$realm $dc_info</div>|;
 
         #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){
-            $bodytag .= Apache::lonmenu::secondary_menu($httphost);
+            unless ($args->{'no_inline_menu'}) {
+                $bodytag .= Apache::lonmenu::secondary_menu($httphost,$ltiscope,$ltimenu,
+                                                            $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'});
+                                $args->{'bread_crumbs'},'','',$hostname,
+                                $ltiscope,$ltiuri,$showncrumbsref);
             } elsif ($forcereg) {
                 $bodytag .= &Apache::lonmenu::innerregister($forcereg,undef,
-                                                            $args->{'group'});
+                                $args->{'group'},$args->{'hide_buttons'},
+                                $hostname,$ltiscope,$ltiuri,$showncrumbsref);
             } else {
                 $bodytag .= 
                     &Apache::lonmenu::prepare_functions($env{'request.noversionuri'},
                                                         $forcereg,$args->{'group'},
                                                         $args->{'bread_crumbs'},
-                                                        $advtoolsref);
+                                                        $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;
 }
 
@@ -5331,15 +7176,47 @@ sub endbodytag {
     unless ((ref($args) eq 'HASH') && ($args->{'notbody'})) {
         $endbodytag='</body>';
     }
-    $endbodytag=&Apache::lontexconvert::jsMath_process()."\n".$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;
 }
 
@@ -5360,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);
@@ -5413,6 +7289,7 @@ body {
   line-height:130%;
   font-size:0.83em;
   color:$font;
+  background-color: $pgbg_or_bgcolor;
 }
 
 a:focus,
@@ -5424,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;
 }
@@ -5444,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;
@@ -5506,6 +7407,17 @@ div.LC_confirm_box .LC_success img {
   vertical-align: middle;
 }
 
+.LC_maxwidth {
+  max-width: 100%;
+  height: auto;
+}
+
+.LC_textsize_mobile {
+  \@media only screen and (max-device-width: 480px) {
+      -webkit-text-size-adjust:100%; -moz-text-size-adjust:100%; -ms-text-size-adjust:100%;
+  }
+}
+
 .LC_icon {
   border: none;
   vertical-align: middle;
@@ -5611,6 +7523,12 @@ ul.LC_breadcrumb_tools_outerlist li {
     float: right;
 }
 
+.LC_placement_prog {
+    padding-right: 20px;
+    font-weight: bold;
+    font-size: 90%;
+}
+
 table#LC_title_bar td {
   background: $tabbg;
 }
@@ -5627,6 +7545,10 @@ table#LC_menubuttons img {
   vertical-align: middle;
 }
 
+.LC_breadcrumbs_hoverable {
+  background: $sidebg;
+}
+
 td.LC_table_cell_checkbox {
   text-align: center;
 }
@@ -5697,6 +7619,11 @@ td.LC_menubuttons_text {
   background: $tabbg;
 }
 
+td.LC_zero_height {
+  line-height: 0; 
+  cellpadding: 0;
+}
+
 table.LC_data_table {
   border: 1px solid #000000;
   border-collapse: separate;
@@ -6018,6 +7945,12 @@ td.LC_parm_overview_restrictions  {
   border-collapse: collapse;
 }
 
+span.LC_parm_recursive,
+td.LC_parm_recursive {
+  font-weight: bold;
+  font-size: smaller;
+}
+
 table.LC_parm_overview_restrictions td {
   border-width: 1px 4px 1px 4px;
   border-style: solid;
@@ -6287,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;
@@ -6369,6 +8303,12 @@ table.LC_data_table tr > td.LC_docs_entr
   color: #990000;
 }
 
+.LC_docs_alias {
+  color: #440055;  
+}
+
+.LC_domprefs_email,
+.LC_docs_alias_name,
 .LC_docs_reinit_warn,
 .LC_docs_ext_edit {
   font-size: x-small;
@@ -6484,7 +8424,7 @@ div.LC_edit_problem_footer,
 div.LC_edit_problem_footer div,
 div.LC_edit_problem_editxml_header,
 div.LC_edit_problem_editxml_header div {
-  margin-top: 5px;
+  z-index: 100;
 }
 
 div.LC_edit_problem_header_title {
@@ -6500,14 +8440,17 @@ table.LC_edit_problem_header_title {
   background: $tabbg;
 }
 
-div.LC_edit_problem_discards {
-  float: left;
-  padding-bottom: 5px;
+div.LC_edit_actionbar {
+    background-color: $sidebg;
+    margin: 0;
+    padding: 0;
+    line-height: 200%;
 }
 
-div.LC_edit_problem_saves {
-  float: right;
-  padding-bottom: 5px;
+div.LC_edit_actionbar div{
+    padding: 0;
+    margin: 0;
+    display: inline-block;
 }
 
 .LC_edit_opt {
@@ -6523,6 +8466,10 @@ div.LC_edit_problem_saves {
     margin-left: 40px;
 }
 
+#LC_edit_problem_codemirror div{
+    margin-left: 0px;
+}
+
 img.stift {
   border-width: 0;
   vertical-align: middle;
@@ -6610,6 +8557,15 @@ fieldset {
   /* overflow: hidden; */
 }
 
+fieldset#LC_selectuser {
+    margin: 0;
+    padding: 0;
+}
+
+article.geogebraweb div {
+    margin: 0;
+}
+
 fieldset > legend {
   font-weight: bold;
   padding: 0 5px 0 5px;
@@ -6637,7 +8593,6 @@ fieldset > legend {
 ol.LC_primary_menu {
   margin: 0;
   padding: 0;
-  background-color: $pgbg_or_bgcolor;
 }
 
 ol#LC_PathBreadcrumbs {
@@ -6649,23 +8604,48 @@ ol.LC_primary_menu li {
   vertical-align: middle;
   text-align: left;
   list-style: none;
+  position: relative;
   float: left;
+  z-index: 100; /* will be displayed above codemirror and underneath the help-layer */
+  line-height: 1.5em;
 }
 
-ol.LC_primary_menu li a {
+ol.LC_primary_menu li a,
+ol.LC_primary_menu li p {
   display: block;
   margin: 0;
   padding: 0 5px 0 10px;
   text-decoration: none;
 }
 
-ol.LC_primary_menu li ul {
+ol.LC_primary_menu li p span.LC_primary_menu_innertitle {
+  display: inline-block;
+  width: 95%;
+  text-align: left;
+}
+
+ol.LC_primary_menu li p span.LC_primary_menu_innerarrow {
+  display: inline-block;	
+  width: 5%;
+  float: right;
+  text-align: right;
+  font-size: 70%;
+}
+
+ol.LC_primary_menu ul {
   display: none;
-  width: 10em;
+  width: 15em;
   background-color: $data_table_light;
+  position: absolute;
+  top: 100%;
+}
+
+ol.LC_primary_menu ul ul {
+  left: 100%;
+  top: 0;
 }
 
-ol.LC_primary_menu li:hover ul, ol.LC_primary_menu li.hover ul {
+ol.LC_primary_menu li:hover > ul, ol.LC_primary_menu li.hover > ul {
   display: block;
   position: absolute;
   margin: 0;
@@ -6674,15 +8654,21 @@ ol.LC_primary_menu li:hover ul, ol.LC_pr
 }
 
 ol.LC_primary_menu li:hover li, ol.LC_primary_menu li.hover li {
+/* First Submenu -> size should be smaller than the menu title of the whole menu */
   font-size: 90%;
   vertical-align: top;
   float: none;
   border-left: 1px solid black;
   border-right: 1px solid black;
+/* A dark bottom border to visualize different menu options; 
+overwritten in the create_submenu routine for the last border-bottom of the menu */
+  border-bottom: 1px solid $data_table_dark; 
 }
 
-ol.LC_primary_menu li:hover li a, ol.LC_primary_menu li.hover li a {
-  background-color:$data_table_light;
+ol.LC_primary_menu li li p:hover {
+  color:$button_hover;
+  text-decoration:none;
+  background-color:$data_table_dark;
 }
 
 ol.LC_primary_menu li li a:hover {
@@ -6690,6 +8676,11 @@ ol.LC_primary_menu li li a:hover {
    background-color:$data_table_dark;
 }
 
+/* Font-size equal to the size of the predecessors*/
+ol.LC_primary_menu li:hover li li {
+  font-size: 100%;
+}
+
 ol.LC_primary_menu li img {
   vertical-align: bottom;
   height: 1.1em;
@@ -7114,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;
 }
@@ -7231,6 +9226,36 @@ 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
+*/
+div.LC_radiofoil, div.LC_rankfoil {
+  margin: .5em 0em .5em 0em;
+}
+table.LC_itemgroup {
+  margin-top: 1em;
+}
+
 /*
   styles used by TTH when "Default set of options to pass to tth/m
   when converting TeX" in course settings has been set
@@ -7252,6 +9277,120 @@ span.roman {font-family: serif; font-sty
 span.overacc2 {position: relative;  left: .8em; top: -1.2ex;}
 span.overacc1 {position: relative;  left: .6em; top: -1.2ex;}
 
+/*
+  sections with roles, for content only
+*/
+section[class^="role-"] {
+  padding-left: 10px;
+  padding-right: 5px;
+  margin-top: 8px;
+  margin-bottom: 8px;
+  border: 1px solid #2A4;
+  border-radius: 5px;
+  box-shadow: 0px 1px 1px #BBB;
+}
+section[class^="role-"]>h1 {
+  position: relative;
+  margin: 0px;
+  padding-top: 10px;
+  padding-left: 40px;
+}
+section[class^="role-"]>h1:before {
+  position: absolute;
+  left: -5px;
+  top: 5px;
+}
+section.role-activity>h1:before {
+  content:url('/adm/daxe/images/section_icons/activity.png');
+}
+section.role-advice>h1:before {
+  content:url('/adm/daxe/images/section_icons/advice.png');
+}
+section.role-bibliography>h1:before {
+  content:url('/adm/daxe/images/section_icons/bibliography.png');
+}
+section.role-citation>h1:before {
+  content:url('/adm/daxe/images/section_icons/citation.png');
+}
+section.role-conclusion>h1:before {
+  content:url('/adm/daxe/images/section_icons/conclusion.png');
+}
+section.role-definition>h1:before {
+  content:url('/adm/daxe/images/section_icons/definition.png');
+}
+section.role-demonstration>h1:before {
+  content:url('/adm/daxe/images/section_icons/demonstration.png');
+}
+section.role-example>h1:before {
+  content:url('/adm/daxe/images/section_icons/example.png');
+}
+section.role-explanation>h1:before {
+  content:url('/adm/daxe/images/section_icons/explanation.png');
+}
+section.role-introduction>h1:before {
+  content:url('/adm/daxe/images/section_icons/introduction.png');
+}
+section.role-method>h1:before {
+  content:url('/adm/daxe/images/section_icons/method.png');
+}
+section.role-more_information>h1:before {
+  content:url('/adm/daxe/images/section_icons/more_information.png');
+}
+section.role-objectives>h1:before {
+  content:url('/adm/daxe/images/section_icons/objectives.png');
+}
+section.role-prerequisites>h1:before {
+  content:url('/adm/daxe/images/section_icons/prerequisites.png');
+}
+section.role-remark>h1:before {
+  content:url('/adm/daxe/images/section_icons/remark.png');
+}
+section.role-reminder>h1:before {
+  content:url('/adm/daxe/images/section_icons/reminder.png');
+}
+section.role-summary>h1:before {
+  content:url('/adm/daxe/images/section_icons/summary.png');
+}
+section.role-syntax>h1:before {
+  content:url('/adm/daxe/images/section_icons/syntax.png');
+}
+section.role-warning>h1:before {
+  content:url('/adm/daxe/images/section_icons/warning.png');
+}
+
+#LC_minitab_header {
+  float:left;
+  width:100%;
+  background:#DAE0D2 url("/res/adm/pages/minitabmenu_bg.gif") repeat-x bottom;
+  font-size:93%;
+  line-height:normal;
+  margin: 0.5em 0 0.5em 0;
+}
+#LC_minitab_header ul {
+  margin:0;
+  padding:10px 10px 0;
+  list-style:none;
+}
+#LC_minitab_header li {
+  float:left;
+  background:url("/res/adm/pages/minitabmenu_left.gif") no-repeat left top;
+  margin:0;
+  padding:0 0 0 9px;
+}
+#LC_minitab_header a {
+  display:block;
+  background:url("/res/adm/pages/minitabmenu_right.gif") no-repeat right top;
+  padding:5px 15px 4px 6px;
+}
+#LC_minitab_header #LC_current_minitab {
+  background-image:url("/res/adm/pages/minitabmenu_left_on.gif");
+}
+#LC_minitab_header #LC_current_minitab a {
+  background-image:url("/res/adm/pages/minitabmenu_right_on.gif");
+  padding-bottom:5px;
+}
+
+
 END
 }
 
@@ -7272,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
@@ -7301,9 +9446,12 @@ sub headtag {
 	'<head>'.
 	&font_settings($args);
 
-    my $inhibitprint = &print_suppression();
+    my $inhibitprint;
+    if ($args->{'print_suppress'}) {
+        $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/}) {
@@ -7311,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();
@@ -7332,33 +9481,205 @@ 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'};
+            if ($requrl eq '') {
+                $requrl = $ENV{'REQUEST_URI'};
+                $requrl =~ s/\?.+$//;
+            }
+            unless (($requrl =~ m{^/adm/(?:switchserver|login|authenticate|logout|groupsort|cleanup|helper|slotrequest|grades)(\?|$)}) ||
+                    (($requrl =~ m{^/res/}) && (($env{'form.submitted'} eq 'scantron') ||
+                     ($env{'form.grade_symb'}) || ($Apache::lonhomework::scantronmode)))) {
+                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') {
+                        if ($domdefs{'offloadnow'}{$lonhost}) {
+                            $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'};
+                                }
+                            }
+                        }
+                    }
+                    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 ($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 {
+                                    $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 ($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
+<meta http-equiv="pragma" content="no-cache" />
+<script type="text/javascript">
+// <![CDATA[
+function LC_Offload_Now() {
+    var dest = "$newurl";
+    if (dest != '') {
+        window.location.href="$newurl";
+    }
+}
+\$(document).ready(function () {
+    window.alert('$msg');
+    if ($disable_submit) {
+        \$(".LC_hwk_submit").prop("disabled", true);
+        \$( ".LC_textline" ).prop( "readonly", "readonly");
+    }
+    setTimeout('LC_Offload_Now()', $timeout);
+});
+// ]]>
+</script>
+OFFLOAD
+                        }
+                    }
+                }
+            }
+        }
     }
     if (!defined($title)) {
 	$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 .= ' /';
     }
     $result .= '>' 
         .$inhibitprint
 	.$head_extra;
-    if ($env{'browser.mobile'}) {
+    my $clientmobile;
+    if (($env{'user.name'} eq '') && ($env{'user.domain'} eq '')) {
+        (undef,undef,undef,undef,undef,undef,$clientmobile) = &decode_user_agent();
+    } else {
+        $clientmobile = $env{'browser.mobile'};
+    }
+    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";
     return $result.'</head>';
 }
 
@@ -7427,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);
+        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 '') {
@@ -7536,13 +9858,25 @@ $args - additional optional args support
                                     head -> skip the <html><head> generation
                                     body -> skip all <body> generation
              no_auto_mt_title -> prevent &mt()ing the title arg
-             inherit_jsmath -> when creating popup window in a page,
-                                    should it have jsmath forced on by the
-                                    current page
              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 
-                               specific group  
+                               specific group
+             use_absolute   -> for request for external resource or syllabus, this
+                               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
 
@@ -7555,12 +9889,83 @@ sub start_page {
     #&Apache::lonnet::logthis("start_page ".join(':',caller(0)));
 
     $env{'internal.start_page'}++;
-    my ($result,@advtools);
+    my ($result,@advtools,$ltiscope,$ltiuri,%ltimenu,$menucoll,%menu);
 
     if (! exists($args->{'skip_phases'}{'head'}) ) {
         $result .= &xml_begin($args->{'frameset'}) . &headtag($title, $head_extra, $args);
     }
-    
+
+    if (($env{'request.course.id'}) && ($env{'request.lti.login'})) {
+        if ($env{'course.'.$env{'request.course.id'}.'.lti.override'}) {
+            unless ($env{'course.'.$env{'request.course.id'}.'.lti.topmenu'}) {
+                $args->{'no_primary_menu'} = 1;
+            }
+            unless ($env{'course.'.$env{'request.course.id'}.'.lti.inlinemenu'}) {
+                $args->{'no_inline_menu'} = 1;
+            }
+            if ($env{'course.'.$env{'request.course.id'}.'.lti.lcmenu'}) {
+                map { $ltimenu{$_} = 1; } split(/,/,$env{'course.'.$env{'request.course.id'}.'.lti.lcmenu'});
+            }
+        } else {
+            my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+            my %lti = &Apache::lonnet::get_domain_lti($cdom,'provider');
+            if (ref($lti{$env{'request.lti.login'}}) eq 'HASH') {
+                unless ($lti{$env{'request.lti.login'}}{'topmenu'}) {
+                    $args->{'no_primary_menu'} = 1;
+                }
+                unless ($lti{$env{'request.lti.login'}}{'inlinemenu'}) {
+                    $args->{'no_inline_menu'} = 1;
+                }
+                if (ref($lti{$env{'request.lti.login'}}{'lcmenu'}) eq 'ARRAY') {
+                    map { $ltimenu{$_} = 1; } @{$lti{$env{'request.lti.login'}}{'lcmenu'}};
+                }
+            }
+        }
+        ($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'},
@@ -7573,7 +9978,8 @@ sub start_page {
                          $args->{'only_body'},      $args->{'domain'},
                          $args->{'force_register'}, $args->{'no_nav_bar'},
                          $args->{'bgcolor'},        $args,
-                         \@advtools);
+                         \@advtools,$ltiscope,$ltiuri,\%ltimenu,$menucoll,
+                         \%menu,\$showncrumbs);
         }
     }
 
@@ -7595,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') {         
@@ -7606,13 +10013,34 @@ sub start_page {
                 if (@advtools > 0) {
                     &Apache::lonmenu::advtools_crumbs(@advtools);
                 }
-
+                my $menulink;
+                # if arg: bread_crumbs_nomenu is true pass 0 as $menulink item.
+                if ((exists($args->{'bread_crumbs_nomenu'})) ||
+                     ($ltiscope eq 'map') || ($ltiscope eq 'resource') ||
+                     ((($args->{'crstype'} eq 'Placement') || (($env{'request.course.id'}) &&
+                     ($env{'course.'.$env{'request.course.id'}.'.type'} eq 'Placement'))) &&
+                     (!$env{'request.role.adv'}))) {
+                    $menulink = 0;
+                } 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'});
-		}else{
-			$result .= &Apache::lonhtmlcommon::breadcrumbs();
+			$result .= &Apache::lonhtmlcommon::breadcrumbs($args->{'bread_crumbs_component'},
+                                                                       '',$menulink,'',
+                                                                       $args->{'bread_crumbs_style'});
+                } else {
+			$result .= &Apache::lonhtmlcommon::breadcrumbs('','',$menulink,'',
+                                                                       $args->{'bread_crumbs_style'});
 		}
+        }
     }
     return $result;
 }
@@ -7649,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">
@@ -7660,10 +10229,12 @@ function set_wishlistlink(title, path) {
         title = title.replace(/^LON-CAPA /,'');
     }
     title = encodeURIComponent(title);
+    title = title.replace("'","\\\'");
     if (!path) {
         path = location.pathname;
     }
     path = encodeURIComponent(path);
+    path = path.replace("'","\\\'");
     Win = window.open('/adm/wishlist?mode=newLink&setTitle='+title+'&setPath='+path,
                       'wishlistNewLink','width=560,height=350,scrollbars=0');
 }
@@ -7706,12 +10277,13 @@ var modalWindow = {
 };
 	var openMyModal = function(source,width,height,scrolling,transparency,style)
 	{
+                source = source.replace(/'/g,"&#39;");
 		modalWindow.windowId = "myModal";
 		modalWindow.width = width;
 		modalWindow.height = height;
-		modalWindow.content = "<iframe width='"+width+"' height='"+height+"' frameborder='0' scrolling='"+scrolling+"' allowtransparency='"+transparency+"' src='" + source + "' style='"+style+"'>&lt/iframe>";
+		modalWindow.content = "<iframe width='"+width+"' height='"+height+"' frameborder='0' scrolling='"+scrolling+"' allowtransparency='"+transparency+"' src='" + source + "' style='"+style+"'></iframe>";
 		modalWindow.open();
-	};	
+	};
 // END LON-CAPA Internal -->
 // ]]>
 </script>
@@ -7730,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[
@@ -7747,6 +10326,7 @@ sub modal_adhoc_script {
                 modalWindow.height = $height;
                 modalWindow.content = '$content';
                 modalWindow.open();
+                $mathjax
         };  
 // ]]>
 </script>
@@ -7754,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'}).
@@ -7763,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>";
 }
 
@@ -7834,8 +10414,9 @@ sub end_togglebox {
 }
 
 sub LCprogressbar_script {
-   my ($id)=@_;
-   return(<<ENDPROGRESS);
+   my ($id,$number_to_do)=@_;
+   if ($number_to_do) {
+       return(<<ENDPROGRESS);
 <script type="text/javascript">
 // <![CDATA[
 \$('#progressbar$id').progressbar({
@@ -7848,21 +10429,43 @@ sub LCprogressbar_script {
 // ]]>
 </script>
 ENDPROGRESS
+   } else {
+       return(<<ENDPROGRESS);
+<script type="text/javascript">
+// <![CDATA[
+\$('#progressbar$id').progressbar({
+  value: false,
+  create: function(event, ui) {
+    \$('.ui-widget-header', this).css({'background':'#F0F0F0'});
+    \$('.ui-progressbar-overlay', this).css({'margin':'0'});
+  }
+});
+// ]]>
+</script>
+ENDPROGRESS
+   }
 }
 
 sub LCprogressbarUpdate_script {
    return(<<ENDPROGRESSUPDATE);
 <style type="text/css">
 .ui-progressbar { position:relative; }
+.progress-label {position: absolute; width: 100%; text-align: center; top: 1px; font-weight: bold; text-shadow: 1px 1px 0 #fff;margin: 0; line-height: 200%; }
 .pblabel { position: absolute; width: 100%; text-align: center; line-height: 1.9em; }
 </style>
 <script type="text/javascript">
 // <![CDATA[
 var LCprogressTxt='---';
 
-function LCupdateProgress(percent,progresstext,id) {
+function LCupdateProgress(percent,progresstext,id,maxnum) {
    LCprogressTxt=progresstext;
-   \$('#progressbar'+id).progressbar('value',percent);
+   if ((maxnum == '') || (maxnum == undefined) || (maxnum == null)) {
+       \$('#progressbar'+id).find('.progress-label').text(LCprogressTxt);
+   } else if (percent === \$('#progressbar'+id).progressbar( "value" )) {
+       \$('#progressbar'+id).find('.pblabel').text(LCprogressTxt);
+   } else {
+       \$('#progressbar'+id).progressbar('value',percent);
+   }
 }
 // ]]>
 </script>
@@ -7874,37 +10477,54 @@ my $LCidcnt;
 my $LCcurrentid;
 
 sub LCprogressbar {
-    my ($r)=(@_);
+    my ($r,$number_to_do,$preamble)=@_;
     $LClastpercent=0;
     $LCidcnt++;
     $LCcurrentid=$$.'_'.$LCidcnt;
-    my $starting=&mt('Starting');
-    my $content=(<<ENDPROGBAR);
+    my ($starting,$content);
+    if ($number_to_do) {
+        $starting=&mt('Starting');
+        $content=(<<ENDPROGBAR);
+$preamble
   <div id="progressbar$LCcurrentid">
     <span class="pblabel">$starting</span>
   </div>
 ENDPROGBAR
-    &r_print($r,$content.&LCprogressbar_script($LCcurrentid));
+    } else {
+        $starting=&mt('Loading...');
+        $LClastpercent='false';
+        $content=(<<ENDPROGBAR);
+$preamble
+  <div id="progressbar$LCcurrentid">
+      <div class="progress-label">$starting</div>
+  </div>
+ENDPROGBAR
+    }
+    &r_print($r,$content.&LCprogressbar_script($LCcurrentid,$number_to_do));
 }
 
 sub LCprogressbarUpdate {
-    my ($r,$val,$text)=@_;
-    unless ($val) { 
-       if ($LClastpercent) {
-           $val=$LClastpercent;
-       } else {
-           $val=0;
-       }
+    my ($r,$val,$text,$number_to_do)=@_;
+    if ($number_to_do) {
+        unless ($val) { 
+            if ($LClastpercent) {
+                $val=$LClastpercent;
+            } else {
+                $val=0;
+            }
+        }
+        if ($val<0) { $val=0; }
+        if ($val>100) { $val=0; }
+        $LClastpercent=$val;
+        unless ($text) { $text=$val.'%'; }
+    } else {
+        $val = 'false';
     }
-    if ($val<0) { $val=0; }
-    if ($val>100) { $val=0; }
-    $LClastpercent=$val;
-    unless ($text) { $text=$val.'%'; }
     $text=&js_ready($text);
     &r_print($r,<<ENDUPDATE);
 <script type="text/javascript">
 // <![CDATA[
-LCupdateProgress($val,'$text','$LCcurrentid');
+LCupdateProgress($val,'$text','$LCcurrentid','$number_to_do');
 // ]]>
 </script>
 ENDUPDATE
@@ -8089,14 +10709,21 @@ function expand_div(caller) {
 
 sub simple_error_page {
     my ($r,$title,$msg,$args) = @_;
+    my %displayargs;
     if (ref($args) eq 'HASH') {
         if (!$args->{'no_auto_mt_msg'}) { $msg = &mt($msg); }
+        if ($args->{'only_body'}) {
+            $displayargs{'only_body'} = 1;
+        }
+        if ($args->{'no_nav_bar'}) {
+            $displayargs{'no_nav_bar'} = 1;
+        }
     } else {
         $msg = &mt($msg);
     }
 
     my $page =
-	&Apache::loncommon::start_page($title).
+	&Apache::loncommon::start_page($title,'',\%displayargs).
 	'<p class="LC_error">'.$msg.'</p>'.
 	&Apache::loncommon::end_page();
     if (ref($r)) {
@@ -8279,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)) {
@@ -8313,7 +10950,7 @@ role status: active, previous or future.
 sub check_user_status {
     my ($udom,$uname,$cdom,$crs,$role,$sec) = @_;
     my %userinfo = &Apache::lonnet::dump('roles',$udom,$uname);
-    my @uroles = keys %userinfo;
+    my @uroles = keys(%userinfo);
     my $srchstr;
     my $active_chk = 'none';
     my $now = time;
@@ -8724,8 +11361,8 @@ Incoming parameters:
 2. user's domain
 3. quota name - portfolio, author, or course
    (if no quota name provided, defaults to portfolio).
-4. crstype - official, unofficial, textbook or community, if quota name is
-   course
+4. crstype - official, unofficial, textbook, placement or community, 
+   if quota name is course
 
 Returns:
 1. Disk quota (in MB) assigned to student.
@@ -8799,7 +11436,8 @@ sub get_user_quota {
             if ($quotaname eq 'course') {
                 my %domdefs = &Apache::lonnet::get_domain_defaults($udom);
                 if (($crstype eq 'official') || ($crstype eq 'unofficial') || 
-                    ($crstype eq 'community') || ($crstype eq 'textbook')) { 
+                    ($crstype eq 'community') || ($crstype eq 'textbook') ||
+                    ($crstype eq 'placement')) { 
                     $defquota = $domdefs{$crstype.'quota'};
                 }
                 if ($defquota eq '') {
@@ -8947,7 +11585,7 @@ Inputs: 7
 4. filename of file for which action is being requested
 5. filesize (kB) of file
 6. action being taken: copy or upload.
-7. quotatype (in course context -- official, unofficial, community or textbook).
+7. quotatype (in course context -- official, unofficial, textbook, placement or community).
 
 Returns: 1 scalar: HTML to display containing warning if quota would be exceeded,
          otherwise return null.
@@ -8970,10 +11608,10 @@ sub excess_filesize_warning {
     }
     $disk_quota = int($disk_quota * 1000);
     if (($current_disk_usage + $filesize) > $disk_quota) {
-        return '<p><span class="LC_warning">'.
+        return '<p class="LC_warning">'.
                 &mt("Unable to $action [_1]. (size = [_2] kilobytes). Disk quota will be exceeded.",
-                    '<span class="LC_filename">'.$filename.'</span>',$filesize).'</span>'.
-               '<br />'.&mt('Disk quota is [_1] kilobytes. Your current disk usage is [_2] kilobytes.',
+                    '<span class="LC_filename">'.$filename.'</span>',$filesize).'</p>'.
+               '<p>'.&mt('Disk quota is [_1] kilobytes. Your current disk usage is [_2] kilobytes.',
                             $disk_quota,$current_disk_usage).
                '</p>';
     }
@@ -9023,8 +11661,24 @@ sub get_secgrprole_info {
 }
 
 sub user_picker {
-    my ($dom,$srch,$forcenewuser,$caller,$cancreate,$usertype,$context) = @_;
+    my ($dom,$srch,$forcenewuser,$caller,$cancreate,$usertype,$context,$fixeddom,$noinstd) = @_;
     my $currdom = $dom;
+    my @alldoms = &Apache::lonnet::all_domains();
+    if (@alldoms == 1) {
+        my %domsrch = &Apache::lonnet::get_dom('configuration',
+                                               ['directorysrch'],$alldoms[0]);
+        my $domdesc = &Apache::lonnet::domain($alldoms[0],'description');
+        my $showdom = $domdesc;
+        if ($showdom eq '') {
+            $showdom = $dom;
+        }
+        if (ref($domsrch{'directorysrch'}) eq 'HASH') {
+            if ((!$domsrch{'directorysrch'}{'available'}) &&
+                ($domsrch{'directorysrch'}{'lcavailable'} eq '0')) {
+                return (&mt('LON-CAPA directory search is not available in domain: [_1]',$showdom),0);
+            }
+        }
+    }
     my %curr_selected = (
                         srchin => 'dom',
                         srchby => 'lastname',
@@ -9045,7 +11699,7 @@ sub user_picker {
         }
         $srchterm = $srch->{'srchterm'};
     }
-    my %lt=&Apache::lonlocal::texthash(
+    my %html_lt=&Apache::lonlocal::texthash(
                     'usr'       => 'Search criteria',
                     'doma'      => 'Domain/institution to search',
                     'uname'     => 'username',
@@ -9058,6 +11712,8 @@ sub user_picker {
                     'exact'     => 'is',
                     'contains'  => 'contains',
                     'begins'    => 'begins with',
+                                       );
+    my %js_lt=&Apache::lonlocal::texthash(
                     'youm'      => "You must include some text to search for.",
                     'thte'      => "The text you are searching for must contain at least two characters when using a 'begins' type search.",
                     'thet'      => "The text you are searching for must contain at least three characters when using a 'contains' type search.",
@@ -9067,7 +11723,25 @@ sub user_picker {
                     'whse'      => "When searching by last,first you must include at least one character in the first name.",
                      'thfo'     => "The following need to be corrected before the search can be run:",
                                        );
-    my $domform = &select_dom_form($currdom,'srchdomain',1,1);
+    &html_escape(\%html_lt);
+    &js_escape(\%js_lt);
+    my $domform;
+    my $allow_blank = 1;
+    if ($fixeddom) {
+        $allow_blank = 0;
+        $domform = &select_dom_form($currdom,'srchdomain',$allow_blank,1,undef,[$currdom]);
+    } else {
+        my $defdom = $env{'request.role.domain'};
+        my ($trusted,$untrusted);
+        if (($context eq 'requestcrs') || ($context eq 'course')) {
+            ($trusted,$untrusted) = &Apache::lonnet::trusted_domains('enroll',$defdom);
+        } elsif ($context eq 'author') {
+            ($trusted,$untrusted) = &Apache::lonnet::trusted_domains('othcoau',$defdom);
+        } elsif ($context eq 'domain') {
+            ($trusted,$untrusted) = &Apache::lonnet::trusted_domains('domroles',$defdom);
+        }
+        $domform = &select_dom_form($currdom,'srchdomain',$allow_blank,1,undef,$trusted,$untrusted);
+    }
     my $srchinsel = ' <select name="srchin">';
 
     my @srchins = ('crs','dom','alc','instd');
@@ -9079,12 +11753,13 @@ sub user_picker {
         next if ($option eq 'alc');
         next if (($option eq 'crs') && ($env{'form.form'} eq 'requestcrs'));  
         next if ($option eq 'crs' && !$env{'request.course.id'});
+        next if (($option eq 'instd') && ($noinstd));
         if ($curr_selected{'srchin'} eq $option) {
             $srchinsel .= ' 
-   <option value="'.$option.'" selected="selected">'.$lt{$option}.'</option>';
+   <option value="'.$option.'" selected="selected">'.$html_lt{$option}.'</option>';
         } else {
             $srchinsel .= '
-   <option value="'.$option.'">'.$lt{$option}.'</option>';
+   <option value="'.$option.'">'.$html_lt{$option}.'</option>';
         }
     }
     $srchinsel .= "\n  </select>\n";
@@ -9093,10 +11768,10 @@ sub user_picker {
     foreach my $option ('lastname','lastfirst','uname') {
         if ($curr_selected{'srchby'} eq $option) {
             $srchbysel .= '
-   <option value="'.$option.'" selected="selected">'.$lt{$option}.'</option>';
+   <option value="'.$option.'" selected="selected">'.$html_lt{$option}.'</option>';
         } else {
             $srchbysel .= '
-   <option value="'.$option.'">'.$lt{$option}.'</option>';
+   <option value="'.$option.'">'.$html_lt{$option}.'</option>';
          }
     }
     $srchbysel .= "\n  </select>\n";
@@ -9105,10 +11780,10 @@ sub user_picker {
     foreach my $option ('begins','contains','exact') {
         if ($curr_selected{'srchtype'} eq $option) {
             $srchtypesel .= '
-   <option value="'.$option.'" selected="selected">'.$lt{$option}.'</option>';
+   <option value="'.$option.'" selected="selected">'.$html_lt{$option}.'</option>';
         } else {
             $srchtypesel .= '
-   <option value="'.$option.'">'.$lt{$option}.'</option>';
+   <option value="'.$option.'">'.$html_lt{$option}.'</option>';
         }
     }
     $srchtypesel .= "\n  </select>\n";
@@ -9193,46 +11868,46 @@ function validateEntry(callingForm) {
 
     if (srchterm == "") {
         checkok = 0;
-        msg += "$lt{'youm'}\\n";
+        msg += "$js_lt{'youm'}\\n";
     }
 
     if (srchtype== 'begins') {
         if (srchterm.length < 2) {
             checkok = 0;
-            msg += "$lt{'thte'}\\n";
+            msg += "$js_lt{'thte'}\\n";
         }
     }
 
     if (srchtype== 'contains') {
         if (srchterm.length < 3) {
             checkok = 0;
-            msg += "$lt{'thet'}\\n";
+            msg += "$js_lt{'thet'}\\n";
         }
     }
     if (srchin == 'instd') {
         if (srchdomain == '') {
             checkok = 0;
-            msg += "$lt{'yomc'}\\n";
+            msg += "$js_lt{'yomc'}\\n";
         }
     }
     if (srchin == 'dom') {
         if (srchdomain == '') {
             checkok = 0;
-            msg += "$lt{'ymcd'}\\n";
+            msg += "$js_lt{'ymcd'}\\n";
         }
     }
     if (srchby == 'lastfirst') {
         if (srchterm.indexOf(",") == -1) {
             checkok = 0;
-            msg += "$lt{'whus'}\\n";
+            msg += "$js_lt{'whus'}\\n";
         }
         if (srchterm.indexOf(",") == srchterm.length -1) {
             checkok = 0;
-            msg += "$lt{'whse'}\\n";
+            msg += "$js_lt{'whse'}\\n";
         }
     }
     if (checkok == 0) {
-        alert("$lt{'thfo'}\\n"+msg);
+        alert("$js_lt{'thfo'}\\n"+msg);
         return;
     }
     if (checkok == 1) {
@@ -9250,10 +11925,10 @@ $new_user_create
 END_BLOCK
 
     $output .= &Apache::lonhtmlcommon::start_pick_box().
-               &Apache::lonhtmlcommon::row_title($lt{'doma'}).
+               &Apache::lonhtmlcommon::row_title($html_lt{'doma'}).
                $domform.
                &Apache::lonhtmlcommon::row_closure().
-               &Apache::lonhtmlcommon::row_title($lt{'usr'}).
+               &Apache::lonhtmlcommon::row_title($html_lt{'usr'}).
                $srchbysel.
                $srchtypesel. 
                '<input type="text" size="15" name="srchterm" value="'.$srchterm.'" />'.
@@ -9261,61 +11936,165 @@ END_BLOCK
                &Apache::lonhtmlcommon::row_closure(1). 
                &Apache::lonhtmlcommon::end_pick_box().
                '<br />';
-    return $output;
+    return ($output,1);
 }
 
 sub user_rule_check {
     my ($usershash,$checks,$alerts,$rulematch,$inst_results,$curr_rules,$got_rules) = @_;
-    my $response;
+    my ($response,%inst_response);
     if (ref($usershash) eq 'HASH') {
-        foreach my $user (keys(%{$usershash})) {
-            my ($uname,$udom) = split(/:/,$user);
-            next if ($udom eq '' || $uname eq '');
-            my ($id,$newuser);
-            if (ref($usershash->{$user}) eq 'HASH') {
-                $newuser = $usershash->{$user}->{'newuser'};
-                $id = $usershash->{$user}->{'id'};
-            }
-            my $inst_response;
+        if (keys(%{$usershash}) > 1) {
+            my (%by_username,%by_id,%userdoms);
+            my $checkid; 
             if (ref($checks) eq 'HASH') {
-                if (defined($checks->{'username'})) {
-                    ($inst_response,%{$inst_results->{$user}}) = 
-                        &Apache::lonnet::get_instuser($udom,$uname);
-                } elsif (defined($checks->{'id'})) {
-                    ($inst_response,%{$inst_results->{$user}}) =
-                        &Apache::lonnet::get_instuser($udom,undef,$id);
+                if ((!defined($checks->{'username'})) && (defined($checks->{'id'}))) {
+                    $checkid = 1;
+                }
+            }
+            foreach my $user (keys(%{$usershash})) {
+                my ($uname,$udom) = split(/:/,$user);
+                if ($checkid) {
+                    if (ref($usershash->{$user}) eq 'HASH') {
+                        if ($usershash->{$user}->{'id'} ne '') {
+                            $by_id{$udom}{$usershash->{$user}->{'id'}} = $uname; 
+                            $userdoms{$udom} = 1;
+                            if (ref($inst_results) eq 'HASH') {
+                                $inst_results->{$uname.':'.$udom} = {};
+                            }
+                        }
+                    }
+                } else {
+                    $by_username{$udom}{$uname} = 1;
+                    $userdoms{$udom} = 1;
+                    if (ref($inst_results) eq 'HASH') {
+                        $inst_results->{$uname.':'.$udom} = {};
+                    }
+                }
+            }
+            foreach my $udom (keys(%userdoms)) {
+                if (!$got_rules->{$udom}) {
+                    my %domconfig = &Apache::lonnet::get_dom('configuration',
+                                                             ['usercreation'],$udom);
+                    if (ref($domconfig{'usercreation'}) eq 'HASH') {
+                        foreach my $item ('username','id') {
+                            if (ref($domconfig{'usercreation'}{$item.'_rule'}) eq 'ARRAY') {
+                                $$curr_rules{$udom}{$item} =
+                                    $domconfig{'usercreation'}{$item.'_rule'};
+                            }
+                        }
+                    }
+                    $got_rules->{$udom} = 1;
+                }
+            }
+            if ($checkid) {
+                foreach my $udom (keys(%by_id)) {
+                    my ($outcome,$results) = &Apache::lonnet::get_multiple_instusers($udom,$by_id{$udom},'id');
+                    if ($outcome eq 'ok') {
+                        foreach my $id (keys(%{$by_id{$udom}})) {
+                            my $uname = $by_id{$udom}{$id};
+                            $inst_response{$uname.':'.$udom} = $outcome;
+                        }
+                        if (ref($results) eq 'HASH') {
+                            foreach my $uname (keys(%{$results})) {
+                                if (exists($inst_response{$uname.':'.$udom})) {
+                                    $inst_response{$uname.':'.$udom} = $outcome;
+                                    $inst_results->{$uname.':'.$udom} = $results->{$uname};
+                                }
+                            }
+                        }
+                    }
                 }
             } else {
-                ($inst_response,%{$inst_results->{$user}}) =
-                    &Apache::lonnet::get_instuser($udom,$uname);
-                return;
+                foreach my $udom (keys(%by_username)) {
+                    my ($outcome,$results) = &Apache::lonnet::get_multiple_instusers($udom,$by_username{$udom});
+                    if ($outcome eq 'ok') {
+                        foreach my $uname (keys(%{$by_username{$udom}})) {
+                            $inst_response{$uname.':'.$udom} = $outcome;
+                        }
+                        if (ref($results) eq 'HASH') {
+                            foreach my $uname (keys(%{$results})) {
+                                $inst_results->{$uname.':'.$udom} = $results->{$uname};
+                            }
+                        }
+                    }
+                }
             }
-            if (!$got_rules->{$udom}) {
-                my %domconfig = &Apache::lonnet::get_dom('configuration',
-                                                  ['usercreation'],$udom);
-                if (ref($domconfig{'usercreation'}) eq 'HASH') {
-                    foreach my $item ('username','id') {
-                        if (ref($domconfig{'usercreation'}{$item.'_rule'}) eq 'ARRAY') {
-                            $$curr_rules{$udom}{$item} = 
-                                $domconfig{'usercreation'}{$item.'_rule'};
+        } elsif (keys(%{$usershash}) == 1) {
+            my $user = (keys(%{$usershash}))[0];
+            my ($uname,$udom) = split(/:/,$user);
+            if (($udom ne '') && ($uname ne '')) {
+                if (ref($usershash->{$user}) eq 'HASH') {
+                    if (ref($checks) eq 'HASH') {
+                        if (defined($checks->{'username'})) {
+                            ($inst_response{$user},%{$inst_results->{$user}}) = 
+                                &Apache::lonnet::get_instuser($udom,$uname);
+                        } elsif (defined($checks->{'id'})) {
+                            if ($usershash->{$user}->{'id'} ne '') {
+                                ($inst_response{$user},%{$inst_results->{$user}}) =
+                                    &Apache::lonnet::get_instuser($udom,undef,
+                                                                  $usershash->{$user}->{'id'});
+                            } else {
+                                ($inst_response{$user},%{$inst_results->{$user}}) =
+                                    &Apache::lonnet::get_instuser($udom,$uname);
+                            }
                         }
+                    } else {
+                       ($inst_response{$user},%{$inst_results->{$user}}) =
+                            &Apache::lonnet::get_instuser($udom,$uname);
+                       return;
+                    }
+                    if (!$got_rules->{$udom}) {
+                        my %domconfig = &Apache::lonnet::get_dom('configuration',
+                                                                 ['usercreation'],$udom);
+                        if (ref($domconfig{'usercreation'}) eq 'HASH') {
+                            foreach my $item ('username','id') {
+                                if (ref($domconfig{'usercreation'}{$item.'_rule'}) eq 'ARRAY') {
+                                   $$curr_rules{$udom}{$item} = 
+                                       $domconfig{'usercreation'}{$item.'_rule'};
+                                }
+                            }
+                        }
+                        $got_rules->{$udom} = 1;
                     }
                 }
-                $got_rules->{$udom} = 1;  
+            } else {
+                return;
+            }
+        } else {
+            return;
+        }
+        foreach my $user (keys(%{$usershash})) {
+            my ($uname,$udom) = split(/:/,$user);
+            next if (($udom eq '') || ($uname eq ''));
+            my $id;
+            if (ref($inst_results) eq 'HASH') {
+                if (ref($inst_results->{$user}) eq 'HASH') {
+                    $id = $inst_results->{$user}->{'id'};
+                }
+            }
+            if ($id eq '') { 
+                if (ref($usershash->{$user})) {
+                    $id = $usershash->{$user}->{'id'};
+                }
             }
             foreach my $item (keys(%{$checks})) {
                 if (ref($$curr_rules{$udom}) eq 'HASH') {
                     if (ref($$curr_rules{$udom}{$item}) eq 'ARRAY') {
                         if (@{$$curr_rules{$udom}{$item}} > 0) {
-                            my %rule_check = &Apache::lonnet::inst_rulecheck($udom,$uname,$id,$item,$$curr_rules{$udom}{$item});
+                            my %rule_check = &Apache::lonnet::inst_rulecheck($udom,$uname,$id,$item,
+                                                                             $$curr_rules{$udom}{$item});
                             foreach my $rule (@{$$curr_rules{$udom}{$item}}) {
                                 if ($rule_check{$rule}) {
                                     $$rulematch{$user}{$item} = $rule;
-                                    if ($inst_response eq 'ok') {
+                                    if ($inst_response{$user} eq 'ok') {
                                         if (ref($inst_results) eq 'HASH') {
                                             if (ref($inst_results->{$user}) eq 'HASH') {
                                                 if (keys(%{$inst_results->{$user}}) == 0) {
                                                     $$alerts{$item}{$udom}{$uname} = 1;
+                                                } elsif ($item eq 'id') {
+                                                    if ($inst_results->{$user}->{'id'} eq '') {
+                                                        $$alerts{$item}{$udom}{$uname} = 1;
+                                                    }
                                                 }
                                             }
                                         }
@@ -9426,7 +12205,14 @@ sub personal_data_fieldtitles {
 
 sub sorted_inst_types {
     my ($dom) = @_;
-    my ($usertypes,$order) = &Apache::lonnet::retrieve_inst_usertypes($dom);
+    my ($usertypes,$order);
+    my %domdefaults = &Apache::lonnet::get_domain_defaults($dom);
+    if (ref($domdefaults{'inststatus'}) eq 'HASH') {
+        $usertypes = $domdefaults{'inststatus'}{'inststatustypes'};
+        $order = $domdefaults{'inststatus'}{'inststatusorder'};
+    } else {
+        ($usertypes,$order) = &Apache::lonnet::retrieve_inst_usertypes($dom);
+    }
     my $othertitle = &mt('All users');
     if ($env{'request.course.id'}) {
         $othertitle  = &mt('Any users');
@@ -9447,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'});
@@ -9462,24 +12252,37 @@ 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;
+                    push(@{$allcourses},$1);
                     $$LC_code{$1} = $2;
                 }
             }
         }
     }
- 
+
     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}) {
-                    push @{$allcourses},$sec;
-                    $$LC_code{$sec} = $lc_sec;
+                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} = $unclutteredlcsec{$crskey}[$i];
                 }
             }
         }
@@ -9576,7 +12379,9 @@ reservable_now - ref to hash of student_
 
     Keys in inner hash are:
     (a) symb: either blank or symb to which slot use is restricted.
-    (b) endreserve: end date of reservation period. 
+    (b) endreserve: end date of reservation period.
+    (c) uniqueperiod: start,end dates when slot is to be uniquely
+        selected.
 
 sorted_future - ref to array of student_schedulable slots reservable in
                 the future, ordered by start date of reservation period.
@@ -9586,7 +12391,9 @@ future_reservable - ref to hash of stude
 
     Keys in inner hash are:
     (a) symb: either blank or symb to which slot use is restricted.
-    (b) startreserve:  start date of reservation period.
+    (b) startreserve: start date of reservation period.
+    (c) uniqueperiod: start,end dates when slot is to be uniquely
+        selected.
 
 =back
 
@@ -9594,13 +12401,35 @@ future_reservable - ref to hash of stude
 
 sub get_future_slots {
     my ($cnum,$cdom,$now,$symb) = @_;
+    my $map;
+    if ($symb) {
+        ($map) = &Apache::lonnet::decode_symb($symb);
+    }
     my (%reservable_now,%future_reservable,@sorted_reservable,@sorted_future);
     my %slots = &Apache::lonnet::get_course_slots($cnum,$cdom);
     foreach my $slot (keys(%slots)) {
         next unless($slots{$slot}->{'type'} eq 'schedulable_student');
         if ($symb) {
-            next if (($slots{$slot}->{'symb'} ne '') && 
-                     ($slots{$slot}->{'symb'} ne $symb));
+            if ($slots{$slot}->{'symb'} ne '') {
+                my $canuse;
+                my %oksymbs;
+                my @slotsymbs = split(/\s*,\s*/,$slots{$slot}->{'symb'});
+                map { $oksymbs{$_} = 1; } @slotsymbs;
+                if ($oksymbs{$symb}) {
+                    $canuse = 1;
+                } else {
+                    foreach my $item (@slotsymbs) {
+                        if ($item =~ /\.(page|sequence)$/) {
+                            (undef,undef,my $sloturl) = &Apache::lonnet::decode_symb($item);
+                            if (($map ne '') && ($map eq $sloturl)) {
+                                $canuse = 1;
+                                last;
+                            }
+                        }
+                    }
+                }
+                next unless ($canuse);
+            }
         }
         if (($slots{$slot}->{'starttime'} > $now) &&
             ($slots{$slot}->{'endtime'} > $now)) {
@@ -9640,6 +12469,10 @@ sub get_future_slots {
             my $startreserve = $slots{$slot}->{'startreserve'};
             my $endreserve = $slots{$slot}->{'endreserve'};
             my $symb = $slots{$slot}->{'symb'};
+            my $uniqueperiod;
+            if (ref($slots{$slot}->{'uniqueperiod'}) eq 'ARRAY') {
+                $uniqueperiod = join(',',@{$slots{$slot}->{'uniqueperiod'}});
+            }
             if (($startreserve < $now) &&
                 (!$endreserve || $endreserve > $now)) {
                 my $lastres = $endreserve;
@@ -9648,13 +12481,15 @@ sub get_future_slots {
                 }
                 $reservable_now{$slot} = {
                                            symb       => $symb,
-                                           endreserve => $lastres
+                                           endreserve => $lastres,
+                                           uniqueperiod => $uniqueperiod,
                                          };
             } elsif (($startreserve > $now) &&
                      (!$endreserve || $endreserve > $startreserve)) {
                 $future_reservable{$slot} = {
                                               symb         => $symb,
-                                              startreserve => $startreserve
+                                              startreserve => $startreserve,
+                                              uniqueperiod => $uniqueperiod,
                                             };
             }
         }
@@ -9812,7 +12647,23 @@ sub get_env_multiple {
     return(@values);
 }
 
+# Looks at given dependencies, and returns something depending on the context.
+# For coursedocs paste, returns (undef, $counter, $numpathchg, \%existing).
+# For syllabus rewrites, returns (undef, $counter, $numpathchg, \%existing, \%mapping).
+# For all other contexts, returns ($output, $counter, $numpathchg).
+# $output: string with the HTML output. Can contain missing dependencies with an upload form, existing dependencies, and dependencies no longer in use.
+# $counter: integer with the number of existing dependencies when no HTML output is returned, and the number of missing dependencies when an HTML output is returned.
+# $numpathchg: integer with the number of cleaned up dependency paths.
+# \%existing: hash reference clean path -> 1 only for existing dependencies.
+# \%mapping: hash reference clean path -> original path for all dependencies.
+# @param {string} actionurl - The path to the handler, indicative of the context.
+# @param {string} state - Can contain HTML with hidden inputs that will be added to the output form.
+# @param {hash reference} allfiles - List of file info from lonnet::extract_embedded_items
+# @param {hash reference} codebase - undef, not modified by lonnet::extract_embedded_items ?
+# @param {hash reference} args - More parameters ! Possible keys: error_on_invalid_names (boolean), ignore_remote_references (boolean), current_path (string), docs_url (string), docs_title (string), context (string)
+# @return {Array} - array depending on the context (not a reference)
 sub ask_for_embedded_content {
+    # NOTE: documentation was added afterwards, it could be wrong
     my ($actionurl,$state,$allfiles,$codebase,$args)=@_;
     my (%subdependencies,%dependencies,%mapping,%existing,%newfiles,%pathchanges,
         %currsubfile,%unused,$rem);
@@ -9828,6 +12679,9 @@ sub ask_for_embedded_content {
     my $heading = &mt('Upload embedded files');
     my $buttontext = &mt('Upload');
 
+    # fills these variables based on the context:
+    # $navmap, $cdom, $cnum, $udom, $uname, $url, $toplevel, $getpropath,
+    # $path, $fileloc, $title, $rem, $filename
     if ($env{'request.course.id'}) {
         if ($actionurl eq '/adm/dependencies') {
             $navmap = Apache::lonnavmaps::navmap->new();
@@ -9889,7 +12743,15 @@ sub ask_for_embedded_content {
                     ($path) =
                         ($toplevel =~ m{^(\Q/uploaded/$cdom/$cnum/\E(?:docs|supplemental)/(?:default|\d+)/\d+)/});
                 }
-                $fileloc = &Apache::lonnet::filelocation('',$toplevel);
+                if ($toplevel=~/^\/*(uploaded|editupload)/) {
+                    $fileloc = $toplevel;
+                    $fileloc=~ s/^\s*(\S+)\s*$/$1/;
+                    my ($udom,$uname,$fname) =
+                        ($fileloc=~ m{^/+(?:uploaded|editupload)/+($match_domain)/+($match_name)/+(.*)$});
+                    $fileloc = propath($udom,$uname).'/userfiles/'.$fname;
+                } else {
+                    $fileloc = &Apache::lonnet::filelocation('',$toplevel);
+                }
                 $fileloc =~ s{^/}{};
                 ($filename) = ($fileloc =~ m{.+/([^/]+)$});
                 $heading = &mt('Status of dependencies in [_1]',"$title ($filename)");
@@ -9904,6 +12766,16 @@ sub ask_for_embedded_content {
         $fileloc = &Apache::lonnet::filelocation('',$toplevel).'/';
         $fileloc =~ s{^/}{};
     }
+    
+    # parses the dependency paths to get some info
+    # fills $newfiles, $mapping, $subdependencies, $dependencies
+    # $newfiles: hash URL -> 1 for new files or external URLs
+    # (will be completed later)
+    # $mapping:
+    #   for external URLs: external URL -> external URL
+    #   for relative paths: clean path -> original path
+    # $subdependencies: hash clean path -> clean file name -> 1 for relative paths in subdirectories
+    # $dependencies: hash clean or not file name -> 1 for relative paths not in subdirectories
     foreach my $file (keys(%{$allfiles})) {
         my $embed_file;
         if (($path eq "/uploaded/$cdom/$cnum/portfolio/syllabus") && ($file =~ m{^\Q$path/\E(.+)$})) {
@@ -9946,6 +12818,19 @@ sub ask_for_embedded_content {
             }
         }
     }
+    
+    # looks for all existing files in dependency subdirectories (from $subdependencies filled above)
+    # and lists
+    # fills $currsubfile, $pathchanges, $existing, $numexisting, $newfiles, $unused
+    # $currsubfile: hash clean path -> file name -> 1 for all existing files in the path
+    # $pathchanges: hash clean path -> 1 if the file in subdirectory exists and
+    #                                    the path had to be cleaned up
+    # $existing: hash clean path -> 1 if the file exists
+    # $numexisting: number of keys in $existing
+    # $newfiles: updated with clean path -> 1 for files in subdirectories that do not exist
+    # $unused: only for /adm/dependencies, hash clean path -> 1 for existing files in
+    #                                      dependency subdirectories that are
+    #                                      not listed as dependencies, with some exceptions using $rem
     my $dirptr = 16384;
     foreach my $path (keys(%subdependencies)) {
         $currsubfile{$path} = {};
@@ -10021,6 +12906,9 @@ sub ask_for_embedded_content {
             }
         }
     }
+    
+    # fills $currfile, hash file name -> 1 or [$size,$mtime]
+    # for files in $url or $fileloc (target directory) in some contexts
     my %currfile;
     if (($actionurl eq '/adm/portfolio') ||
         ($actionurl eq '/adm/coursegrp_portfolio')) {
@@ -10059,6 +12947,8 @@ sub ask_for_embedded_content {
             }
         }
     }
+    # updates $pathchanges, $existing, $numexisting, $newfiles and $unused for files that
+    # are not in subdirectories, using $currfile
     foreach my $file (keys(%dependencies)) {
         if (exists($currfile{$file})) {
             unless ($mapping{$file} eq $file) {
@@ -10087,6 +12977,8 @@ sub ask_for_embedded_content {
             $unused{$file} = 1;
         }
     }
+    
+    # returns some results for coursedocs paste and syllabus rewrites ($output is undef)
     if (($actionurl eq '/adm/coursedocs') && (ref($args) eq 'HASH') &&
         ($args->{'context'} eq 'paste')) {
         $counter = scalar(keys(%existing));
@@ -10098,6 +12990,12 @@ sub ask_for_embedded_content {
         $numpathchg = scalar(keys(%pathchanges));
         return ($output,$counter,$numpathchg,\%existing,\%mapping);
     }
+    
+    # returns HTML otherwise, with dependency results and to ask for more uploads
+    
+    # $upload_output: missing dependencies (with upload form)
+    # $modify_output: uploaded dependencies (in use)
+    # $delete_output: files no longer in use (unused files are not listed for londocs, bug?)
     foreach my $embed_file (sort {lc($a) cmp lc($b)} keys(%newfiles)) {
         if ($actionurl eq '/adm/dependencies') {
             next if ($embed_file =~ m{^\w+://});
@@ -10711,7 +13609,7 @@ sub modify_html_refs {
                 return;
             }
         } 
-        if (open(my $fh,"<$container")) {
+        if (open(my $fh,'<',$container)) {
             $content = join('', <$fh>);
             close($fh);
         } else {
@@ -10776,7 +13674,7 @@ sub modify_html_refs {
                         }
                     }
                 } else {
-                    if (open(my $fh,">$container")) {
+                    if (open(my $fh,'>',$container)) {
                         print $fh $content;
                         close($fh);
                         $output = '<p>'.&mt('Updated [quant,_1,reference] in [_2].',
@@ -10920,11 +13818,11 @@ sub check_for_upload {
                     if ($currsize < $filesize) {
                         my $extra = $filesize - $currsize;
                         if (($current_disk_usage + $extra) > $disk_quota) {
-                            my $msg = '<span class="LC_error">'.
+                            my $msg = '<p class="LC_warning">'.
                                       &mt('Unable to upload [_1]. (size = [_2] kilobytes). Disk quota will be exceeded if existing (smaller) file with same name (size = [_3] kilobytes) is replaced.',
-                                          '<span class="LC_filename">'.$fname.'</span>',$filesize,$currsize).'</span>'.
-                                      '<br />'.&mt('Disk quota is [_1] kilobytes. Your current disk usage is [_2] kilobytes.',
-                                                   $disk_quota,$current_disk_usage);
+                                          '<span class="LC_filename">'.$fname.'</span>',$filesize,$currsize).'</p>'.
+                                      '<p>'.&mt('Disk quota is [_1] kilobytes. Your current disk usage is [_2] kilobytes.',
+                                                   $disk_quota,$current_disk_usage).'</p>';
                             return ('will_exceed_quota',$msg);
                         }
                     }
@@ -10933,21 +13831,21 @@ sub check_for_upload {
         }
     }
     if (($current_disk_usage + $filesize) > $disk_quota){
-        my $msg = '<span class="LC_error">'.
-                &mt('Unable to upload [_1]. (size = [_2] kilobytes). Disk quota will be exceeded.','<span class="LC_filename">'.$fname.'</span>',$filesize).'</span>'.
-                  '<br />'.&mt('Disk quota is [_1] kilobytes. Your current disk usage is [_2] kilobytes.',$disk_quota,$current_disk_usage);
+        my $msg = '<p class="LC_warning">'.
+                &mt('Unable to upload [_1]. (size = [_2] kilobytes). Disk quota will be exceeded.','<span class="LC_filename">'.$fname.'</span>',$filesize).'</p>'.
+                  '<p>'.&mt('Disk quota is [_1] kilobytes. Your current disk usage is [_2] kilobytes.',$disk_quota,$current_disk_usage).'</p>';
         return ('will_exceed_quota',$msg);
     } elsif ($found_file) {
         if ($locked_file) {
-            my $msg = '<span class="LC_error">';
+            my $msg = '<p class="LC_warning">';
             $msg .= &mt('Unable to upload [_1]. A locked file by that name was found in [_2].','<span class="LC_filename">'.$fname.'</span>','<span class="LC_filename">'.$port_path.$env{'form.currentpath'}.'</span>');
-            $msg .= '</span><br />';
+            $msg .= '</p>';
             $msg .= &mt('You will be able to rename or delete existing [_1] after a grade has been assigned.','<span class="LC_filename">'.$fname.'</span>');
             return ('file_locked',$msg);
         } else {
-            my $msg = '<span class="LC_error">';
+            my $msg = '<p class="LC_error">';
             $msg .= &mt(' A file by that name: [_1] was found in [_2].','<span class="LC_filename">'.$fname.'</span>',$port_path.$env{'form.currentpath'});
-            $msg .= '</span>';
+            $msg .= '</p>';
             return ('existingfile',$msg);
         }
     }
@@ -11045,7 +13943,7 @@ sub decompress_form {
                         "$topdir/media/player.swf",
                         "$topdir/media/swfobject.js",
                         "$topdir/media/expressInstall.swf");
-        my @camtasia8 = ("$topdir/","$topdir/$topdir.html",
+        my @camtasia8_1 = ("$topdir/","$topdir/$topdir.html",
                          "$topdir/$topdir.mp4",
                          "$topdir/$topdir\_config.xml",
                          "$topdir/$topdir\_controller.swf",
@@ -11067,13 +13965,36 @@ sub decompress_form {
                          "$topdir/skins/express_show/",
                          "$topdir/skins/express_show/player-min.css",
                          "$topdir/skins/express_show/spritesheet.png");
+        my @camtasia8_4 = ("$topdir/","$topdir/$topdir.html",
+                         "$topdir/$topdir.mp4",
+                         "$topdir/$topdir\_config.xml",
+                         "$topdir/$topdir\_controller.swf",
+                         "$topdir/$topdir\_embed.css",
+                         "$topdir/$topdir\_First_Frame.png",
+                         "$topdir/$topdir\_player.html",
+                         "$topdir/$topdir\_Thumbnails.png",
+                         "$topdir/playerProductInstall.swf",
+                         "$topdir/scripts/",
+                         "$topdir/scripts/config_xml.js",
+                         "$topdir/scripts/techsmith-smart-player.min.js",
+                         "$topdir/skins/",
+                         "$topdir/skins/configuration_express.xml",
+                         "$topdir/skins/express_show/",
+                         "$topdir/skins/express_show/spritesheet.min.css",
+                         "$topdir/skins/express_show/spritesheet.png",
+                         "$topdir/skins/express_show/techsmith-smart-player.min.css");
         my @diffs = &compare_arrays(\@paths,\@camtasia6);
         if (@diffs == 0) {
             $is_camtasia = 6;
         } else {
-            @diffs = &compare_arrays(\@paths,\@camtasia8);
+            @diffs = &compare_arrays(\@paths,\@camtasia8_1);
             if (@diffs == 0) {
                 $is_camtasia = 8;
+            } else {
+                @diffs = &compare_arrays(\@paths,\@camtasia8_4);
+                if (@diffs == 0) {
+                    $is_camtasia = 8;
+                }
             }
         }
     }
@@ -11087,7 +14008,6 @@ function camtasiaToggle() {
     for (var i=0; i<document.uploaded_decompress.autoextract_camtasia.length; i++) {
         if (document.uploaded_decompress.autoextract_camtasia[i].checked) {
             if (document.uploaded_decompress.autoextract_camtasia[i].value == $is_camtasia) {
-
                 document.getElementById('camtasia_titles').style.display='block';
             } else {
                 document.getElementById('camtasia_titles').style.display='none';
@@ -11271,8 +14191,20 @@ sub decompress_uploaded_file {
 
 sub process_decompression {
     my ($docudom,$docuname,$file,$destination,$dir_root,$hiddenelem) = @_;
+    unless (($dir_root eq '/userfiles') && ($destination =~ m{^(docs|supplemental)/(default|\d+)/\d+$})) {
+        return '<p class="LC_error">'.&mt('Not extracted.').'<br />'.
+               &mt('Unexpected file path.').'</p>'."\n";
+    }
+    unless (($docudom =~ /^$match_domain$/) && ($docuname =~ /^$match_courseid$/)) {
+        return '<p class="LC_error">'.&mt('Not extracted.').'<br />'.
+               &mt('Unexpected course context.').'</p>'."\n";
+    }
+    unless ($file eq &Apache::lonnet::clean_filename($file)) {
+        return '<p class="LC_error">'.&mt('Not extracted.').'<br />'.
+               &mt('Filename contained unexpected characters.').'</p>'."\n";
+    }
     my ($dir,$error,$warning,$output);
-    if ($file !~ /\.(zip|tar|bz2|gz|tar.gz|tar.bz2|tgz)$/) {
+    if ($file !~ /\.(zip|tar|bz2|gz|tar.gz|tar.bz2|tgz)$/i) {
         $error = &mt('Filename not a supported archive file type.').
                  '<br />'.&mt('Filename should end with one of: [_1].',
                               '.zip, .tar, .bz2, .gz, .tar.gz, .tar.bz2, .tgz');
@@ -11305,30 +14237,44 @@ sub process_decompression {
                 }
             }
             my $numskip = scalar(@to_skip);
-            if (($numskip > 0) && 
-                ($numskip == $env{'form.archive_itemcount'})) {
+            my $numoverwrite = scalar(@to_overwrite);
+            if (($numskip) && (!$numoverwrite)) { 
                 $warning = &mt('All items in the archive file already exist, and no overwriting of existing files has been requested.');         
             } elsif ($dir eq '') {
                 $error = &mt('Directory containing archive file unavailable.');
             } elsif (!$error) {
                 my ($decompressed,$display);
-                if ($numskip > 0) {
+                if (($numskip) || ($numoverwrite)) {
                     my $tempdir = time.'_'.$$.int(rand(10000));
                     mkdir("$dir/$tempdir",0755);
-                    system("mv $dir/$file $dir/$tempdir/$file");
-                    ($decompressed,$display) = 
-                        &decompress_uploaded_file($file,"$dir/$tempdir");
-                    foreach my $item (@to_skip) {
-                        if (($item ne '') && ($item !~ /\.\./)) {
-                            if (-f "$dir/$tempdir/$item") { 
-                                unlink("$dir/$tempdir/$item");
-                            } elsif (-d "$dir/$tempdir/$item") {
-                                system("rm -rf $dir/$tempdir/$item");
+                    if (&File::Copy::move("$dir/$file","$dir/$tempdir/$file")) {
+                        ($decompressed,$display) = 
+                            &decompress_uploaded_file($file,"$dir/$tempdir");
+                        foreach my $item (@to_skip) {
+                            if (($item ne '') && ($item !~ /\.\./)) {
+                                if (-f "$dir/$tempdir/$item") { 
+                                    unlink("$dir/$tempdir/$item");
+                                } elsif (-d "$dir/$tempdir/$item") {
+                                    &File::Path::remove_tree("$dir/$tempdir/$item",{ safe => 1 });
+                                }
+                            }
+                        }
+                        foreach my $item (@to_overwrite) {
+                            if ((-e "$dir/$tempdir/$item") && (-e "$dir/$item")) {
+                                if (($item ne '') && ($item !~ /\.\./)) {
+                                    if (-f "$dir/$item") {
+                                        unlink("$dir/$item");
+                                    } elsif (-d "$dir/$item") {
+                                        &File::Path::remove_tree("$dir/$item",{ safe => 1 });
+                                    }
+                                    &File::Copy::move("$dir/$tempdir/$item","$dir/$item");
+                                }
                             }
                         }
+                        if (&File::Copy::move("$dir/$tempdir/$file","$dir/$file")) {
+                            &File::Path::remove_tree("$dir/$tempdir",{ safe => 1 });
+                        }
                     }
-                    system("mv $dir/$tempdir/* $dir");
-                    rmdir("$dir/$tempdir");   
                 } else {
                     ($decompressed,$display) = 
                         &decompress_uploaded_file($file,$dir);
@@ -11346,8 +14292,7 @@ sub process_decompression {
                     if (ref($newdirlistref) eq 'ARRAY') {
                         foreach my $dir_line (@{$newdirlistref}) {
                             my ($item,undef,undef,$testdir)=split(/\&/,$dir_line,5);
-                            unless (($item =~ /^\.+$/) || ($item eq $file) || 
-                                    ((@to_skip > 0) && (grep(/^\Q$item\E$/,@to_skip)))) {
+                            unless (($item =~ /^\.+$/) || ($item eq $file)) {
                                 push(@newitems,$item);
                                 if ($dirptr&$testdir) {
                                     $is_dir{$item} = 1;
@@ -11832,7 +14777,7 @@ END
 sub process_extracted_files {
     my ($context,$docudom,$docuname,$destination,$dir_root,$hiddenelem) = @_;
     my $numitems = $env{'form.archive_count'};
-    return unless ($numitems);
+    return if ((!$numitems) || ($numitems =~ /\D/));
     my @ids=&Apache::lonnet::current_machine_ids();
     my ($prefix,$pathtocheck,$dir,$ishome,$error,$warning,%toplevelitems,%is_dir,
         %folders,%containers,%mapinner,%prompttofetch);
@@ -11845,7 +14790,7 @@ sub process_extracted_files {
     } else {
         $prefix = $Apache::lonnet::perlvar{'lonDocRoot'};
         $pathtocheck = "$dir_root/$docudom/$docuname/$destination";
-        $dir = "$dir_root/$docudom/$docuname";    
+        $dir = "$dir_root/$docudom/$docuname";
     }
     my $currdir = "$dir_root/$destination";
     (my $docstype,$mapinner{'0'}) = ($destination =~ m{^(docs|supplemental)/(\w+)/});
@@ -11934,7 +14879,9 @@ sub process_extracted_files {
                                                         '.'.$containers{$outer},1,1);
                             $newseqid{$i} = $newidx;
                             unless ($errtext) {
-                                $result .=  '<li>'.&mt('Folder: [_1] added to course',$docstitle).'</li>'."\n";
+                                $result .=  '<li>'.&mt('Folder: [_1] added to course',
+                                                       &HTML::Entities::encode($docstitle,'<>&"')).
+                                            '</li>'."\n";
                             }
                         }
                     } else {
@@ -11943,38 +14890,49 @@ sub process_extracted_files {
                             my $url = '/uploaded/'.$docudom.'/'.$docuname.'/'.
                                       $docstype.'/'.$mapinner{$outer}.'/'.$newidx.'/'.
                                       $title;
-                            if (!-e "$prefix$dir/$docstype/$mapinner{$outer}") {
-                                mkdir("$prefix$dir/$docstype/$mapinner{$outer}",0755);
-                            }
-                            if (!-e "$prefix$dir/$docstype/$mapinner{$outer}/$newidx") {
-                                mkdir("$prefix$dir/$docstype/$mapinner{$outer}/$newidx");
-                            }
-                            if (-e "$prefix$dir/$docstype/$mapinner{$outer}/$newidx") {
-                                system("mv $prefix$path $prefix$dir/$docstype/$mapinner{$outer}/$newidx/$title");
-                                $newdest{$i} = "$prefix$dir/$docstype/$mapinner{$outer}/$newidx";
-                                unless ($ishome) {
-                                    my $fetch = "$newdest{$i}/$title";
-                                    $fetch =~ s/^\Q$prefix$dir\E//;
-                                    $prompttofetch{$fetch} = 1;
+                            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);
                                 }
-                            }
-                            $LONCAPA::map::resources[$newidx]=
-                                $docstitle.':'.$url.':false:normal:res';
-                            push(@LONCAPA::map::order, $newidx);
-                            my ($outtext,$errtext)=
-                                &LONCAPA::map::storemap('/uploaded/'.$docudom.'/'.
-                                                        $docuname.'/'.$folders{$outer}.
-                                                        '.'.$containers{$outer},1,1);
-                            unless ($errtext) {
-                                if (-e "$prefix$dir/$docstype/$mapinner{$outer}/$newidx/$title") {
-                                    $result .= '<li>'.&mt('File: [_1] added to course',$docstitle).'</li>'."\n";
+                                if (!-e "$prefix$dir/$docstype/$mapinner{$outer}/$newidx") {
+                                    mkdir("$prefix$dir/$docstype/$mapinner{$outer}/$newidx");
+                                }
+                                if (-e "$prefix$dir/$docstype/$mapinner{$outer}/$newidx") {
+                                    if (rename("$prefix$path","$prefix$dir/$docstype/$mapinner{$outer}/$newidx/$title")) {
+                                        $newdest{$i} = "$prefix$dir/$docstype/$mapinner{$outer}/$newidx";
+                                        unless ($ishome) {
+                                            my $fetch = "$newdest{$i}/$title";
+                                            $fetch =~ s/^\Q$prefix$dir\E//;
+                                            $prompttofetch{$fetch} = 1;
+                                        }
+                                    }
+                                }
+                                $LONCAPA::map::resources[$newidx]=
+                                    $docstitle.':'.$url.':false:normal:res';
+                                push(@LONCAPA::map::order, $newidx);
+                                my ($outtext,$errtext)=
+                                    &LONCAPA::map::storemap('/uploaded/'.$docudom.'/'.
+                                                            $docuname.'/'.$folders{$outer}.
+                                                            '.'.$containers{$outer},1,1);
+                                unless ($errtext) {
+                                    if (-e "$prefix$dir/$docstype/$mapinner{$outer}/$newidx/$title") {
+                                        $result .= '<li>'.&mt('File: [_1] added to course',
+                                                              &HTML::Entities::encode($docstitle,'<>&"')).
+                                                   '</li>'."\n";
+                                    }
                                 }
+                            } else {
+                                $warning .= &mt('Item extracted from archive: [_1] has unexpected path.',
+                                                &HTML::Entities::encode($path,'<>&"')).'<br />';
                             }
                         }
                     }
                 }
             } else {
-                $warning .= &mt('Item extracted from archive: [_1] has unexpected path.',$path).'<br />'; 
+                $warning .= &mt('Item extracted from archive: [_1] has unexpected path.',
+                                &HTML::Entities::encode($path,'<>&"')).'<br />'; 
             }
         }
         for (my $i=1; $i<=$numitems; $i++) {
@@ -12035,7 +14993,9 @@ sub process_extracted_files {
                         }
                         if ($fullpath ne '') {
                             if (-e "$prefix$path") {
-                                system("mv $prefix$path $fullpath/$title");
+                                unless (rename("$prefix$path","$fullpath/$title")) {
+                                     $warning .= &mt('Failed to rename dependency').'<br />';
+                                }
                             }
                             if (-e "$fullpath/$title") {
                                 my $showpath;
@@ -12044,21 +15004,26 @@ sub process_extracted_files {
                                 } else {
                                     $showpath = "/$title";
                                 } 
-                                $result .= '<li>'.&mt('[_1] included as a dependency',$showpath).'</li>'."\n";
-                            } 
-                            unless ($ishome) {
-                                my $fetch = "$fullpath/$title";
-                                $fetch =~ s/^\Q$prefix$dir\E//; 
-                                $prompttofetch{$fetch} = 1;
+                                $result .= '<li>'.&mt('[_1] included as a dependency',
+                                                      &HTML::Entities::encode($showpath,'<>&"')).
+                                           '</li>'."\n";
+                                unless ($ishome) {
+                                    my $fetch = "$fullpath/$title";
+                                    $fetch =~ s/^\Q$prefix$dir\E//; 
+                                    $prompttofetch{$fetch} = 1;
+                                }
                             }
                         }
                     }
                 } elsif ($env{'form.archive_'.$referrer{$i}} eq 'discard') {
                     $warning .= &mt('[_1] is a dependency of [_2], which was discarded.',
-                                    $path,$env{'form.archive_content_'.$referrer{$i}}).'<br />';
+                                    &HTML::Entities::encode($path,'<>&"'),
+                                    &HTML::Entities::encode($env{'form.archive_content_'.$referrer{$i}},'<>&"')).
+                                '<br />';
                 }
             } else {
-                $warning .= &mt('Item extracted from archive: [_1] has unexpected path.',$path).'<br />'; 
+                $warning .= &mt('Item extracted from archive: [_1] has unexpected path.',
+                                &HTML::Entities::encode($path)).'<br />';
             }
         }
         if (keys(%todelete)) {
@@ -12332,12 +15297,15 @@ sub upfile_store {
     $env{'form.upfile'}=~s/\n+/\n/gs;
     $env{'form.upfile'}=~s/\n+$//gs;
 
-    my $datatoken=$env{'user.name'}.'_'.$env{'user.domain'}.
-	'_enroll_'.$env{'request.course.id'}.'_'.time.'_'.$$;
+    my $datatoken = &valid_datatoken($env{'user.name'}.'_'.$env{'user.domain'}.
+                                     '_enroll_'.$env{'request.course.id'}.'_'.
+                                     time.'_'.$$);
+    return if ($datatoken eq '');
+
     {
         my $datafile = $r->dir_config('lonDaemons').
                            '/tmp/'.$datatoken.'.tmp';
-        if ( open(my $fh,">$datafile") ) {
+        if ( open(my $fh,'>',$datafile) ) {
             print $fh $env{'form.upfile'};
             close($fh);
         }
@@ -12347,21 +15315,22 @@ sub upfile_store {
 
 =pod
 
-=item * &load_tmp_file($r)
+=item * &load_tmp_file($r,$datatoken)
 
 Load uploaded file from tmp, $r should be the HTTP Request object,
-needs $env{'form.datatoken'},
+$datatoken is the name to assign to the temporary file.
 sets $env{'form.upfile'} to the contents of the file
 
 =cut
 
 sub load_tmp_file {
-    my $r=shift;
+    my ($r,$datatoken) = @_;
+    return if ($datatoken eq '');
     my @studentdata=();
     {
         my $studentfile = $r->dir_config('lonDaemons').
-                              '/tmp/'.$env{'form.datatoken'}.'.tmp';
-        if ( open(my $fh,"<$studentfile") ) {
+                              '/tmp/'.$datatoken.'.tmp';
+        if ( open(my $fh,'<',$studentfile) ) {
             @studentdata=<$fh>;
             close($fh);
         }
@@ -12369,6 +15338,14 @@ sub load_tmp_file {
     $env{'form.upfile'}=join('',@studentdata);
 }
 
+sub valid_datatoken {
+    my ($datatoken) = @_;
+    if ($datatoken =~ /^$match_username\_$match_domain\_enroll_(|$match_domain\_$match_courseid)\_\d+_\d+$/) {
+        return $datatoken;
+    }
+    return;
+}
+
 =pod
 
 =item * &upfile_record_sep()
@@ -12809,7 +15786,7 @@ sub DrawBarGraph {
         @Labels = @$labels;
     } else {
         for (my $i=0;$i<@{$Values[0]};$i++) {
-            push (@Labels,$i+1);
+            push(@Labels,$i+1);
         }
     }
     #
@@ -13255,7 +16232,14 @@ requestsmail, updatesmail, or idconflict
 defdom (domain for which to retrieve configuration settings),
 
 origmail (scalar - email address of recipient from loncapa.conf, 
-i.e., predates configuration by DC via domainprefs.pm 
+i.e., predates configuration by DC via domainprefs.pm
+
+$requname username of requester (if mailing type is helpdeskmail)
+
+$requdom domain of requester (if mailing type is helpdeskmail)
+
+$reqemail e-mail address of requester (if mailing type is helpdeskmail)
+
 
 Returns: comma separated list of addresses to which to send e-mail.
 
@@ -13266,11 +16250,11 @@ Returns: comma separated list of address
 ############################################################
 ############################################################
 sub build_recipient_list {
-    my ($defmail,$mailing,$defdom,$origmail) = @_;
+    my ($defmail,$mailing,$defdom,$origmail,$requname,$requdom,$reqemail) = @_;
     my @recipients;
-    my $otheremails;
+    my ($otheremails,$lastresort,$allbcc,$addtext);
     my %domconfig =
-         &Apache::lonnet::get_dom('configuration',['contacts'],$defdom);
+        &Apache::lonnet::get_dom('configuration',['contacts'],$defdom);
     if (ref($domconfig{'contacts'}) eq 'HASH') {
         if (exists($domconfig{'contacts'}{$mailing})) {
             if (ref($domconfig{'contacts'}{$mailing}) eq 'HASH') {
@@ -13282,14 +16266,183 @@ sub build_recipient_list {
                             push(@recipients,$addr);
                         }
                     }
-                    $otheremails = $domconfig{'contacts'}{$mailing}{'others'};
+                }
+                $otheremails = $domconfig{'contacts'}{$mailing}{'others'};
+                if ($mailing eq 'helpdeskmail') {
+                    if ($domconfig{'contacts'}{$mailing}{'bcc'}) {
+                        my @bccs = split(/,/,$domconfig{'contacts'}{$mailing}{'bcc'});
+                        my @ok_bccs;
+                        foreach my $bcc (@bccs) {
+                            $bcc =~ s/^\s+//g;
+                            $bcc =~ s/\s+$//g;
+                            if ($bcc =~ m/^[^\@]+\@[^\@]+$/) {
+                                if (!(grep(/^\Q$bcc\E$/,@ok_bccs))) {
+                                    push(@ok_bccs,$bcc);
+                                }
+                            }
+                        }
+                        if (@ok_bccs > 0) {
+                            $allbcc = join(', ',@ok_bccs);
+                        }
+                    }
+                    $addtext = $domconfig{'contacts'}{$mailing}{'include'};
                 }
             }
         } elsif ($origmail ne '') {
-            push(@recipients,$origmail);
+            $lastresort = $origmail;
+        }
+        if ($mailing eq 'helpdeskmail') {
+            if ((ref($domconfig{'contacts'}{'overrides'}) eq 'HASH') &&
+                (keys(%{$domconfig{'contacts'}{'overrides'}}))) {
+                my ($inststatus,$inststatus_checked);
+                if (($env{'user.name'} ne '') && ($env{'user.domain'} ne '') &&
+                    ($env{'user.domain'} ne 'public')) {
+                    $inststatus_checked = 1;
+                    $inststatus = $env{'environment.inststatus'};
+                }
+                unless ($inststatus_checked) {
+                    if (($requname ne '') && ($requdom ne '')) {
+                        if (($requname =~ /^$match_username$/) &&
+                            ($requdom =~ /^$match_domain$/) &&
+                            (&Apache::lonnet::domain($requdom))) {
+                            my $requhome = &Apache::lonnet::homeserver($requname,
+                                                                      $requdom);
+                            unless ($requhome eq 'no_host') {
+                                my %userenv = &Apache::lonnet::userenvironment($requdom,$requname,'inststatus');
+                                $inststatus = $userenv{'inststatus'};
+                                $inststatus_checked = 1;
+                            }
+                        }
+                    }
+                }
+                unless ($inststatus_checked) {
+                    if ($reqemail =~ /^[^\@]+\@[^\@]+$/) {
+                        my %srch = (srchby     => 'email',
+                                    srchdomain => $defdom,
+                                    srchterm   => $reqemail,
+                                    srchtype   => 'exact');
+                        my %srch_results = &Apache::lonnet::usersearch(\%srch);
+                        foreach my $uname (keys(%srch_results)) {
+                            if (ref($srch_results{$uname}{'inststatus'}) eq 'ARRAY') {
+                                $inststatus = join(',',@{$srch_results{$uname}{'inststatus'}});
+                                $inststatus_checked = 1;
+                                last;
+                            }
+                        }
+                        unless ($inststatus_checked) {
+                            my ($dirsrchres,%srch_results) = &Apache::lonnet::inst_directory_query(\%srch);
+                            if ($dirsrchres eq 'ok') {
+                                foreach my $uname (keys(%srch_results)) {
+                                    if (ref($srch_results{$uname}{'inststatus'}) eq 'ARRAY') {
+                                        $inststatus = join(',',@{$srch_results{$uname}{'inststatus'}});
+                                        $inststatus_checked = 1;
+                                        last;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+                if ($inststatus ne '') {
+                    foreach my $status (split(/\:/,$inststatus)) {
+                        if (ref($domconfig{'contacts'}{'overrides'}{$status}) eq 'HASH') {
+                            my @contacts = ('adminemail','supportemail');
+                            foreach my $item (@contacts) {
+                                if ($domconfig{'contacts'}{'overrides'}{$status}{$item}) {
+                                    my $addr = $domconfig{'contacts'}{'overrides'}{$status};
+                                    if (!grep(/^\Q$addr\E$/,@recipients)) {
+                                        push(@recipients,$addr);
+                                    }
+                                }
+                            }
+                            $otheremails = $domconfig{'contacts'}{'overrides'}{$status}{'others'};
+                            if ($domconfig{'contacts'}{'overrides'}{$status}{'bcc'}) {
+                                my @bccs = split(/,/,$domconfig{'contacts'}{'overrides'}{$status}{'bcc'});
+                                my @ok_bccs;
+                                foreach my $bcc (@bccs) {
+                                    $bcc =~ s/^\s+//g;
+                                    $bcc =~ s/\s+$//g;
+                                    if ($bcc =~ m/^[^\@]+\@[^\@]+$/) {
+                                        if (!(grep(/^\Q$bcc\E$/,@ok_bccs))) {
+                                            push(@ok_bccs,$bcc);
+                                        }
+                                    }
+                                }
+                                if (@ok_bccs > 0) {
+                                    $allbcc = join(', ',@ok_bccs);
+                                }
+                            }
+                            $addtext = $domconfig{'contacts'}{'overrides'}{$status}{'include'};
+                            last;
+                        }
+                    }
+                }
+            }
         }
     } elsif ($origmail ne '') {
-        push(@recipients,$origmail);
+        $lastresort = $origmail;
+    }
+    if (($mailing eq 'helpdeskmail') && ($lastresort ne '')) {
+        unless (grep(/^\Q$defdom\E$/,&Apache::lonnet::current_machine_domains())) {
+            my $lonhost = $Apache::lonnet::perlvar{'lonHostID'};
+            my $machinedom = $Apache::lonnet::perlvar{'lonDefDomain'};
+            my %what = (
+                          perlvar => 1,
+                       );
+            my $primary = &Apache::lonnet::domain($defdom,'primary');
+            if ($primary) {
+                my $gotaddr;
+                my ($result,$returnhash) =
+                    &Apache::lonnet::get_remote_globals($primary,{ perlvar => 1 });
+                if (($result eq 'ok') && (ref($returnhash) eq 'HASH')) {
+                    if ($returnhash->{'lonSupportEMail'} =~ /^[^\@]+\@[^\@]+$/) {
+                        $lastresort = $returnhash->{'lonSupportEMail'};
+                        $gotaddr = 1;
+                    }
+                }
+                unless ($gotaddr) {
+                    my $uintdom = &Apache::lonnet::internet_dom($primary);
+                    my $intdom = &Apache::lonnet::internet_dom($lonhost);
+                    unless ($uintdom eq $intdom) {
+                        my %domconfig =
+                            &Apache::lonnet::get_dom('configuration',['contacts'],$machinedom);
+                        if (ref($domconfig{'contacts'}) eq 'HASH') {
+                            if (ref($domconfig{'contacts'}{'otherdomsmail'}) eq 'HASH') {
+                                my @contacts = ('adminemail','supportemail');
+                                foreach my $item (@contacts) {
+                                    if ($domconfig{'contacts'}{'otherdomsmail'}{$item}) {
+                                        my $addr = $domconfig{'contacts'}{$item};
+                                        if (!grep(/^\Q$addr\E$/,@recipients)) {
+                                            push(@recipients,$addr);
+                                        }
+                                    }
+                                }
+                                if ($domconfig{'contacts'}{'otherdomsmail'}{'others'}) {
+                                    $otheremails = $domconfig{'contacts'}{'otherdomsmail'}{'others'};
+                                }
+                                if ($domconfig{'contacts'}{'otherdomsmail'}{'bcc'}) {
+                                    my @bccs = split(/,/,$domconfig{'contacts'}{'otherdomsmail'}{'bcc'});
+                                    my @ok_bccs;
+                                    foreach my $bcc (@bccs) {
+                                        $bcc =~ s/^\s+//g;
+                                        $bcc =~ s/\s+$//g;
+                                        if ($bcc =~ m/^[^\@]+\@[^\@]+$/) {
+                                            if (!(grep(/^\Q$bcc\E$/,@ok_bccs))) {
+                                                push(@ok_bccs,$bcc);
+                                            }
+                                        }
+                                    }
+                                    if (@ok_bccs > 0) {
+                                        $allbcc = join(', ',@ok_bccs);
+                                    }
+                                }
+                                $addtext = $domconfig{'contacts'}{'otherdomsmail'}{'include'};
+                            }
+                        }
+                    }
+                }
+            }
+        }
     }
     if (defined($defmail)) {
         if ($defmail ne '') {
@@ -13309,8 +16462,106 @@ sub build_recipient_list {
             }
         }
     }
-    my $recipientlist = join(',',@recipients); 
-    return $recipientlist;
+    if ($mailing eq 'helpdeskmail') {
+        if ((!@recipients) && ($lastresort ne '')) {
+            push(@recipients,$lastresort);
+        }
+    } elsif ($lastresort ne '') {
+        if (!grep(/^\Q$lastresort\E$/,@recipients)) {
+            push(@recipients,$lastresort);
+        }
+    }
+    my $recipientlist = join(',',@recipients);
+    if (wantarray) {
+        return ($recipientlist,$allbcc,$addtext);
+    } else {
+        return $recipientlist;
+    }
+}
+
+############################################################
+############################################################
+
+=pod
+
+=over 4
+
+=item * &mime_email()
+
+Sends an email with a possible attachment
+
+Inputs:
+
+=over 4
+
+from -              Sender's email address
+
+replyto -           Reply-To email address
+
+to -                Email address of recipient
+
+subject -           Subject of email
+
+body -              Body of email
+
+cc_string -         Carbon copy email address
+
+bcc -               Blind carbon copy email address
+
+attachment_path -   Path of file to be attached
+
+file_name -         Name of file to be attached
+
+attachment_text -   The body of an attachment of type "TEXT"
+
+=back
+
+=back
+
+=cut
+
+############################################################
+############################################################
+
+sub mime_email {
+    my ($from,$replyto,$to,$subject,$body,$cc_string,$bcc,$attachment_path, 
+        $file_name,$attachment_text) = @_;
+ 
+    my $msg = MIME::Lite->new(
+             From    => $from,
+             To      => $to,
+             Subject => $subject,
+             Type    =>'TEXT',
+             Data    => $body,
+             );
+    if ($replyto ne '') {
+        $msg->add("Reply-To" => $replyto);
+    }
+    if ($cc_string ne '') {
+        $msg->add("Cc" => $cc_string);
+    }
+    if ($bcc ne '') {
+        $msg->add("Bcc" => $bcc);
+    }
+    $msg->attr("content-type"         => "text/plain");
+    $msg->attr("content-type.charset" => "UTF-8");
+    # Attach file if given
+    if ($attachment_path) {
+        unless ($file_name) {
+            if ($attachment_path =~ m-/([^/]+)$-) { $file_name = $1; }
+        }
+        my ($type, $encoding) = MIME::Types::by_suffix($attachment_path);
+        $msg->attach(Type     => $type,
+                     Path     => $attachment_path,
+                     Filename => $file_name
+                     );
+    # Otherwise attach text if given
+    } elsif ($attachment_text) {
+        $msg->attach(Type => 'TEXT',
+                     Data => $attachment_text);
+    }
+    # Send it
+    $msg->send('sendmail');
 }
 
 ############################################################
@@ -13401,6 +16652,8 @@ jsarray (reference to array of categorie
 subcats (reference to hash of arrays containing all subcategories within each 
          category, -recursive)
 
+maxd (reference to hash used to hold max depth for all top-level categories).
+
 Returns: nothing
 
 Side effects: populates trails and allitems hash references.
@@ -13408,7 +16661,7 @@ Side effects: populates trails and allit
 =cut
 
 sub extract_categories {
-    my ($categories,$cats,$trails,$allitems,$idx,$jsarray,$subcats) = @_;
+    my ($categories,$cats,$trails,$allitems,$idx,$jsarray,$subcats,$maxd) = @_;
     if (ref($categories) eq 'HASH') {
         &gather_categories($categories,$cats,$idx,$jsarray);
         if (ref($cats->[0]) eq 'ARRAY') {
@@ -13420,6 +16673,8 @@ sub extract_categories {
                     $trailstr = &mt('Official courses (with institutional codes)');
                 } elsif ($name eq 'communities') {
                     $trailstr = &mt('Communities');
+                } elsif ($name eq 'placement') {
+                    $trailstr = &mt('Placement Tests');
                 } else {
                     $trailstr = $name;
                 }
@@ -13434,12 +16689,15 @@ sub extract_categories {
                         if (ref($subcats) eq 'HASH') {
                             push(@{$subcats->{$item}},&escape($category).':'.&escape($name).':1');
                         }
-                        &recurse_categories($cats,2,$category,$trails,$allitems,\@parents,$subcats);
+                        &recurse_categories($cats,2,$category,$trails,$allitems,\@parents,$subcats,$maxd);
                     }
                 } else {
                     if (ref($subcats) eq 'HASH') {
                         $subcats->{$item} = [];
                     }
+                    if (ref($maxd) eq 'HASH') {
+                        $maxd->{$name} = 1;
+                    }
                 }
             }
         }
@@ -13477,13 +16735,13 @@ Side effects: populates trails and allit
 =cut
 
 sub recurse_categories {
-    my ($cats,$depth,$category,$trails,$allitems,$parents,$subcats) = @_;
+    my ($cats,$depth,$category,$trails,$allitems,$parents,$subcats,$maxd) = @_;
     my $shallower = $depth - 1;
     if (ref($cats->[$depth]{$category}) eq 'ARRAY') {
         for (my $k=0; $k<@{$cats->[$depth]{$category}}; $k++) {
             my $name = $cats->[$depth]{$category}[$k];
             my $item = &escape($category).':'.&escape($parents->[-1]).':'.$shallower;
-            my $trailstr = join(' -&gt; ',(@{$parents},$category));
+            my $trailstr = join(' &raquo; ',(@{$parents},$category));
             if ($allitems->{$item} eq '') {
                 push(@{$trails},$trailstr);
                 $allitems->{$item} = scalar(@{$trails})-1;
@@ -13504,16 +16762,21 @@ sub recurse_categories {
                 }
             }
             &recurse_categories($cats,$deeper,$name,$trails,$allitems,$parents,
-                                $subcats);
+                                $subcats,$maxd);
             pop(@{$parents});
         }
     } else {
         my $item = &escape($category).':'.&escape($parents->[-1]).':'.$shallower;
-        my $trailstr = join(' -&gt; ',(@{$parents},$category));
+        my $trailstr = join(' &raquo; ',(@{$parents},$category));
         if ($allitems->{$item} eq '') {
             push(@{$trails},$trailstr);
             $allitems->{$item} = scalar(@{$trails})-1;
         }
+        if (ref($maxd) eq 'HASH') {
+            if ($depth > $maxd->{$parents->[0]}) {
+                $maxd->{$parents->[0]} = $depth;
+            }
+        }
     }
     return;
 }
@@ -13534,16 +16797,19 @@ currcat - scalar with an & separated lis
 
 type    - scalar contains course type (Course or Community).
 
+disabled - scalar (optional) contains disabled="disabled" if input elements are
+           to be readonly (e.g., Domain Helpdesk role viewing course settings).
+
 Returns: $output (markup to be displayed) 
 
 =cut
 
 sub assign_categories_table {
-    my ($cathash,$currcat,$type) = @_;
+    my ($cathash,$currcat,$type,$disabled) = @_;
     my $output;
     if (ref($cathash) eq 'HASH') {
-        my (@cats,@trails,%allitems,%idx,@jsarray,@path,$maxdepth);
-        &extract_categories($cathash,\@cats,\@trails,\%allitems,\%idx,\@jsarray);
+        my (@cats,@trails,%allitems,%idx,@jsarray,%maxd,@path,$maxdepth);
+        &extract_categories($cathash,\@cats,\@trails,\%allitems,\%idx,\@jsarray,\%maxd);
         $maxdepth = scalar(@cats);
         if (@cats > 0) {
             my $itemcount = 0;
@@ -13558,8 +16824,10 @@ sub assign_categories_table {
                     next if ($parent eq 'instcode');
                     if ($type eq 'Community') {
                         next unless ($parent eq 'communities');
+                    } elsif ($type eq 'Placement') {
+                        next unless ($parent eq 'placement');
                     } else {
-                        next if ($parent eq 'communities');
+                        next if (($parent eq 'communities') || ($parent eq 'placement'));
                     }
                     my $css_class = $itemcount%2?' class="LC_odd_row"':'';
                     my $item = &escape($parent).'::0';
@@ -13572,14 +16840,16 @@ sub assign_categories_table {
                     my $parent_title = $parent;
                     if ($parent eq 'communities') {
                         $parent_title = &mt('Communities');
+                    } elsif ($parent eq 'placement') {
+                        $parent_title = &mt('Placement Tests');
                     }
                     $table .= '<tr '.$css_class.'><td><span class="LC_nobreak">'.
                               '<input type="checkbox" name="usecategory" value="'.
-                              $item.'"'.$checked.' />'.$parent_title.'</span>'.
+                              $item.'"'.$checked.$disabled.' />'.$parent_title.'</span>'.
                               '<input type="hidden" name="catname" value="'.$parent.'" /></td>';
                     my $depth = 1;
                     push(@path,$parent);
-                    $table .= &assign_category_rows($itemcount,\@cats,$depth,$parent,\@path,\@currcategories);
+                    $table .= &assign_category_rows($itemcount,\@cats,$depth,$parent,\@path,\@currcategories,$disabled);
                     pop(@path);
                     $table .= '</tr><tr><td colspan="'.$maxdepth.'" class="LC_row_separator"></td></tr>';
                     $itemcount ++;
@@ -13618,12 +16888,15 @@ path - Array containing all categories b
 
 currcategories - reference to array of current categories assigned to the course
 
+disabled - scalar (optional) contains disabled="disabled" if input elements are
+           to be readonly (e.g., Domain Helpdesk role viewing course settings).
+
 Returns: $output (markup to be displayed).
 
 =cut
 
 sub assign_category_rows {
-    my ($itemcount,$cats,$depth,$parent,$path,$currcategories) = @_;
+    my ($itemcount,$cats,$depth,$parent,$path,$currcategories,$disabled) = @_;
     my ($text,$name,$item,$chgstr);
     if (ref($cats) eq 'ARRAY') {
         my $maxdepth = scalar(@{$cats});
@@ -13646,12 +16919,12 @@ sub assign_category_rows {
                     }
                     $text .= '<tr><td><span class="LC_nobreak"><label>'.
                              '<input type="checkbox" name="usecategory" value="'.
-                             $item.'"'.$checked.' />'.$name.'</label></span>'.
+                             $item.'"'.$checked.$disabled.' />'.$name.'</label></span>'.
                              '<input type="hidden" name="catname" value="'.$name.'" />'.
                              '</td><td>';
                     if (ref($path) eq 'ARRAY') {
                         push(@{$path},$name);
-                        $text .= &assign_category_rows($itemcount,$cats,$deeper,$name,$path,$currcategories);
+                        $text .= &assign_category_rows($itemcount,$cats,$deeper,$name,$path,$currcategories,$disabled);
                         pop(@{$path});
                     }
                     $text .= '</td></tr>';
@@ -13663,32 +16936,44 @@ sub assign_category_rows {
     return $text;
 }
 
+=pod
+
+=back
+
+=cut
+
 ############################################################
 ############################################################
 
 
 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"; 
@@ -13708,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";
@@ -13744,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');
@@ -13771,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 '') {
@@ -13856,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') {
@@ -13864,59 +17157,154 @@ 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'}) && 
+	if (($env{'request.role.domain'} eq $args->{'clonedomain'}) &&
             (&Apache::lonnet::allowed('ccc',$env{'request.role.domain'}))) {
 	    $can_clone = 1;
 	} else {
-	    my %clonehash = &Apache::lonnet::get('environment',['cloners'],
+	    my %clonehash = &Apache::lonnet::get('environment',['cloners','internal.coursecode'],
 						 $args->{'clonedomain'},$args->{'clonecourse'});
-	    my @cloners = split(/,/,$clonehash{'cloners'});
-            if (grep(/^\*$/,@cloners)) {
-                $can_clone = 1;
-            } elsif (grep(/^\*\:\Q$args->{'ccdomain'}\E$/,@cloners)) {
-                $can_clone = 1;
+            if ($clonehash{'cloners'} eq '') {
+                my %domdefs = &Apache::lonnet::get_domain_defaults($args->{'course_domain'});
+                if ($domdefs{'canclone'}) {
+                    unless ($domdefs{'canclone'} eq 'none') {
+                        if ($domdefs{'canclone'} eq 'domain') {
+                            if ($args->{'ccdomain'} eq $args->{'clonedomain'}) {
+                                $can_clone = 1;
+                            }
+                        } elsif (($clonehash{'internal.coursecode'}) && ($args->{'crscode'}) && 
+                                 ($args->{'clonedomain'} eq  $args->{'course_domain'})) {
+                            if (&Apache::lonnet::default_instcode_cloning($args->{'clonedomain'},$domdefs{'canclone'},
+                                                                          $clonehash{'internal.coursecode'},$args->{'crscode'})) {
+                                $can_clone = 1;
+                            }
+                        }
+                    }
+                }
             } else {
+	        my @cloners = split(/,/,$clonehash{'cloners'});
+                if (grep(/^\*$/,@cloners)) {
+                    $can_clone = 1;
+                } elsif (grep(/^\*\:\Q$args->{'ccdomain'}\E$/,@cloners)) {
+                    $can_clone = 1;
+                } elsif (grep(/^\Q$args->{'ccuname'}\E:\Q$args->{'ccdomain'}\E$/,@cloners)) {
+                    $can_clone = 1;
+                }
+                unless ($can_clone) {
+                    if (($clonehash{'internal.coursecode'}) && ($args->{'crscode'}) && 
+                        ($args->{'clonedomain'} eq  $args->{'course_domain'})) {
+                        my (%gotdomdefaults,%gotcodedefaults);
+                        foreach my $cloner (@cloners) {
+                            if (($cloner ne '*') && ($cloner !~ /^\*\:$match_domain$/) &&
+                                ($cloner !~ /^$match_username\:$match_domain$/) && ($cloner ne '')) {
+                                my (%codedefaults,@code_order);
+                                if (ref($gotcodedefaults{$args->{'clonedomain'}}) eq 'HASH') {
+                                    if (ref($gotcodedefaults{$args->{'clonedomain'}}{'defaults'}) eq 'HASH') {
+                                        %codedefaults = %{$gotcodedefaults{$args->{'clonedomain'}}{'defaults'}};
+                                    }
+                                    if (ref($gotcodedefaults{$args->{'clonedomain'}}{'order'}) eq 'ARRAY') {
+                                        @code_order = @{$gotcodedefaults{$args->{'clonedomain'}}{'order'}};
+                                    }
+                                } else {
+                                    &Apache::lonnet::auto_instcode_defaults($args->{'clonedomain'},
+                                                                            \%codedefaults,
+                                                                            \@code_order);
+                                    $gotcodedefaults{$args->{'clonedomain'}}{'defaults'} = \%codedefaults;
+                                    $gotcodedefaults{$args->{'clonedomain'}}{'order'} = \@code_order;
+                                }
+                                if (@code_order > 0) {
+                                    if (&Apache::lonnet::check_instcode_cloning(\%codedefaults,\@code_order,
+                                                                                $cloner,$clonehash{'internal.coursecode'},
+                                                                                $args->{'crscode'})) {
+                                        $can_clone = 1;
+                                        last;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            unless ($can_clone) {
                 my $ccrole = 'cc';
                 if ($args->{'crstype'} eq 'Community') {
                     $ccrole = 'co';
                 }
 	        my %roleshash =
 		    &Apache::lonnet::get_my_roles($args->{'ccuname'},
-					 $args->{'ccdomain'},
-                                         'userroles',['active'],[$ccrole],
-					 [$args->{'clonedomain'}]);
-	        if (($roleshash{$args->{'clonecourse'}.':'.$args->{'clonedomain'}.':'.$ccrole}) || (grep(/^\Q$args->{'ccuname'}\E:\Q$args->{'ccdomain'}\E$/,@cloners))) {
+					          $args->{'ccdomain'},
+                                                  'userroles',['active'],[$ccrole],
+					          [$args->{'clonedomain'}]);
+	        if ($roleshash{$args->{'clonecourse'}.':'.$args->{'clonedomain'}.':'.$ccrole}) {
                     $can_clone = 1;
-                } elsif (&Apache::lonnet::is_course_owner($args->{'clonedomain'},$args->{'clonecourse'},$args->{'ccuname'},$args->{'ccdomain'})) {
+                } elsif (&Apache::lonnet::is_course_owner($args->{'clonedomain'},$args->{'clonecourse'},
+                                                          $args->{'ccuname'},$args->{'ccdomain'})) {
                     $can_clone = 1;
+                }
+            }
+            unless ($can_clone) {
+                if ($args->{'crstype'} eq 'Community') {
+                    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 {
-                    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'});
-                    } 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;
+    my ($args,$logmsg,$courseid,$crsudom,$crsunum,$udom,$uname,$context,
+        $cnum,$category,$coderef,$callercontext,$user_lh) = @_;
+    my ($outcome,$msgref,$clonemsgref);
     my $linefeed =  '<br />'."\n";
     if ($context eq 'auto') {
         $linefeed = "\n";
@@ -13925,25 +17313,23 @@ 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);
 	}
     }
 
 #
 # Open course
 #
-    my $crstype = lc($args->{'crstype'});
+    my $showncrstype;
+    if ($args->{'crstype'} eq 'Placement') {
+        $showncrstype = 'placement test'; 
+    } else {  
+        $showncrstype = lc($args->{'crstype'});
+    }
     my %cenv=();
     $$courseid=&Apache::lonnet::createcourse($args->{'course_domain'},
                                              $args->{'cdescr'},
@@ -13954,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]',$crstype,$$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);
     }
 
 #
@@ -13971,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]',$crstype,$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
@@ -14012,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'};
@@ -14028,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'};
     }
@@ -14049,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/,/) {
@@ -14062,8 +17470,12 @@ 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') {
-                    push @badclasses, $class;
+                if ($addcheck eq 'ok') {
+                    unless (grep(/^\Q$gp\E$/,@oklcsecs)) {
+                        push(@oklcsecs,$gp);
+                    }
+                } else {
+                    push(@badclasses,$class);
                 }
             }
             $cenv{'internal.sectionnums'} =~ s/,$//;
@@ -14090,8 +17502,12 @@ 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') {
-                    push @badclasses, $xl;
+                if ($addcheck eq 'ok') {
+                    unless (grep(/^\Q$gp\E$/,@oklcsecs)) {
+                        push(@oklcsecs,$gp);
+                    }
+                } else {
+                    push(@badclasses,$xl);
                 }
             }
             $cenv{'internal.crosslistings'} =~ s/,$//;
@@ -14126,32 +17542,63 @@ sub construct_course {
     }
     if (@badclasses > 0) {
         my %lt=&Apache::lonlocal::texthash(
-                'tclb' => 'The courses listed below were included as sections or crosslistings affiliated with your new LON-CAPA course.  However, if automated course roster updates are enabled for this class, these particular sections/crosslistings will not contribute towards enrollment, because the user identified as the course owner for this LON-CAPA course',
-                'dnhr' => 'does not have rights to access enrollment in these classes',
-                'adby' => 'as determined by the policies of your institution on access to official classlists'
+                'tclb' => 'The courses listed below were included as sections or crosslistings affiliated with your new LON-CAPA course.',
+                'howi' => 'However, if automated course roster updates are enabled for this class, these particular sections/crosslistings are not guaranteed to contribute towards enrollment.',
+                'itis' => 'It is possible that rights to access enrollment for these classes will be available through assignment of co-owners.',
         );
-        my $badclass_msg = $cenv{'internal.courseowner'}.') - '.$lt{'dnhr'}.
-                           ' ('.$lt{'adby'}.')';
+        my $badclass_msg = $lt{'tclb'}.$linefeed.$lt{'howi'}.$linefeed.
+                           &mt('That is because the user identified as the course owner ([_1]) does not have rights to access enrollment in these classes, as determined by the policies of your institution on access to official classlists',$cenv{'internal.courseowner'}).$linefeed.$lt{'itis'};
         if ($context eq 'auto') {
             $outcome .= $badclass_msg.$linefeed;
+        } else {
             $outcome .= '<div class="LC_warning">'.$badclass_msg.$linefeed.'<ul>'."\n";
-            foreach my $item (@badclasses) {
-                if ($context eq 'auto') {
-                    $outcome .= " - $item\n";
-                } else {
-                    $outcome .= "<li>$item</li>\n";
-                }
-            }
+        }
+        foreach my $item (@badclasses) {
             if ($context eq 'auto') {
-                $outcome .= $linefeed;
+                $outcome .= " - $item\n";
             } else {
-                $outcome .= "</ul><br /><br /></div>\n";
+                $outcome .= "<li>$item</li>\n";
             }
+        }
+        if ($context eq 'auto') {
+            $outcome .= $linefeed;
+        } else {
+            $outcome .= "</ul><br /><br /></div>\n";
         } 
     }
     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'};
@@ -14179,6 +17626,9 @@ sub construct_course {
        if ($args->{'setcontent'}) {
            $cenv{'question.email'}=$args->{'ccuname'}.':'.$args->{'ccdomain'};
        }
+       if ($args->{'setcomment'}) {
+           $cenv{'comment.email'}=$args->{'ccuname'}.':'.$args->{'ccdomain'};
+       }
     }
     if ($args->{'reshome'}) {
 	$cenv{'reshome'}=$args->{'reshome'}.'/';
@@ -14250,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';
@@ -14285,7 +17739,31 @@ sub construct_course {
         $outcome .= ($fatal?$errtext:'write ok').$linefeed;
     }
 
-    return (1,$outcome);
+# 
+# Set params for Placement Tests
+#
+    if ($args->{'crstype'} eq 'Placement') {
+       my %storecontent; 
+       my $prefix=$$crsudom.'_'.$$crsunum.'.0.';
+       my %defaults = (
+                        buttonshide   => { value => 'yes',
+                                           type => 'string_yesno',},
+                        type          => { value => 'randomizetry',
+                                           type  => 'string_questiontype',},
+                        maxtries      => { value => 1,
+                                           type => 'int_pos',},
+                        problemstatus => { value => 'no',
+                                           type  => 'string_problemstatus',},
+                      );
+       foreach my $key (keys(%defaults)) {
+           $storecontent{$prefix.$key} = $defaults{$key}{'value'};
+           $storecontent{$prefix.$key.'.type'} = $defaults{$key}{'type'};
+       }
+       &Apache::lonnet::cput
+                 ('resourcedata',\%storecontent,$$crsudom,$$crsunum); 
+    }
+
+    return (1,$outcome,\@clonemsg);
 }
 
 sub make_unique_code {
@@ -14345,8 +17823,7 @@ sub generate_code {
 ############################################################
 ############################################################
 
-#SD
-# only Community and Course, or anything else?
+# Community, Course and Placement Test
 sub course_type {
     my ($cid) = @_;
     if (!defined($cid)) {
@@ -14364,17 +17841,20 @@ sub group_term {
     my %names = (
                   'Course' => 'group',
                   'Community' => 'group',
+                  'Placement' => 'group',
                 );
     return $names{$crstype};
 }
 
 sub course_types {
-    my @types = ('official','unofficial','community','textbook');
+    my @types = ('official','unofficial','community','textbook','placement','lti');
     my %typename = (
                          official   => 'Official course',
                          unofficial => 'Unofficial course',
                          community  => 'Community',
                          textbook   => 'Textbook course',
+                         placement  => 'Placement test',
+                         lti        => 'LTI provider',
                    );
     return (\@types,\%typename);
 }
@@ -14435,7 +17915,7 @@ sub escape_url {
     my ($url)   = @_;
     my @urlslices = split(/\//, $url,-1);
     my $lastitem = &escape(pop(@urlslices));
-    return join('/',@urlslices).'/'.$lastitem;
+    return &HTML::Entities::encode(join('/',@urlslices),"'").'/'.$lastitem;
 }
 
 sub compare_arrays {
@@ -14454,6 +17934,24 @@ sub compare_arrays {
     return @difference;
 }
 
+sub lon_status_items {
+    my %defaults = (
+                     E         => 100,
+                     W         => 4,
+                     N         => 1,
+                     U         => 5,
+                     threshold => 200,
+                     sysmail   => 2500,
+                   );
+    my %names = (
+                   E => 'Errors',
+                   W => 'Warnings',
+                   N => 'Notices',
+                   U => 'Unsent',
+                );
+    return (\%defaults,\%names);
+}
+
 # -------------------------------------------------------- Initialize user login
 sub init_user_environment {
     my ($r, $username, $domain, $authhost, $form, $args) = @_;
@@ -14461,9 +17959,8 @@ sub init_user_environment {
 
     my $public=($username eq 'public' && $domain eq 'public');
 
-# See if old ID present, if so, remove
-
-    my ($filename,$cookie,$userroles,$firstaccenv,$timerintenv);
+    my ($filename,$cookie,$userroles,$firstaccenv,$timerintenv,
+        $coauthorenv);
     my $now=time;
 
     if ($public) {
@@ -14484,15 +17981,43 @@ sub init_user_environment {
 	}
 	if (!$cookie) { $cookie="publicuser_$oldest"; }
     } else {
-	# if this isn't a robot, kill any existing non-robot sessions
+	# See if old ID present, if so, remove if this isn't a robot,
+	# killing any existing non-robot sessions
 	if (!$args->{'robot'}) {
 	    opendir(DIR,$lonids);
 	    while ($filename=readdir(DIR)) {
 		if ($filename=~/^$username\_\d+\_$domain\_$authhost\.id$/) {
-		    unlink($lonids.'/'.$filename);
+                    if (tie(my %oldenv,'GDBM_File',"$lonids/$filename",
+                            &GDBM_READER(),0640)) {
+                        my $linkedfile;
+                        if (exists($oldenv{'user.linkedenv'})) {
+                            $linkedfile = $oldenv{'user.linkedenv'};
+                        }
+                        untie(%oldenv);
+                        if (unlink("$lonids/$filename")) {
+                            if ($linkedfile =~ /^[a-f0-9]+_linked$/) {
+                                if (-l "$lonids/$linkedfile.id") {
+                                    unlink("$lonids/$linkedfile.id");
+                                }
+                            }
+                        }
+                    } else {
+                        unlink($lonids.'/'.$filename);
+                    }
 		}
 	    }
 	    closedir(DIR);
+# If there is a undeleted lockfile for the user's paste buffer remove it.
+            my $namespace = 'nohist_courseeditor';
+            my $lockingkey = 'paste'."\0".'locked_num';
+            my %lockhash = &Apache::lonnet::get($namespace,[$lockingkey],
+                                                $domain,$username);
+            if (exists($lockhash{$lockingkey})) {
+                my $delresult = &Apache::lonnet::del($namespace,[$lockingkey],$domain,$username);
+                unless ($delresult eq 'ok') {
+                    &Apache::lonnet::logthis("Failed to delete paste buffer locking key in $namespace for ".$username.":".$domain." Result was: $delresult");
+                }
+            }
 	}
 # Give them a new cookie
 	my $id = ($args->{'robot'} ? 'robot'.$args->{'robot'}
@@ -14501,20 +18026,19 @@ 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
 
-    my ($httpbrowser,$clientbrowser,$clientversion,$clientmathml,
-        $clientunicode,$clientos,$clientmobile,$clientinfo) = &decode_user_agent($r);
+    my ($httpbrowser,$clientbrowser,$clientversion,$clientmathml,$clientunicode,
+        $clientos,$clientmobile,$clientinfo,$clientosversion) = &decode_user_agent($r);
 
 # ------------------------------------------------------------- Get environment
 
     my %userenv = &Apache::lonnet::dump('environment',$domain,$username);
     my ($tmp) = keys(%userenv);
-    if ($tmp !~ /^(con_lost|error|no_such_host)/i) {
-    } else {
+    if ($tmp =~ /^(con_lost|error|no_such_host)/i) {
 	undef(%userenv);
     }
     if (($userenv{'interface'}) && (!$form->{'interface'})) {
@@ -14529,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,
@@ -14540,13 +18065,14 @@ sub init_user_environment {
 	     "browser.os"         => $clientos,
              "browser.mobile"     => $clientmobile,
              "browser.info"       => $clientinfo,
+             "browser.osversion"  => $clientosversion,
 	     "server.domain"      => $Apache::lonnet::perlvar{'lonDefDomain'},
 	     "request.course.fn"  => '',
 	     "request.course.uri" => '',
 	     "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'};
@@ -14565,39 +18091,92 @@ sub init_user_environment {
             $env{'user.noloadbalance'} = $lonhost;
         }
 
-        my %is_adv = ( is_adv => $env{'user.adv'} );
-        my %domdef;
-        unless ($domain eq 'public') {
-            %domdef = &Apache::lonnet::get_domain_defaults($domain);
+        if ($form->{'noloadbalance'}) {
+            my @hosts = &Apache::lonnet::current_machine_ids();
+            my $hosthere = $form->{'noloadbalance'};
+            if (grep(/^\Q$hosthere\E$/,@hosts)) {
+                $initial_env{"user.noloadbalance"} = $hosthere;
+                $env{'user.noloadbalance'} = $hosthere;
+            }
         }
 
-        foreach my $tool ('aboutme','blog','webdav','portfolio') {
-            $userenv{'availabletools.'.$tool} = 
-                &Apache::lonnet::usertools_access($username,$domain,$tool,'reload',
-                                                  undef,\%userenv,\%domdef,\%is_adv);
-        }
+        unless ($domain eq 'public') {
+            my %is_adv = ( is_adv => $env{'user.adv'} );
+            my %domdef = &Apache::lonnet::get_domain_defaults($domain);
 
-        foreach my $crstype ('official','unofficial','community','textbook') {
-            $userenv{'canrequest.'.$crstype} =
-                &Apache::lonnet::usertools_access($username,$domain,$crstype,
-                                                  'reload','requestcourses',
-                                                  \%userenv,\%domdef,\%is_adv);
-        }
+            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);
+            }
+
+            foreach my $crstype ('official','unofficial','community','textbook','placement','lti') {
+                $userenv{'canrequest.'.$crstype} =
+                    &Apache::lonnet::usertools_access($username,$domain,$crstype,
+                                                      'reload','requestcourses',
+                                                      \%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',
-                                        \%userenv,\%domdef,\%is_adv);
-        my %reqauthor = &Apache::lonnet::get('requestauthor',['author_status','author'],
-                                             $domain,$username);
-        my $reqstatus = $reqauthor{'author_status'};
-        if ($reqstatus eq 'approval' || $reqstatus eq 'approved') { 
-            if (ref($reqauthor{'author'}) eq 'HASH') {
-                $userenv{'requestauthorqueued'} = $reqstatus.':'.
-                                                  $reqauthor{'author'}{'timestamp'};
+            $userenv{'canrequest.author'} =
+                &Apache::lonnet::usertools_access($username,$domain,'requestauthor',
+                                                  'reload','requestauthor',
+                                                  \%userenv,\%domdef,\%is_adv);
+            my %reqauthor = &Apache::lonnet::get('requestauthor',['author_status','author'],
+                                                 $domain,$username);
+            my $reqstatus = $reqauthor{'author_status'};
+            if ($reqstatus eq 'approval' || $reqstatus eq 'approved') { 
+                if (ref($reqauthor{'author'}) eq 'HASH') {
+                    $userenv{'requestauthorqueued'} = $reqstatus.':'.
+                                                      $reqauthor{'author'}{'timestamp'};
+                }
+            }
+            my ($types,$typename) = &course_types();
+            if (ref($types) eq 'ARRAY') {
+                my @options = ('approval','validate','autolimit');
+                my $optregex = join('|',@options);
+                my (%willtrust,%trustchecked);
+                foreach my $type (@{$types}) {
+                    my $dom_str = $env{'environment.reqcrsotherdom.'.$type};
+                    if ($dom_str ne '') {
+                        my $updatedstr = '';
+                        my @possdomains = split(',',$dom_str);
+                        foreach my $entry (@possdomains) {
+                            my ($extdom,$extopt) = split(':',$entry);
+                            unless ($trustchecked{$extdom}) {
+                                $willtrust{$extdom} = &Apache::lonnet::will_trust('reqcrs',$domain,$extdom);
+                                $trustchecked{$extdom} = 1;
+                            }
+                            if ($willtrust{$extdom}) {
+                                $updatedstr .= $entry.',';
+                            }
+                        }
+                        $updatedstr =~ s/,$//;
+                        if ($updatedstr) {
+                            $userenv{'reqcrsotherdom.'.$type} = $updatedstr;
+                        } else {
+                            delete($userenv{'reqcrsotherdom.'.$type});
+                        }
+                    }
+                }
             }
         }
-
 	$env{'user.environment'} = "$lonids/$cookie.id";
 
 	if (tie(my %disk_env,'GDBM_File',"$lonids/$cookie.id",
@@ -14611,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'});
 	    }
@@ -14682,53 +18266,814 @@ sub clean_symb {
     return ($symb,$enc);
 }
 
-sub build_release_hashes {
-    my ($checkparms,$checkresponsetypes,$checkcrstypes,$anonsurvey,$randomizetry) = @_;
-    return unless((ref($checkparms) eq 'HASH') && (ref($checkresponsetypes) eq 'HASH') &&
-                  (ref($checkcrstypes) eq 'HASH') && (ref($anonsurvey) eq 'HASH') &&
-                  (ref($randomizetry) eq 'HASH'));
-    foreach my $key (keys(%Apache::lonnet::needsrelease)) {
-        my ($item,$name,$value) = split(/:/,$key);
-        if ($item eq 'parameter') {
-            if (ref($checkparms->{$name}) eq 'ARRAY') {
-                unless(grep(/^\Q$name\E$/,@{$checkparms->{$name}})) {
-                    push(@{$checkparms->{$name}},$value);
-                }
+############################################################
+############################################################
+
+=pod
+
+=head1 Routines for building display used to search for courses
+
+
+=over 4
+
+=item * &build_filters()
+
+Create markup for a table used to set filters to use when selecting
+courses in a domain.  Used by lonpickcourse.pm, lonmodifycourse.pm
+and quotacheck.pl
+
+
+Inputs:
+
+filterlist - anonymous array of fields to include as potential filters 
+
+crstype - course type
+
+roleelement - fifth arg in selectcourse_link() populates fifth arg in javascript: opencrsbrowser() function, used
+              to pop-open a course selector (will contain "extra element"). 
+
+multelement - if multiple course selections will be allowed, this will be a hidden form element: name: multiple; value: 1
+
+filter - anonymous hash of criteria and their values
+
+action - form action
+
+numfiltersref - ref to scalar (count of number of elements in institutional codes -- e.g., 4 for year, semester, department, and number)
+
+caller - caller context (e.g., set to 'modifycourse' when routine is called from lonmodifycourse.pm)
+
+cloneruname - username of owner of new course who wants to clone
+
+clonerudom - domain of owner of new course who wants to clone
+
+typeelem - text to use for left column in row containing course type (i.e., Course, Community or Course/Community) 
+
+codetitlesref - reference to array of titles of components in institutional codes (official courses)
+
+codedom - domain
+
+formname - value of form element named "form". 
+
+fixeddom - domain, if fixed.
+
+prevphase - value to assign to form element named "phase" when going back to the previous screen  
+
+cnameelement - name of form element in form on opener page which will receive title of selected course 
+
+cnumelement - name of form element in form on opener page which will receive courseID  of selected course
+
+cdomelement - name of form element in form on opener page which will receive domain of selected course
+
+setroles - includes access constraint identifier when setting a roles-based condition for acces to a portfolio file
+
+clonetext - hidden form elements containing list of courses cloneable by intended course owner when DC creates a course
+
+clonewarning - warning message about missing information for intended course owner when DC creates a course
+
+
+Returns: $output - HTML for display of search criteria, and hidden form elements.
+
+
+Side Effects: None
+
+=cut
+
+# ---------------------------------------------- search for courses based on last activity etc.
+
+sub build_filters {
+    my ($filterlist,$crstype,$roleelement,$multelement,$filter,$action,
+        $numtitlesref,$caller,$cloneruname,$clonerudom,$typeelement,
+        $codetitlesref,$codedom,$formname,$fixeddom,$prevphase,
+        $cnameelement,$cnumelement,$cdomelement,$setroles,
+        $clonetext,$clonewarning) = @_;
+    my ($list,$jscript);
+    my $onchange = 'javascript:updateFilters(this)';
+    my ($domainselectform,$sincefilterform,$createdfilterform,
+        $ownerdomselectform,$persondomselectform,$instcodeform,
+        $typeselectform,$instcodetitle);
+    if ($formname eq '') {
+        $formname = $caller;
+    }
+    foreach my $item (@{$filterlist}) {
+        unless (($item eq 'descriptfilter') || ($item eq 'instcodefilter') ||
+                ($item eq 'sincefilter') || ($item eq 'createdfilter')) {
+            if ($item eq 'domainfilter') {
+                $filter->{$item} = &LONCAPA::clean_domain($filter->{$item});
+            } elsif ($item eq 'coursefilter') {
+                $filter->{$item} = &LONCAPA::clean_courseid($filter->{$item});
+            } elsif ($item eq 'ownerfilter') {
+                $filter->{$item} = &LONCAPA::clean_username($filter->{$item});
+            } elsif ($item eq 'ownerdomfilter') {
+                $filter->{'ownerdomfilter'} =
+                    &LONCAPA::clean_domain($filter->{$item});
+                $ownerdomselectform = &select_dom_form($filter->{'ownerdomfilter'},
+                                                       'ownerdomfilter',1);
+            } elsif ($item eq 'personfilter') {
+                $filter->{$item} = &LONCAPA::clean_username($filter->{$item});
+            } elsif ($item eq 'persondomfilter') {
+                $persondomselectform = &select_dom_form($filter->{'persondomfilter'},
+                                                        'persondomfilter',1);
             } else {
-                push(@{$checkparms->{$name}},$value);
+                $filter->{$item} =~ s/\W//g;
             }
-        } elsif ($item eq 'resourcetag') {
-            if ($name eq 'responsetype') {
-                $checkresponsetypes->{$value} = $Apache::lonnet::needsrelease{$key}
+            if (!$filter->{$item}) {
+                $filter->{$item} = '';
             }
-        } elsif ($item eq 'course') {
-            if ($name eq 'crstype') {
-                $checkcrstypes->{$value} = $Apache::lonnet::needsrelease{$key};
+        }
+        if ($item eq 'domainfilter') {
+            my $allow_blank = 1;
+            if ($formname eq 'portform') {
+                $allow_blank=0;
+            } elsif ($formname eq 'studentform') {
+                $allow_blank=0;
+            }
+            if ($fixeddom) {
+                $domainselectform = '<input type="hidden" name="domainfilter"'.
+                                    ' value="'.$codedom.'" />'.
+                                    &Apache::lonnet::domain($codedom,'description');
+            } else {
+                $domainselectform = &select_dom_form($filter->{$item},
+                                                     'domainfilter',
+                                                      $allow_blank,'',$onchange);
             }
+        } else {
+            $list->{$item} = &HTML::Entities::encode($filter->{$item},'<>&"');
+        }
+    }
+
+    # last course activity filter and selection
+    $sincefilterform = &timebased_select_form('sincefilter',$filter);
+
+    # course created filter and selection
+    if (exists($filter->{'createdfilter'})) {
+        $createdfilterform = &timebased_select_form('createdfilter',$filter);
+    }
+
+    my $prefix = $crstype;
+    if ($crstype eq 'Placement') {
+        $prefix = 'Placement Test'
+    }
+    my %lt = &Apache::lonlocal::texthash(
+                'cac' => "$prefix Activity",
+                'ccr' => "$prefix Created",
+                'cde' => "$prefix Title",
+                'cdo' => "$prefix Domain",
+                'ins' => 'Institutional Code',
+                'inc' => 'Institutional Categorization',
+                'cow' => "$prefix Owner/Co-owner",
+                'cop' => "$prefix Personnel Includes",
+                'cog' => 'Type',
+             );
+
+    if (($formname eq 'ccrs') || ($formname eq 'requestcrs')) {
+        my $typeval = 'Course';
+        if ($crstype eq 'Community') {
+            $typeval = 'Community';
+        } elsif ($crstype eq 'Placement') {
+            $typeval = 'Placement';
+        }
+        $typeselectform = '<input type="hidden" name="type" value="'.$typeval.'" />';
+    } else {
+        $typeselectform =  '<select name="type" size="1"';
+        if ($onchange) {
+            $typeselectform .= ' onchange="'.$onchange.'"';
+        }
+        $typeselectform .= '>'."\n";
+        foreach my $posstype ('Course','Community','Placement') {
+            my $shown;
+            if ($posstype eq 'Placement') {
+                $shown = &mt('Placement Test');
+            } else {
+                $shown = &mt($posstype);
+            }
+            $typeselectform.='<option value="'.$posstype.'"'.
+                ($posstype eq $crstype ? ' selected="selected" ' : ''). ">".$shown."</option>\n";
+        }
+        $typeselectform.="</select>";
+    }
+
+    my ($cloneableonlyform,$cloneabletitle);
+    if (exists($filter->{'cloneableonly'})) {
+        my $cloneableon = '';
+        my $cloneableoff = ' checked="checked"';
+        if ($filter->{'cloneableonly'}) {
+            $cloneableon = $cloneableoff;
+            $cloneableoff = '';
+        }
+        $cloneableonlyform = '<span class="LC_nobreak"><label><input type="radio" name="cloneableonly" value="1" '.$cloneableon.'/>&nbsp;'.&mt('Required').'</label>'.('&nbsp;'x3).'<label><input type="radio" name="cloneableonly" value="" '.$cloneableoff.' />&nbsp;'.&mt('No restriction').'</label></span>';
+        if ($formname eq 'ccrs') {
+            $cloneabletitle = &mt('Cloneable for [_1]',$cloneruname.':'.$clonerudom);
+        } else {
+            $cloneabletitle = &mt('Cloneable by you');
+        }
+    }
+    my $officialjs;
+    if ($crstype eq 'Course') {
+        if (exists($filter->{'instcodefilter'})) {
+#            if (($fixeddom) || ($formname eq 'requestcrs') ||
+#                ($formname eq 'modifycourse') || ($formname eq 'filterpicker')) {
+            if ($codedom) { 
+                $officialjs = 1;
+                ($instcodeform,$jscript,$$numtitlesref) =
+                    &Apache::courseclassifier::instcode_selectors($codedom,'filterpicker',
+                                                                  $officialjs,$codetitlesref);
+                if ($jscript) {
+                    $jscript = '<script type="text/javascript">'."\n".
+                               '// <![CDATA['."\n".
+                               $jscript."\n".
+                               '// ]]>'."\n".
+                               '</script>'."\n";
+                }
+            }
+            if ($instcodeform eq '') {
+                $instcodeform =
+                    '<input type="text" name="instcodefilter" size="10" value="'.
+                    $list->{'instcodefilter'}.'" />';
+                $instcodetitle = $lt{'ins'};
+            } else {
+                $instcodetitle = $lt{'inc'};
+            }
+            if ($fixeddom) {
+                $instcodetitle .= '<br />('.$codedom.')';
+            }
+        }
+    }
+    my $output = qq|
+<form method="post" name="filterpicker" action="$action">
+<input type="hidden" name="form" value="$formname" />
+|;
+    if ($formname eq 'modifycourse') {
+        $output .= '<input type="hidden" name="phase" value="courselist" />'."\n".
+                   '<input type="hidden" name="prevphase" value="'.
+                   $prevphase.'" />'."\n";
+    } elsif ($formname eq 'quotacheck') {
+        $output .= qq|
+<input type="hidden" name="sortby" value="" />
+<input type="hidden" name="sortorder" value="" />
+|;
+    } else {
+        my $name_input;
+        if ($cnameelement ne '') {
+            $name_input = '<input type="hidden" name="cnameelement" value="'.
+                          $cnameelement.'" />';
+        }
+        $output .= qq|
+<input type="hidden" name="cnumelement" value="$cnumelement" />
+<input type="hidden" name="cdomelement" value="$cdomelement" />
+$name_input
+$roleelement
+$multelement
+$typeelement
+|;
+        if ($formname eq 'portform') {
+            $output .= '<input type="hidden" name="setroles" value="'.$setroles.'" />'."\n";
         }
     }
-    ($anonsurvey->{major},$anonsurvey->{minor}) = split(/\./,$Apache::lonnet::needsrelease{'parameter:type:anonsurvey'});
-    ($randomizetry->{major},$randomizetry->{minor}) = split(/\./,$Apache::lonnet::needsrelease{'parameter:type:randomizetry'});
+    if ($fixeddom) {
+        $output .= '<input type="hidden" name="fixeddom" value="'.$fixeddom.'" />'."\n";
+    }
+    $output .= "<br />\n".&Apache::lonhtmlcommon::start_pick_box();
+    if ($sincefilterform) {
+        $output .= &Apache::lonhtmlcommon::row_title($lt{'cac'})
+                  .$sincefilterform
+                  .&Apache::lonhtmlcommon::row_closure();
+    }
+    if ($createdfilterform) {
+        $output .= &Apache::lonhtmlcommon::row_title($lt{'ccr'})
+                  .$createdfilterform
+                  .&Apache::lonhtmlcommon::row_closure();
+    }
+    if ($domainselectform) {
+        $output .= &Apache::lonhtmlcommon::row_title($lt{'cdo'})
+                  .$domainselectform
+                  .&Apache::lonhtmlcommon::row_closure();
+    }
+    if ($typeselectform) {
+        if (($formname eq 'ccrs') || ($formname eq 'requestcrs')) {
+            $output .= $typeselectform;
+        } else {
+            $output .= &Apache::lonhtmlcommon::row_title($lt{'cog'})
+                      .$typeselectform
+                      .&Apache::lonhtmlcommon::row_closure();
+        }
+    }
+    if ($instcodeform) {
+        $output .= &Apache::lonhtmlcommon::row_title($instcodetitle)
+                  .$instcodeform
+                  .&Apache::lonhtmlcommon::row_closure();
+    }
+    if (exists($filter->{'ownerfilter'})) {
+        $output .= &Apache::lonhtmlcommon::row_title($lt{'cow'}).
+                   '<table><tr><td>'.&mt('Username').'<br />'.
+                   '<input type="text" name="ownerfilter" size="20" value="'.
+                   $list->{'ownerfilter'}.'" /></td><td>'.&mt('Domain').'<br />'.
+                   $ownerdomselectform.'</td></tr></table>'.
+                   &Apache::lonhtmlcommon::row_closure();
+    }
+    if (exists($filter->{'personfilter'})) {
+        $output .= &Apache::lonhtmlcommon::row_title($lt{'cop'}).
+                   '<table><tr><td>'.&mt('Username').'<br />'.
+                   '<input type="text" name="personfilter" size="20" value="'.
+                   $list->{'personfilter'}.'" /></td><td>'.&mt('Domain').'<br />'.
+                   $persondomselectform.'</td></tr></table>'.
+                   &Apache::lonhtmlcommon::row_closure();
+    }
+    if (exists($filter->{'coursefilter'})) {
+        $output .= &Apache::lonhtmlcommon::row_title(&mt('LON-CAPA course ID'))
+                  .'<input type="text" name="coursefilter" size="25" value="'
+                  .$list->{'coursefilter'}.'" />'
+                  .&Apache::lonhtmlcommon::row_closure();
+    }
+    if ($cloneableonlyform) {
+        $output .= &Apache::lonhtmlcommon::row_title($cloneabletitle).
+                   $cloneableonlyform.&Apache::lonhtmlcommon::row_closure();
+    }
+    if (exists($filter->{'descriptfilter'})) {
+        $output .= &Apache::lonhtmlcommon::row_title($lt{'cde'})
+                  .'<input type="text" name="descriptfilter" size="40" value="'
+                  .$list->{'descriptfilter'}.'" />'
+                  .&Apache::lonhtmlcommon::row_closure(1);
+    }
+    $output .= &Apache::lonhtmlcommon::end_pick_box().'<p>'.$clonetext."\n".
+               '<input type="hidden" name="updater" value="" />'."\n".
+               '<input type="submit" name="gosearch" value="'.
+               &mt('Search').'" /></p>'."\n".'</form>'."\n".'<hr />'."\n";
+    return $jscript.$clonewarning.$output;
+}
+
+=pod 
+
+=item * &timebased_select_form()
+
+Create markup for a dropdown list used to select a time-based
+filter e.g., Course Activity, Course Created, when searching for courses
+or communities
+
+Inputs:
+
+item - name of form element (sincefilter or createdfilter)
+
+filter - anonymous hash of criteria and their values
+
+Returns: HTML for a select box contained a blank, then six time selections,
+         with value set in incoming form variables currently selected. 
+
+Side Effects: None
+
+=cut
+
+sub timebased_select_form {
+    my ($item,$filter) = @_;
+    if (ref($filter) eq 'HASH') {
+        $filter->{$item} =~ s/[^\d-]//g;
+        if (!$filter->{$item}) { $filter->{$item}=-1; }
+        return &select_form(
+                            $filter->{$item},
+                            $item,
+                            {      '-1' => '',
+                                '86400' => &mt('today'),
+                               '604800' => &mt('last week'),
+                              '2592000' => &mt('last month'),
+                              '7776000' => &mt('last three months'),
+                             '15552000' => &mt('last six months'),
+                             '31104000' => &mt('last year'),
+                    'select_form_order' =>
+                           ['-1','86400','604800','2592000','7776000',
+                            '15552000','31104000']});
+    }
+}
+
+=pod
+
+=item * &js_changer()
+
+Create script tag containing Javascript used to submit course search form
+when course type or domain is changed, and also to hide 'Searching ...' on
+page load completion for page showing search result.
+
+Inputs: None
+
+Returns: markup containing updateFilters() and hideSearching() javascript functions. 
+
+Side Effects: None
+
+=cut
+
+sub js_changer {
+    return <<ENDJS;
+<script type="text/javascript">
+// <![CDATA[
+function updateFilters(caller) {
+    if (typeof(caller) != "undefined") {
+        document.filterpicker.updater.value = caller.name;
+    }
+    document.filterpicker.submit();
+}
+
+function hideSearching() {
+    if (document.getElementById('searching')) {
+        document.getElementById('searching').style.display = 'none';
+    }
     return;
 }
 
+// ]]>
+</script>
+
+ENDJS
+}
+
+=pod
+
+=item * &search_courses()
+
+Process selected filters form course search form and pass to lonnet::courseiddump
+to retrieve a hash for which keys are courseIDs which match the selected filters.
+
+Inputs:
+
+dom - domain being searched 
+
+type - course type ('Course' or 'Community' or '.' if any).
+
+filter - anonymous hash of criteria and their values
+
+numtitles - for institutional codes - number of categories
+
+cloneruname - optional username of new course owner
+
+clonerudom - optional domain of new course owner
+
+domcloner - optional "domcloner" flag; has value=1 if user has ccc priv in domain being filtered by, 
+            (used when DC is using course creation form)
+
+codetitles - reference to array of titles of components in institutional codes (official courses).
+
+cc_clone - escaped comma separated list of courses for which course cloner has active CC role
+           (and so can clone automatically)
+
+reqcrsdom - domain of new course, where search_courses is used to identify potential courses to clone
+
+reqinstcode - institutional code of new course, where search_courses is used to identify potential 
+              courses to clone 
+
+Returns: %courses - hash of courses satisfying search criteria, keys = course IDs, values are corresponding colon-separated escaped description, institutional code, owner and type.
+
+
+Side Effects: None
+
+=cut
+
+
+sub search_courses {
+    my ($dom,$type,$filter,$numtitles,$cloneruname,$clonerudom,$domcloner,$codetitles,
+        $cc_clone,$reqcrsdom,$reqinstcode) = @_;
+    my (%courses,%showcourses,$cloner);
+    if (($filter->{'ownerfilter'} ne '') ||
+        ($filter->{'ownerdomfilter'} ne '')) {
+        $filter->{'combownerfilter'} = $filter->{'ownerfilter'}.':'.
+                                       $filter->{'ownerdomfilter'};
+    }
+    foreach my $item ('descriptfilter','coursefilter','combownerfilter') {
+        if (!$filter->{$item}) {
+            $filter->{$item}='.';
+        }
+    }
+    my $now = time;
+    my $timefilter =
+       ($filter->{'sincefilter'}==-1?1:$now-$filter->{'sincefilter'});
+    my ($createdbefore,$createdafter);
+    if (($filter->{'createdfilter'} ne '') && ($filter->{'createdfilter'} !=-1)) {
+        $createdbefore = $now;
+        $createdafter = $now-$filter->{'createdfilter'};
+    }
+    my ($instcodefilter,$regexpok);
+    if ($numtitles) {
+        if ($env{'form.official'} eq 'on') {
+            $instcodefilter =
+                &Apache::courseclassifier::instcode_search_str($dom,$numtitles,$codetitles);
+            $regexpok = 1;
+        } elsif ($env{'form.official'} eq 'off') {
+            $instcodefilter = &Apache::courseclassifier::instcode_search_str($dom,$numtitles,$codetitles);
+            unless ($instcodefilter eq '') {
+                $regexpok = -1;
+            }
+        }
+    } else {
+        $instcodefilter = $filter->{'instcodefilter'};
+    }
+    if ($instcodefilter eq '') { $instcodefilter = '.'; }
+    if ($type eq '') { $type = '.'; }
+
+    if (($clonerudom ne '') && ($cloneruname ne '')) {
+        $cloner = $cloneruname.':'.$clonerudom;
+    }
+    %courses = &Apache::lonnet::courseiddump($dom,
+                                             $filter->{'descriptfilter'},
+                                             $timefilter,
+                                             $instcodefilter,
+                                             $filter->{'combownerfilter'},
+                                             $filter->{'coursefilter'},
+                                             undef,undef,$type,$regexpok,undef,undef,
+                                             undef,undef,$cloner,$cc_clone,
+                                             $filter->{'cloneableonly'},
+                                             $createdbefore,$createdafter,undef,
+                                             $domcloner,undef,$reqcrsdom,$reqinstcode);
+    if (($filter->{'personfilter'} ne '') && ($filter->{'persondomfilter'} ne '')) {
+        my $ccrole;
+        if ($type eq 'Community') {
+            $ccrole = 'co';
+        } else {
+            $ccrole = 'cc';
+        }
+        my %rolehash = &Apache::lonnet::get_my_roles($filter->{'personfilter'},
+                                                     $filter->{'persondomfilter'},
+                                                     'userroles',undef,
+                                                     [$ccrole,'in','ad','ep','ta','cr'],
+                                                     $dom);
+        foreach my $role (keys(%rolehash)) {
+            my ($cnum,$cdom,$courserole) = split(':',$role);
+            my $cid = $cdom.'_'.$cnum;
+            if (exists($courses{$cid})) {
+                if (ref($courses{$cid}) eq 'HASH') {
+                    if (ref($courses{$cid}{roles}) eq 'ARRAY') {
+                        if (!grep(/^\Q$courserole\E$/,@{$courses{$cid}{roles}})) {
+                            push(@{$courses{$cid}{roles}},$courserole);
+                        }
+                    } else {
+                        $courses{$cid}{roles} = [$courserole];
+                    }
+                    $showcourses{$cid} = $courses{$cid};
+                }
+            }
+        }
+        %courses = %showcourses;
+    }
+    return %courses;
+}
+
+=pod
+
+=back
+
+=head1 Routines for version requirements for current course.
+
+=over 4
+
+=item * &check_release_required()
+
+Compares required LON-CAPA version with version on server, and
+if required version is newer looks for a server with the required version.
+
+Looks first at servers in user's owen domain; if none suitable, looks at
+servers in course's domain are permitted to host sessions for user's domain.
+
+Inputs:
+
+$loncaparev - Version on current server (format: Major.Minor.Subrelease-datestamp)
+
+$courseid - Course ID of current course
+
+$rolecode - User's current role in course (for switchserver query string).
+
+$required - LON-CAPA version needed by course (format: Major.Minor).
+
+
+Returns:
+
+$switchserver - query string tp append to /adm/switchserver call (if 
+                current server's LON-CAPA version is too old. 
+
+$warning - Message is displayed if no suitable server could be found.
+
+=cut
+
+sub check_release_required {
+    my ($loncaparev,$courseid,$rolecode,$required) = @_;
+    my ($switchserver,$warning);
+    if ($required ne '') {
+        my ($reqdmajor,$reqdminor) = ($required =~ /^(\d+)\.(\d+)$/);
+        my ($major,$minor) = ($loncaparev =~ /^\'?(\d+)\.(\d+)\.[\w.\-]+\'?$/);
+        if ($reqdmajor ne '' && $reqdminor ne '') {
+            my $otherserver;
+            if (($major eq '' && $minor eq '') ||
+                (($reqdmajor > $major) || (($reqdmajor == $major) && ($reqdminor > $minor)))) {
+                my ($userdomserver) = &Apache::lonnet::choose_server($env{'user.domain'},undef,$required,1);
+                my $switchlcrev =
+                    &Apache::lonnet::get_server_loncaparev($env{'user.domain'},
+                                                           $userdomserver);
+                my ($swmajor,$swminor) = ($switchlcrev =~ /^\'?(\d+)\.(\d+)\.[\w.\-]+\'?$/);
+                if (($swmajor eq '' && $swminor eq '') || ($reqdmajor > $swmajor) ||
+                    (($reqdmajor == $swmajor) && ($reqdminor > $swminor))) {
+                    my $cdom = $env{'course.'.$courseid.'.domain'};
+                    if ($cdom ne $env{'user.domain'}) {
+                        my ($coursedomserver,$coursehostname) = &Apache::lonnet::choose_server($cdom,undef,$required,1);
+                        my $serverhomeID = &Apache::lonnet::get_server_homeID($coursehostname);
+                        my $serverhomedom = &Apache::lonnet::host_domain($serverhomeID);
+                        my %defdomdefaults = &Apache::lonnet::get_domain_defaults($serverhomedom);
+                        my %udomdefaults = &Apache::lonnet::get_domain_defaults($env{'user.domain'});
+                        my $remoterev = &Apache::lonnet::get_server_loncaparev($serverhomedom,$coursedomserver);
+                        my $canhost =
+                            &Apache::lonnet::can_host_session($env{'user.domain'},
+                                                              $coursedomserver,
+                                                              $remoterev,
+                                                              $udomdefaults{'remotesessions'},
+                                                              $defdomdefaults{'hostedsessions'});
+
+                        if ($canhost) {
+                            $otherserver = $coursedomserver;
+                        } else {
+                            $warning = &mt('Requires LON-CAPA version [_1].',$env{'course.'.$courseid.'.internal.releaserequired'}).'<br />'. &mt("No suitable server could be found amongst servers in either your own domain or in the course's domain.");
+                        }
+                    } else {
+                        $warning = &mt('Requires LON-CAPA version [_1].',$env{'course.'.$courseid.'.internal.releaserequired'}).'<br />'.&mt("No suitable server could be found amongst servers in your own domain (which is also the course's domain).");
+                    }
+                } else {
+                    $otherserver = $userdomserver;
+                }
+            }
+            if ($otherserver ne '') {
+                $switchserver = 'otherserver='.$otherserver.'&amp;role='.$rolecode;
+            }
+        }
+    }
+    return ($switchserver,$warning);
+}
+
+=pod
+
+=item * &check_release_result()
+
+Inputs:
+
+$switchwarning - Warning message if no suitable server found to host session.
+
+$switchserver - query string to append to /adm/switchserver containing lonHostID
+                and current role.
+
+Returns: HTML to display with information about requirement to switch server.
+         Either displaying warning with link to Roles/Courses screen or
+         display link to switchserver.
+
+=cut
+
+sub check_release_result {
+    my ($switchwarning,$switchserver) = @_;
+    my $output = &start_page('Selected course unavailable on this server').
+                 '<p class="LC_warning">';
+    if ($switchwarning) {
+        $output .= $switchwarning.'<br /><a href="/adm/roles">';
+        if (&show_course()) {
+            $output .= &mt('Display courses');
+        } else {
+            $output .= &mt('Display roles');
+        }
+        $output .= '</a>';
+    } elsif ($switchserver) {
+        $output .= &mt('This course requires a newer version of LON-CAPA than is installed on this server.').
+                   '<br />'.
+                   '<a href="/adm/switchserver?'.$switchserver.'">'.
+                   &mt('Switch Server').
+                   '</a>';
+    }
+    $output .= '</p>'.&end_page();
+    return $output;
+}
+
+=pod
+
+=item * &needs_coursereinit()
+
+Determine if course contents stored for user's session needs to be
+refreshed, because content has changed since "Big Hash" last tied.
+
+Check for change is made if time last checked is more than 10 minutes ago
+(by default).
+
+Inputs:
+
+$loncaparev - Version on current server (format: Major.Minor.Subrelease-datestamp)
+
+$interval (optional) - Time which may elapse (in s) between last check for content
+                       change in current course. (default: 600 s).  
+
+Returns: an array; first element is:
+
+=over 4
+
+'switch' - if content updates mean user's session
+           needs to be switched to a server running a newer LON-CAPA version
+ 
+'update' - if course session needs to be refreshed (i.e., Big Hash needs to be reloaded)
+           on current server hosting user's session                
+
+''       - if no action required.
+
+=back
+
+If first item element is 'switch':
+
+second item is $switchwarning - Warning message if no suitable server found to host session. 
+
+third item is $switchserver - query string to append to /adm/switchserver containing lonHostID
+                              and current role. 
+
+otherwise: no other elements returned.
+
+=back
+
+=cut
+
+sub needs_coursereinit {
+    my ($loncaparev,$interval) = @_;
+    return() unless ($env{'request.course.id'} && $env{'request.course.tied'});
+    my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+    my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
+    my $now = time;
+    if ($interval eq '') {
+        $interval = 600;
+    }
+    if (($now-$env{'request.course.timechecked'})>$interval) {
+        &Apache::lonnet::appenv({'request.course.timechecked'=>$now});
+        my $blocked = &blocking_status('reinit',undef,$cnum,$cdom,undef,1);
+        if ($blocked) {
+            return ();
+        }
+        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 ();
+}
+
+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 ();
+}
+
 sub update_content_constraints {
     my ($cdom,$cnum,$chome,$cid) = @_;
     my %curr_reqd_hash = &Apache::lonnet::userenvironment($cdom,$cnum,'internal.releaserequired');
     my ($reqdmajor,$reqdminor) = split(/\./,$curr_reqd_hash{'internal.releaserequired'});
-    my %checkresponsetypes;
+    my (%checkresponsetypes,%checkcrsrestypes);
     foreach my $key (keys(%Apache::lonnet::needsrelease)) {
         my ($item,$name,$value) = split(/:/,$key);
         if ($item eq 'resourcetag') {
             if ($name eq 'responsetype') {
                 $checkresponsetypes{$value} = $Apache::lonnet::needsrelease{$key}
             }
+        } elsif ($item eq 'course') {
+            if ($name eq 'courserestype') {
+                $checkcrsrestypes{$value} = $Apache::lonnet::needsrelease{$key};
+            }
         }
     }
     my $navmap = Apache::lonnavmaps::navmap->new();
     if (defined($navmap)) {
-        my %allresponses;
-        foreach my $res ($navmap->retrieveResources(undef,sub { $_[0]->is_problem() },1,0)) {
+        my (%allresponses,%allcrsrestypes);
+        foreach my $res ($navmap->retrieveResources(undef,sub { $_[0]->is_problem() || $_[0]->is_tool() },1,0)) {
+            if ($res->is_tool()) {
+                if ($allcrsrestypes{'exttool'}) {
+                    $allcrsrestypes{'exttool'} ++;
+                } else {
+                    $allcrsrestypes{'exttool'} = 1;
+                }
+                next;
+            }
             my %responses = $res->responseTypes();
             foreach my $key (keys(%responses)) {
                 next unless(exists($checkresponsetypes{$key}));
@@ -14741,8 +19086,20 @@ sub update_content_constraints {
                 ($reqdmajor,$reqdminor) = ($major,$minor);
             }
         }
+        foreach my $key (keys(%allcrsrestypes)) {
+            my ($major,$minor) = split(/\./,$checkcrsrestypes{$key});
+            if (($major > $reqdmajor) || ($major == $reqdmajor && $minor > $reqdminor)) {
+                ($reqdmajor,$reqdminor) = ($major,$minor);
+            }
+        }
         undef($navmap);
     }
+    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);
+        }
+    }
     unless (($reqdmajor eq '') && ($reqdminor eq '')) {
         &Apache::lonnet::update_released_required($reqdmajor.'.'.$reqdminor,$cdom,$cnum,$chome,$cid);
     }
@@ -14789,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);
@@ -14798,33 +19157,152 @@ 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,$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,$errors) = &recurse_supplemental($cnum,$cdom,$1,$numfiles,$errors);
+                            $errors = &recurse_supplemental($cnum,$cdom,$1,$errors,$possdel,$suppids,
+                                                            $hiddensupp,$hiddensupp->{$id});
                         } else {
-                            $numfiles ++;
+                            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);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+    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});
+            }
         }
     }
-    return ($numfiles,$errors);
+}
+
+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 {
-    my ($symb) = @_;
-    return unless ($symb);
+    my ($symb,$navmapref) = @_;
+    return unless ($symb && ref($navmapref));
     my ($mapurl,$id,$resurl) = &Apache::lonnet::decode_symb($symb);
     if ($resurl=~/\.(sequence|page)$/) {
         $mapurl=$resurl;
@@ -14832,9 +19310,11 @@ sub symb_to_docspath {
         $mapurl=$env{'course.'.$env{'request.course.id'}.'.url'};
     }
     my $mapresobj;
-    my $navmap = Apache::lonnavmaps::navmap->new();
-    if (ref($navmap)) {
-        $mapresobj = $navmap->getResourceByUrl($mapurl);
+    unless (ref($$navmapref)) {
+        $$navmapref = Apache::lonnavmaps::navmap->new();
+    }
+    if (ref($$navmapref)) {
+        $mapresobj = $$navmapref->getResourceByUrl($mapurl);
     }
     $mapurl=~s{^.*/([^/]+)\.(\w+)$}{$1};
     my $type=$2;
@@ -14844,7 +19324,7 @@ sub symb_to_docspath {
         if ($pcslist ne '') {
             foreach my $pc (split(/,/,$pcslist)) {
                 next if ($pc <= 1);
-                my $res = $navmap->getByMapPc($pc);
+                my $res = $$navmapref->getByMapPc($pc);
                 if (ref($res)) {
                     my $thisurl = $res->src();
                     $thisurl=~s{^.*/([^/]+)\.\w+$}{$1};
@@ -14890,32 +19370,94 @@ 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) = &get_captcha_config($context,$lonhost);
+    my ($captcha,$pubkey,$privkey,$version) = 
+        &get_captcha_config($context,$lonhost,$defdom);
     if ($captcha eq 'original') {
         $output = &create_captcha();
         unless ($output) {
             $error = 'captcha';
         }
     } elsif ($captcha eq 'recaptcha') {
-        $output = &create_recaptcha($pubkey);
+        $output = &create_recaptcha($pubkey,$version);
         unless ($output) {
             $error = 'recaptcha';
         }
     }
-    return ($output,$error,$captcha);
+    return ($output,$error,$captcha,$version);
 }
 
 sub captcha_response {
-    my ($context,$lonhost) = @_;
+    my ($context,$lonhost,$defdom) = @_;
     my ($captcha_chk,$captcha_error);
-    my ($captcha,$pubkey,$privkey) = &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') {
-        $captcha_chk = &check_recaptcha($privkey);
+        $captcha_chk = &check_recaptcha($privkey,$version);
     } else {
         $captcha_chk = 1;
     }
@@ -14923,8 +19465,8 @@ sub captcha_response {
 }
 
 sub get_captcha_config {
-    my ($context,$lonhost) = @_;
-    my ($captcha,$pubkey,$privkey,$hashtocheck);
+    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);
     my $serverhomedom = &Apache::lonnet::host_domain($serverhomeID);
@@ -14940,6 +19482,10 @@ sub get_captcha_config {
                     }
                     if ($privkey && $pubkey) {
                         $captcha = 'recaptcha';
+                        $version = $hashtocheck->{'recaptchaversion'};
+                        if ($version ne '2') {
+                            $version = 1;
+                        }
                     } else {
                         $captcha = 'original';
                     }
@@ -14957,14 +19503,39 @@ sub get_captcha_config {
             $privkey = $domconfhash{$serverhomedom.'.login.recaptchakeys_private'};
             if ($privkey && $pubkey) {
                 $captcha = 'recaptcha';
+                $version = $domconfhash{$serverhomedom.'.login.recaptchaversion'};
+                if ($version ne '2') {
+                    $version = 1; 
+                }
             } else {
                 $captcha = 'original';
             }
         } elsif ($domconfhash{$serverhomedom.'.login.captcha'} eq 'original') {
             $captcha = 'original';
         }
-    }
-    return ($captcha,$pubkey,$privkey);
+    } 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);
 }
 
 sub create_captcha {
@@ -14980,13 +19551,17 @@ 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;
         }
     }
+    if ($output eq '') {
+        &Apache::lonnet::logthis("Failed to create Captcha code after $tries attempts.");
+    }
     return $output;
 }
 
@@ -15023,38 +19598,68 @@ sub check_captcha {
 }
 
 sub create_recaptcha {
-    my ($pubkey) = @_;
-    my $use_ssl;
-    if ($ENV{'SERVER_PORT'} == 443) {
-        $use_ssl = 1;
-    }
-    my $captcha = Captcha::reCAPTCHA->new;
-    return $captcha->get_options_setter({theme => 'white'})."\n".
-           $captcha->get_html($pubkey,undef,$use_ssl).
-           &mt('If either word is hard to read, [_1] will replace them.',
-               '<img src="/res/adm/pages/refresh.gif" alt="reCAPTCHA refresh" />').
-           '<br /><br />';
+    my ($pubkey,$version) = @_;
+    if ($version >= 2) {
+        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) {
+            $use_ssl = 1;
+        }
+        my $captcha = Captcha::reCAPTCHA->new;
+        return $captcha->get_options_setter({theme => 'white'})."\n".
+               $captcha->get_html($pubkey,undef,$use_ssl).
+               &mt('If the text is hard to read, [_1] will replace them.',
+                   '<img src="/res/adm/pages/refresh.gif" alt="reCAPTCHA refresh" />').
+               '<br /><br />';
+    }
 }
 
 sub check_recaptcha {
-    my ($privkey) = @_;
+    my ($privkey,$version) = @_;
     my $captcha_chk;
-    my $captcha = Captcha::reCAPTCHA->new;
-    my $captcha_result =
-        $captcha->check_answer(
-                                $privkey,
-                                $ENV{'REMOTE_ADDR'},
-                                $env{'form.recaptcha_challenge_field'},
-                                $env{'form.recaptcha_response_field'},
-                              );
-    if ($captcha_result->{is_valid}) {
-        $captcha_chk = 1;
+    my $ip = &Apache::lonnet::get_requestor_ip();
+    if ($version >= 2) {
+        my %info = (
+                     secret   => $privkey, 
+                     response => $env{'form.g-recaptcha-response'},
+                     remoteip => $ip,
+                   );
+        my $request=new HTTP::Request('POST','https://www.google.com/recaptcha/api/siteverify');
+        $request->content(join('&',map {
+                         my $name = escape($_);
+                         "$name=" . ( ref($info{$_}) eq 'ARRAY'
+                         ? join("&$name=", map {escape($_) } @{$info{$_}})
+                         : &escape($info{$_}) );
+        } keys(%info)));
+        my $response = &LONCAPA::LWPReq::makerequest('',$request,'','',10,1);
+        if ($response->is_success)  {
+            my $data = JSON::DWIW->from_json($response->decoded_content);
+            if (ref($data) eq 'HASH') {
+                if ($data->{'success'}) {
+                    $captcha_chk = 1;
+                }
+            }
+        }
+    } else {
+        my $captcha = Captcha::reCAPTCHA->new;
+        my $captcha_result =
+            $captcha->check_answer(
+                                    $privkey,
+                                    $ip,
+                                    $env{'form.recaptcha_challenge_field'},
+                                    $env{'form.recaptcha_response_field'},
+                                  );
+        if ($captcha_result->{is_valid}) {
+            $captcha_chk = 1;
+        }
     }
     return $captcha_chk;
 }
 
 sub emailusername_info {
-    my @fields = ('firstname','lastname','institution','web','location','officialemail');
+    my @fields = ('firstname','lastname','institution','web','location','officialemail','id');
     my %titles = &Apache::lonlocal::texthash (
                      lastname      => 'Last Name',
                      firstname     => 'First Name',
@@ -15062,6 +19667,7 @@ sub emailusername_info {
                      location      => "School's city, state/province, country",
                      web           => "School's web address",
                      officialemail => 'E-mail address at institution (if different)',
+                     id            => 'Student/Employee ID',
                  );
     return (\@fields,\%titles);
 }
@@ -15088,6 +19694,45 @@ sub cleanup_html {
     return $outgoing;
 }
 
+# Checks for critical messages and returns a redirect url if one exists.
+# $interval indicates how often to check for messages.
+# $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',undef,$cnum,$cdom,undef,1);
+            if ($blocked) {
+                my $checkrole = "cm./$cdom/$cnum";
+                if ($env{'request.course.sec'} ne '') {
+                    $checkrole .= "/$env{'request.course.sec'}";
+                }
+                unless ((&Apache::lonnet::allowed('evb',undef,undef,$checkrole)) &&
+                        ($env{'request.role'} !~ m{^st\./$cdom/$cnum})) {
+                    return;
+                }
+            }
+        }
+        my @what=&Apache::lonnet::dump('critical', $env{'user.domain'}, 
+                                        $env{'user.name'});
+        &Apache::lonnet::appenv({'user.criticalcheck.time'=>time});
+        my $redirecturl;
+        if ($what[0]) {
+	    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);
+            }
+        }
+    } 
+    return ();
+}
+
 # Use:
 #   my $answer=reply("encrypt:passwd:$udom:$uname:$upass",$tryserver);
 #
@@ -15122,19 +19767,354 @@ sub des_decrypt {
     } else {
         $cypher=new DES $keybin;
     }
-    my $plaintext=
-        $cypher->decrypt(unpack("a8",pack("H16",substr($cyphertext,0,16))));
-    $plaintext.=
-        $cypher->decrypt(unpack("a8",pack("H16",substr($cyphertext,16,16))));
-    $plaintext=substr($plaintext,1,ord(substr($plaintext,0,1)) );
+    my $plaintext='';
+    my $cypherlength = length($cyphertext);
+    my $numchunks = int($cypherlength/32);
+    for (my $j=0; $j<$numchunks; $j++) {
+        my $start = $j*32;
+        my $cypherblock = substr($cyphertext,$start,32);
+        my $chunk =
+            $cypher->decrypt(unpack("a8",pack("H16",substr($cypherblock,0,16))));
+        $chunk .=
+            $cypher->decrypt(unpack("a8",pack("H16",substr($cypherblock,16,16))));
+        $chunk=substr($chunk,1,ord(substr($chunk,0,1)) );
+        $plaintext .= $chunk;
+    }
     return $plaintext;
 }
 
-=pod
+sub get_requested_shorturls {
+    my ($cdom,$cnum,$navmap) = @_;
+    return unless (ref($navmap));
+    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);
+        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);
+            my $init = '';
+            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" => $lockuser,
+                            };
+            my $tries = 0;
+            my $gotlock = &Apache::lonnet::newput_dom('tiny',$lockhash,$cdom);
+            my ($code,$error);
+            while (($gotlock ne 'ok') && ($tries<3)) {
+                $tries ++;
+                sleep 1;
+                $gotlock = &Apache::lonnet::newput_dom('tiny',$lockhash,$cdom);
+            }
+            if ($gotlock eq 'ok') {
+                $init = &shorten_symbs($cdom,$init,$su,\%coursetiny,\%tocreate,\%newunique,
+                                       \%addcourse,\%courseonly,\%failed);
+                if (keys(%failed)) {
+                    my $numfailed = scalar(keys(%failed));
+                    push(@errors,&mt('error: could not obtain unique six character URL for [quant,_1,resource]',$numfailed));
+                }
+                if (keys(%newunique)) {
+                    my $putres = &Apache::lonnet::newput_dom('tiny',\%newunique,$cdom);
+                    if ($putres eq 'ok') {
+                        $numnew = scalar(keys(%newunique));
+                        my $newputres = &Apache::lonnet::newput('tiny',\%addcourse,$cdom,$cnum);
+                        unless ($newputres eq 'ok') {
+                            push(@errors,&mt('error: could not store course look-up of short URLs'));
+                        }
+                    } else {
+                        push(@errors,&mt('error: could not store unique six character URLs'));
+                    }
+                }
+                my $dellockres = &Apache::lonnet::del_dom('tiny',["lock\0$now"],$cdom);
+                unless ($dellockres eq 'ok') {
+                    push(@errors,&mt('error: could not release lockfile'));
+                }
+            } else {
+                push(@errors,&mt('error: could not obtain lockfile'));
+            }
+            if (keys(%courseonly)) {
+                my $result = &Apache::lonnet::newput('tiny',\%courseonly,$cdom,$cnum);
+                if ($result ne 'ok') {
+                    push(@errors,&mt('error: could not update course look-up of short URLs'));
+                }
+            }
+        }
+    }
+    return ($numnew,\@errors);
+}
 
-=back
+sub shorten_symbs {
+    my ($cdom,$init,$su,$coursetiny,$tocreate,$newunique,$addcourse,$courseonly,$failed) = @_;
+    return unless ((ref($su)) && (ref($coursetiny) eq 'HASH') && (ref($tocreate) eq 'HASH') &&
+                   (ref($newunique) eq 'HASH') && (ref($addcourse) eq 'HASH') &&
+                   (ref($courseonly) eq 'HASH') && (ref($failed) eq 'HASH'));
+    my (%possibles,%collisions);
+    foreach my $key (keys(%{$tocreate})) {
+        my $num = String::CRC32::crc32($key);
+        my $tiny = $su->encode($num,$init);
+        if ($tiny) {
+            $possibles{$tiny} = $key;
+        }
+    }
+    if (!$init) {
+        $init = 1;
+    } else {
+        $init ++;
+    }
+    if (keys(%possibles)) {
+        my @posstiny = keys(%possibles);
+        my $configuname = &Apache::lonnet::get_domainconfiguser($cdom);
+        my %currtiny = &Apache::lonnet::get('tiny',\@posstiny,$cdom,$configuname);
+        if (keys(%currtiny)) {
+            foreach my $key (keys(%currtiny)) {
+                next if ($currtiny{$key} eq '');
+                if ($currtiny{$key} eq $possibles{$key}) {
+                    my ($tcnum,$tsymb) = split(/\&/,$currtiny{$key});
+                    unless (($coursetiny->{$tsymb} eq $key) || ($addcourse->{$tsymb} eq $key) || ($courseonly->{$tsymb} eq $key)) {
+                        $courseonly->{$tsymb} = $key;
+                    }
+                } else {
+                    $collisions{$possibles{$key}} = 1;
+                }
+                delete($possibles{$key});
+            }
+        }
+        foreach my $key (keys(%possibles)) {
+            $newunique->{$key} = $possibles{$key};
+            my ($tcnum,$tsymb) = split(/\&/,$possibles{$key});
+            unless (($coursetiny->{$tsymb} eq $key) || ($addcourse->{$tsymb} eq $key) || ($courseonly->{$tsymb} eq $key)) {
+                $addcourse->{$tsymb} = $key;
+            }
+        }
+    }
+    if (keys(%collisions)) {
+        if ($init <5) {
+            if (!$init) {
+                $init = 1;
+            } else {
+                $init ++;
+            }
+            $init = &shorten_symbs($cdom,$init,$su,$coursetiny,\%collisions,
+                                   $newunique,$addcourse,$courseonly,$failed);
+        } else {
+            foreach my $key (keys(%collisions)) {
+                $failed->{$key} = 1;
+            }
+        }
+    }
+    return $init;
+}
 
-=cut
+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__;