--- loncom/lonnet/perl/lonnet.pm	2021/06/07 12:15:04	1.1458
+++ loncom/lonnet/perl/lonnet.pm	2024/05/01 12:06:25	1.1526
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # TCP networking package
 #
-# $Id: lonnet.pm,v 1.1458 2021/06/07 12:15:04 raeburn Exp $
+# $Id: lonnet.pm,v 1.1526 2024/05/01 12:06:25 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -415,6 +415,63 @@ sub remote_devalidate_cache {
     return $response;
 }
 
+sub sign_lti {
+    my ($cdom,$cnum,$crsdef,$type,$context,$url,$ltinum,$keynum,$paramsref,$inforef) = @_;
+    my $chome;
+    if (&domain($cdom) ne '') {
+        if ($crsdef) {
+            $chome = &homeserver($cnum,$cdom);
+        } else {
+            $chome = &domain($cdom,'primary');
+        }
+    }
+    if ($cdom && $chome && ($chome ne 'no_host')) {
+        if ((ref($paramsref) eq 'HASH') &&
+            (ref($inforef) eq 'HASH')) {
+            my $rep;
+            if (grep { $_ eq $chome } &current_machine_ids()) {
+                # domain information is hosted on this machine
+                $rep =
+                    &LONCAPA::Lond::sign_lti_payload($cdom,$cnum,$crsdef,$type,
+                                                     $context,$url,$ltinum,$keynum,
+                                                     $perlvar{'lonVersion'},
+                                                     $paramsref,$inforef);
+                if (ref($rep) eq 'HASH') {
+                    return ('ok',$rep);
+                }
+            } else {
+                my ($escurl,$params,$info);
+                $escurl = &escape($url);
+                if (ref($paramsref) eq 'HASH') {
+                    $params = &freeze_escape($paramsref);
+                }
+                if (ref($inforef) eq 'HASH') {
+                    $info = &freeze_escape($inforef);
+                }
+                $rep=&reply("encrypt:signlti:$cdom:$cnum:$crsdef:$type:$context:$escurl:$ltinum:$keynum:$params:$info",$chome);
+            }
+            if (($rep eq '') || ($rep =~ /^con_lost|error|no_such_host|unknown_cmd/i)) {
+                return ();
+            } elsif (($inforef->{'respfmt'} eq 'to_post_body') ||
+                     ($inforef->{'respfmt'} eq 'to_authorization_header')) {
+                return ('ok',$rep);
+            } else {
+                my %returnhash;
+                foreach my $item (split(/\&/,$rep)) {
+                    my ($name,$value)=split(/\=/,$item);
+                    $returnhash{&unescape($name)}=&thaw_unescape($value);
+                }
+                return('ok',\%returnhash);
+            }
+        } else {
+            return ();
+        }
+    } else {
+        return ();
+        &logthis("sign_lti failed - no homeserver and/or domain ($cdom) ($chome)");
+    }
+}
+
 # -------------------------------------------------- Non-critical communication
 sub subreply {
     my ($cmd,$server)=@_;
@@ -468,14 +525,15 @@ sub reply {
             my $subcmd = $1;
             if (($subcmd eq 'auth') || ($subcmd eq 'passwd') ||
                 ($subcmd eq 'changeuserauth') || ($subcmd eq 'makeuser') ||
-                ($subcmd eq 'putdom') || ($subcmd eq 'autoexportgrades')) {
+                ($subcmd eq 'putdom') || ($subcmd eq 'autoexportgrades') ||
+                ($subcmd eq 'put')) {
                 (undef,undef,my @rest) = split(/:/,$cmd);
                 if (($subcmd eq 'auth') || ($subcmd eq 'putdom')) {
                     splice(@rest,2,1,'Hidden');
                 } elsif ($subcmd eq 'passwd') {
                     splice(@rest,2,2,('Hidden','Hidden'));
                 } elsif (($subcmd eq 'changeuserauth') || ($subcmd eq 'makeuser') ||
-                         ($subcmd eq 'autoexportgrades')) {
+                         ($subcmd eq 'autoexportgrades') || ($subcmd eq 'put')) {
                     splice(@rest,3,1,'Hidden');
                 }
                 $logged = join(':',('encrypt:'.$subcmd,@rest));
@@ -984,9 +1042,9 @@ sub spareserver {
                                                      :  $userloadpercent;
     my ($uint_dom,$remotesessions);
     if (($udom ne '') && (&domain($udom) ne '')) {
-        my $uprimary_id = &Apache::lonnet::domain($udom,'primary');
-        $uint_dom = &Apache::lonnet::internet_dom($uprimary_id);
-        my %udomdefaults = &Apache::lonnet::get_domain_defaults($udom);
+        my $uprimary_id = &domain($udom,'primary');
+        $uint_dom = &internet_dom($uprimary_id);
+        my %udomdefaults = &get_domain_defaults($udom);
         $remotesessions = $udomdefaults{'remotesessions'};
     }
     my $spareshash = &this_host_spares($udom);
@@ -1022,7 +1080,7 @@ sub spareserver {
                 if ($protocol{$spare_server} eq 'https') {
                     $protocol = $protocol{$spare_server};
                 }
-                my $alias = &Apache::lonnet::use_proxy_alias($r,$spare_server);
+                my $alias = &use_proxy_alias($r,$spare_server);
                 $hostname = $alias if ($alias ne '');
 	        $spare_server = $protocol.'://'.$hostname;
             }
@@ -1210,7 +1268,7 @@ sub choose_server {
         unless (defined($cached)) {
             my $cachetime = 60*60*24;
             my %domconfig =
-                &Apache::lonnet::get_dom('configuration',['loadbalancing'],$udom);
+                &get_dom('configuration',['loadbalancing'],$udom);
             if (ref($domconfig{'loadbalancing'}) eq 'HASH') {
                 $balancers = &do_cache_new('loadbalancing',$udom,$domconfig{'loadbalancing'},
                                            $cachetime);
@@ -1322,7 +1380,7 @@ sub changepass {
 sub queryauthenticate {
     my ($uname,$udom)=@_;
     my $uhome=&homeserver($uname,$udom);
-    if (!$uhome) {
+    if ((!$uhome) || ($uhome eq 'no_host')) {
 	&logthis("User $uname at $udom is unknown when looking for authentication mechanism");
 	return 'no_host';
     }
@@ -1371,16 +1429,39 @@ sub authenticate {
     }
     if ($answer eq 'non_authorized') {
 	&logthis("User $uname at $udom rejected by $uhome");
-	return 'no_host'; 
+	return 'no_host';
     }
     &logthis("User $uname at $udom threw error $answer when checking authentication mechanism");
     return 'no_host';
 }
 
+sub can_switchserver {
+    my ($udom,$home) = @_;
+    my ($canswitch,@intdoms);
+    my $internet_names = &get_internet_names($home);
+    if (ref($internet_names) eq 'ARRAY') {
+        @intdoms = @{$internet_names};
+    }
+    my $uint_dom = &internet_dom(&domain($udom,'primary'));
+    if ($uint_dom ne '' && grep(/^\Q$uint_dom\E$/,@intdoms)) {
+        $canswitch = 1;
+    } else {
+         my $serverhomeID = &get_server_homeID(&hostname($home));
+         my $serverhomedom = &host_domain($serverhomeID);
+         my %defdomdefaults = &get_domain_defaults($serverhomedom);
+         my %udomdefaults = &get_domain_defaults($udom);
+         my $remoterev = &get_server_loncaparev('',$home);
+         $canswitch = &can_host_session($udom,$home,$remoterev,
+                                        $udomdefaults{'remotesessions'},
+                                        $defdomdefaults{'hostedsessions'});
+    }
+    return $canswitch;
+}
+
 sub can_host_session {
     my ($udom,$lonhost,$remoterev,$remotesessions,$hostedsessions) = @_;
     my $canhost = 1;
-    my $host_idn = &Apache::lonnet::internet_dom($lonhost);
+    my $host_idn = &internet_dom($lonhost);
     if (ref($remotesessions) eq 'HASH') {
         if (ref($remotesessions->{'excludedomain'}) eq 'ARRAY') {
             if (grep(/^\Q$host_idn\E$/,@{$remotesessions->{'excludedomain'}})) {
@@ -1416,8 +1497,8 @@ sub can_host_session {
     }
     if ($canhost) {
         if (ref($hostedsessions) eq 'HASH') {
-            my $uprimary_id = &Apache::lonnet::domain($udom,'primary');
-            my $uint_dom = &Apache::lonnet::internet_dom($uprimary_id);
+            my $uprimary_id = &domain($udom,'primary');
+            my $uint_dom = &internet_dom($uprimary_id);
             if (ref($hostedsessions->{'excludedomain'}) eq 'ARRAY') {
                 if (($uint_dom ne '') && 
                     (grep(/^\Q$uint_dom\E$/,@{$hostedsessions->{'excludedomain'}}))) {
@@ -1509,7 +1590,7 @@ sub spares_for_offload  {
     } else {
         my $cachetime = 60*60*24;
         my %domconfig =
-            &Apache::lonnet::get_dom('configuration',['usersessions'],$dom_in_use);
+            &get_dom('configuration',['usersessions'],$dom_in_use);
         if (ref($domconfig{'usersessions'}) eq 'HASH') {
             if (ref($domconfig{'usersessions'}{'spares'}) eq 'HASH') {
                 if (ref($domconfig{'usersessions'}{'spares'}{$lonhost_in_use}) eq 'HASH') {
@@ -1561,9 +1642,9 @@ sub check_loadbalancing {
         $rule_in_effect,$offloadto,$otherserver,$setcookie,$dom_balancers);
     my $lonhost = $perlvar{'lonHostID'};
     my @hosts = &current_machine_ids();
-    my $uprimary_id = &Apache::lonnet::domain($udom,'primary');
-    my $uintdom = &Apache::lonnet::internet_dom($uprimary_id);
-    my $intdom = &Apache::lonnet::internet_dom($lonhost);
+    my $uprimary_id = &domain($udom,'primary');
+    my $uintdom = &internet_dom($uprimary_id);
+    my $intdom = &internet_dom($lonhost);
     my $serverhomedom = &host_domain($lonhost);
     my $domneedscache;
     my $cachetime = 60*60*24;
@@ -1577,7 +1658,7 @@ sub check_loadbalancing {
     my ($result,$cached)=&is_cached_new('loadbalancing',$dom_in_use);
     unless (defined($cached)) {
         my %domconfig =
-            &Apache::lonnet::get_dom('configuration',['loadbalancing'],$dom_in_use);
+            &get_dom('configuration',['loadbalancing'],$dom_in_use);
         if (ref($domconfig{'loadbalancing'}) eq 'HASH') {
             $result = &do_cache_new('loadbalancing',$dom_in_use,$domconfig{'loadbalancing'},$cachetime);
         } else {
@@ -1638,7 +1719,7 @@ sub check_loadbalancing {
         ($result,$cached)=&is_cached_new('loadbalancing',$serverhomedom);
         unless (defined($cached)) {
             my %domconfig =
-                &Apache::lonnet::get_dom('configuration',['loadbalancing'],$serverhomedom);
+                &get_dom('configuration',['loadbalancing'],$serverhomedom);
             if (ref($domconfig{'loadbalancing'}) eq 'HASH') {
                 $result = &do_cache_new('loadbalancing',$serverhomedom,$domconfig{'loadbalancing'},$cachetime);
             } else {
@@ -1771,7 +1852,7 @@ sub get_loadbalancer_targets {
             }
         } elsif ($rule_in_effect eq 'externalbalancer') {
             my %domconfig =
-                &Apache::lonnet::get_dom('configuration',['loadbalancing'],$udom);
+                &get_dom('configuration',['loadbalancing'],$udom);
             if (ref($domconfig{'loadbalancing'}) eq 'HASH') {
                 if ($domconfig{'loadbalancing'}{'lonhost'} ne '') {
                     if (&hostname($domconfig{'loadbalancing'}{'lonhost'}) ne '') {
@@ -1835,15 +1916,15 @@ sub trusted_domains {
         return ($trusted,$untrusted);
     }
     my $callprimary = &domain($calldom,'primary');
-    my $intcalldom = &Apache::lonnet::internet_dom($callprimary);
+    my $intcalldom = &internet_dom($callprimary);
     if ($intcalldom eq '') {
         return ($trusted,$untrusted);
     }
 
-    my ($trustconfig,$cached)=&Apache::lonnet::is_cached_new('trust',$calldom);
+    my ($trustconfig,$cached)=&is_cached_new('trust',$calldom);
     unless (defined($cached)) {
-        my %domconfig = &Apache::lonnet::get_dom('configuration',['trust'],$calldom);
-        &Apache::lonnet::do_cache_new('trust',$calldom,$domconfig{'trust'},3600);
+        my %domconfig = &get_dom('configuration',['trust'],$calldom);
+        &do_cache_new('trust',$calldom,$domconfig{'trust'},3600);
         $trustconfig = $domconfig{'trust'};
     }
     if (ref($trustconfig)) {
@@ -2146,7 +2227,7 @@ sub dump_dom {
 # ------------------------------------------ get items from domain db files   
 
 sub get_dom {
-    my ($namespace,$storearr,$udom,$uhome)=@_;
+    my ($namespace,$storearr,$udom,$uhome,$encrypt)=@_;
     return if ($udom eq 'public');
     my $items='';
     foreach my $item (@$storearr) {
@@ -2172,13 +2253,9 @@ sub get_dom {
         my $rep;
         if (grep { $_ eq $uhome } &current_machine_ids()) {
             # domain information is hosted on this machine
-            my $cmd = 'getdom';
-            if ($namespace =~ /^enc/) {
-                $cmd = 'egetdom';
-            }
-            $rep = &LONCAPA::Lond::get_dom("$cmd:$udom:$namespace:$items");
+            $rep = &LONCAPA::Lond::get_dom("getdom:$udom:$namespace:$items");
         } else {
-            if ($namespace =~ /^enc/) {
+            if ($encrypt) {
                 $rep=&reply("encrypt:egetdom:$udom:$namespace:$items",$uhome);
             } else {
                 $rep=&reply("getdom:$udom:$namespace:$items",$uhome);
@@ -2206,7 +2283,7 @@ sub get_dom {
 # -------------------------------------------- put items in domain db files 
 
 sub put_dom {
-    my ($namespace,$storehash,$udom,$uhome)=@_;
+    my ($namespace,$storehash,$udom,$uhome,$encrypt)=@_;
     if (!$udom) {
         $udom=$env{'user.domain'};
         if (defined(&domain($udom,'primary'))) {
@@ -2227,7 +2304,7 @@ sub put_dom {
             $items.=&escape($item).'='.&freeze_escape($$storehash{$item}).'&';
         }
         $items=~s/\&$//;
-        if ($namespace =~ /^enc/) {
+        if ($encrypt) {
             return &reply("encrypt:putdom:$udom:$namespace:$items",$uhome);
         } else {
             return &reply("putdom:$udom:$namespace:$items",$uhome);
@@ -2265,6 +2342,57 @@ sub del_dom {
     }
 }
 
+sub store_dom {
+    my ($storehash,$id,$namespace,$dom,$home,$encrypt) = @_;
+    $$storehash{'ip'}=&get_requestor_ip();
+    $$storehash{'host'}=$perlvar{'lonHostID'};
+    my $namevalue='';
+    foreach my $key (keys(%{$storehash})) {
+        $namevalue.=&escape($key).'='.&freeze_escape($$storehash{$key}).'&';
+    }
+    $namevalue=~s/\&$//;
+    if (grep { $_ eq $home } current_machine_ids()) {
+        return LONCAPA::Lond::store_dom("storedom:$dom:$namespace:$id:$namevalue");
+    } else {
+        if ($namespace eq 'private') {
+            return 'refused';
+        } elsif ($encrypt) {
+            return reply("encrypt:storedom:$dom:$namespace:$id:$namevalue",$home);
+        } else {
+            return reply("storedom:$dom:$namespace:$id:$namevalue",$home);
+        }
+    }
+}
+
+sub restore_dom {
+    my ($id,$namespace,$dom,$home,$encrypt) = @_;
+    my $answer;
+    if (grep { $_ eq $home } current_machine_ids()) {
+        $answer = LONCAPA::Lond::restore_dom("restoredom:$dom:$namespace:$id");
+    } elsif ($namespace ne 'private') {
+        if ($encrypt) {
+            $answer=&reply("encrypt:restoredom:$dom:$namespace:$id",$home);
+        } else {
+            $answer=&reply("restoredom:$dom:$namespace:$id",$home);
+        }
+    }
+    my %returnhash=();
+    unless (($answer eq '') || ($answer eq 'con_lost') || ($answer eq 'refused') || 
+            ($answer eq 'unknown_cmd') || ($answer eq 'rejected')) {
+        foreach my $line (split(/\&/,$answer)) {
+            my ($name,$value)=split(/\=/,$line);
+            $returnhash{&unescape($name)}=&thaw_unescape($value);
+        }
+        my $version;
+        for ($version=1;$version<=$returnhash{'version'};$version++) {
+            foreach my $item (split(/\:/,$returnhash{$version.':keys'})) {
+                $returnhash{$item}=$returnhash{$version.':'.$item};
+            }
+        }
+    }
+    return %returnhash;
+}
+
 # ----------------------------------construct domainconfig user for a domain 
 sub get_domainconfiguser {
     my ($udom) = @_;
@@ -2274,7 +2402,7 @@ sub get_domainconfiguser {
 sub retrieve_inst_usertypes {
     my ($udom) = @_;
     my (%returnhash,@order);
-    my %domdefs = &Apache::lonnet::get_domain_defaults($udom);
+    my %domdefs = &get_domain_defaults($udom);
     if ((ref($domdefs{'inststatustypes'}) eq 'HASH') && 
         (ref($domdefs{'inststatusorder'}) eq 'ARRAY')) {
         return ($domdefs{'inststatustypes'},$domdefs{'inststatusorder'});
@@ -2307,7 +2435,7 @@ sub retrieve_inst_usertypes {
 
 sub is_domainimage {
     my ($url) = @_;
-    if ($url=~m-^/+res/+($match_domain)/+\1\-domainconfig/+(img|logo|domlogo)/+[^/]-) {
+    if ($url=~m-^/+res/+($match_domain)/+\1\-domainconfig/+(img|logo|domlogo|login)/+[^/]-) {
         if (&domain($1) ne '') {
             return '1';
         }
@@ -2545,6 +2673,10 @@ sub inst_rulecheck {
                     $response=&unescape(&reply('instselfcreatecheck:'.
                                                &escape($udom).':'.&escape($uname).
                                               ':'.$rulestr,$homeserver));
+                } elsif ($item eq 'unamemap') {
+                    $response=&unescape(&reply('instunamemapcheck:'.
+                                               &escape($udom).':'.&escape($uname).
+                                              ':'.$rulestr,$homeserver));
                 }
                 if ($response ne 'refused') {
                     my @pairs=split(/\&/,$response);
@@ -2574,6 +2706,9 @@ sub inst_userrules {
             } elsif ($check eq 'email') {
                 $response=&reply('instemailrules:'.&escape($udom),
                                  $homeserver);
+            } elsif ($check eq 'unamemap') {
+                $response=&reply('unamemaprules:'.&escape($udom),
+                                 $homeserver); 
             } else {
                 $response=&reply('instuserrules:'.&escape($udom),
                                  $homeserver);
@@ -2615,12 +2750,14 @@ sub get_domain_defaults {
     }
     my %domdefaults;
     my %domconfig =
-         &Apache::lonnet::get_dom('configuration',['defaults','quotas',
+         &get_dom('configuration',['defaults','quotas',
                                   'requestcourses','inststatus',
                                   'coursedefaults','usersessions',
-                                  'requestauthor','selfenrollment',
-                                  'coursecategories','ssl','autoenroll',
-                                  'trust','helpsettings','wafproxy'],$domain);
+                                  'requestauthor','authordefaults',
+                                  'selfenrollment','coursecategories',
+                                  'ssl','autoenroll','trust',
+                                  'helpsettings','wafproxy',
+                                  'ltisec','toolsec','privacy'],$domain);
     my @coursetypes = ('official','unofficial','community','textbook','placement');
     if (ref($domconfig{'defaults'}) eq 'HASH') {
         $domdefaults{'lang_def'} = $domconfig{'defaults'}{'lang_def'}; 
@@ -2629,9 +2766,12 @@ sub get_domain_defaults {
         $domdefaults{'timezone_def'} = $domconfig{'defaults'}{'timezone_def'};
         $domdefaults{'datelocale_def'} = $domconfig{'defaults'}{'datelocale_def'};
         $domdefaults{'portal_def'} = $domconfig{'defaults'}{'portal_def'};
+        $domdefaults{'portal_def_email'} = $domconfig{'defaults'}{'portal_def_email'};
+        $domdefaults{'portal_def_web'} = $domconfig{'defaults'}{'portal_def_web'};
         $domdefaults{'intauth_cost'} = $domconfig{'defaults'}{'intauth_cost'};
         $domdefaults{'intauth_switch'} = $domconfig{'defaults'}{'intauth_switch'};
         $domdefaults{'intauth_check'} = $domconfig{'defaults'}{'intauth_check'};
+        $domdefaults{'unamemap_rule'} = $domconfig{'defaults'}{'unamemap_rule'};
     } else {
         $domdefaults{'lang_def'} = &domain($domain,'lang_def');
         $domdefaults{'auth_def'} = &domain($domain,'auth_def');
@@ -2643,7 +2783,7 @@ sub get_domain_defaults {
         } else {
             $domdefaults{'defaultquota'} = $domconfig{'quotas'};
         }
-        my @usertools = ('aboutme','blog','webdav','portfolio');
+        my @usertools = ('aboutme','blog','webdav','portfolio','portaccess');
         foreach my $item (@usertools) {
             if (ref($domconfig{'quotas'}{$item}) eq 'HASH') {
                 $domdefaults{$item} = $domconfig{'quotas'}{$item};
@@ -2661,6 +2801,17 @@ sub get_domain_defaults {
     if (ref($domconfig{'requestauthor'}) eq 'HASH') {
         $domdefaults{'requestauthor'} = $domconfig{'requestauthor'};
     }
+    if (ref($domconfig{'authordefaults'}) eq 'HASH') {
+        foreach my $item ('nocodemirror','copyright','sourceavail','domcoordacc','editors') {
+            if ($item eq 'editors') {
+                if (ref($domconfig{'authordefaults'}{'editors'}) eq 'ARRAY') {
+                    $domdefaults{$item} = join(',',@{$domconfig{'authordefaults'}{'editors'}});
+                }
+            } else {
+                $domdefaults{$item} = $domconfig{'authordefaults'}{$item};
+            }
+        }
+    }
     if (ref($domconfig{'inststatus'}) eq 'HASH') {
         foreach my $item ('inststatustypes','inststatusorder','inststatusguest') {
             $domdefaults{$item} = $domconfig{'inststatus'}{$item};
@@ -2669,6 +2820,7 @@ sub get_domain_defaults {
     if (ref($domconfig{'coursedefaults'}) eq 'HASH') {
         $domdefaults{'canuse_pdfforms'} = $domconfig{'coursedefaults'}{'canuse_pdfforms'};
         $domdefaults{'usejsme'} = $domconfig{'coursedefaults'}{'usejsme'};
+        $domdefaults{'inline_chem'} = $domconfig{'coursedefaults'}{'inline_chem'};
         $domdefaults{'uselcmath'} = $domconfig{'coursedefaults'}{'uselcmath'};
         if (ref($domconfig{'coursedefaults'}{'postsubmit'}) eq 'HASH') {
             $domdefaults{'postsubmit'} = $domconfig{'coursedefaults'}{'postsubmit'}{'client'};
@@ -2682,12 +2834,33 @@ sub get_domain_defaults {
             if (ref($domconfig{'coursedefaults'}{'uploadquota'}) eq 'HASH') {
                 $domdefaults{$type.'quota'} = $domconfig{'coursedefaults'}{'uploadquota'}{$type};
             }
+            if (ref($domconfig{'coursedefaults'}{'coursequota'}) eq 'HASH') {
+                $domdefaults{$type.'coursequota'} = $domconfig{'coursedefaults'}{'coursequota'}{$type};
+            }
             if ($domdefaults{'postsubmit'} eq 'on') {
                 if (ref($domconfig{'coursedefaults'}{'postsubmit'}{'timeout'}) eq 'HASH') {
                     $domdefaults{$type.'postsubtimeout'} = 
                         $domconfig{'coursedefaults'}{'postsubmit'}{'timeout'}{$type}; 
                 }
             }
+            if (ref($domconfig{'coursedefaults'}{'domexttool'}) eq 'HASH') {
+                $domdefaults{$type.'domexttool'} = $domconfig{'coursedefaults'}{'domexttool'}{$type};
+            } else {
+                $domdefaults{$type.'domexttool'} = 1;
+            }
+            if (ref($domconfig{'coursedefaults'}{'exttool'}) eq 'HASH') {
+                $domdefaults{$type.'exttool'} = $domconfig{'coursedefaults'}{'exttool'}{$type};
+            } else {
+                $domdefaults{$type.'exttool'} = 0;
+            }
+            if (ref($domconfig{'coursedefaults'}{'crsauthor'}) eq 'HASH') {
+                $domdefaults{$type.'crsauthor'} = $domconfig{'coursedefaults'}{'crsauthor'}{$type};
+            } else {
+                $domdefaults{$type.'crsauthor'} = 1;
+            }
+            if (ref($domconfig{'coursedefaults'}{'crseditors'}) eq 'ARRAY') {
+                $domdefaults{'crseditors'}=join(',',@{$domconfig{'coursedefaults'}{'crseditors'}});
+            }
         }
         if (ref($domconfig{'coursedefaults'}{'canclone'}) eq 'HASH') {
             if (ref($domconfig{'coursedefaults'}{'canclone'}{'instcode'}) eq 'ARRAY') {
@@ -2701,7 +2874,10 @@ sub get_domain_defaults {
         }
         if ($domconfig{'coursedefaults'}{'texengine'}) {
             $domdefaults{'texengine'} = $domconfig{'coursedefaults'}{'texengine'};
-        } 
+        }
+        if (exists($domconfig{'coursedefaults'}{'ltiauth'})) {
+            $domdefaults{'crsltiauth'} = $domconfig{'coursedefaults'}{'ltiauth'};
+        }
     }
     if (ref($domconfig{'usersessions'}) eq 'HASH') {
         if (ref($domconfig{'usersessions'}{'remote'}) eq 'HASH') {
@@ -2776,6 +2952,7 @@ sub get_domain_defaults {
     }
     if (ref($domconfig{'autoenroll'}) eq 'HASH') {
         $domdefaults{'autofailsafe'} = $domconfig{'autoenroll'}{'autofailsafe'};
+        $domdefaults{'failsafe'} = $domconfig{'autoenroll'}{'failsafe'};
     }
     if (ref($domconfig{'helpsettings'}) eq 'HASH') {
         $domdefaults{'submitbugs'} = $domconfig{'helpsettings'}{'submitbugs'};
@@ -2789,7 +2966,56 @@ sub get_domain_defaults {
                 $domdefaults{'waf_'.$item} = $domconfig{'wafproxy'}{$item};
             }
         }
-    } 
+    }
+    if (ref($domconfig{'ltisec'}) eq 'HASH') {
+        if (ref($domconfig{'ltisec'}{'encrypt'}) eq 'HASH') {
+            $domdefaults{'linkprotenc_crs'} = $domconfig{'ltisec'}{'encrypt'}{'crs'};
+            $domdefaults{'linkprotenc_dom'} = $domconfig{'ltisec'}{'encrypt'}{'dom'};
+            $domdefaults{'ltienc_consumers'} = $domconfig{'ltisec'}{'encrypt'}{'consumers'};
+        }
+        if (ref($domconfig{'ltisec'}{'private'}) eq 'HASH') {
+            if (ref($domconfig{'ltisec'}{'private'}{'keys'}) eq 'ARRAY') {
+                $domdefaults{'ltiprivhosts'} = $domconfig{'ltisec'}{'private'}{'keys'};
+            }
+        }
+        if (ref($domconfig{'ltisec'}{'suggested'}) eq 'HASH') {
+            my %suggestions = %{$domconfig{'ltisec'}{'suggested'}};
+            foreach my $item (keys(%{$domconfig{'ltisec'}{'suggested'}})) {
+                unless (ref($domconfig{'ltisec'}{'suggested'}{$item}) eq 'HASH') {
+                    delete($suggestions{$item});
+                }
+            }
+            if (keys(%suggestions)) {
+                $domdefaults{'linkprotsuggested'} = \%suggestions;
+            }
+        }
+    }
+    if (ref($domconfig{'toolsec'}) eq 'HASH') {
+        if (ref($domconfig{'toolsec'}{'encrypt'}) eq 'HASH') {
+            $domdefaults{'toolenc_crs'} = $domconfig{'toolsec'}{'encrypt'}{'crs'};
+            $domdefaults{'toolenc_dom'} = $domconfig{'toolsec'}{'encrypt'}{'dom'};
+        }
+        if (ref($domconfig{'toolsec'}{'private'}) eq 'HASH') {
+            if (ref($domconfig{'toolsec'}{'private'}{'keys'}) eq 'ARRAY') {
+                $domdefaults{'toolprivhosts'} = $domconfig{'toolsec'}{'private'}{'keys'};
+            }
+        }
+    }
+    if (ref($domconfig{'privacy'}) eq 'HASH') {
+        if (ref($domconfig{'privacy'}{'approval'}) eq 'HASH') {
+            foreach my $domtype ('instdom','extdom') {
+                if (ref($domconfig{'privacy'}{'approval'}{$domtype}) eq 'HASH') {
+                    foreach my $roletype ('domain','author','course','community') {
+                        if ($domconfig{'privacy'}{'approval'}{$domtype}{$roletype} eq 'user') {
+                            $domdefaults{'userapprovals'} = 1;
+                            last;
+                        }
+                    }
+                }
+                last if ($domdefaults{'userapprovals'});
+            }
+        }
+    }
     &do_cache_new('domdefaults',$domain,\%domdefaults,$cachetime);
     return %domdefaults;
 }
@@ -2809,7 +3035,7 @@ sub get_dom_cats {
         } else {
             $cats = {};
         }
-        &Apache::lonnet::do_cache_new('cats',$dom,$cats,3600);
+        &do_cache_new('cats',$dom,$cats,3600);
     }
     return $cats;
 }
@@ -2826,6 +3052,7 @@ sub get_dom_instcats {
             if (&auto_instcode_format($caller,$dom,\%coursecodes,\%codes,
                                       \@codetitles,\%cat_titles,\%cat_order) eq 'ok') {
                 $instcats = {
+                                totcodes => $totcodes,
                                 codes => \%codes,
                                 codetitles => \@codetitles,
                                 cat_titles => \%cat_titles,
@@ -2864,13 +3091,33 @@ sub course_portal_url {
     if ($domdefaults{'portal_def'}) {
         $firsturl = $domdefaults{'portal_def'};
     } else {
-        my $alias = &Apache::lonnet::use_proxy_alias($r,$chome);
+        my $alias = &use_proxy_alias($r,$chome);
         $hostname = $alias if ($alias ne '');
         $firsturl = $protocol.'://'.$hostname;
     }
     return $firsturl;
 }
 
+sub url_prefix {
+    my ($r,$dom,$home,$context) = @_;
+    my $prefix;
+    my %domdefs = &get_domain_defaults($dom);
+    if ($domdefs{'portal_def'} && $domdefs{'portal_def_'.$context}) {
+        if ($domdefs{'portal_def'} =~ m{^(https?://[^/]+)}) {
+            $prefix = $1;
+        }
+    }
+    if ($prefix eq '') {
+        my $hostname = &hostname($home);
+        my $protocol = $protocol{$home};
+        $protocol = 'http' if ($protocol{$home} ne 'https');
+        my $alias = &use_proxy_alias($r,$home);
+        $hostname = $alias if ($alias ne '');
+        $prefix = $protocol.'://'.$hostname;
+    }
+    return $prefix;
+}
+
 # --------------------------------------------- Get domain config for passwords
 
 sub get_passwdconf {
@@ -3042,7 +3289,7 @@ sub courseid_to_courseurl {
 	return "/$cdom/$cnum";
     }
 
-    my %courseinfo=&Apache::lonnet::coursedescription($courseid);
+    my %courseinfo=&coursedescription($courseid);
     if (exists($courseinfo{'num'})) {
 	return "/$courseinfo{'domain'}/$courseinfo{'num'}";
     }
@@ -3240,14 +3487,14 @@ sub userenvironment {
 # ---------------------------------------------------------- Get a studentphoto
 sub studentphoto {
     my ($udom,$unam,$ext) = @_;
-    my $home=&Apache::lonnet::homeserver($unam,$udom);
+    my $home=&homeserver($unam,$udom);
     if (defined($env{'request.course.id'})) {
         if ($env{'course.'.$env{'request.course.id'}.'.internal.showphoto'}) {
             if ($udom eq $env{'course.'.$env{'request.course.id'}.'.domain'}) {
                 return(&retrievestudentphoto($udom,$unam,$ext)); 
             } else {
                 my ($result,$perm_reqd)=
-		    &Apache::lonnet::auto_photo_permission($unam,$udom);
+		    &auto_photo_permission($unam,$udom);
                 if ($result eq 'ok') {
                     if (!($perm_reqd eq 'yes')) {
                         return(&retrievestudentphoto($udom,$unam,$ext));
@@ -3257,7 +3504,7 @@ sub studentphoto {
         }
     } else {
         my ($result,$perm_reqd) = 
-	    &Apache::lonnet::auto_photo_permission($unam,$udom);
+	    &auto_photo_permission($unam,$udom);
         if ($result eq 'ok') {
             if (!($perm_reqd eq 'yes')) {
                 return(&retrievestudentphoto($udom,$unam,$ext));
@@ -3269,14 +3516,14 @@ sub studentphoto {
 
 sub retrievestudentphoto {
     my ($udom,$unam,$ext,$type) = @_;
-    my $home=&Apache::lonnet::homeserver($unam,$udom);
-    my $ret=&Apache::lonnet::reply("studentphoto:$udom:$unam:$ext:$type",$home);
+    my $home=&homeserver($unam,$udom);
+    my $ret=&reply("studentphoto:$udom:$unam:$ext:$type",$home);
     if ($ret eq 'ok') {
         my $url="/uploaded/$udom/$unam/internal/studentphoto.$ext";
         if ($type eq 'thumbnail') {
             $url="/uploaded/$udom/$unam/internal/studentphoto_tn.$ext"; 
         }
-        my $tokenurl=&Apache::lonnet::tokenwrapper($url);
+        my $tokenurl=&tokenwrapper($url);
         return $tokenurl;
     } else {
         if ($type eq 'thumbnail') {
@@ -3553,8 +3800,8 @@ sub ssi {
         ($form{'grade_courseid'} eq $env{'request.course.id'}) &&
         ($form{'grade_username'} ne '') && ($form{'grade_domain'} ne '') &&
         ($form{'grade_symb'} ne '') &&
-        (&Apache::lonnet::allowed('mgr',$env{'request.course.id'}.
-                                 ($env{'request.course.sec'}?'/'.$env{'request.course.sec'}:'')))) {
+        (&allowed('mgr',$env{'request.course.id'}.
+                        ($env{'request.course.sec'}?'/'.$env{'request.course.sec'}:'')))) {
         $islocal = 1;
     }
     $response= &LONCAPA::LWPReq::makerequest($lonhost,$request,'',\%perlvar,
@@ -3704,8 +3951,31 @@ sub can_edit_resource {
         }
     }
 
+#
+# For /adm/viewcoauthors can only edit if author or co-author who is manager.
+#
+
+    if (($resurl eq '/adm/viewcoauthors') && ($cnum ne '') && ($cdom ne '')) {
+        if (((&allowed('cca',"$cdom/$cnum")) ||
+             (&allowed('caa',"$cdom/$cnum"))) ||
+             ((&allowed('vca',"$cdom/$cnum") ||
+               &allowed('vaa',"$cdom/$cnum")) &&
+              ($env{"environment.internal.manager./$cdom/$cnum"}))) {
+            $home = $env{'user.home'};
+            $cfile = $resurl;
+            if ($env{'form.forceedit'}) {
+                $forceview = 1;
+            } else {
+                $forceedit = 1;
+            }
+            return ($cfile,$home,$switchserver,$forceedit,$forceview);
+        } else {
+            return;
+        }
+    }
+
     if ($env{'request.course.id'}) {
-        my $crsedit = &Apache::lonnet::allowed('mdc',$env{'request.course.id'});
+        my $crsedit = &allowed('mdc',$env{'request.course.id'});
         if ($group ne '') {
 # if this is a group homepage or group bulletin board, check group privs
             my $allowed = 0;
@@ -3734,14 +4004,19 @@ sub can_edit_resource {
             }
         } else {
             if ($resurl =~ m{^/?adm/viewclasslist$}) {
-                unless (&Apache::lonnet::allowed('opa',$env{'request.course.id'})) {
+                unless (&allowed('opa',$env{'request.course.id'})) {
                     return;
                 }
             } elsif (!$crsedit) {
+                if ($env{'request.role'} =~ m{^st\./$cdom/$cnum}) {
 #
 # No edit allowed where CC has switched to student role.
 #
-                return;
+                    return;
+                } elsif (($resurl !~ m{^/res/$match_domain/$match_username/}) ||
+                         ($resurl =~ m{^/res/lib/templates/})) {
+                    return;
+                }
             }
         }
     }
@@ -3767,7 +4042,7 @@ sub can_edit_resource {
                     $forceedit = 1;
                 }
                 $cfile = $resurl;
-            } elsif (($resurl ne '') && (&is_on_map($resurl))) { 
+            } elsif (($resurl ne '') && (&is_on_map($resurl))) {
                 if ($resurl =~ m{^/adm/$match_domain/$match_username/\d+/smppg|bulletinboard$}) {
                     $incourse = 1;
                     if ($env{'form.forceedit'}) {
@@ -4150,7 +4425,7 @@ sub resizeImage {
 # input: $formname - the contents of the file are in $env{"form.$formname"}
 #                    the desired filename is in $env{"form.$formname.filename"}
 #        $context - possible values: coursedoc, existingfile, overwrite, 
-#                                    canceloverwrite, scantron or ''.
+#                                    canceloverwrite, scantron, toollogo  or ''.
 #                   if 'coursedoc': upload to the current course
 #                   if 'existingfile': write file to tmp/overwrites directory 
 #                   if 'canceloverwrite': delete file written to tmp/overwrites directory
@@ -4162,8 +4437,8 @@ sub resizeImage {
 #                          Section => 4, CODE => 5, FirstQuestion => 9 }).
 #        $allfiles - reference to hash for embedded objects
 #        $codebase - reference to hash for codebase of java objects
-#        $desuname - username for permanent storage of uploaded file
-#        $dsetudom - domain for permanaent storage of uploaded file
+#        $destuname - username for permanent storage of uploaded file
+#        $destudom - domain for permanaent storage of uploaded file
 #        $thumbwidth - width (pixels) of thumbnail to make for uploaded image 
 #        $thumbheight - height (pixels) of thumbnail to make for uploaded image
 #        $resizewidth - width (pixels) to which to resize uploaded image
@@ -4373,11 +4648,24 @@ sub finishuserfileupload {
     if (($thumbwidth =~ /^\d+$/) && ($thumbheight =~ /^\d+$/)) {
         my $input = $filepath.'/'.$file;
         my $output = $filepath.'/'.'tn-'.$file;
+        my $makethumb; 
         my $thumbsize = $thumbwidth.'x'.$thumbheight;
-        my @args = ('convert','-sample',$thumbsize,$input,$output);
-        system({$args[0]} @args);
-        if (-e $filepath.'/'.'tn-'.$file) {
-            $fetchthumb  = 1; 
+        if ($context eq 'toollogo') {
+            my ($fullwidth,$fullheight) = &check_dimensions($input);
+            if ($fullwidth ne '' && $fullheight ne '') {
+                if ($fullwidth > $thumbwidth && $fullheight > $thumbheight) {
+                    $makethumb = 1;
+                }
+            }
+        } else {
+            $makethumb = 1;
+        }
+        if ($makethumb) {
+            my @args = ('convert','-sample',$thumbsize,$input,$output);
+            system({$args[0]} @args);
+            if (-e $filepath.'/'.'tn-'.$file) {
+                $fetchthumb  = 1; 
+            }
         }
     }
  
@@ -4609,6 +4897,30 @@ sub embedded_dependency {
     return;
 }
 
+sub check_dimensions {
+    my ($inputfile) = @_;
+    my ($fullwidth,$fullheight);
+    if (($inputfile =~ m|^[/\w.\-]+$|) && (-e $inputfile)) {
+        my $mm = new File::MMagic;
+        my $mime_type = $mm->checktype_filename($inputfile);
+        if ($mime_type =~ m{^image/}) {
+            if (open(PIPE,"identify $inputfile 2>&1 |")) {
+                my $imageinfo = <PIPE>;
+                if (!close(PIPE)) {
+                    &Apache::lonnet::logthis("Failed to close PIPE opened to retrieve image information for $inputfile");
+                }
+                chomp($imageinfo);
+                my ($fullsize) =
+                    ($imageinfo =~ /^\Q$inputfile\E\s+\w+\s+(\d+x\d+)/);
+                if ($fullsize) {
+                    ($fullwidth,$fullheight) = split(/x/,$fullsize);
+                }
+            }
+        }
+    }
+    return ($fullwidth,$fullheight);
+}
+
 sub bubblesheet_converter {
     my ($cdom,$fullpath,$config,$format) = @_;
     if ((&domain($cdom) ne '') &&
@@ -4662,7 +4974,7 @@ sub bubblesheet_converter {
                     next if (($num == 1) && ($csvoptions{'hdr'} == 1));
                     $line =~ s{[\r\n]+$}{};
                     my %found;
-                    my @values = split(/,/,$line);
+                    my @values = split(/,/,$line,-1);
                     my ($qstart,$record);
                     for (my $i=0; $i<@values; $i++) {
                         if ((($qstart ne '') && ($i > $qstart)) ||
@@ -4845,6 +5157,7 @@ sub get_scantronformat_file {
                 close($fh);
             }
         }
+        chomp(@lines);
     }
     return @lines;
 }
@@ -4966,6 +5279,29 @@ sub flushcourselogs {
             if (! defined($dom) || $dom eq '' || 
                 ! defined($name) || $name eq '') {
                 my $cid = $env{'request.course.id'};
+#
+# FIXME 11/29/2021
+# Typo in rev. 1.458 (2003/12/09)??
+# These should likely by $env{'course.'.$cid.'.domain'} and $env{'course.'.$cid.'.num'}
+#
+# While these remain as $env{'request.'.$cid.'.domain'} and $env{'request.'.$cid.'.num'}
+# $dom and $name will always be null, so the &inc() call will default to storing this data
+# in a nohist_accesscount.db file for the user rather than the course.
+#
+# That said there is a lot of noise in the data being stored.
+# So counts for prtspool/  and adm/ etc. are recorded.
+#
+# A review of which items ending '___count' are written to %accesshash should likely be 
+# made before deciding whether to set these to 'course.' instead of 'request.'
+#
+# Under the current scheme each user receives a nohist_accesscount.db file listing 
+# accesses for things which are not published resources, regardless of course, and
+# there is not a nohist_accesscount.db file in a course, which might log accesses from
+# anyone in the course for things which are not published resources.
+#
+# For an author, nohist_accesscount.db ends up having records for other items
+# mixed up with the legitimate access counts for the author's published resources.
+#
                 $dom  = $env{'request.'.$cid.'.domain'};
                 $name = $env{'request.'.$cid.'.num'};
             }
@@ -4992,7 +5328,7 @@ sub flushcourselogs {
     foreach my $entry (keys(%userrolehash)) {
         my ($role,$uname,$udom,$runame,$rudom,$rsec)=
 	    split(/\:/,$entry);
-        if (&Apache::lonnet::put('nohist_userroles',
+        if (&put('nohist_userroles',
              { $role.':'.$uname.':'.$udom.':'.$rsec => $userrolehash{$entry} },
                 $rudom,$runame) eq 'ok') {
 	    delete $userrolehash{$entry};
@@ -5159,7 +5495,8 @@ sub userrolelog {
 }
 
 sub courserolelog {
-    my ($trole,$username,$domain,$area,$tstart,$tend,$delflag,$selfenroll,$context)=@_;
+    my ($trole,$username,$domain,$area,$tstart,$tend,$delflag,$selfenroll,
+        $context,$othdomby,$requester)=@_;
     if ($area =~ m-^/($match_domain)/($match_courseid)/?([^/]*)-) {
         my $cdom = $1;
         my $cnum = $2;
@@ -5172,11 +5509,51 @@ sub courserolelog {
                            selfenroll => $selfenroll,
                            context    => $context,
                         );
+        if ($othdomby) {
+            if ($othdomby eq 'othdombydc') {
+                $storehash{'approval'} = 'domain';
+            } elsif ($othdomby eq 'othdombyuser') {
+                $storehash{'approval'} = 'user'; 
+            }
+            if ($requester ne '') {
+                $storehash{'requester'} = $requester;
+            }
+        }
         if ($trole eq 'gr') {
             $namespace = 'groupslog';
             $storehash{'group'} = $sec;
         } else {
             $storehash{'section'} = $sec;
+            my ($curruserdomstr,$newuserdomstr);
+            if (exists($env{'course.'.$cdom.'_'.$cnum.'.internal.userdomains'})) {
+                $curruserdomstr = $env{'course.'.$env{'request.course.id'}.'.internal.userdomains'};
+            } else {
+                my %courseinfo = &coursedescription($cdom.'/'.$cnum);
+                $curruserdomstr = $courseinfo{'internal.userdomains'};
+            }
+            if ($curruserdomstr ne '') {
+                my @udoms = split(/,/,$curruserdomstr);
+                unless (grep(/^\Q$domain\E/,@udoms)) {
+                    push(@udoms,$domain);
+                    $newuserdomstr = join(',',sort(@udoms));
+                }
+            } else {
+                $newuserdomstr = $domain;
+            }
+            if ($newuserdomstr ne '') {
+                my $putresult = &put('environment',{ 'internal.userdomains' => $newuserdomstr },
+                                     $cdom,$cnum);
+                if ($putresult eq 'ok') {
+                    unless (($selfenroll) || ($context eq 'selfenroll')) { 
+                        if (($context eq 'createcourse') || ($context eq 'requestcourses') ||
+                            ($context eq 'automated') || ($context eq 'domain')) {
+                            $env{'course.'.$cdom.'_'.$cnum.'.internal.userdomains'} = $newuserdomstr;
+                        } elsif ($env{'request.course.id'} eq $cdom.'_'.$cnum) {
+                            &appenv({'course.'.$cdom.'_'.$cnum.'.internal.userdomains' => $newuserdomstr});
+                        }
+                    }
+                }
+            }
         }
         &write_log('course',$namespace,\%storehash,$delflag,$username,
                    $domain,$cnum,$cdom);
@@ -5188,10 +5565,11 @@ sub courserolelog {
 }
 
 sub domainrolelog {
-    my ($trole,$username,$domain,$area,$tstart,$tend,$delflag,$context)=@_;
+    my ($trole,$username,$domain,$area,$tstart,$tend,$delflag,
+        $context,$othdomby,$requester)=@_;
     if ($area =~ m{^/($match_domain)/$}) {
         my $cdom = $1;
-        my $domconfiguser = &Apache::lonnet::get_domainconfiguser($cdom);
+        my $domconfiguser = &get_domainconfiguser($cdom);
         my $namespace = 'rolelog';
         my %storehash = (
                            role    => $trole,
@@ -5199,6 +5577,16 @@ sub domainrolelog {
                            end     => $tend,
                            context => $context,
                         );
+        if ($othdomby) {
+            if ($othdomby eq 'othdombydc') {
+                $storehash{'approval'} = 'domain';
+            } elsif ($othdomby eq 'othdombyuser') {
+                $storehash{'approval'} = 'user';
+            }
+            if ($requester ne '') {
+                $storehash{'requester'} = $requester;
+            }
+        }
         &write_log('domain',$namespace,\%storehash,$delflag,$username,
                    $domain,$domconfiguser,$cdom);
     }
@@ -5207,7 +5595,8 @@ sub domainrolelog {
 }
 
 sub coauthorrolelog {
-    my ($trole,$username,$domain,$area,$tstart,$tend,$delflag,$context)=@_;
+    my ($trole,$username,$domain,$area,$tstart,$tend,$delflag,
+        $context,$othdomby,$requester)=@_;
     if ($area =~ m{^/($match_domain)/($match_username)$}) {
         my $audom = $1;
         my $auname = $2;
@@ -5218,6 +5607,16 @@ sub coauthorrolelog {
                            end     => $tend,
                            context => $context,
                         );
+        if ($othdomby) {
+            if ($othdomby eq 'othdombydc') {
+                $storehash{'approval'} = 'domain';
+            } elsif ($othdomby eq 'othdombyuser') {
+                $storehash{'approval'} = 'user';
+            }
+            if ($requester ne '') {
+                $storehash{'requester'} = $requester;
+            }
+        }
         &write_log('author',$namespace,\%storehash,$delflag,$username,
                    $domain,$auname,$audom);
     }
@@ -5422,8 +5821,8 @@ sub get_my_adhocroles {
     } elsif ($cid =~ /^($match_domain)_($match_courseid)$/) {
         $cdom = $1;
         $cnum = $2;
-        %info = &Apache::lonnet::get('environment',['internal.coursecode'],
-                                     $cdom,$cnum);
+        %info = &get('environment',['internal.coursecode'],
+                     $cdom,$cnum);
     }
     if (($info{'internal.coursecode'} ne '') && ($checkreg)) {
         my $user = $env{'user.name'}.':'.$env{'user.domain'};
@@ -5750,7 +6149,7 @@ sub extract_lastaccess {
 
 sub dcmailput {
     my ($domain,$msgid,$message,$server)=@_;
-    my $status = &Apache::lonnet::critical(
+    my $status = &critical(
        'dcmailput:'.$domain.':'.&escape($msgid).'='.
        &escape($message),$server);
     return $status;
@@ -6598,7 +6997,7 @@ sub rolesinit {
     my %firstaccess = &dump('firstaccesstimes', $domain, $username);
     my %timerinterval = &dump('timerinterval', $domain, $username);
     my (%coursetimerstarts, %firstaccchk, %firstaccenv, %coursetimerintervals,
-        %timerintchk, %timerintenv);
+        %timerintchk, %timerintenv, %coauthorenv);
 
     foreach my $key (keys(%firstaccess)) {
         my ($cid, $rest) = split(/\0/, $key);
@@ -6612,6 +7011,8 @@ sub rolesinit {
 
     my %allroles=();
     my %allgroups=();
+    my %gotcoauconfig=();
+    my %domdefaults=();
 
     for my $area (grep { ! /^rolesdef_/ } keys(%rolesdump)) {
         my $role = $rolesdump{$area};
@@ -6663,6 +7064,37 @@ sub rolesinit {
         } else {
         # Normal role, defined in roles.tab
             &standard_roleprivs(\%allroles,$trole,$tdomain,$spec,$trest,$area);
+            if (($trole eq 'ca') || ($trole eq 'aa')) {
+                (undef,my ($audom,$auname)) = split(/\//,$area);
+                unless ($gotcoauconfig{$area}) {
+                    my @ca_settings = ('authoreditors','coauthorlist','coauthoroptin');
+                    my %info = &userenvironment($audom,$auname,@ca_settings);
+                    $gotcoauconfig{$area} = 1;
+                    foreach my $item (@ca_settings) {
+                        if (exists($info{$item})) {
+                            my $name = $item;
+                            if ($item eq 'authoreditors') {
+                                $name = 'editors';
+                                unless ($info{'authoreditors'}) {
+                                    my %domdefs;
+                                    if (ref($domdefaults{$audom}) eq 'HASH') {
+                                        %domdefs = %{$domdefaults{$audom}};
+                                    } else {
+                                        %domdefs = &get_domain_defaults($audom);
+                                        $domdefaults{$audom} = \%domdefs;
+                                    }
+                                    if ($domdefs{$name} ne '') {
+                                        $info{'authoreditors'} = $domdefs{$name};
+                                    } else {
+                                        $info{'authoreditors'} = 'edit,xml';
+                                    }
+                                }
+                            }
+                            $coauthorenv{"environment.internal.$name.$area"} = $info{$item};
+                        }
+                    }
+                }
+            }
         }
 
         my $cid = $tdomain.'_'.$trest;
@@ -6691,7 +7123,7 @@ sub rolesinit {
     $env{'user.adv'} = $userroles{'user.adv'};
     $env{'user.rar'} = $userroles{'user.rar'};
 
-    return (\%userroles,\%firstaccenv,\%timerintenv);
+    return (\%userroles,\%firstaccenv,\%timerintenv,\%coauthorenv);
 }
 
 sub set_arearole {
@@ -6752,31 +7184,31 @@ sub course_adhocrole_privs {
             $full{$priv} = $restrict;
         }
         foreach my $item (split(/,/,$overrides{"internal.adhocpriv.$rolename"})) {
-             next if ($item eq '');
-             my ($rule,$rest) = split(/=/,$item);
-             next unless (($rule eq 'off') || ($rule eq 'on'));
-             foreach my $priv (split(/:/,$rest)) {
-                 if ($priv ne '') {
-                     if ($rule eq 'off') {
-                         $possremove{$priv} = 1;
-                     } else {
-                         $possadd{$priv} = 1;
-                     }
-                 }
-             }
-         }
-         foreach my $priv (sort(keys(%full))) {
-             if (exists($currprivs{$priv})) {
-                 unless (exists($possremove{$priv})) {
-                     $storeprivs{$priv} = $currprivs{$priv};
-                 }
-             } elsif (exists($possadd{$priv})) {
-                 $storeprivs{$priv} = $full{$priv};
-             }
-         }
-         $coursepriv = ':'.join(':',map { $_.'&'.$storeprivs{$_}; } sort(keys(%storeprivs)));
-     }
-     return $coursepriv;
+            next if ($item eq '');
+            my ($rule,$rest) = split(/=/,$item);
+            next unless (($rule eq 'off') || ($rule eq 'on'));
+            foreach my $priv (split(/:/,$rest)) {
+                if ($priv ne '') {
+                    if ($rule eq 'off') {
+                        $possremove{$priv} = 1;
+                    } else {
+                        $possadd{$priv} = 1;
+                    }
+                }
+            }
+        }
+        foreach my $priv (sort(keys(%full))) {
+            if (exists($currprivs{$priv})) {
+                unless (exists($possremove{$priv})) {
+                    $storeprivs{$priv} = $currprivs{$priv};
+                }
+            } elsif (exists($possadd{$priv})) {
+                $storeprivs{$priv} = $full{$priv};
+            }
+        }
+        $coursepriv = ':'.join(':',map { $_.'&'.$storeprivs{$_}; } sort(keys(%storeprivs)));
+    }
+    return $coursepriv;
 }
 
 sub group_roleprivs {
@@ -7117,7 +7549,7 @@ sub unserialize {
 # see Lond::dump_with_regexp
 # if $escapedkeys hash keys won't get unescaped.
 sub dump {
-    my ($namespace,$udomain,$uname,$regexp,$range,$escapedkeys)=@_;
+    my ($namespace,$udomain,$uname,$regexp,$range,$escapedkeys,$encrypt)=@_;
     if (!$udomain) { $udomain=$env{'user.domain'}; }
     if (!$uname) { $uname=$env{'user.name'}; }
     my $uhome=&homeserver($uname,$udomain);
@@ -7133,7 +7565,12 @@ sub dump {
                     $uname, $namespace, $regexp, $range)), $perlvar{'lonVersion'});
         return %{unserialize($reply, $escapedkeys)};
     }
-    my $rep=&reply("dump:$udomain:$uname:$namespace:$regexp:$range",$uhome);
+    my $rep;
+    if ($encrypt) {
+        $rep=&reply("encrypt:edump:$udomain:$uname:$namespace:$regexp:$range",$uhome);
+    } else {
+        $rep=&reply("dump:$udomain:$uname:$namespace:$regexp:$range",$uhome);
+    }
     my @pairs=split(/\&/,$rep);
     my %returnhash=();
     if (!($rep =~ /^error/ )) {
@@ -7280,7 +7717,7 @@ sub inc {
 # --------------------------------------------------------------- put interface
 
 sub put {
-   my ($namespace,$storehash,$udomain,$uname)=@_;
+   my ($namespace,$storehash,$udomain,$uname,$encrypt)=@_;
    if (!$udomain) { $udomain=$env{'user.domain'}; }
    if (!$uname) { $uname=$env{'user.name'}; }
    my $uhome=&homeserver($uname,$udomain);
@@ -7289,7 +7726,11 @@ sub put {
        $items.=&escape($item).'='.&freeze_escape($$storehash{$item}).'&';
    }
    $items=~s/\&$//;
-   return &reply("put:$udomain:$uname:$namespace:$items",$uhome);
+   if ($encrypt) {
+       return &reply("encrypt:put:$udomain:$uname:$namespace:$items",$uhome);
+   } else {
+       return &reply("put:$udomain:$uname:$namespace:$items",$uhome);
+   }
 }
 
 # ------------------------------------------------------------ newput interface
@@ -7334,7 +7775,7 @@ sub putstore {
                      '&host='.&escape($perlvar{'lonHostID'}).
                      '&version='.$esc_v.
                      '&by='.&escape($env{'user.name'}.':'.$env{'user.domain'});
-       &Apache::lonnet::courselog($symb.':'.$uname.':'.$udomain.':PUTSTORE:'.$namevalue);
+       &courselog($symb.':'.$uname.':'.$udomain.':PUTSTORE:'.$namevalue);
    }
    if ($reply eq 'unknown_cmd') {
        # gfall back to way things use to be done
@@ -7484,16 +7925,16 @@ sub get_timebased_id {
     my $tries = 0;
 
 # attempt to get lock on nohist_$namespace file
-    my $gotlock = &Apache::lonnet::newput('nohist_'.$namespace,$lockhash,$cdom,$cnum);
+    my $gotlock = &newput('nohist_'.$namespace,$lockhash,$cdom,$cnum);
     while (($gotlock ne 'ok') && $tries <$locktries) {
         $tries ++;
         sleep 1;
-        $gotlock = &Apache::lonnet::newput('nohist_'.$namespace,$lockhash,$cdom,$cnum);
+        $gotlock = &newput('nohist_'.$namespace,$lockhash,$cdom,$cnum);
     }
 
 # attempt to get unique identifier, based on current timestamp
     if ($gotlock eq 'ok') {
-        my %inuse = &Apache::lonnet::dump('nohist_'.$namespace,$cdom,$cnum,$prefix);
+        my %inuse = &dump('nohist_'.$namespace,$cdom,$cnum,$prefix);
         my $id = time;
         $newid = $id;
         if ($idtype eq 'addcode') {
@@ -7514,7 +7955,7 @@ sub get_timebased_id {
             my %new_item =  (
                               $prefix."\0".$newid => $who,
                             );
-            my $putresult = &Apache::lonnet::put('nohist_'.$namespace,\%new_item,
+            my $putresult = &put('nohist_'.$namespace,\%new_item,
                                                  $cdom,$cnum);
             if ($putresult ne 'ok') {
                 undef($newid);
@@ -7554,15 +7995,15 @@ sub portfolio_access {
     if ($result) {
         my %setters;
         if ($env{'user.name'} eq 'public' && $env{'user.domain'} eq 'public') {
-            my ($startblock,$endblock) =
-                &Apache::loncommon::blockcheck(\%setters,'port',$unum,$udom);
-            if ($startblock && $endblock) {
+            my ($startblock,$endblock,$triggerblock,$by_ip,$blockdom) =
+                &Apache::loncommon::blockcheck(\%setters,'port',$clientip,$unum,$udom);
+            if (($startblock && $endblock) || ($by_ip)) {
                 return 'B';
             }
         } else {
-            my ($startblock,$endblock) =
-                &Apache::loncommon::blockcheck(\%setters,'port');
-            if ($startblock && $endblock) {
+            my ($startblock,$endblock,$triggerblock,$by_ip,$blockdom) =
+                &Apache::loncommon::blockcheck(\%setters,'port',$clientip);
+            if (($startblock && $endblock) || ($by_ip)) {
                 return 'B';
             }
         }
@@ -7576,7 +8017,7 @@ sub portfolio_access {
 }
 
 sub get_portfolio_access {
-    my ($udom,$unum,$file_name,$group,$clientip,$access_hash) = @_;
+    my ($udom,$unum,$file_name,$group,$clientip,$access_hash,$portaccessref) = @_;
 
     if (!ref($access_hash)) {
 	my $current_perms = &get_portfile_permissions($udom,$unum);
@@ -7585,11 +8026,19 @@ sub get_portfolio_access {
 	$access_hash = $access_controls{$file_name};
     }
 
-    my ($public,$guest,@domains,@users,@courses,@groups,@ips);
+    my $portaccess;
+    if (ref($portaccess) eq 'SCALAR') {
+        $portaccess = $$portaccessref;
+    } else {
+        $portaccess = &usertools_access($unum,$udom,'portaccess',undef,'tools');
+    }
+
+    my ($public,$guest,@domains,@users,@courses,@groups,@ips,@userips);
     my $now = time;
     if (ref($access_hash) eq 'HASH') {
         foreach my $key (keys(%{$access_hash})) {
             my ($num,$scope,$end,$start) = ($key =~ /^([^:]+):([a-z]+)_(\d*)_?(\d*)$/);
+            next if (($scope ne 'ip') && ($portaccess == 0));
             if ($start > $now) {
                 next;
             }
@@ -7611,6 +8060,8 @@ sub get_portfolio_access {
                 push(@groups,$key);
             } elsif ($scope eq 'ip') {
                 push(@ips,$key);
+            } elsif ($scope eq 'userip') {
+                push(@userips,$key);
             }
         }
         if ($public) {
@@ -7628,6 +8079,19 @@ sub get_portfolio_access {
             if ($allowed) {
                 return 'ok';
             }
+        } elsif (@userips > 0) {
+            my $allowed;
+            foreach my $useripkey (@userips) {
+                if (ref($access_hash->{$useripkey}{'ip'}) eq 'ARRAY') {
+                    if (&Apache::loncommon::check_ip_acc(join(',',@{$access_hash->{$useripkey}{'ip'}}),$clientip)) {
+                        $allowed = 1;
+                        last;
+                    }
+                }
+            }
+            if ($allowed) {
+                return 'ok';
+            }
         }
         if ($env{'user.name'} eq 'public' && $env{'user.domain'} eq 'public') {
             if ($guest) {
@@ -7803,6 +8267,17 @@ sub is_portfolio_file {
     return;
 }
 
+sub is_coursetool_logo {
+    my ($uri) = @_;
+    if ($env{'request.course.id'}) {
+        my $courseurl = &courseid_to_courseurl($env{'request.course.id'});
+        if ($uri =~ m{^/*uploaded\Q$courseurl\E/toollogo/\d+/[^/]+$}) {
+            return 1;
+        }
+    }
+    return;
+}
+
 sub usertools_access {
     my ($uname,$udom,$tool,$action,$context,$userenvref,$domdefref,$is_advref)=@_;
     my ($access,%tools);
@@ -7822,12 +8297,18 @@ sub usertools_access {
         %tools = (
                       requestauthor => 1,
                  );
+    } elsif ($context eq 'authordefaults') {
+        %tools = (
+                      webdav    => 1,
+                 );
     } else {
         %tools = (
                       aboutme   => 1,
                       blog      => 1,
                       webdav    => 1,
                       portfolio => 1,
+                      portaccess => 1,
+                      timezone  => 1,
                  );
     }
     return if (!defined($tools{$tool}));
@@ -7843,6 +8324,10 @@ sub usertools_access {
                 return $env{'environment.canrequest.'.$tool};
             } elsif ($context eq 'requestauthor') {
                 return $env{'environment.canrequest.author'};
+            } elsif ($context eq 'authordefaults') {
+                if ($tool eq 'webdav') {
+                    return $env{'environment.availabletools.'.$tool};
+                }
             } else {
                 return $env{'environment.availabletools.'.$tool};
             }
@@ -7851,7 +8336,11 @@ sub usertools_access {
 
     my ($toolstatus,$inststatus,$envkey);
     if ($context eq 'requestauthor') {
-        $envkey = $context; 
+        $envkey = $context;
+    } elsif ($context eq 'authordefaults') {
+        if ($tool eq 'webdav') {
+            $envkey = 'tools.'.$tool;
+        }
     } else {
         $envkey = $context.'.'.$tool;
     }
@@ -7952,7 +8441,7 @@ sub is_course_owner {
             if ($env{'course.'.$cdom.'_'.$cnum.'.internal.courseowner'} eq $uname.':'.$udom) {
                 return 1;
             } else {
-                my %courseinfo = &Apache::lonnet::coursedescription($cdom.'/'.$cnum);
+                my %courseinfo = &coursedescription($cdom.'/'.$cnum);
                 if ($courseinfo{'internal.courseowner'} eq $uname.':'.$udom) {
                     return 1;
                 }
@@ -7963,7 +8452,8 @@ sub is_course_owner {
 }
 
 sub is_advanced_user {
-    my ($udom,$uname) = @_;
+    my ($udom,$uname,$nocache) = @_;
+    my ($is_adv,$is_author,$use_cache,$hashid);
     if ($udom ne '' && $uname ne '') {
         if (($udom eq $env{'user.domain'}) && ($uname eq $env{'user.name'})) {
             if (wantarray) {
@@ -7971,11 +8461,21 @@ sub is_advanced_user {
             } else {
                 return $env{'user.adv'};
             }
+        } elsif (!$nocache) {
+            $use_cache = 1;
+            $hashid = "$udom:$uname";  
+            my ($info,$cached)=&is_cached_new('isadvau',$hashid);
+            if ($cached) {
+                ($is_adv,$is_author) = split(/:/,$info);
+                if (wantarray) {
+                    return ($is_adv,$is_author);
+                }
+                return $is_adv; 
+            }
         }
     }
     my %roleshash = &get_my_roles($uname,$udom,'userroles',undef,undef,undef,1);
     my %allroles;
-    my ($is_adv,$is_author);
     foreach my $role (keys(%roleshash)) {
         my ($trest,$tdomain,$trole,$sec) = split(/:/,$role);
         my $area = '/'.$tdomain.'/'.$trest;
@@ -8006,6 +8506,10 @@ sub is_advanced_user {
             }
         }
     }
+    if ($use_cache) {
+        my $cachetime = 600;
+        &do_cache_new('isadvau',$hashid,$is_adv.':'.$is_author,$cachetime);
+    }
     if (wantarray) {
         return ($is_adv,$is_author);
     }
@@ -8023,6 +8527,7 @@ sub check_can_request {
     my @options = ('approval','validate','autolimit');
     my $optregex = join('|',@options);
     if ((ref($can_request) eq 'HASH') && (ref($types) eq 'ARRAY')) {
+        my %willtrust;
         foreach my $type (@{$types}) {
             if (&usertools_access($uname,$udom,$type,undef,
                                   'requestcourses')) {
@@ -8042,12 +8547,17 @@ sub check_can_request {
                         if (ref($request_domains) eq 'HASH') {
                             my ($otherdom) = ($item =~ /^($match_domain):($optregex)(=?\d*)$/);
                             if ($otherdom ne '') {
-                                if (ref($request_domains->{$type}) eq 'ARRAY') {
-                                    unless (grep(/^\Q$otherdom\E$/,@{$request_domains->{$type}})) {
+                                unless (exists($willtrust{$otherdom})) {
+                                    $willtrust{$otherdom} = &will_trust('reqcrs',$env{'user.domain'},$otherdom);
+                                }
+                                if ($willtrust{$otherdom}) {
+                                    if (ref($request_domains->{$type}) eq 'ARRAY') {
+                                        unless (grep(/^\Q$otherdom\E$/,@{$request_domains->{$type}})) {
+                                            push(@{$request_domains->{$type}},$otherdom);
+                                        }
+                                    } else {
                                         push(@{$request_domains->{$type}},$otherdom);
                                     }
-                                } else {
-                                    push(@{$request_domains->{$type}},$otherdom);
                                 }
                             }
                         }
@@ -8117,14 +8627,14 @@ sub customaccess {
 # ------------------------------------------------- Check for a user privilege
 
 sub allowed {
-    my ($priv,$uri,$symb,$role,$clientip,$noblockcheck,$ignorecache)=@_;
+    my ($priv,$uri,$symb,$role,$clientip,$noblockcheck,$ignorecache,$nodeeplinkcheck,$nodeeplinkout)=@_;
     my $ver_orguri=$uri;
     $uri=&deversion($uri);
     my $orguri=$uri;
     $uri=&declutter($uri);
 
     if ($priv eq 'evb') {
-# Evade communication block restrictions for specified role in a course
+# Evade communication block restrictions for specified role in a course or domain
         if ($env{'user.priv.'.$role} =~/evb\&([^\:]*)/) {
             return $1;
         } else {
@@ -8145,9 +8655,9 @@ sub allowed {
     if (($space=~/^(uploaded|editupload)$/) && ($env{'user.name'} eq $name) && 
 	($env{'user.domain'} eq $domain) && ('portfolio' eq $dir[0])) {
         my %setters;
-        my ($startblock,$endblock) = 
-            &Apache::loncommon::blockcheck(\%setters,'port');
-        if ($startblock && $endblock) {
+        my ($startblock,$endblock,$triggerblock,$by_ip,$blockdom) = 
+            &Apache::loncommon::blockcheck(\%setters,'port',$clientip);
+        if (($startblock && $endblock) || ($by_ip)) {
             return 'B';
         } else {
             return 'F';
@@ -8342,7 +8852,10 @@ sub allowed {
             if ($env{'user.priv.'.$env{'request.role'}.'./'}
                   =~/\Q$priv\E\&([^\:]*)/) {
                 my $value = $1;
-                my $deeplinkblock = &deeplink_check($priv,$symb,$uri);
+                my $deeplinkblock;
+                unless ($nodeeplinkcheck) {
+                    $deeplinkblock = &deeplink_check($priv,$symb,$uri);
+                }
                 if ($deeplinkblock) {
                     $thisallowed='D';
                 } elsif ($noblockcheck) {
@@ -8365,7 +8878,10 @@ sub allowed {
                     $refuri=&declutter($refuri);
                     my ($match) = &is_on_map($refuri);
                     if ($match) {
-                        my $deeplinkblock = &deeplink_check($priv,$symb,$refuri);
+                        my $deeplinkblock;
+                        unless ($nodeeplinkcheck) {
+                            $deeplinkblock = &deeplink_check($priv,$symb,$refuri);
+                        }
                         if ($deeplinkblock) {
                             $thisallowed='D';
                         } elsif ($noblockcheck) {
@@ -8398,7 +8914,7 @@ sub allowed {
 
 # If this is generating or modifying users, exit with special codes
 
-    if (':csu:cdc:ccc:cin:cta:cep:ccr:cst:cad:cli:cau:cdg:cca:caa:'=~/\:\Q$priv\E\:/) {
+    if (':csu:cdc:ccc:cin:cta:cep:ccr:cst:cad:cli:cau:cdg:cca:caa:vca:vaa:'=~/\:\Q$priv\E\:/) {
 	if (($priv eq 'cca') || ($priv eq 'caa')) {
 	    my ($audom,$auname)=split('/',$uri);
 # no author name given, so this just checks on the general right to make a co-author in this domain
@@ -8407,6 +8923,13 @@ sub allowed {
 	    if (($auname ne $env{'user.name'} && $env{'request.role'} !~ /^dc\./) ||
 		(($audom ne $env{'user.domain'} && $env{'request.role'} !~ /^dc\./) &&
 		 ($audom ne $env{'request.role.domain'}))) { return ''; }
+	} elsif (($priv eq 'vca') || ($priv eq 'vaa')) {
+            my ($audom,$auname)=split('/',$uri);
+            unless ($auname) { return $thisallowed; }
+            unless (($env{'request.role'} eq "dc./$audom") ||
+                    ($env{'request.role'} eq "ca./$uri")) {
+                return '';
+            }
 	}
 	return $thisallowed;
     }
@@ -8418,6 +8941,12 @@ sub allowed {
 
     if ($env{'request.course.id'}) {
 
+        if ($priv eq 'bre') {
+            if (&is_coursetool_logo($uri)) {
+                return 'F';
+            }
+        }
+
 # If this is modifying password (internal auth) domains must match for user and user's role.
 
         if ($priv eq 'mip') {
@@ -8441,7 +8970,10 @@ sub allowed {
                =~/\Q$priv\E\&([^\:]*)/) {
                my $value = $1;
                if ($priv eq 'bre') {
-                   my $deeplinkblock = &deeplink_check($priv,$symb,$uri);
+                   my $deeplinkblock;
+                   unless ($nodeeplinkcheck) {
+                       $deeplinkblock = &deeplink_check($priv,$symb,$uri);
+                   }
                    if ($deeplinkblock) {
                        $thisallowed = 'D';
                    } elsif ($noblockcheck) {
@@ -8486,7 +9018,10 @@ sub allowed {
                   =~/\Q$priv\E\&([^\:]*)/) {
                   my $value = $1;
                   if ($priv eq 'bre') {
-                      my $deeplinkblock = &deeplink_check($priv,$symb,$refuri);
+                      my $deeplinkblock;
+                      unless ($nodeeplinkcheck) {
+                          $deeplinkblock = &deeplink_check($priv,$symb,$refuri);
+                      }
                       if ($deeplinkblock) {
                           $thisallowed = 'D';
                       } elsif ($noblockcheck) {
@@ -8669,6 +9204,17 @@ sub allowed {
        }
    }
 
+# Restricted for deeplinked session?
+
+    if ($env{'request.deeplink.login'}) {
+        if ($env{'acc.deeplinkout'} && !$nodeeplinkout) {
+            if (!$symb) { $symb=&symbread($uri,1); }
+            if (($symb) && ($env{'acc.deeplinkout'}=~/\&\Q$symb\E\&/)) {
+                return '';
+            }
+        }
+    }
+
 # Restricted by state or randomout?
 
    if ($thisallowed=~/X/) {
@@ -8721,22 +9267,41 @@ sub constructaccess {
        if (exists($env{'user.priv.au./'.$ownerdomain.'/./'})) {
           return ($ownername,$ownerdomain,$ownerhome);
        }
-    } else {
-# Co-author for this?
-        if (exists($env{'user.priv.ca./'.$ownerdomain.'/'.$ownername.'./'}) ||
-            exists($env{'user.priv.aa./'.$ownerdomain.'/'.$ownername.'./'}) ) {
-            $ownerhome = &homeserver($ownername,$ownerdomain);
-            return ($ownername,$ownerdomain,$ownerhome);
-        }
+    } elsif (&is_course($ownerdomain,$ownername)) {
+# Course Authoring Space?
         if ($env{'request.course.id'}) {
             if (($ownername eq $env{'course.'.$env{'request.course.id'}.'.num'}) &&
                 ($ownerdomain eq $env{'course.'.$env{'request.course.id'}.'.domain'})) {
                 if (&allowed('mdc',$env{'request.course.id'})) {
+                    return if ($env{'course.'.$env{'request.course.id'}.'.internal.crsauthor'} eq '0');
+                    unless ($env{'course.'.$env{'request.course.id'}.'.internal.crsauthor'}) {
+                        my %domdefs = &get_domain_defaults($ownerdomain);
+                        my $type = lc($env{'course.'.$env{'request.course.id'}.'.type'});
+                        unless (($type eq 'community') || ($type eq 'placement')) {
+                            $type = 'unofficial';
+                            if ($env{'course.'.$env{'request.course.id'}.'internal.coursecode'} ne '') {
+                                $type = 'official';
+                            } elsif ($env{'course.'.$env{'request.course.id'}.'internal.textbook'} ne '') {
+                                $type = 'textbook';
+                            } else {
+                                $type = 'unofficial';
+                            }
+                        }
+                        return if ($domdefs{$type.'crsauthor'} eq '0');
+                    }
                     $ownerhome = $env{'course.'.$env{'request.course.id'}.'.home'};
                     return ($ownername,$ownerdomain,$ownerhome);
                 }
             }
         }
+        return '';
+    } else {
+# Co-author for this?
+        if (exists($env{'user.priv.ca./'.$ownerdomain.'/'.$ownername.'./'}) ||
+            exists($env{'user.priv.aa./'.$ownerdomain.'/'.$ownername.'./'}) ) {
+            $ownerhome = &homeserver($ownername,$ownerdomain);
+            return ($ownername,$ownerdomain,$ownerhome);
+        }
     }
 
 # We don't have any access right now. If we are not possibly going to do anything about this,
@@ -8815,7 +9380,7 @@ sub get_comm_blocks {
     if ((defined($cached)) && (ref($blocksref) eq 'HASH')) {
         %commblocks = %{$blocksref};
     } else {
-        %commblocks = &Apache::lonnet::dump('comm_block',$cdom,$cnum);
+        %commblocks = &dump('comm_block',$cdom,$cnum);
         my $cachetime = 600;
         &do_cache_new('comm_block',$hashid,\%commblocks,$cachetime);
     }
@@ -8826,7 +9391,11 @@ sub get_commblock_resources {
     my ($blocks) = @_;
     my %blockers = ();
     return %blockers unless ($env{'request.course.id'});
-    return %blockers if ($env{'user.priv.'.$env{'request.role'}} =~/evb\&([^\:]*)/);
+    my $courseurl = &courseid_to_courseurl($env{'request.course.id'});
+    if ($env{'request.course.sec'}) {
+        $courseurl .= '/'.$env{'request.course.sec'};
+    }
+    return %blockers if ($env{'user.priv.'.$env{'request.role'}.'.'.$courseurl} =~/evb\&([^\:]*)/);
     my %commblocks;
     if (ref($blocks) eq 'HASH') {
         %commblocks = %{$blocks};
@@ -8858,10 +9427,9 @@ sub get_commblock_resources {
             }
         } elsif ($block =~ /^firstaccess____(.+)$/) {
             my $item = $1;
-            my @to_test;
             if (ref($commblocks{$block}{'blocks'}) eq 'HASH') {
                 if (ref($commblocks{$block}{'blocks'}{'docs'}) eq 'HASH') {
-                    my @interval;
+                    my (@interval,$mapname);
                     my $type = 'map';
                     if ($item eq 'course') {
                         $type = 'course';
@@ -8870,36 +9438,11 @@ sub get_commblock_resources {
                         if ($item =~ /___\d+___/) {
                             $type = 'resource';
                             @interval=&EXT("resource.0.interval",$item);
-                            if (ref($navmap)) {                        
-                                my $res = $navmap->getBySymb($item); 
-                                push(@to_test,$res);
-                            }
                         } else {
-                            my $mapsymb = &symbread($item,1);
-                            if ($mapsymb) {
-                                if (ref($navmap)) {
-                                    my $mapres = $navmap->getBySymb($mapsymb);
-                                    if (ref($mapres)) {
-                                        my $first = $mapres->map_start();
-                                        my $finish = $mapres->map_finish();
-                                        my $it = $navmap->getIterator($first,$finish,undef,0,0);
-                                        if (ref($it)) {
-                                            my $res;
-                                            while ($res = $it->next(undef,1)) {
-                                                next unless (ref($res));
-                                                my $symb = $res->symb();
-                                                next if (($symb eq $mapsymb) || ($symb eq ''));
-                                                @interval=&EXT("resource.0.interval",$symb);
-                                                if ($interval[1] eq 'map') {
-                                                    if ($res->answerable()) {
-                                                        push(@to_test,$res);
-                                                        last;
-                                                    }
-                                                }
-                                            }
-                                        }
-                                    }
-                                }
+                            $mapname = &deversion($item);
+                            if (ref($navmap)) {
+                                my $timelimit = $navmap->get_mapparam(undef,$mapname,'0.interval');
+                                @interval = ($timelimit,'map');
                             }
                         }
                     }
@@ -8917,10 +9460,37 @@ sub get_commblock_resources {
                             my $timesup = $first_access+$timelimit;
                             if ($timesup > $now) {
                                 my $activeblock;
-                                foreach my $res (@to_test) {
-                                    if ($res->answerable()) {
-                                        $activeblock = 1;
-                                        last;
+                                if ($type eq 'resource') {
+                                    if (ref($navmap)) {
+                                        my $res = $navmap->getBySymb($item);
+                                        if ($res->answerable()) {
+                                            $activeblock = 1;
+                                        }
+                                    }
+                                } elsif ($type eq 'map') {
+                                    my $mapsymb = &symbread($mapname,1);
+                                    if (($mapsymb) && (ref($navmap))) {
+                                        my $mapres = $navmap->getBySymb($mapsymb);
+                                        if (ref($mapres)) {
+                                            my $first = $mapres->map_start();
+                                            my $finish = $mapres->map_finish();
+                                            my $it = $navmap->getIterator($first,$finish,undef,0,0);
+                                            if (ref($it)) {
+                                                my $res;
+                                                while ($res = $it->next(undef,1)) {
+                                                    next unless (ref($res));
+                                                    my $symb = $res->symb();
+                                                    next if (($symb eq $mapsymb) || ($symb eq ''));
+                                                    @interval=&EXT("resource.0.interval",$symb);
+                                                    if ($interval[1] eq 'map') {
+                                                        if ($res->answerable()) {
+                                                            $activeblock = 1;
+                                                            last;
+                                                        }
+                                                    }
+                                                }
+                                            }
+                                        }
                                     }
                                 }
                                 if ($activeblock) {
@@ -8950,8 +9520,12 @@ sub has_comm_blocking {
     my @blockers;
     return unless ($env{'request.course.id'});
     return unless ($priv eq 'bre');
-    return if ($env{'user.priv.'.$env{'request.role'}} =~/evb\&([^\:]*)/);
     return if ($env{'request.state'} eq 'construct');
+    my $courseurl = &courseid_to_courseurl($env{'request.course.id'});
+    if ($env{'request.course.sec'}) {
+        $courseurl .= '/'.$env{'request.course.sec'};
+    }
+    return if ($env{'user.priv.'.$env{'request.role'}.'.'.$courseurl} =~/evb\&([^\:]*)/);
     my %blockinfo;
     if (ref($blocks) eq 'HASH') {
         %blockinfo = &get_commblock_resources($blocks);
@@ -9026,29 +9600,9 @@ sub deeplink_check {
         @symbs = keys(%possibles);
     }
 
-    my ($login,$switchrole,$allow);
-    if ($env{'request.deeplink.login'} =~ m{^\Q/tiny/$cdom/\E(\w+)$}) {
-        my $key = $1;
-        my $tinyurl;
-        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,$posslogin) = split(/\&/,$tinyurl);
-            if ($cnumreq eq $cnum) {
-                $login = $posslogin;
-            } else {
-                $switchrole = 1;
-            }
-        }
+    my ($deeplink_symb,$allow);
+    if ($env{'request.deeplink.login'}) {
+        $deeplink_symb = &Apache::loncommon::deeplink_login_symb($cnum,$cdom);
     }
     foreach my $symb (@symbs) {
         last if ($allow);
@@ -9056,34 +9610,48 @@ sub deeplink_check {
         if ($deeplink eq '') {
             $allow = 1;
         } else {
-            my ($listed,$scope,$access) = split(/,/,$deeplink);
-            if ($access eq 'any') {
+            my ($state,$others,$listed,$scope,$protect) = split(/,/,$deeplink);
+            if ($state ne 'only') {
                 $allow = 1;
-            } elsif ($login) {
-                if ($access eq 'only') {
-                    if ($scope eq 'res') {
-                        if ($symb eq $login) {
-                            $allow = 1;
+            } else {
+                my $check_deeplink_entry;
+                if ($protect ne 'none') {
+                    my ($acctype,$item) = split(/:/,$protect);
+                    if (($acctype eq 'ltic') && ($env{'user.linkprotector'})) {
+                        if (grep(/^\Q$item\Ec$/,split(/,/,$env{'user.linkprotector'}))) {
+                            $check_deeplink_entry = 1
                         }
-                    } elsif ($scope eq 'map') {
-#FIXME Compare map for $env{'request.deeplink.login'} with map for $symb
-                    } elsif ($scope eq 'rec') {
-#FIXME Recurse up for $env{'request.deeplink.login'} with map for $symb
-                    }
-                } else {
-                    my ($acctype,$item) = split(/:/,$access);
-                    if (($acctype eq 'lti') && ($env{'user.linkprotector'})) {
-                        if (grep(/^\Q$item\E$/,split(/,/,$env{'user.linkprotector'}))) {
-                            my %tinyurls = &get('tiny',[$symb],$cdom,$cnum);
-                            if (grep(/\Q$tinyurls{$symb}\E$/,split(/,/,$env{'user.linkproturis'}))) {
-                                $allow = 1;
-                            }
+                    } elsif (($acctype eq 'ltid') && ($env{'user.linkprotector'})) {
+                        if (grep(/^\Q$item\Ed$/,split(/,/,$env{'user.linkprotector'}))) {
+                            $check_deeplink_entry = 1;
                         }
                     } elsif (($acctype eq 'key') && ($env{'user.deeplinkkey'})) {
                         if (grep(/^\Q$item\E$/,split(/,/,$env{'user.deeplinkkey'}))) {
-                            my %tinyurls = &get('tiny',[$symb],$cdom,$cnum);
-                            if (grep(/\Q$tinyurls{$symb}\E$/,split(/,/,$env{'user.keyedlinkuri'}))) {
+                            $check_deeplink_entry = 1;
+                        }
+                    }
+                }
+                if (($protect eq 'none') || ($check_deeplink_entry)) {
+                    if ($scope eq 'res') {
+                        if ($symb eq $deeplink_symb) {
+                            $allow = 1;
+                        }
+                    } elsif (($scope eq 'map') || ($scope eq 'rec')) {
+                        my ($map_from_symb,$map_from_login);
+                        $map_from_symb = &deversion((&decode_symb($symb))[0]);
+                        if ($deeplink_symb =~ /\.(page|sequence)$/) {
+                            $map_from_login = &deversion((&decode_symb($deeplink_symb))[2]);
+                        } else {
+                            $map_from_login = &deversion((&decode_symb($deeplink_symb))[0]);
+                        }
+                        if (($map_from_symb) && ($map_from_login)) {
+                            if ($map_from_symb eq $map_from_login) {
                                 $allow = 1;
+                            } elsif ($scope eq 'rec') {
+                                my @recurseup = &get_map_hierarchy($map_from_symb,$env{'request.course.id'});
+                                if (grep(/^\Q$map_from_login\E$/,@recurseup)) {
+                                    $allow = 1;
+                                }
                             }
                         }
                     }
@@ -9356,7 +9924,7 @@ sub fetch_enrollment_query {
 }
 
 sub get_query_reply {
-    my ($queryid,$sleep,$loopmax) = @_;;
+    my ($queryid,$sleep,$loopmax) = @_;
     if (($sleep eq '') || ($sleep !~ /^\d+\.?\d*$/)) {
         $sleep = 0.2;
     }
@@ -9779,6 +10347,38 @@ sub auto_validate_class_sec {
     return $response;
 }
 
+sub auto_instsec_reformat {
+    my ($cdom,$action,$instsecref) = @_;
+    return unless(($action eq 'clutter') || ($action eq 'declutter'));
+    my @homeservers;
+    if (defined(&domain($cdom,'primary'))) {
+        push(@homeservers,&domain($cdom,'primary'));
+    } else {
+        my %servers = &get_servers($cdom,'library');
+        foreach my $tryserver (keys(%servers)) {
+            if (!grep(/^\Q$tryserver\E$/,@homeservers)) {
+                push(@homeservers,$tryserver);
+            }
+        }
+    }
+    my $response;
+    my %reformatted = %{$instsecref};
+    foreach my $server (@homeservers) {
+        if (ref($instsecref) eq 'HASH') {
+            my $info = &freeze_escape($instsecref);
+            my $response=&reply('autoinstsecreformat:'.$cdom.':'.
+                                $action.':'.$info,$server);
+            next if ($response =~ /(con_lost|error|no_such_host|refused|unknown_command)/);
+            my @items = split(/&/,$response);
+            foreach my $item (@items) {
+                my ($key,$value) = split(/=/,$item);
+                $reformatted{&unescape($key)} = &thaw_unescape($value);
+            }
+        }
+    }
+    return %reformatted;
+}
+
 sub auto_validate_instclasses {
     my ($cdom,$cnum,$owners,$classesref) = @_;
     my ($homeserver,%validations);
@@ -9980,11 +10580,13 @@ sub toggle_coursegroup_status {
 }
 
 sub modify_group_roles {
-    my ($cdom,$cnum,$group_id,$user,$end,$start,$userprivs,$selfenroll,$context) = @_;
+    my ($cdom,$cnum,$group_id,$user,$end,$start,$userprivs,$selfenroll,$context,
+        $othdomby,$requester) = @_;
     my $url = '/'.$cdom.'/'.$cnum.'/'.$group_id;
     my $role = 'gr/'.&escape($userprivs);
     my ($uname,$udom) = split(/:/,$user);
-    my $result = &assignrole($udom,$uname,$url,$role,$end,$start,'',$selfenroll,$context);
+    my $result = &assignrole($udom,$uname,$url,$role,$end,$start,'',$selfenroll,$context,
+                             $othdomby,$requester);
     if ($result eq 'ok') {
         &devalidate_getgroups_cache($udom,$uname,$cdom,$cnum);
     }
@@ -10112,43 +10714,66 @@ sub plaintext {
 
 sub assignrole {
     my ($udom,$uname,$url,$role,$end,$start,$deleteflag,$selfenroll,
-        $context)=@_;
-    my $mrole;
+        $context,$othdomby,$requester,$reqsec,$reqrole)=@_;
+    my ($mrole,$rolelogcontext);
     if ($role =~ /^cr\//) {
         my $cwosec=$url;
         $cwosec=~s/^\/($match_domain)\/($match_courseid)\/.*/$1\/$2/;
-	unless (&allowed('ccr',$cwosec)) {
-           my $refused = 1;
-           if ($context eq 'requestcourses') {
-               if (($env{'user.name'} ne '') && ($env{'user.domain'} ne '')) {
-                   if ($role =~ m{^cr/($match_domain)/($match_username)/([^/]+)$}) {
-                       if (($1 eq $env{'user.domain'}) && ($2 eq $env{'user.name'})) {
-                           my ($cdom,$cnum) = ($cwosec =~ m{^/?($match_domain)/($match_courseid)$});
-                           my %crsenv = &userenvironment($cdom,$cnum,('internal.courseowner'));
-                           if ($crsenv{'internal.courseowner'} eq
-                               $env{'user.name'}.':'.$env{'user.domain'}) {
-                               $refused = '';
-                           }
-                       }
-                   }
-               }
-           }
-           if ($refused) {
-               &logthis('Refused custom assignrole: '.
-                        $udom.' '.$uname.' '.$url.' '.$role.' '.$end.' '.$start.
-                        ' by '.$env{'user.name'}.' at '.$env{'user.domain'});
-               return 'refused';
-           }
+        if ((!&allowed('ccr',$cwosec)) && (!&allowed('ccr',$udom))) {
+            my $refused = 1;
+            if ($context eq 'requestcourses') {
+                if (($env{'user.name'} ne '') && ($env{'user.domain'} ne '')) {
+                    if ($role =~ m{^cr/($match_domain)/($match_username)/([^/]+)$}) {
+                        if (($1 eq $env{'user.domain'}) && ($2 eq $env{'user.name'})) {
+                            my ($cdom,$cnum) = ($cwosec =~ m{^/?($match_domain)/($match_courseid)$});
+                            my %crsenv = &userenvironment($cdom,$cnum,('internal.courseowner'));
+                            if ($crsenv{'internal.courseowner'} eq
+                                $env{'user.name'}.':'.$env{'user.domain'}) {
+                                $refused = '';
+                            }
+                        }
+                    }
+                }
+            } elsif (($context eq 'course') && ($othdomby eq 'othdombyuser')) {
+                my ($cdom,$cnum) = ($cwosec =~ m{^/?($match_domain)/($match_courseid)$});
+                my ($sec) = ($url =~ m{^/\Q$cwosec\E/(.*)$});
+                my $key = "$uname:$udom:$role:$sec";
+                my %queuedrolereq = &Apache::lonnet::get('nohist_othdomqueued',[$key],$cdom,$cnum);
+                if ((exists($queuedrolereq{$key})) && (ref($queuedrolereq{$key}) eq 'HASH')) {
+                    if (($queuedrolereq{$key}{'adj'} eq 'user') && ($queuedrolereq{$key}{'requester'} eq $requester)) {
+                        $refused = '';
+                    }
+                }
+            }
+            if ($refused) {
+                &logthis('Refused custom assignrole: '.
+                         $udom.' '.$uname.' '.$url.' '.$role.' '.$end.' '.$start.
+                         ' by '.$env{'user.name'}.' at '.$env{'user.domain'});
+                return 'refused';
+            }
         }
         $mrole='cr';
     } elsif ($role =~ /^gr\//) {
         my $cwogrp=$url;
         $cwogrp=~s{^/($match_domain)/($match_courseid)/.*}{$1/$2};
-        unless (&allowed('mdg',$cwogrp)) {
-            &logthis('Refused group assignrole: '.
-              $udom.' '.$uname.' '.$url.' '.$role.' '.$end.' '.$start.' by '.
-                    $env{'user.name'}.' at '.$env{'user.domain'});
-            return 'refused';
+        if (!&allowed('mdg',$cwogrp)) {
+            my $refused = 1;
+            if (($refused) && ($othdomby eq 'othdombyuser') && ($requester ne '') && ($reqrole ne '')) {
+                my ($cdom,$cnum) = ($cwogrp =~ m{^/?($match_domain)/($match_courseid)$});
+                my $key = "$uname:$udom:$reqrole:$reqsec";
+                my %queuedrolereq = &Apache::lonnet::get('nohist_othdomqueued',[$key],$cdom,$cnum);
+                if ((exists($queuedrolereq{$key})) && (ref($queuedrolereq{$key}) eq 'HASH')) {
+                    if (($queuedrolereq{$key}{'adj'} eq 'user') && ($queuedrolereq{$key}{'requester'} eq $requester)) {
+                        $refused = '';
+                    }
+                }
+            }
+            if ($refused) {
+                &logthis('Refused group assignrole: '.
+                         $udom.' '.$uname.' '.$url.' '.$role.' '.$end.' '.$start.' by '.
+                         $env{'user.name'}.' at '.$env{'user.domain'});
+                return 'refused';
+            }
         }
         $mrole='gr';
     } else {
@@ -10165,7 +10790,8 @@ sub assignrole {
             }
             if ($refused) {
                 my ($cdom,$cnum) = ($cwosec =~ m{^/?($match_domain)/($match_courseid)$});
-                if (!$selfenroll && (($context eq 'course') || ($context eq 'ltienroll' && $env{'request.lti.login'}))) {
+                if (!$selfenroll && ($othdomby ne 'othdombyuser') &&
+                   (($context eq 'course') || ($context eq 'ltienroll' && $env{'request.lti.login'}))) {
                     my %crsenv;
                     if ($role eq 'cc' || $role eq 'co') {
                         %crsenv = &userenvironment($cdom,$cnum,('internal.courseowner'));
@@ -10191,6 +10817,49 @@ sub assignrole {
                     } elsif (($context eq 'ltienroll') && ($env{'request.lti.login'})) {
                         $refused = '';
                     }
+                } elsif ($othdomby eq 'othdombyuser') {
+                    my ($key,%queuedrolereq);
+                    if ($context eq 'course') {
+                        my ($sec) = ($url =~ m{^/\Q$cwosec\E/(.*)$});
+                        $key = "$uname:$udom:$role:$sec";
+                        %queuedrolereq = &Apache::lonnet::get('nohist_othdomqueued',[$key],$cdom,$cnum);
+                        if ((exists($queuedrolereq{$key})) && (ref($queuedrolereq{$key}) eq 'HASH')) {
+                            if (($queuedrolereq{$key}{'adj'} eq 'user') && ($queuedrolereq{$key}{'requester'} eq $requester)) {
+                                if ((($role eq 'cc') && ($cnum !~ /^$match_community$/)) || 
+                                    (($role eq 'co') && ($cnum =~ /^$match_community$/))) {
+                                    my %crsenv = &userenvironment($cdom,$cnum,('internal.courseowner'));
+                                    if ($crsenv{'internal.courseowner'} eq $requester) {
+                                        $refused = '';
+                                    }
+                                } elsif ($role =~ /^(?:in|ta|ep|st)$/) {
+                                    $refused = '';
+                                }
+                            }
+                        }
+                    } elsif (($context eq 'author') && ($role =~ /^ca|aa$/)) {
+                        my $key = "$uname:$udom:$role"; 
+                        my ($audom,$auname) = ($url =~ m{^/($match_domain)/($match_username)$});
+                        if (($audom ne '') && ($auname ne '')) {
+                            my %queuedrolereq = &Apache::lonnet::get('nohist_othdomqueued',[$key],$audom,$auname);
+                            if ((exists($queuedrolereq{$key})) && (ref($queuedrolereq{$key}) eq 'HASH')) {
+                                if (($queuedrolereq{$key}{'adj'} eq 'user') && ($queuedrolereq{$key}{'requester'} eq $requester)) {
+                                    $refused = '';
+                                }
+                            }
+                        }
+                    } elsif (($context eq 'domain') && ($role ne 'dc') && ($role ne 'su')) {
+                        my $key = "$uname:$udom:$role";
+                        my ($roledom) = ($url =~ m{^/($match_domain)/\Q$role\E$});
+                        if ($roledom ne '') {
+                            my $confname = $roledom.'-domainconfig';
+                            my %queuedrolereq = &Apache::lonnet::get('nohist_othdomqueued',[$key],$roledom,$confname);
+                            if ((exists($queuedrolereq{$key})) && (ref($queuedrolereq{$key}) eq 'HASH')) {
+                                if (($queuedrolereq{$key}{'adj'} eq 'user') && ($queuedrolereq{$key}{'requester'} eq $requester)) {
+                                    $refused = '';
+                                }
+                            }
+                        }
+                    }
                 } elsif ($context eq 'requestcourses') {
                     my @possroles = ('st','ta','ep','in','cc','co');
                     if ((grep(/^\Q$role\E$/,@possroles)) && ($env{'user.name'} ne '' && $env{'user.domain'} ne '')) {
@@ -10243,6 +10912,15 @@ sub assignrole {
                             }
                         }
                     }
+                } elsif (($context eq 'author') && (($role eq 'ca' || $role eq 'aa'))) {
+                    if ($url =~ m{^/($match_domain)/($match_username)$}) {
+                        my ($audom,$auname) = ($1,$2);
+                        if ((&Apache::lonnet::allowed('v'.$role,"$audom/$auname")) &&
+                            ($env{"environment.internal.manager.$url"})) {
+                            $refused = '';
+                            $rolelogcontext = 'coauthor';
+                        }
+                    }
                 }
                 if ($refused) {
                     &logthis('Refused assignrole: '.$udom.' '.$uname.' '.$url.
@@ -10303,15 +10981,18 @@ sub assignrole {
                                                  $origstart,$selfenroll,$context);
             }
             &courserolelog($role,$uname,$udom,$url,$origstart,$origend,$delflag,
-                           $selfenroll,$context);
+                           $selfenroll,$context,$othdomby,$requester);
         } elsif (($role eq 'li') || ($role eq 'dg') || ($role eq 'sc') ||
                  ($role eq 'au') || ($role eq 'dc') || ($role eq 'dh') ||
                  ($role eq 'da')) {
             &domainrolelog($role,$uname,$udom,$url,$origstart,$origend,$delflag,
-                           $context);
+                           $context,$othdomby,$requester);
         } elsif (($role eq 'ca') || ($role eq 'aa')) {
+            if ($rolelogcontext eq '') {
+                $rolelogcontext = $context;
+            }
             &coauthorrolelog($role,$uname,$udom,$url,$origstart,$origend,$delflag,
-                             $context); 
+                             $rolelogcontext,$othdomby,$requester); 
         }
         if ($role eq 'cc') {
             &autoupdate_coowners($url,$end,$start,$uname,$udom);
@@ -10406,10 +11087,10 @@ sub store_coowners {
     }
     if (($putresult eq 'ok') || ($delresult eq 'ok')) {
         my %crsinfo =
-            &Apache::lonnet::courseiddump($cdom,'.',1,'.','.',$cnum,undef,undef,'.');
+            &courseiddump($cdom,'.',1,'.','.',$cnum,undef,undef,'.');
         if (ref($crsinfo{$cid}) eq 'HASH') {
             $crsinfo{$cid}{'co-owners'} = \@newcoowners;
-            my $cidput = &Apache::lonnet::courseidput($cdom,\%crsinfo,$chome,'notime');
+            my $cidput = &courseidput($cdom,\%crsinfo,$chome,'notime');
         }
     }
 }
@@ -10627,7 +11308,7 @@ sub modifyuser {
         return 'error: '.$reply;
     }
     if ($names{'permanentemail'} ne $oldnames{'permanentemail'}) {
-        &Apache::lonnet::devalidate_cache_new('emailscache',$uname.':'.$udom);
+        &devalidate_cache_new('emailscache',$uname.':'.$udom);
     }
     my $sqlresult = &update_allusers_table($uname,$udom,\%names);
     &devalidate_cache_new('namescache',$uname.':'.$udom);
@@ -10663,7 +11344,7 @@ sub modifystudent {
 
 sub modify_student_enrollment {
     my ($udom,$uname,$uid,$first,$middle,$last,$gene,$usec,$end,$start,$type,
-        $locktype,$cid,$selfenroll,$context,$credits,$instsec) = @_;
+        $locktype,$cid,$selfenroll,$context,$credits,$instsec,$othdomby,$requester) = @_;
     my ($cdom,$cnum,$chome);
     if (!$cid) {
 	unless ($cid=$env{'request.course.id'}) {
@@ -10707,7 +11388,7 @@ sub modify_student_enrollment {
     }
     my $fullname = &format_name($first,$middle,$last,$gene,'lastname');
     my $user = "$uname:$udom";
-    my %old_entry = &Apache::lonnet::get('classlist',[$user],$cdom,$cnum);
+    my %old_entry = &get('classlist',[$user],$cdom,$cnum);
     my $reply=cput('classlist',
 		   {$user => 
 			join(':',$end,$start,$uid,$usec,$fullname,$type,$locktype,$credits,$instsec) },
@@ -10724,7 +11405,7 @@ sub modify_student_enrollment {
 	$uurl.='/'.$usec;
     }
     my $result = &assignrole($udom,$uname,$uurl,'st',$end,$start,undef,
-                             $selfenroll,$context);
+                             $selfenroll,$context,$othdomby,$requester);
     if ($result ne 'ok') {
         if ($old_entry{$user} ne '') {
             $reply = &cput('classlist',\%old_entry,$cdom,$cnum);
@@ -10841,7 +11522,7 @@ sub createcourse {
         }
     }
     my %host_servers =
-        &Apache::lonnet::get_servers($udom,'library');
+        &get_servers($udom,'library');
     unless ($host_servers{$course_server}) {
         return 'error: invalid home server for course: '.$course_server;
     }
@@ -11000,9 +11681,11 @@ sub store_userdata {
 # ---------------------------------------------------------- Assign Custom Role
 
 sub assigncustomrole {
-    my ($udom,$uname,$url,$rdom,$rnam,$rolename,$end,$start,$deleteflag,$selfenroll,$context)=@_;
+    my ($udom,$uname,$url,$rdom,$rnam,$rolename,$end,$start,$deleteflag,
+        $selfenroll,$context,$othdomby,$requester)=@_;
     return &assignrole($udom,$uname,$url,'cr/'.$rdom.'/'.$rnam.'/'.$rolename,
-                       $end,$start,$deleteflag,$selfenroll,$context);
+                       $end,$start,$deleteflag,$selfenroll,$context,$othdomby,
+                       $requester);
 }
 
 # ----------------------------------------------------------------- Revoke Role
@@ -11809,14 +12492,20 @@ sub stat_file {
 # or corresponding Published Resource Space, and populate the hash ref:
 # $dirhashref with URLs of all directories, and if $filehashref hash
 # ref arg is provided, the URLs of any files, excluding versioned, .meta,
-# or .rights files in resource space, and .meta, .save, .log, and .bak
-# files in Authoring Space.
+# or .rights files in resource space, and .meta, .save, .log, .bak and
+# .rights files in Authoring Space.
 #
 # Inputs:
 #
 # $is_home - true if current server is home server for user's space
-# $context - either: priv, or res respectively for Authoring or Resource Space.
-# $docroot - Document root (i.e., /home/httpd/html
+# $recurse - if true will also traverse subdirectories recursively
+# $include - reference to hash containing allowed file extensions.  If provided,
+#             files which do not have a matching extension will be ignored.
+# $exclude - reference to hash containing excluded file extensions.  If provided,
+#             files which have a matching extension will be ignored.
+# $nonemptydir - if true, will only populate $fileshashref hash entry for a particular
+#             directory with first file found (with acceptable extension).
+# $addtopdir - if true, set $dirhashref->{'/'} = 1 
 # $toppath - Top level directory (i.e., /res/$dom/$uname or /priv/$dom/$uname
 # $relpath - Current path (relative to top level).
 # $dirhashref - reference to hash to populate with URLs of directories (Required)
@@ -11833,39 +12522,61 @@ sub stat_file {
 #
 
 sub recursedirs {
-    my ($is_home,$context,$docroot,$toppath,$relpath,$dirhashref,$filehashref) = @_;
+    my ($is_home,$recurse,$include,$exclude,$nonemptydir,$addtopdir,$toppath,$relpath,$dirhashref,$filehashref) = @_;
     return unless (ref($dirhashref) eq 'HASH');
+    my $docroot = $perlvar{'lonDocRoot'};
     my $currpath = $docroot.$toppath;
-    if ($relpath) {
+    if ($relpath ne '') {
         $currpath .= "/$relpath";
     }
-    my $savefile;
+    my ($savefile,$checkinc,$checkexc);
     if (ref($filehashref)) {
         $savefile = 1;
     }
+    if (ref($include) eq 'HASH') {
+        $checkinc = 1;
+    }
+    if (ref($exclude) eq 'HASH') {
+        $checkexc = 1;
+    }
     if ($is_home) {
-        if (opendir(my $dirh,$currpath)) {
+        if ((-e $currpath) && (opendir(my $dirh,$currpath))) {
+            my $filecount = 0;
             foreach my $item (sort { lc($a) cmp lc($b) } grep(!/^\.+$/,readdir($dirh))) {
                 next if ($item eq '');
                 if (-d "$currpath/$item") {
                     my $newpath;
-                    if ($relpath) {
+                    if ($relpath ne '') {
                         $newpath = "$relpath/$item";
                     } else {
                         $newpath = $item;
                     }
                     $dirhashref->{&Apache::lonlocal::js_escape($newpath)} = 1;
-                    &recursedirs($is_home,$context,$docroot,$toppath,$newpath,$dirhashref,$filehashref);
-                } elsif ($savefile) {
-                    if ($context eq 'priv') {
-                        unless ($item =~ /\.(meta|save|log|bak|DS_Store)$/) {
-                            $filehashref->{&Apache::lonlocal::js_escape($relpath)}{$item} = 1;
+                    if ($recurse) {
+                        &recursedirs($is_home,$recurse,$include,$exclude,$nonemptydir,$addtopdir,$toppath,$newpath,$dirhashref,$filehashref);
+                    }
+                } elsif (($savefile) || ($relpath eq '')) {
+                    next if ($nonemptydir && $filecount);
+                    if ($checkinc || $checkexc) {
+                        my ($extension) = ($item =~ /\.(\w+)$/);
+                        if ($checkinc) {
+                            next unless ($extension && $include->{$extension});
                         }
-                    } else {
-                        unless (($item =~ /\.meta$/) || ($item =~ /\.\d+\.\w+$/) || ($item =~ /\.rights$/)) {
+                        if ($checkexc) {
+                            next if ($extension && $exclude->{$extension});
+                        }
+                    }
+                    if (($relpath eq '') && (!exists($dirhashref->{'/'}))) {
+                        $dirhashref->{'/'} = 1;
+                    }
+                    if ($savefile) {
+                        if ($relpath eq '') {
+                            $filehashref->{'/'}{$item} = 1;
+                        } else {
                             $filehashref->{&Apache::lonlocal::js_escape($relpath)}{$item} = 1;
                         }
                     }
+                    $filecount ++;
                 }
             }
             closedir($dirh);
@@ -11876,6 +12587,7 @@ sub recursedirs {
         my @dir_lines;
         my $dirptr=16384;
         if (ref($dirlistref) eq 'ARRAY') {
+            my $filecount = 0;
             foreach my $dir_line (sort
                               {
                                   my ($afile)=split('&',$a,2);
@@ -11891,28 +12603,57 @@ sub recursedirs {
                     if ($relpath) {
                         $newpath = "$relpath/$item";
                     } else {
-                        $relpath = '/';
                         $newpath = $item;
                     }
                     $dirhashref->{&Apache::lonlocal::js_escape($newpath)} = 1;
-                    &recursedirs($is_home,$context,$docroot,$toppath,$newpath,$dirhashref,$filehashref);
-                } elsif ($savefile) {
-                    if ($context eq 'priv') {
-                        unless ($item =~ /\.(meta|save|log|bak|DS_Store)$/) {
-                            $filehashref->{$relpath}{$item} = 1;
+                    if ($recurse) {
+                        &recursedirs($is_home,$recurse,$include,$exclude,$nonemptydir,$addtopdir,$toppath,$newpath,$dirhashref,$filehashref);
+                    }
+                } elsif (($savefile) || ($relpath eq '')) {
+                    next if ($nonemptydir && $filecount);
+                    if ($checkinc || $checkexc) {
+                        my $extension;
+                        if ($checkinc) {
+                            next unless ($extension && $include->{$extension});
                         }
-                    } else {
-                        unless (($item =~ /\.meta$/) || ($item =~ /\.\d+\.\w+$/)) {
-                            $filehashref->{$relpath}{$item} = 1;
+                        if ($checkexc) {
+                            next if ($extension && $exclude->{$extension});
                         }
                     }
+                    if (($relpath eq '') && (!exists($dirhashref->{'/'}))) {
+                        $dirhashref->{'/'} = 1;
+                    }
+                    if ($savefile) {
+                        if ($relpath eq '') {
+                            $filehashref->{'/'}{$item} = 1;
+                        } else {
+                            $filehashref->{&Apache::lonlocal::js_escape($relpath)}{$item} = 1;
+                        }
+                    }
+                    $filecount ++; 
                 }
             }
         }
     }
+    if ($addtopdir) {
+        if (($relpath eq '') && (!exists($dirhashref->{'/'}))) {
+            $dirhashref->{'/'} = 1;
+        }
+    }
     return;
 }
 
+sub priv_exclude {
+    return {
+             meta => 1,
+             save => 1,
+             log => 1,
+             bak => 1,
+             rights => 1,
+             DS_Store => 1,
+           };
+}
+
 # -------------------------------------------------------- Value of a Condition
 
 # gets the value of a specific preevaluated condition
@@ -12126,15 +12867,22 @@ sub resdata {
 
 sub get_domain_lti {
     my ($cdom,$context) = @_;
-    my ($name,%lti);
+    my ($name,$cachename,%lti);
     if ($context eq 'consumer') {
         $name = 'ltitools';
     } elsif ($context eq 'provider') {
         $name = 'lti';
+    } elsif ($context eq 'linkprot') {
+        $name = 'ltisec';
     } else {
         return %lti;
     }
-    my ($result,$cached)=&is_cached_new($name,$cdom);
+    if ($context eq 'linkprot') {
+        $cachename = $context;
+    } else {
+        $cachename = $name;
+    }
+    my ($result,$cached)=&is_cached_new($cachename,$cdom);
     if (defined($cached)) {
         if (ref($result) eq 'HASH') {
             %lti = %{$result};
@@ -12142,43 +12890,218 @@ sub get_domain_lti {
     } else {
         my %domconfig = &get_dom('configuration',[$name],$cdom);
         if (ref($domconfig{$name}) eq 'HASH') {
-            %lti = %{$domconfig{$name}};
-            my %encdomconfig = &get_dom('encconfig',[$name],$cdom);
-            if (ref($encdomconfig{$name}) eq 'HASH') {
-                foreach my $id (keys(%lti)) {
-                    if (ref($encdomconfig{$name}{$id}) eq 'HASH') {
-                        foreach my $item ('key','secret') {
-                            $lti{$id}{$item} = $encdomconfig{$name}{$id}{$item};
-                        }
-                    }
+            if ($context eq 'linkprot') {
+                if (ref($domconfig{$name}{'linkprot'}) eq 'HASH') {
+                    %lti = %{$domconfig{$name}{'linkprot'}};
                 }
+            } else {
+                %lti = %{$domconfig{$name}};
             }
         }
         my $cachetime = 24*60*60;
-        &do_cache_new($name,$cdom,\%lti,$cachetime);
+        &do_cache_new($cachename,$cdom,\%lti,$cachetime);
     }
     return %lti;
 }
 
-sub get_numsuppfiles {
-    my ($cnum,$cdom,$ignorecache)=@_;
+sub get_course_lti {
+    my ($cnum,$cdom,$context) = @_;
+    my ($name,$cachename,%lti);
+    if ($context eq 'consumer') {
+        $name = 'ltitools';
+        $cachename = 'courseltitools';
+    } elsif ($context eq 'provider') {
+        $name = 'lti';
+        $cachename = 'courselti';
+    } else {
+        return %lti;
+    }
+    my $hashid=$cdom.'_'.$cnum;
+    my ($result,$cached)=&is_cached_new($cachename,$hashid);
+    if (defined($cached)) {
+        if (ref($result) eq 'HASH') {
+            %lti = %{$result};
+        }
+    } else {
+        %lti = &dump($name,$cdom,$cnum,undef,undef,undef,1);
+        my $cachetime = 24*60*60;
+        &do_cache_new($cachename,$hashid,\%lti,$cachetime);
+    }
+    return %lti;
+}
+
+sub courselti_itemid {
+    my ($cnum,$cdom,$url,$method,$params,$context) = @_;
+    my ($chome,$itemid);
+    $chome = &homeserver($cnum,$cdom);
+    return if ($chome eq 'no_host');
+    if (ref($params) eq 'HASH') {
+        my $rep;
+        if (grep { $_ eq $chome } current_machine_ids()) {
+            $rep = LONCAPA::Lond::crslti_itemid($cdom,$cnum,$url,$method,$params,$perlvar{'lonVersion'});
+        } else {
+            my $escurl = &escape($url);
+            my $escmethod = &escape($method);
+            my $items = &freeze_escape($params);
+            $rep = &reply("encrypt:lti:$cdom:$cnum:$context:$escurl:$escmethod:$items",$chome);
+        }
+        unless (($rep=~/^(refused|rejected|error)/) || ($rep eq 'con_lost') ||
+                ($rep eq 'unknown_cmd')) {
+            $itemid = $rep;
+        }
+    }
+    return $itemid;
+}
+
+sub domainlti_itemid {
+    my ($cdom,$url,$method,$params,$context) = @_;
+    my ($primary_id,$itemid);
+    $primary_id = &domain($cdom,'primary');
+    return if ($primary_id eq '');
+    if (ref($params) eq 'HASH') {
+        my $rep;
+        if (grep { $_ eq $primary_id } current_machine_ids()) {
+            $rep = LONCAPA::Lond::domlti_itemid($cdom,$context,$url,$method,$params,$perlvar{'lonVersion'});
+        } else {
+            my $cnum = '';
+            my $escurl = &escape($url);
+            my $escmethod = &escape($method);
+            my $items = &freeze_escape($params);
+            $rep = &reply("encrypt:lti:$cdom:$cnum:$context:$escurl:$escmethod:$items",$primary_id);
+        }
+        unless (($rep=~/^(refused|rejected|error)/) || ($rep eq 'con_lost') ||
+                ($rep eq 'unknown_cmd')) {
+            $itemid = $rep;
+        }
+    }
+    return $itemid;
+}
+
+sub get_ltitools_id {
+    my ($context,$cdom,$cnum,$title) = @_;
+    my ($lockhash,$tries,$gotlock,$id,$error);
+
+    # get lock on ltitools db
+    $lockhash = {
+                   lock => $env{'user.name'}.
+                           ':'.$env{'user.domain'},
+                };
+    $tries = 0;
+    if ($context eq 'domain') {
+        $gotlock = &newput_dom('ltitools',$lockhash,$cdom);
+    } else {
+        $gotlock = &newput('ltitools',$lockhash,$cdom,$cnum);
+    }
+    while (($gotlock ne 'ok') && ($tries<10)) {
+        $tries ++;
+        sleep (0.1);
+        if ($context eq 'domain') {
+            $gotlock = &newput_dom('ltitools',$lockhash,$cdom);
+        } else {
+            $gotlock = &newput('ltitools',$lockhash,$cdom,$cnum);
+        }
+    }
+    if ($gotlock eq 'ok') {
+        my %currids;
+        if ($context eq 'domain') {
+            %currids = &dump_dom('ltitools',$cdom);
+        } else {
+            %currids = &dump('ltitools',$cdom,$cnum);
+        }
+        if ($currids{'lock'}) {
+            delete($currids{'lock'});
+            if (keys(%currids)) {
+                my @curr = sort { $a <=> $b } keys(%currids);
+                if ($curr[-1] =~ /^\d+$/) {
+                    $id = 1 + $curr[-1];
+                }
+            } else {
+                $id = 1;
+            }
+            if ($id) {
+                if ($context eq 'domain') {
+                    unless (&newput_dom('ltitools',{ $id => $title },$cdom) eq 'ok') {
+                        $error = 'nostore';
+                    }
+                } else {
+                    unless (&newput('ltitools',{ $id => $title },$cdom,$cnum) eq 'ok') {
+                        $error = 'nostore';
+                    }
+                }
+            } else {
+                $error = 'nonumber';
+            }
+        }
+        my $dellockoutcome;
+        if ($context eq 'domain') {
+            $dellockoutcome = &del_dom('ltitools',['lock'],$cdom);
+        } else {
+            $dellockoutcome = &del('ltitools',['lock'],$cdom,$cnum);
+        }
+    } else {
+        $error = 'nolock';
+    }
+    return ($id,$error);
+}
+
+sub count_supptools {
+    my ($cnum,$cdom,$ignorecache,$reload)=@_;
     my $hashid=$cnum.':'.$cdom;
-    my ($suppcount,$cached);
+    my ($numexttools,$cached);
     unless ($ignorecache) {
-        ($suppcount,$cached) = &is_cached_new('suppcount',$hashid);
+        ($numexttools,$cached) = &is_cached_new('supptools',$hashid);
     }
     unless (defined($cached)) {
         my $chome=&homeserver($cnum,$cdom);
+        $numexttools = 0;
         unless ($chome eq 'no_host') {
-            ($suppcount,my $supptools,my $errors) = (0,0,0);
-            my $suppmap = 'supplemental.sequence';
-            ($suppcount,$supptools,$errors) =
-                &Apache::loncommon::recurse_supplemental($cnum,$cdom,$suppmap,$suppcount,
-                                                         $supptools,$errors);
+            my ($supplemental) = &Apache::loncommon::get_supplemental($cnum,$cdom,$reload);
+            if (ref($supplemental) eq 'HASH') {
+                if ((ref($supplemental->{'ids'}) eq 'HASH') && (ref($supplemental->{'hidden'}) eq 'HASH')) {
+                    foreach my $key (keys(%{$supplemental->{'ids'}})) {
+                        if ($key =~ m{^/adm/$cdom/$cnum/\d+/ext\.tool$}) {
+                            $numexttools ++;
+                        }
+                    }
+                }
+            }
         }
-        &do_cache_new('suppcount',$hashid,$suppcount,600);
+        &do_cache_new('supptools',$hashid,$numexttools,600);
     }
-    return $suppcount;
+    return $numexttools;
+}
+
+sub has_unhidden_suppfiles {
+    my ($cnum,$cdom,$ignorecache,$possdel)=@_;
+    my $hashid=$cnum.':'.$cdom;
+    my ($showsupp,$cached);
+    unless ($ignorecache) {
+        ($showsupp,$cached) = &is_cached_new('showsupp',$hashid);
+    }
+    unless (defined($cached)) {
+        my $chome=&homeserver($cnum,$cdom);
+        unless ($chome eq 'no_host') {
+            my ($supplemental) = &Apache::loncommon::get_supplemental($cnum,$cdom,$ignorecache,$possdel);
+            if (ref($supplemental) eq 'HASH') {
+                if ((ref($supplemental->{'ids'}) eq 'HASH') && (ref($supplemental->{'hidden'}) eq 'HASH')) {
+                    foreach my $key (keys(%{$supplemental->{'ids'}})) {
+                        next if ($key =~ /\.sequence$/);
+                        if (ref($supplemental->{'ids'}->{$key}) eq 'ARRAY') {
+                            foreach my $id (@{$supplemental->{'ids'}->{$key}}) {
+                                unless ($supplemental->{'hidden'}->{$id}) {
+                                    $showsupp = 1;
+                                    last;
+                                }
+                            }
+                        }
+                        last if ($showsupp);
+                    }
+                }
+            }
+        }
+        &do_cache_new('showsupp',$hashid,$showsupp,600);
+    }
+    return $showsupp;
 }
 
 #
@@ -12219,7 +13142,7 @@ sub EXT_cache_set {
 # --------------------------------------------------------- Value of a Variable
 sub EXT {
 
-    my ($varname,$symbparm,$udom,$uname,$usection,$recurse,$cid)=@_;
+    my ($varname,$symbparm,$udom,$uname,$usection,$recurse,$cid,$recurseupref)=@_;
     unless ($varname) { return ''; }
     #get real user name/domain, courseid and symb
     my $courseid;
@@ -12251,7 +13174,7 @@ sub EXT {
 	    if ( (defined($Apache::lonhomework::parsing_a_problem)
 		  || defined($Apache::lonhomework::parsing_a_task))
 		 &&
-		 ($symbparm eq &symbread()) ) {	
+		 ($symbparm eq &symbread()) ) {
 		# if we are in the middle of processing the resource the
 		# get the value we are planning on committing
                 if (defined($Apache::lonhomework::results{$qualifierrest})) {
@@ -12373,6 +13296,10 @@ sub EXT {
         }
 
 	my ($section, $group, @groups, @recurseup, $recursed);
+        if (ref($recurseupref) eq 'ARRAY') {
+            @recurseup = @{$recurseupref};
+            $recursed = 1;
+        }
 	my ($courselevelm,$courseleveli,$courselevel,$mapp);
         if (($courseid eq '') && ($cid)) {
             $courseid = $cid;
@@ -13250,13 +14177,13 @@ sub get_reservable_slots {
 sub get_course_slots {
     my ($cnum,$cdom) = @_;
     my $hashid=$cnum.':'.$cdom;
-    my ($result,$cached) = &Apache::lonnet::is_cached_new('allslots',$hashid);
+    my ($result,$cached) = &is_cached_new('allslots',$hashid);
     if (defined($cached)) {
         if (ref($result) eq 'HASH') {
             return %{$result};
         }
     } else {
-        my %slots=&Apache::lonnet::dump('slots',$cdom,$cnum);
+        my %slots=&dump('slots',$cdom,$cnum);
         my ($tmp) = keys(%slots);
         if ($tmp !~ /^(con_lost|error|no_such_host)/i) {
             &do_cache_new('allslots',$hashid,\%slots,600);
@@ -13302,11 +14229,68 @@ sub get_coursechange {
 }
 
 sub devalidate_coursechange_cache {
-    my ($cnum,$cdom)=@_;
-    my $hashid=$cnum.':'.$cdom;
+    my ($cdom,$cnum)=@_;
+    my $hashid=$cdom.'_'.$cnum;
     &devalidate_cache_new('crschange',$hashid);
 }
 
+sub get_suppchange {
+    my ($cdom,$cnum) = @_;
+    if ($cdom eq '' || $cnum eq '') {
+        return unless ($env{'request.course.id'});
+        $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
+        $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+    }
+    my $hashid=$cdom.'_'.$cnum;
+    my ($change,$cached)=&is_cached_new('suppchange',$hashid);
+    if ((defined($cached)) && ($change ne '')) {
+        return $change;
+    } else {
+        my %crshash = &get('environment',['internal.supplementalchange'],$cdom,$cnum);
+        if ($crshash{'internal.supplementalchange'} eq '') {
+            $change = $env{'course.'.$cdom.'_'.$cnum.'.internal.created'};
+            if ($change eq '') {
+                %crshash = &get('environment',['internal.created'],$cdom,$cnum);
+                $change = $crshash{'internal.created'};
+            }
+        } else {
+            $change = $crshash{'internal.supplementalchange'};
+        }
+        my $cachetime = 600;
+        &do_cache_new('suppchange',$hashid,$change,$cachetime);
+    }
+    return $change;
+}
+
+sub devalidate_suppchange_cache {
+    my ($cdom,$cnum)=@_;
+    my $hashid=$cdom.'_'.$cnum;
+    &devalidate_cache_new('suppchange',$hashid);
+}
+
+sub update_supp_caches {
+    my ($cdom,$cnum) = @_;
+    my %servers = &internet_dom_servers($cdom);
+    my @ids=&current_machine_ids();
+    foreach my $server (keys(%servers)) {
+        next if (grep(/^\Q$server\E$/,@ids));
+        my $hashid=$cnum.':'.$cdom;
+        my $cachekey = &escape('showsupp').':'.&escape($hashid);
+        &remote_devalidate_cache($server,[$cachekey]);
+    }
+    &has_unhidden_suppfiles($cnum,$cdom,1,1);
+    &count_supptools($cnum,$cdom,1);
+    my $now = time;
+    if ($env{'request.course.id'} eq $cdom.'_'.$cnum) {
+        &Apache::lonnet::appenv({'request.course.suppupdated' => $now});
+    }
+    &put('environment',{'internal.supplementalchange' => $now},
+         $cdom,$cnum);
+    &Apache::lonnet::appenv(
+        {'course.'.$cdom.'_'.$cnum.'.internal.supplementalchange' => $now});
+    &do_cache_new('suppchange',$cdom.'_'.$cnum,$now,600);
+}
+
 # ------------------------------------------------- Update symbolic store links
 
 sub symblist {
@@ -13493,17 +14477,10 @@ sub symbread {
     my %bighash;
     my $syval='';
     if (($env{'request.course.fn'}) && ($thisfn)) {
-        my $targetfn = $thisfn;
-        if ( ($thisfn =~ m/^(uploaded|editupload)\//) && ($thisfn !~ m/\.(page|sequence)$/) ) {
-            $targetfn = 'adm/wrapper/'.$thisfn;
-        }
-	if ($targetfn =~ m|^adm/wrapper/(ext/.*)|) {
-	    $targetfn=$1;
-	}
         unless ($ignoresymbdb) {
             if (tie(%hash,'GDBM_File',$env{'request.course.fn'}.'_symb.db',
                           &GDBM_READER(),0640)) {
-	        $syval=$hash{$targetfn};
+	        $syval=$hash{$thisfn};
                 untie(%hash);
             }
             if ($syval && $checkforblock) {
@@ -14311,10 +15288,15 @@ sub machine_ids {
 
 sub additional_machine_domains {
     my @domains;
-    open(my $fh,"<","$perlvar{'lonTabDir'}/expected_domains.tab");
-    while( my $line = <$fh>) {
-        $line =~ s/\s//g;
-        push(@domains,$line);
+    if (-e "$perlvar{'lonTabDir'}/expected_domains.tab") {
+        if (open(my $fh,"<","$perlvar{'lonTabDir'}/expected_domains.tab")) {
+            while (my $line = <$fh>) {
+                chomp($line);           
+                $line =~ s/\s//g;
+                push(@domains,$line);
+            }
+            close($fh);
+        }
     }
     return @domains;
 }
@@ -14472,7 +15454,7 @@ sub get_requestor_ip {
 
 sub get_proxy_settings {
     my ($dom_in_use) = @_;
-    my %domdefaults = &Apache::lonnet::get_domain_defaults($dom_in_use);
+    my %domdefaults = &get_domain_defaults($dom_in_use);
     my $proxyinfo = {
                        ipheader => $domdefaults{'waf_ipheader'},
                        trusted  => $domdefaults{'waf_trusted'},
@@ -14505,12 +15487,11 @@ sub get_proxy_alias {
         if ($cached) {
             return $alias;
         }
-        my $dom = &Apache::lonnet::host_domain($lonid);
+        my $dom = &host_domain($lonid);
         if ($dom ne '') {
             my $cachetime = 60*60*24;
             my %domconfig =
-                &Apache::lonnet::get_dom('configuration',['wafproxy'],$dom);
-            my $alias;
+                &get_dom('configuration',['wafproxy'],$dom);
             if (ref($domconfig{'wafproxy'}) eq 'HASH') {
                 if (ref($domconfig{'wafproxy'}{'alias'}) eq 'HASH') {
                     $alias = $domconfig{'wafproxy'}{'alias'}{$lonid};
@@ -14528,7 +15509,7 @@ sub use_proxy_alias {
     if ($alias) {
         my $dom = &host_domain($lonid);
         if ($dom ne '') {
-            my $proxyinfo = &get_proxy_settings($dom );
+            my $proxyinfo = &get_proxy_settings($dom);
             my ($vpnint,$remote_ip);
             if (ref($proxyinfo) eq 'HASH') {
                 $vpnint = $proxyinfo->{'vpnint'};
@@ -14544,6 +15525,81 @@ sub use_proxy_alias {
     return;
 }
 
+sub alias_sso {
+    my ($lonid) = @_;
+    if ($lonid eq '') {
+        $lonid = $perlvar{'lonHostID'};
+    }
+    if (!defined(&hostname($lonid))) {
+        return;
+    }
+    if ($lonid ne '') {
+        my ($use_alias,$cached) = &is_cached_new('proxysaml',$lonid);
+        if ($cached) {
+            return $use_alias;
+        }
+        my $dom = &host_domain($lonid);
+        if ($dom ne '') {
+            my $cachetime = 60*60*24;
+            my %domconfig =
+                &get_dom('configuration',['wafproxy'],$dom);
+            if (ref($domconfig{'wafproxy'}) eq 'HASH') {
+                if (ref($domconfig{'wafproxy'}{'saml'}) eq 'HASH') {
+                    $use_alias = $domconfig{'wafproxy'}{'saml'}{$lonid};
+                }
+            }
+            return &do_cache_new('proxysaml',$lonid,$use_alias,$cachetime);
+        }
+    }
+    return;
+}
+
+sub get_saml_landing {
+    my ($lonid) = @_;
+    if ($lonid eq '') {
+        my $defdom = &default_login_domain();
+        my @hosts = &current_machine_ids();
+        if (@hosts > 1) {
+            foreach my $hostid (@hosts) {
+                if (&host_domain($hostid) eq $defdom) {
+                    $lonid = $hostid;
+                    last;
+                }
+            }
+        } else {
+            $lonid = $perlvar{'lonHostID'};
+        }
+        if ($lonid) {
+            unless (&host_domain($lonid) eq $defdom) {
+                return;
+            }
+        } else {
+            return;
+        }
+    } elsif (!defined(&hostname($lonid))) {
+        return;
+    }
+    my ($landing,$cached) = &is_cached_new('samllanding',$lonid);
+    if ($cached) {
+        return $landing;
+    }
+    my $dom = &host_domain($lonid);
+    if ($dom ne '') {
+        my $cachetime = 60*60*24;
+        my %domconfig =
+            &get_dom('configuration',['login'],$dom);
+        if (ref($domconfig{'login'}) eq 'HASH') {
+            if (ref($domconfig{'login'}{'saml'}) eq 'HASH') {
+                if (ref($domconfig{'login'}{'saml'}{$lonid}) eq 'HASH') {
+                    $landing = 1;
+                }
+            }
+        }
+        return &do_cache_new('samllanding',$lonid,$landing,$cachetime);
+    }
+    return;
+}
+
 # ------------------------------------------------------------- Declutters URLs
 
 sub declutter {
@@ -14661,7 +15717,7 @@ sub get_dns {
     my ($url,$func,$ignore_cache,$nocache,$hashref) = @_;
     if (!$ignore_cache) {
 	my ($content,$cached)=
-	    &Apache::lonnet::is_cached_new('dns',$url);
+	    &is_cached_new('dns',$url);
 	if ($cached) {
 	    &$func($content,$hashref);
 	    return;
@@ -14740,7 +15796,7 @@ sub get_dns {
 sub parse_dns_checksums_tab {
     my ($lines,$hashref) = @_;
     my $lonhost = $perlvar{'lonHostID'};
-    my $machine_dom = &Apache::lonnet::host_domain($lonhost);
+    my $machine_dom = &host_domain($lonhost);
     my $loncaparev = &get_server_loncaparev($machine_dom);
     my $distro = (split(/\:/,&get_server_distarch($lonhost)))[0];
     my $webconfdir = '/etc/httpd/conf';
@@ -14784,7 +15840,7 @@ sub parse_dns_checksums_tab {
 
 sub fetch_dns_checksums {
     my %checksums;
-    my $machine_dom = &Apache::lonnet::host_domain($perlvar{'lonHostID'});
+    my $machine_dom = &host_domain($perlvar{'lonHostID'});
     my $loncaparev = &get_server_loncaparev($machine_dom,$perlvar{'lonHostID'});
     my ($release,$timestamp) = split(/\-/,$loncaparev);
     &get_dns("/adm/dns/checksums/$release",\&parse_dns_checksums_tab,1,1,
@@ -15172,7 +16228,7 @@ sub parse_getdns_url {
 		return %iphost;
 	    }
 	    my ($ip_info,$cached)=
-		&Apache::lonnet::is_cached_new('iphost','iphost');
+		&is_cached_new('iphost','iphost');
 	    if ($cached) {
 		%iphost      = %{$ip_info->[0]};
 		%name_to_ip  = %{$ip_info->[1]};
@@ -15184,7 +16240,7 @@ sub parse_getdns_url {
 	# get yesterday's info for fallback
 	my %old_name_to_ip;
 	my ($ip_info,$cached)=
-	    &Apache::lonnet::is_cached_new('iphost','iphost');
+	    &is_cached_new('iphost','iphost');
 	if ($cached) {
 	    %old_name_to_ip = %{$ip_info->[1]};
 	}
@@ -15251,7 +16307,7 @@ sub parse_getdns_url {
         my ($lonid) = @_;
         return if ($lonid eq '');
         my ($idnref,$cached)=
-            &Apache::lonnet::is_cached_new('internetnames',$lonid);
+            &is_cached_new('internetnames',$lonid);
         if ($cached) {
             return $idnref;
         }
@@ -16091,10 +17147,6 @@ data base, returning a hash that is keye
 values that are the resource value.  I believe that the timestamps and
 versions are also returned.
 
-get_numsuppfiles($cnum,$cdom) : retrieve number of files in a course's
-supplemental content area. This routine caches the number of files for 
-10 minutes.
-
 =back
 
 =head2 Course Modification