--- loncom/lonnet/perl/lonnet.pm	2011/01/11 10:51:48	1.1098
+++ loncom/lonnet/perl/lonnet.pm	2012/08/03 10:55:53	1.1182
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # TCP networking package
 #
-# $Id: lonnet.pm,v 1.1098 2011/01/11 10:51:48 foxr Exp $
+# $Id: lonnet.pm,v 1.1182 2012/08/03 10:55:53 foxr Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -75,8 +75,12 @@ use LWP::UserAgent();
 use HTTP::Date;
 use Image::Magick;
 
+
+use Encode;
+
 use vars qw(%perlvar %spareid %pr %prp $memcache %packagetab $tmpdir
-            $_64bit %env %protocol %loncaparevs %serverhomeIDs %needsrelease);
+            $_64bit %env %protocol %loncaparevs %serverhomeIDs %needsrelease
+            %managerstab);
 
 my (%badServerCache, $memcache, %courselogs, %accesshash, %domainrolehash,
     %userrolehash, $processmarker, $dumpcount, %coursedombuf,
@@ -95,6 +99,9 @@ use Math::Random;
 use File::MMagic;
 use LONCAPA qw(:DEFAULT :match);
 use LONCAPA::Configuration;
+use LONCAPA::lonmetadata;
+use LONCAPA::Lond;
+
 use File::Copy;
 
 my $readit;
@@ -196,6 +203,29 @@ sub get_server_timezone {
     }
 }
 
+sub get_server_distarch {
+    my ($lonhost,$ignore_cache) = @_;
+    if (defined($lonhost)) {
+        if (!defined(&hostname($lonhost))) {
+            return;
+        }
+        my $cachetime = 12*3600;
+        if (!$ignore_cache) {
+            my ($distarch,$cached)=&is_cached_new('serverdistarch',$lonhost);
+            if (defined($cached)) {
+                return $distarch;
+            }
+        }
+        my $rep = &reply('serverdistarch',$lonhost);
+        unless ($rep eq 'unknown_command' || $rep eq 'no_such_host' ||
+                $rep eq 'con_lost' || $rep eq 'rejected' || $rep eq 'refused' ||
+                $rep eq '') {
+            return &do_cache_new('serverdistarch',$lonhost,$rep,$cachetime);
+        }
+    }
+    return;
+}
+
 sub get_server_loncaparev {
     my ($dom,$lonhost,$ignore_cache,$caller) = @_;
     if (defined($lonhost)) {
@@ -282,6 +312,52 @@ sub get_server_homeID {
     return &do_cache_new('serverhomeID',$hostname,$serverhomeID,$cachetime);
 }
 
+sub get_remote_globals {
+    my ($lonhost,$whathash,$ignore_cache) = @_;
+    my ($result,%returnhash,%whatneeded);
+    if (ref($whathash) eq 'HASH') {
+        foreach my $what (sort(keys(%{$whathash}))) {
+            my $hashid = $lonhost.'-'.$what;
+            my ($response,$cached);
+            unless ($ignore_cache) {
+                ($response,$cached)=&is_cached_new('lonnetglobal',$hashid);
+            }
+            if (defined($cached)) {
+                $returnhash{$what} = $response;
+            } else {
+                $whatneeded{$what} = 1;
+            }
+        }
+        if (keys(%whatneeded) == 0) {
+            $result = 'ok';
+        } else {
+            my $requested = &freeze_escape(\%whatneeded);
+            my $rep=&reply('readlonnetglobal:'.$requested,$lonhost);
+            if (($rep=~/^(refused|rejected|error)/) || ($rep eq 'con_lost') ||
+                ($rep eq 'unknown_cmd')) {
+                $result = $rep;
+            } else {
+                $result = 'ok';
+                my @pairs=split(/\&/,$rep);
+                foreach my $item (@pairs) {
+                    my ($key,$value)=split(/=/,$item,2);
+                    my $what = &unescape($key);
+                    my $hashid = $lonhost.'-'.$what;
+                    $returnhash{$what}=&thaw_unescape($value);
+                    &do_cache_new('lonnetglobal',$hashid,$returnhash{$what},600);
+                }
+            }
+        }
+    }
+    return ($result,\%returnhash);
+}
+
+sub remote_devalidate_cache {
+    my ($lonhost,$name,$id) = @_;
+    my $response = &reply('devalidatecache:'.&escape($name).':'.&escape($id),$lonhost);
+    return $response;
+}
+
 # -------------------------------------------------- Non-critical communication
 sub subreply {
     my ($cmd,$server)=@_;
@@ -524,13 +600,21 @@ sub transfer_profile_to_env {
 
 # ---------------------------------------------------- Check for valid session 
 sub check_for_valid_session {
-    my ($r) = @_;
+    my ($r,$name) = @_;
     my %cookies=CGI::Cookie->parse($r->header_in('Cookie'));
-    my $lonid=$cookies{'lonID'};
+    if ($name eq '') {
+        $name = 'lonID';
+    }
+    my $lonid=$cookies{$name};
     return undef if (!$lonid);
 
     my $handle=&LONCAPA::clean_handle($lonid->value);
-    my $lonidsdir=$r->dir_config('lonIDsDir');
+    my $lonidsdir;
+    if ($name eq 'lonDAV') {
+        $lonidsdir=$r->dir_config('lonDAVsessDir');
+    } else {
+        $lonidsdir=$r->dir_config('lonIDsDir');
+    }
     return undef if (!-e "$lonidsdir/$handle.id");
 
     my $opened = open(my $idf,'+<',"$lonidsdir/$handle.id");
@@ -612,11 +696,20 @@ sub appenv {
 # ----------------------------------------------------- Delete from Environment
 
 sub delenv {
-    my ($delthis,$regexp) = @_;
-    if (($delthis=~/user\.role/) || ($delthis=~/user\.priv/)) {
-        &logthis("<font color=\"blue\">WARNING: ".
-                "Attempt to delete from environment ".$delthis);
-        return 'error';
+    my ($delthis,$regexp,$roles) = @_;
+    if (($delthis=~/^user\.role/) || ($delthis=~/^user\.priv/)) {
+        my $refused = 1;
+        if (ref($roles) eq 'ARRAY') {
+            my ($type,$role) = ($delthis =~ /^user\.(role|priv)\.([^.]+)\./);
+            if (grep(/^\Q$role\E$/,@{$roles})) {
+                $refused = 0;
+            }
+        }
+        if ($refused) {
+            &logthis("<font color=\"blue\">WARNING: ".
+                     "Attempt to delete from environment ".$delthis);
+            return 'error';
+        }
     }
     my $opened = open(my $env_file,'+<',$env{'user.environment'});
     if ($opened
@@ -740,26 +833,33 @@ sub spareserver {
         my %udomdefaults = &Apache::lonnet::get_domain_defaults($udom);
         $remotesessions = $udomdefaults{'remotesessions'};
     }
-    foreach my $try_server (@{ $spareid{'primary'} }) {
-        if ($uint_dom) {
-             next unless (&spare_can_host($udom,$uint_dom,$remotesessions,
-                                          $try_server));
+    my $spareshash = &this_host_spares($udom);
+    if (ref($spareshash) eq 'HASH') {
+        if (ref($spareshash->{'primary'}) eq 'ARRAY') {
+            foreach my $try_server (@{ $spareshash->{'primary'} }) {
+                if ($uint_dom) {
+                    next unless (&spare_can_host($udom,$uint_dom,$remotesessions,
+                                                 $try_server));
+                }
+	        ($spare_server, $lowest_load) =
+	            &compare_server_load($try_server, $spare_server, $lowest_load);
+            }
         }
-	($spare_server, $lowest_load) =
-	    &compare_server_load($try_server, $spare_server, $lowest_load);
-    }
-
-    my $found_server = ($spare_server ne '' && $lowest_load < 100);
 
-    if (!$found_server) {
-	foreach my $try_server (@{ $spareid{'default'} }) {
-            if ($uint_dom) {
-                next unless (&spare_can_host($udom,$uint_dom,$remotesessions,
-                                             $try_server));
-            }
-	    ($spare_server, $lowest_load) =
-		&compare_server_load($try_server, $spare_server, $lowest_load);
-	}
+        my $found_server = ($spare_server ne '' && $lowest_load < 100);
+
+        if (!$found_server) {
+            if (ref($spareshash->{'default'}) eq 'ARRAY') { 
+	        foreach my $try_server (@{ $spareshash->{'default'} }) {
+                    if ($uint_dom) {
+                        next unless (&spare_can_host($udom,$uint_dom,
+                                                     $remotesessions,$try_server));
+                    }
+	            ($spare_server, $lowest_load) =
+		        &compare_server_load($try_server, $spare_server, $lowest_load);
+                }
+	    }
+        }
     }
 
     if (!$want_server_name) {
@@ -784,7 +884,7 @@ sub compare_server_load {
     my $userloadans = &reply('userload',$try_server);
 
     if ($loadans !~ /\d/ && $userloadans !~ /\d/) {
-	return; #didn't get a number from the server
+	return ($spare_server, $lowest_load); #didn't get a number from the server
     }
 
     my $load;
@@ -810,9 +910,18 @@ sub compare_server_load {
 # --------------------------- ask offload servers if user already has a session
 sub find_existing_session {
     my ($udom,$uname) = @_;
-    foreach my $try_server (@{ $spareid{'primary'} },
-			    @{ $spareid{'default'} }) {
-	return $try_server if (&has_user_session($try_server, $udom, $uname));
+    my $spareshash = &this_host_spares($udom);
+    if (ref($spareshash) eq 'HASH') {
+        if (ref($spareshash->{'primary'}) eq 'ARRAY') {
+            foreach my $try_server (@{ $spareshash->{'primary'} }) {
+                return $try_server if (&has_user_session($try_server, $udom, $uname));
+            }
+        }
+        if (ref($spareshash->{'default'}) eq 'ARRAY') {
+            foreach my $try_server (@{ $spareshash->{'default'} }) {
+                return $try_server if (&has_user_session($try_server, $udom, $uname));
+            }
+        }
     }
     return;
 }
@@ -830,22 +939,40 @@ sub has_user_session {
 # --------- determine least loaded server in a user's domain which allows login
 
 sub choose_server {
-    my ($udom) = @_;
+    my ($udom,$checkloginvia) = @_;
     my %domconfhash = &Apache::loncommon::get_domainconf($udom);
     my %servers = &get_servers($udom);
     my $lowest_load = 30000;
-    my ($login_host,$hostname);
+    my ($login_host,$hostname,$portal_path,$isredirect);
     foreach my $lonhost (keys(%servers)) {
-        my $loginvia = $domconfhash{$udom.'.login.loginvia_'.$lonhost};
-        if ($loginvia eq '') {
+        my $loginvia;
+        if ($checkloginvia) {
+            $loginvia = $domconfhash{$udom.'.login.loginvia_'.$lonhost};
+            if ($loginvia) {
+                my ($server,$path) = split(/:/,$loginvia);
+                ($login_host, $lowest_load) =
+                    &compare_server_load($server, $login_host, $lowest_load);
+                if ($login_host eq $server) {
+                    $portal_path = $path;
+                    $isredirect = 1;
+                }
+            } else {
+                ($login_host, $lowest_load) =
+                    &compare_server_load($lonhost, $login_host, $lowest_load);
+                if ($login_host eq $lonhost) {
+                    $portal_path = '';
+                    $isredirect = ''; 
+                }
+            }
+        } else {
             ($login_host, $lowest_load) =
-            &compare_server_load($lonhost, $login_host, $lowest_load);
+                &compare_server_load($lonhost, $login_host, $lowest_load);
         }
     }
     if ($login_host ne '') {
-        $hostname = $servers{$login_host};
+        $hostname = &hostname($login_host);
     }
-    return ($login_host,$hostname);
+    return ($login_host,$hostname,$portal_path,$isredirect);
 }
 
 # --------------------------------------------- Try to change a user's password
@@ -986,15 +1113,19 @@ 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);
             if (ref($hostedsessions->{'excludedomain'}) eq 'ARRAY') {
-                if (grep(/^\Q$udom\E$/,@{$hostedsessions->{'excludedomain'}})) {
+                if (($uint_dom ne '') && 
+                    (grep(/^\Q$uint_dom\E$/,@{$hostedsessions->{'excludedomain'}}))) {
                     $canhost = 0;
                 } else {
                     $canhost = 1;
                 }
             }
             if (ref($hostedsessions->{'includedomain'}) eq 'ARRAY') {
-                if (grep(/^\Q$udom\E$/,@{$hostedsessions->{'includedomain'}})) {
+                if (($uint_dom ne '') && 
+                    (grep(/^\Q$uint_dom\E$/,@{$hostedsessions->{'includedomain'}}))) {
                     $canhost = 1;
                 } else {
                     $canhost = 0;
@@ -1025,6 +1156,316 @@ sub spare_can_host {
     return $canhost;
 }
 
+sub this_host_spares {
+    my ($dom) = @_;
+    my ($dom_in_use,$lonhost_in_use,$result);
+    my @hosts = &current_machine_ids();
+    foreach my $lonhost (@hosts) {
+        if (&host_domain($lonhost) eq $dom) {
+            $dom_in_use = $dom;
+            $lonhost_in_use = $lonhost;
+            last;
+        }
+    }
+    if ($dom_in_use ne '') {
+        $result = &spares_for_offload($dom_in_use,$lonhost_in_use);
+    }
+    if (ref($result) ne 'HASH') {
+        $lonhost_in_use = $perlvar{'lonHostID'};
+        $dom_in_use = &host_domain($lonhost_in_use);
+        $result = &spares_for_offload($dom_in_use,$lonhost_in_use);
+        if (ref($result) ne 'HASH') {
+            $result = \%spareid;
+        }
+    }
+    return $result;
+}
+
+sub spares_for_offload  {
+    my ($dom_in_use,$lonhost_in_use) = @_;
+    my ($result,$cached)=&is_cached_new('spares',$dom_in_use);
+    if (defined($cached)) {
+        return $result;
+    } else {
+        my $cachetime = 60*60*24;
+        my %domconfig =
+            &Apache::lonnet::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') {
+                    return &do_cache_new('spares',$dom_in_use,$domconfig{'usersessions'}{'spares'}{$lonhost_in_use},$cachetime);
+                }
+            }
+        }
+    }
+    return;
+}
+
+sub get_lonbalancer_config {
+    my ($servers) = @_;
+    my ($currbalancer,$currtargets);
+    if (ref($servers) eq 'HASH') {
+        foreach my $server (keys(%{$servers})) {
+            my %what = (
+                         spareid => 1,
+                         perlvar => 1,
+                       );
+            my ($result,$returnhash) = &get_remote_globals($server,\%what);
+            if ($result eq 'ok') {
+                if (ref($returnhash) eq 'HASH') {
+                    if (ref($returnhash->{'perlvar'}) eq 'HASH') {
+                        if ($returnhash->{'perlvar'}->{'lonBalancer'} eq 'yes') {
+                            $currbalancer = $server;
+                            $currtargets = {};
+                            if (ref($returnhash->{'spareid'}) eq 'HASH') {
+                                if (ref($returnhash->{'spareid'}->{'primary'}) eq 'ARRAY') {
+                                    $currtargets->{'primary'} = $returnhash->{'spareid'}->{'primary'};
+                                }
+                                if (ref($returnhash->{'spareid'}->{'default'}) eq 'ARRAY') {
+                                    $currtargets->{'default'} = $returnhash->{'spareid'}->{'default'};
+                                }
+                            }
+                            last;
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return ($currbalancer,$currtargets);
+}
+
+sub check_loadbalancing {
+    my ($uname,$udom) = @_;
+    my ($is_balancer,$dom_in_use,$homeintdom,$rule_in_effect,
+        $offloadto,$otherserver);
+    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 $serverhomedom = &host_domain($lonhost);
+
+    my $cachetime = 60*60*24;
+
+    if (($uintdom ne '') && ($uintdom eq $intdom)) {
+        $dom_in_use = $udom;
+        $homeintdom = 1;
+    } else {
+        $dom_in_use = $serverhomedom;
+    }
+    my ($result,$cached)=&is_cached_new('loadbalancing',$dom_in_use);
+    unless (defined($cached)) {
+        my %domconfig =
+            &Apache::lonnet::get_dom('configuration',['loadbalancing'],$dom_in_use);
+        if (ref($domconfig{'loadbalancing'}) eq 'HASH') {
+            $result = &do_cache_new('loadbalancing',$dom_in_use,$domconfig{'loadbalancing'},$cachetime);
+        }
+    }
+    if (ref($result) eq 'HASH') {
+        my $currbalancer = $result->{'lonhost'};
+        my $currtargets = $result->{'targets'};
+        my $currrules = $result->{'rules'};
+        if ($currbalancer ne '') {
+            if (grep(/^\Q$currbalancer\E$/,@hosts)) {
+                $is_balancer = 1;
+            }
+        }
+        if ($is_balancer) {
+            if (ref($currrules) eq 'HASH') {
+                if ($homeintdom) {
+                    if ($uname ne '') {
+                        if (($currrules->{'_LC_adv'} ne '') || ($currrules->{'_LC_author'} ne '')) {
+                            my ($is_adv,$is_author) = &is_advanced_user($udom,$uname);
+                            if (($currrules->{'_LC_author'} ne '') && ($is_author)) {
+                                $rule_in_effect = $currrules->{'_LC_author'};
+                            } elsif (($currrules->{'_LC_adv'} ne '') && ($is_adv)) {
+                                $rule_in_effect = $currrules->{'_LC_adv'}
+                            }
+                        }
+                        if ($rule_in_effect eq '') {
+                            my %userenv = &userenvironment($udom,$uname,'inststatus');
+                            if ($userenv{'inststatus'} ne '') {
+                                my @statuses = map { &unescape($_); } split(/:/,$userenv{'inststatus'});
+                                my ($othertitle,$usertypes,$types) =
+                                    &Apache::loncommon::sorted_inst_types($udom);
+                                if (ref($types) eq 'ARRAY') {
+                                    foreach my $type (@{$types}) {
+                                        if (grep(/^\Q$type\E$/,@statuses)) {
+                                            if (exists($currrules->{$type})) {
+                                                $rule_in_effect = $currrules->{$type};
+                                            }
+                                        }
+                                    }
+                                }
+                            } else {
+                                if (exists($currrules->{'default'})) {
+                                    $rule_in_effect = $currrules->{'default'};
+                                }
+                            }
+                        }
+                    } else {
+                        if (exists($currrules->{'default'})) {
+                            $rule_in_effect = $currrules->{'default'};
+                        }
+                    }
+                } else {
+                    if ($currrules->{'_LC_external'} ne '') {
+                        $rule_in_effect = $currrules->{'_LC_external'};
+                    }
+                }
+                $offloadto = &get_loadbalancer_targets($rule_in_effect,$currtargets,
+                                                       $uname,$udom);
+            }
+        }
+    } elsif (($homeintdom) && ($udom ne $serverhomedom)) {
+        my ($result,$cached)=&is_cached_new('loadbalancing',$serverhomedom);
+        unless (defined($cached)) {
+            my %domconfig =
+                &Apache::lonnet::get_dom('configuration',['loadbalancing'],$serverhomedom);
+            if (ref($domconfig{'loadbalancing'}) eq 'HASH') {
+                $result = &do_cache_new('loadbalancing',$dom_in_use,$domconfig{'loadbalancing'},$cachetime);
+            }
+        }
+        if (ref($result) eq 'HASH') {
+            my $currbalancer = $result->{'lonhost'};
+            my $currtargets = $result->{'targets'};
+            my $currrules = $result->{'rules'};
+
+            if ($currbalancer eq $lonhost) {
+                $is_balancer = 1;
+                if (ref($currrules) eq 'HASH') {
+                    if ($currrules->{'_LC_internetdom'} ne '') {
+                        $rule_in_effect = $currrules->{'_LC_internetdom'};
+                    }
+                }
+                $offloadto = &get_loadbalancer_targets($rule_in_effect,$currtargets,
+                                                       $uname,$udom);
+            }
+        } else {
+            if ($perlvar{'lonBalancer'} eq 'yes') {
+                $is_balancer = 1;
+                $offloadto = &this_host_spares($dom_in_use);
+            }
+        }
+    } else {
+        if ($perlvar{'lonBalancer'} eq 'yes') {
+            $is_balancer = 1;
+            $offloadto = &this_host_spares($dom_in_use);
+        }
+    }
+    if ($is_balancer) {
+        my $lowest_load = 30000;
+        if (ref($offloadto) eq 'HASH') {
+            if (ref($offloadto->{'primary'}) eq 'ARRAY') {
+                foreach my $try_server (@{$offloadto->{'primary'}}) {
+                    ($otherserver,$lowest_load) =
+                        &compare_server_load($try_server,$otherserver,$lowest_load);
+                }
+            }
+            my $found_server = ($otherserver ne '' && $lowest_load < 100);
+
+            if (!$found_server) {
+                if (ref($offloadto->{'default'}) eq 'ARRAY') {
+                    foreach my $try_server (@{$offloadto->{'default'}}) {
+                        ($otherserver,$lowest_load) =
+                            &compare_server_load($try_server,$otherserver,$lowest_load);
+                    }
+                }
+            }
+        } elsif (ref($offloadto) eq 'ARRAY') {
+            if (@{$offloadto} == 1) {
+                $otherserver = $offloadto->[0];
+            } elsif (@{$offloadto} > 1) {
+                foreach my $try_server (@{$offloadto}) {
+                    ($otherserver,$lowest_load) =
+                        &compare_server_load($try_server,$otherserver,$lowest_load);
+                }
+            }
+        }
+        if (($otherserver ne '') && (grep(/^\Q$otherserver\E$/,@hosts))) {
+            $is_balancer = 0;
+            if ($uname ne '' && $udom ne '') {
+                if (($env{'user.name'} eq $uname) && ($env{'user.domain'} eq $udom)) {
+                    
+                    &appenv({'user.loadbalexempt'     => $lonhost,  
+                             'user.loadbalcheck.time' => time});
+                }
+            }
+        }
+    }
+    return ($is_balancer,$otherserver);
+}
+
+sub get_loadbalancer_targets {
+    my ($rule_in_effect,$currtargets,$uname,$udom) = @_;
+    my $offloadto;
+    if ($rule_in_effect eq 'none') {
+        return [$perlvar{'lonHostID'}];
+    } elsif ($rule_in_effect eq '') {
+        $offloadto = $currtargets;
+    } else {
+        if ($rule_in_effect eq 'homeserver') {
+            my $homeserver = &homeserver($uname,$udom);
+            if ($homeserver ne 'no_host') {
+                $offloadto = [$homeserver];
+            }
+        } elsif ($rule_in_effect eq 'externalbalancer') {
+            my %domconfig =
+                &Apache::lonnet::get_dom('configuration',['loadbalancing'],$udom);
+            if (ref($domconfig{'loadbalancing'}) eq 'HASH') {
+                if ($domconfig{'loadbalancing'}{'lonhost'} ne '') {
+                    if (&hostname($domconfig{'loadbalancing'}{'lonhost'}) ne '') {
+                        $offloadto = [$domconfig{'loadbalancing'}{'lonhost'}];
+                    }
+                }
+            } else {
+                my %servers = &internet_dom_servers($udom);
+                my ($remotebalancer,$remotetargets) = &get_lonbalancer_config(\%servers);
+                if (&hostname($remotebalancer) ne '') {
+                    $offloadto = [$remotebalancer];
+                }
+            }
+        } elsif (&hostname($rule_in_effect) ne '') {
+            $offloadto = [$rule_in_effect];
+        }
+    }
+    return $offloadto;
+}
+
+sub internet_dom_servers {
+    my ($dom) = @_;
+    my (%uniqservers,%servers);
+    my $primaryserver = &hostname(&domain($dom,'primary'));
+    my @machinedoms = &machine_domains($primaryserver);
+    foreach my $mdom (@machinedoms) {
+        my %currservers = %servers;
+        my %server = &get_servers($mdom);
+        %servers = (%currservers,%server);
+    }
+    my %by_hostname;
+    foreach my $id (keys(%servers)) {
+        push(@{$by_hostname{$servers{$id}}},$id);
+    }
+    foreach my $hostname (sort(keys(%by_hostname))) {
+        if (@{$by_hostname{$hostname}} > 1) {
+            my $match = 0;
+            foreach my $id (@{$by_hostname{$hostname}}) {
+                if (&host_domain($id) eq $dom) {
+                    $uniqservers{$id} = $hostname;
+                    $match = 1;
+                }
+            }
+            unless ($match) {
+                $uniqservers{$by_hostname{$hostname}[0]} = $hostname;
+            }
+        } else {
+            $uniqservers{$by_hostname{$hostname}[0]} = $hostname;
+        }
+    }
+    return %uniqservers;
+}
+
 # ---------------------- Find the homebase for a user from domain's lib servers
 
 my %homecache;
@@ -1112,16 +1553,13 @@ sub idput {
 
 # ------------------------------dump from db file owned by domainconfig user
 sub dump_dom {
-    my ($namespace,$udom,$regexp,$range)=@_;
-    if (!$udom) {
-        $udom=$env{'user.domain'};
-    }
-    my %returnhash;
-    if ($udom) {
-        my $uname = &get_domainconfiguser($udom);
-        %returnhash = &dump($namespace,$udom,$uname,$regexp,$range);
-    }
-    return %returnhash;
+    my ($namespace, $udom, $regexp) = @_;
+
+    $udom ||= $env{'user.domain'};
+
+    return () unless $udom;
+
+    return &dump($namespace, $udom, &get_domainconfiguser($udom), $regexp);
 }
 
 # ------------------------------------------ get items from domain db files   
@@ -1508,6 +1946,7 @@ sub get_domain_defaults {
         $domdefaults{'auth_arg_def'} = $domconfig{'defaults'}{'auth_arg_def'};
         $domdefaults{'timezone_def'} = $domconfig{'defaults'}{'timezone_def'};
         $domdefaults{'datelocale_def'} = $domconfig{'defaults'}{'datelocale_def'};
+        $domdefaults{'portal_def'} = $domconfig{'defaults'}{'portal_def'};
     } else {
         $domdefaults{'lang_def'} = &domain($domain,'lang_def');
         $domdefaults{'auth_def'} = &domain($domain,'auth_def');
@@ -1519,7 +1958,7 @@ sub get_domain_defaults {
         } else {
             $domdefaults{'defaultquota'} = $domconfig{'quotas'};
         } 
-        my @usertools = ('aboutme','blog','portfolio');
+        my @usertools = ('aboutme','blog','webdav','portfolio');
         foreach my $item (@usertools) {
             if (ref($domconfig{'quotas'}{$item}) eq 'HASH') {
                 $domdefaults{$item} = $domconfig{'quotas'}{$item};
@@ -1734,8 +2173,7 @@ sub getsection {
     # If there is a role which has expired, return it.
     #
     $courseid = &courseid_to_courseurl($courseid);
-    my $extra = &freeze_escape({'skipcheck' => 1});
-    my %roleshash = &dump('roles',$udom,$unam,$courseid,undef,$extra);
+    my %roleshash = &dump('roles',$udom,$unam,$courseid);
     foreach my $key (keys(%roleshash)) {
         next if ($key !~/^\Q$courseid\E(?:\/)*(\w+)*\_st$/);
         my $section=$1;
@@ -1801,7 +2239,7 @@ sub is_cached_new {
     my ($name,$id,$debug) = @_;
     $id=&make_key($name,$id);
     if (exists($remembered{$id})) {
-	if ($debug) { &Apache::lonnet::logthis("Earyl return $id of $remembered{$id} "); }
+	if ($debug) { &Apache::lonnet::logthis("Early return $id of $remembered{$id} "); }
 	$accessed{$id}=[&gettimeofday()];
 	$hits++;
 	return ($remembered{$id},1);
@@ -1967,20 +2405,29 @@ sub getversion {
 
 sub currentversion {
     my $fname=shift;
-    my ($result,$cached)=&is_cached_new('resversion',$fname);
-    if (defined($cached)) { return $result; }
     my $author=$fname;
     $author=~s/\/home\/httpd\/html\/res\/([^\/]*)\/([^\/]*).*/$1\/$2/;
     my ($udom,$uname)=split(/\//,$author);
-    my $home=homeserver($uname,$udom);
+    my $home=&homeserver($uname,$udom);
     if ($home eq 'no_host') { 
         return -1; 
     }
-    my $answer=reply("currentversion:$fname",$home);
+    my $answer=&reply("currentversion:$fname",$home);
     if (($answer eq 'con_lost') || ($answer eq 'rejected')) {
 	return -1;
     }
-    return &do_cache_new('resversion',$fname,$answer,600);
+    return $answer;
+}
+
+#
+# Return special version number of resource if set by override, empty otherwise
+#
+sub usedversion {
+    my $fname=shift;
+    unless ($fname) { $fname=$env{'request.uri'}; }
+    my ($urlversion)=($fname=~/\.(\d+)\.\w+$/);
+    if ($urlversion) { return $urlversion; }
+    return '';
 }
 
 # ----------------------------- Subscribe to a resource, return URL if possible
@@ -2008,10 +2455,11 @@ sub subscribe {
 sub repcopy {
     my $filename=shift;
     $filename=~s/\/+/\//g;
-    if ($filename=~m|^/home/httpd/html/adm/|) { return 'ok'; }
-    if ($filename=~m|^/home/httpd/html/lonUsers/|) { return 'ok'; }
-    if ($filename=~m|^/home/httpd/html/userfiles/| or
-	$filename=~m -^/*(uploaded|editupload)/-) { 
+    my $londocroot = $perlvar{'lonDocRoot'};
+    if ($filename=~m{^\Q$londocroot/adm/\E}) { return 'ok'; }
+    if ($filename=~m{^\Q/home/httpd/lonUsers/\E}) { return 'ok'; }
+    if ($filename=~m{^\Q$londocroot/userfiles/\E} or
+	$filename=~m{^/*(uploaded|editupload)/}) {
 	return &repcopy_userfile($filename);
     }
     $filename=~s/[\n\r]//g;
@@ -2038,7 +2486,7 @@ sub repcopy {
         unless ($home eq $perlvar{'lonHostID'}) {
            my @parts=split(/\//,$filename);
            my $path="/$parts[1]/$parts[2]/$parts[3]/$parts[4]";
-           if ($path ne "$perlvar{'lonDocRoot'}/res") {
+           if ($path ne "$londocroot/res") {
                &logthis("Malconfiguration for replication: $filename");
 	       return 'bad_request';
            }
@@ -2137,12 +2585,14 @@ sub ssi {
     }
 
     $request->header(Cookie => $ENV{'HTTP_COOKIE'});
-    my $response=$ua->request($request);
+    my $response= $ua->request($request);
+    my $content = $response->content;
+
 
     if (wantarray) {
-	return ($response->content, $response);
+	return ($content, $response);
     } else {
-	return $response->content;
+	return $content;
     }
 }
 
@@ -2376,7 +2826,7 @@ sub resizeImage {
 #        $resizewidth - width (pixels) to which to resize uploaded image
 #        $resizeheight - height (pixels) to which to resize uploaded image
 #        $mimetype - reference to scalar to accommodate mime type determined
-#                    from File::MMagic if $parser = parse.
+#                    from File::MMagic.
 # 
 # output: url of file in userspace, or error: <message> 
 #             or /adm/notfound.html if failure to upload occurse
@@ -2516,7 +2966,7 @@ sub finishuserfileupload {
 	    return '/adm/notfound.html';
 	}
         if ($context eq 'overwrite') {
-            my $source =  $perlvar{'lonDaemons'}.'/tmp/overwrites/'.$docudom.'/'.$docuname.'/'.$fname;
+            my $source =  LONCAPA::tempdir().'/overwrites/'.$docudom.'/'.$docuname.'/'.$fname;
             my $target = $filepath.'/'.$file;
             if (-e $source) {
                 my @info = stat($source);
@@ -2545,10 +2995,17 @@ sub finishuserfileupload {
             }  
 	}
     }
+    if (($context eq 'coursedoc') || ($parser eq 'parse')) {
+        if (ref($mimetype)) {
+            if ($$mimetype eq '') {
+                my $mm = new File::MMagic;
+                my $type = $mm->checktype_filename($filepath.'/'.$file);
+                $$mimetype = $type;
+            }
+        }
+    }
     if ($parser eq 'parse') {
-        my $mm = new File::MMagic;
-        my $type = $mm->checktype_filename($filepath.'/'.$file);
-        if ($type eq 'text/html') {
+        if ((ref($mimetype)) && ($$mimetype eq 'text/html')) {
             my $parse_result = &extract_embedded_items($filepath.'/'.$file,
                                                        $allfiles,$codebase);
             unless ($parse_result eq 'ok') {
@@ -2556,9 +3013,6 @@ sub finishuserfileupload {
 	   	         ' for embedded media: '.$parse_result); 
             }
         }
-        if (ref($mimetype)) {
-            $$mimetype = $type;
-        }
     }
     if (($thumbwidth =~ /^\d+$/) && ($thumbheight =~ /^\d+$/)) {
         my $input = $filepath.'/'.$file;
@@ -2595,6 +3049,7 @@ sub finishuserfileupload {
 sub extract_embedded_items {
     my ($fullpath,$allfiles,$codebase,$content) = @_;
     my @state = ();
+    my (%lastids,%related,%shockwave,%flashvars);
     my %javafiles = (
                       codebase => '',
                       code => '',
@@ -2624,10 +3079,30 @@ sub extract_embedded_items {
 		&add_filetype($allfiles,$attr->{'href'},'href');
 	    }
             if (lc($tagname) eq 'script') {
+                my $src;
                 if ($attr->{'archive'} =~ /\.jar$/i) {
                     &add_filetype($allfiles,$attr->{'archive'},'archive');
                 } else {
-                    &add_filetype($allfiles,$attr->{'src'},'src');
+                    if ($attr->{'src'} ne '') {
+                        $src = $attr->{'src'};
+                        &add_filetype($allfiles,$src,'src');
+                    }
+                }
+                my $text = $p->get_trimmed_text();
+                if ($text =~ /\Qswfobject.registerObject(\E([^\)]+)\)/) {
+                    my @swfargs = split(/,/,$1);
+                    foreach my $item (@swfargs) {
+                        $item =~ s/["']//g;
+                        $item =~ s/^\s+//;
+                        $item =~ s/\s+$//;
+                    }
+                    if (($swfargs[0] ne'') && ($swfargs[2] ne '')) {
+                        if (ref($related{$swfargs[0]}) eq 'ARRAY') {
+                            push(@{$related{$swfargs[0]}},$swfargs[2]);
+                        } else {
+                            $related{$swfargs[0]} = [$swfargs[2]];
+                        }
+                    }
                 }
             }
             if (lc($tagname) eq 'link') {
@@ -2640,6 +3115,9 @@ sub extract_embedded_items {
 		foreach my $item (keys(%javafiles)) {
 		    $javafiles{$item} = '';
 		}
+                if ((lc($tagname) eq 'object') && (lc($state[-2]) ne 'object')) {
+                    $lastids{lc($tagname)} = $attr->{'id'};
+                }
 	    }
 	    if (lc($state[-2]) eq 'object' && lc($tagname) eq 'param') {
 		my $name = lc($attr->{'name'});
@@ -2649,12 +3127,22 @@ sub extract_embedded_items {
 			last;
 		    }
 		}
+                my $pathfrom;
 		foreach my $item (keys(%mediafiles)) {
 		    if ($name eq $item) {
-			&add_filetype($allfiles, $attr->{'value'}, 'value');
+                        $pathfrom = $attr->{'value'};
+                        $shockwave{$lastids{lc($state[-2])}} = $pathfrom;
+			&add_filetype($allfiles,$pathfrom,$name);
 			last;
 		    }
 		}
+                if ($name eq 'flashvars') {
+                    $flashvars{$lastids{lc($state[-2])}} = $attr->{'value'};
+                }
+                if ($pathfrom ne '') {
+                    &embedded_dependency($allfiles,\%related,$lastids{lc($state[-2])},
+                                         $pathfrom);
+                }
 	    }
 	    if (lc($tagname) eq 'embed' || lc($tagname) eq 'applet') {
 		foreach my $item (keys(%javafiles)) {
@@ -2669,7 +3157,16 @@ sub extract_embedded_items {
 			last;
 		    }
 		}
+                if (lc($tagname) eq 'embed') {
+                    if (($attr->{'name'} ne '') && ($attr->{'src'} ne '')) {
+                        &embedded_dependency($allfiles,\%related,$attr->{'name'},
+                                             $attr->{'src'});
+                    }
+                }
 	    }
+            if ($t->[4] =~ m{/>$}) {
+                pop(@state);  
+            }
 	} elsif ($t->[0] eq 'E') {
 	    my ($tagname) = ($t->[1]);
 	    if ($javafiles{'codebase'} ne '') {
@@ -2689,6 +3186,23 @@ sub extract_embedded_items {
 	    pop @state;
 	}
     }
+    foreach my $id (sort(keys(%flashvars))) {
+        if ($shockwave{$id} ne '') {
+            my @pairs = split(/\&/,$flashvars{$id});
+            foreach my $pair (@pairs) {
+                my ($key,$value) = split(/\=/,$pair);
+                if ($key eq 'thumb') {
+                    &add_filetype($allfiles,$value,$key);
+                } elsif ($key eq 'content') {
+                    my ($path) = ($shockwave{$id} =~ m{^(.+/)[^/]+$});
+                    my ($ext) = ($value =~ /\.([^.]+)$/);
+                    if ($ext ne '') {
+                        &add_filetype($allfiles,$path.$value,$ext);
+                    }
+                }
+            }
+        }
+    }
     return 'ok';
 }
 
@@ -2703,6 +3217,21 @@ sub add_filetype {
     }
 }
 
+sub embedded_dependency {
+    my ($allfiles,$related,$identifier,$pathfrom) = @_;
+    if ((ref($allfiles) eq 'HASH') && (ref($related) eq 'HASH')) {
+        if (($identifier ne '') &&
+            (ref($related->{$identifier}) eq 'ARRAY') &&
+            ($pathfrom ne '')) {
+            my ($path) = ($pathfrom =~ m{^(.+/)[^/]+$});
+            foreach my $dep (@{$related->{$identifier}}) {
+                &add_filetype($allfiles,$path.$dep,'object');
+            }
+        }
+    }
+    return;
+}
+
 sub removeuploadedurl {
     my ($url)=@_;	
     my (undef,undef,$udom,$uname,$fname)=split('/',$url,5);    
@@ -2829,15 +3358,10 @@ sub flushcourselogs {
             my $result = &inc('nohist_accesscount',\%temphash,$dom,$name);
             if ($result eq 'ok') {
                 delete $accesshash{$entry};
-            } elsif ($result eq 'unknown_cmd') {
-                # Target server has old code running on it.
-                my %temphash=($entry => $value);
-                if (&put('nohist_resevaldata',\%temphash,$dom,$name) eq 'ok') {
-                    delete $accesshash{$entry};
-                }
             }
         } else {
             my ($dom,$name) = ($entry=~m{___($match_domain)/($match_name)/(.*)___(\w+)$});
+            if (($dom eq 'uploaded') || ($dom eq 'adm')) { next; }
             my %temphash=($entry => $accesshash{$entry});
             if (&put('nohist_resevaldata',\%temphash,$dom,$name) eq 'ok') {
                 delete $accesshash{$entry};
@@ -2916,7 +3440,7 @@ sub courseacclog {
     my $fnsymb=shift;
     unless ($env{'request.course.id'}) { return ''; }
     my $what=$fnsymb.':'.$env{'user.name'}.':'.$env{'user.domain'};
-    if ($fnsymb=~/(problem|exam|quiz|assess|survey|form|task|page)$/) {
+    if ($fnsymb=~/$LONCAPA::assess_re/) {
         $what.=':POST';
         # FIXME: Probably ought to escape things....
 	foreach my $key (keys(%env)) {
@@ -2948,7 +3472,13 @@ sub countacc {
     my $url=&declutter(shift);
     return if (! defined($url) || $url eq '');
     unless ($env{'request.course.id'}) { return ''; }
+#
+# Mark that this url was used in this course
+#
     $accesshash{$env{'request.course.id'}.'___'.$url.'___course'}=1;
+#
+# Increase the access count for this resource in this child process
+#
     my $key=$$.$processmarker.'_'.$dumpcount.'___'.$url.'___count';
     $accesshash{$key}++;
 }
@@ -2960,31 +3490,37 @@ sub linklog {
     $accesshash{$from.'___'.$to.'___comefrom'}=1;
     $accesshash{$to.'___'.$from.'___goto'}=1;
 }
+
+sub statslog {
+    my ($symb,$part,$users,$av_attempts,$degdiff)=@_;
+    if ($users<2) { return; }
+    my %dynstore=&LONCAPA::lonmetadata::dynamic_metadata_storage({
+            'course'       => $env{'request.course.id'},
+            'sections'     => '"all"',
+            'num_students' => $users,
+            'part'         => $part,
+            'symb'         => $symb,
+            'mean_tries'   => $av_attempts,
+            'deg_of_diff'  => $degdiff});
+    foreach my $key (keys(%dynstore)) {
+        $accesshash{$key}=$dynstore{$key};
+    }
+}
   
 sub userrolelog {
     my ($trole,$username,$domain,$area,$tstart,$tend)=@_;
-    if (($trole=~/^ca/) || ($trole=~/^aa/) ||
-        ($trole=~/^in/) || ($trole=~/^cc/) ||
-        ($trole=~/^ep/) || ($trole=~/^cr/) ||
-        ($trole=~/^ta/) || ($trole=~/^co/)) {
+    if ( $trole =~ /^(ca|aa|in|cc|ep|cr|ta|co)/ ) {
        my (undef,$rudom,$runame,$rsec)=split(/\//,$area);
        $userrolehash
          {$trole.':'.$username.':'.$domain.':'.$runame.':'.$rudom.':'.$rsec}
                     =$tend.':'.$tstart;
     }
-    if (($env{'request.role'} =~ /dc\./) &&
-	(($trole=~/^au/) || ($trole=~/^in/) ||
-	 ($trole=~/^cc/) || ($trole=~/^ep/) ||
-	 ($trole=~/^cr/) || ($trole=~/^ta/) ||
-         ($trole=~/^co/))) {
+    if ($env{'request.role'} =~ /dc\./ && $trole =~ /^(au|in|cc|ep|cr|ta|co)/) {
        $userrolehash
          {$trole.':'.$username.':'.$domain.':'.$env{'user.name'}.':'.$env{'user.domain'}.':'}
                     =$tend.':'.$tstart;
     }
-    if (($trole=~/^dc/) || ($trole=~/^ad/) ||
-        ($trole=~/^li/) || ($trole=~/^li/) ||
-        ($trole=~/^au/) || ($trole=~/^dg/) ||
-        ($trole=~/^sc/)) {
+    if ($trole =~ /^(dc|ad|li|au|dg|sc)/ ) {
        my (undef,$rudom,$runame,$rsec)=split(/\//,$area);
        $domainrolehash
          {$trole.':'.$username.':'.$domain.':'.$runame.':'.$rudom.':'.$rsec}
@@ -3093,8 +3629,7 @@ sub get_my_roles {
     unless (defined($udom)) { $udom=$env{'user.domain'}; }
     my (%dumphash,%nothide);
     if ($context eq 'userroles') {
-        my $extra = &freeze_escape({'skipcheck' => 1});
-        %dumphash = &dump('roles',$udom,$uname,'.',undef,$extra);
+        %dumphash = &dump('roles',$udom,$uname);
     } else {
         %dumphash=
             &dump('nohist_userroles',$udom,$uname);
@@ -3115,6 +3650,7 @@ sub get_my_roles {
     foreach my $entry (keys(%dumphash)) {
         my ($role,$tend,$tstart);
         if ($context eq 'userroles') {
+            next if ($entry =~ /^rolesdef/);
 	    ($role,$tend,$tstart)=split(/_/,$dumphash{$entry});
         } else {
             ($tend,$tstart)=split(/\:/,$dumphash{$entry});
@@ -3154,6 +3690,10 @@ sub get_my_roles {
                     if (!grep(/^cr$/,@{$roles})) {
                         next;
                     }
+                } elsif ($role =~ /^gr\//) {
+                    if (!grep(/^gr$/,@{$roles})) {
+                        next;
+                    }
                 } else {
                     next;
                 }
@@ -3285,18 +3825,32 @@ sub courseiddump {
 
 	    if (($domfilter eq '') ||
 		(&host_domain($tryserver) eq $domfilter)) {
-                my $rep = 
-                  &reply('courseiddump:'.&host_domain($tryserver).':'.
-                         $sincefilter.':'.&escape($descfilter).':'.
-                         &escape($instcodefilter).':'.&escape($ownerfilter).
-                         ':'.&escape($coursefilter).':'.&escape($typefilter).
-                         ':'.&escape($regexp_ok).':'.$as_hash.':'.
-                         &escape($selfenrollonly).':'.&escape($catfilter).':'.
-                         $showhidden.':'.$caller.':'.&escape($cloner).':'.
-                         &escape($cc_clone).':'.$cloneonly.':'.
-                         &escape($createdbefore).':'.&escape($createdafter).':'.
-                         &escape($creationcontext).':'.$domcloner,
-                         $tryserver);
+                my $rep;
+                if (grep { $_ eq $tryserver } current_machine_ids()) {
+                    $rep = LONCAPA::Lond::dump_course_id_handler(
+                        join(":", (&host_domain($tryserver), $sincefilter, 
+                                &escape($descfilter), &escape($instcodefilter), 
+                                &escape($ownerfilter), &escape($coursefilter),
+                                &escape($typefilter), &escape($regexp_ok), 
+                                $as_hash, &escape($selfenrollonly), 
+                                &escape($catfilter), $showhidden, $caller, 
+                                &escape($cloner), &escape($cc_clone), $cloneonly, 
+                                &escape($createdbefore), &escape($createdafter), 
+                                &escape($creationcontext), $domcloner)));
+                } else {
+                    $rep = &reply('courseiddump:'.&host_domain($tryserver).':'.
+                             $sincefilter.':'.&escape($descfilter).':'.
+                             &escape($instcodefilter).':'.&escape($ownerfilter).
+                             ':'.&escape($coursefilter).':'.&escape($typefilter).
+                             ':'.&escape($regexp_ok).':'.$as_hash.':'.
+                             &escape($selfenrollonly).':'.&escape($catfilter).':'.
+                             $showhidden.':'.$caller.':'.&escape($cloner).':'.
+                             &escape($cc_clone).':'.$cloneonly.':'.
+                             &escape($createdbefore).':'.&escape($createdafter).':'.
+                             &escape($creationcontext).':'.$domcloner,
+                             $tryserver);
+                }
+                     
                 my @pairs=split(/\&/,$rep);
                 foreach my $item (@pairs) {
                     my ($key,$value)=split(/\=/,$item,2);
@@ -3424,11 +3978,34 @@ sub get_domain_roles {
 
 # ----------------------------------------------------------- Interval timing 
 
+{
+# Caches needed for speedup of navmaps
+# We don't want to cache this for very long at all (5 seconds at most)
+# 
+# The user for whom we cache
+my $cachedkey='';
+# The cached times for this user
+my %cachedtimes=();
+# When this was last done
+my $cachedtime=();
+
+sub load_all_first_access {
+    my ($uname,$udom)=@_;
+    if (($cachedkey eq $uname.':'.$udom) &&
+        (abs($cachedtime-time)<5) && (!$env{'form.markaccess'})) {
+        return;
+    }
+    $cachedtime=time;
+    $cachedkey=$uname.':'.$udom;
+    %cachedtimes=&dump('firstaccesstimes',$udom,$uname);
+}
+
 sub get_first_access {
-    my ($type,$argsymb)=@_;
+    my ($type,$argsymb,$argmap)=@_;
     my ($symb,$courseid,$udom,$uname)=&whichuser();
     if ($argsymb) { $symb=$argsymb; }
     my ($map,$id,$res)=&decode_symb($symb);
+    if ($argmap) { $map = $argmap; }
     if ($type eq 'course') {
 	$res='course';
     } elsif ($type eq 'map') {
@@ -3436,12 +4013,12 @@ sub get_first_access {
     } else {
 	$res=$symb;
     }
-    my %times=&get('firstaccesstimes',["$courseid\0$res"],$udom,$uname);
-    return $times{"$courseid\0$res"};
+    &load_all_first_access($uname,$udom);
+    return $cachedtimes{"$courseid\0$res"};
 }
 
 sub set_first_access {
-    my ($type)=@_;
+    my ($type,$interval)=@_;
     my ($symb,$courseid,$udom,$uname)=&whichuser();
     my ($map,$id,$res)=&decode_symb($symb);
     if ($type eq 'course') {
@@ -3451,13 +4028,27 @@ sub set_first_access {
     } else {
 	$res=$symb;
     }
-    my $firstaccess=&get_first_access($type,$symb);
+    $cachedkey='';
+    my $firstaccess=&get_first_access($type,$symb,$map);
     if (!$firstaccess) {
-	return &put('firstaccesstimes',{"$courseid\0$res"=>time},$udom,$uname);
+        my $start = time;
+	my $putres = &put('firstaccesstimes',{"$courseid\0$res"=>$start},
+                          $udom,$uname);
+        if ($putres eq 'ok') {
+            &put('timerinterval',{"$courseid\0$res"=>$interval},
+                 $udom,$uname); 
+            &appenv(
+                     {
+                        'course.'.$courseid.'.firstaccess.'.$res   => $start,
+                        'course.'.$courseid.'.timerinterval.'.$res => $interval,
+                     }
+                  );
+        }
+        return $putres;
     }
     return 'already_set';
 }
-
+}
 # --------------------------------------------- Set Expire Date for Spreadsheet
 
 sub expirespread {
@@ -3562,7 +4153,7 @@ sub hashref2str {
       $result.='=';
       #print("Got a ref of ".(ref($key))." skipping.");
     } else {
-	if ($key) {$result.=&escape($key).'=';} else { last; }
+	if (defined($key)) {$result.=&escape($key).'=';} else { last; }
     }
 
     if(ref($hashref->{$key}) eq 'ARRAY') {
@@ -3714,7 +4305,7 @@ sub tmpreset {
   if ($domain eq 'public' && $stuname eq 'public') {
       $stuname=$ENV{'REMOTE_ADDR'};
   }
-  my $path=$perlvar{'lonDaemons'}.'/tmp';
+  my $path=LONCAPA::tempdir();
   my %hash;
   if (tie(%hash,'GDBM_File',
 	  $path.'/tmpstore_'.$stuname.'_'.$domain.'_'.$namespace.'.db',
@@ -3753,7 +4344,7 @@ sub tmpstore {
   }
   my $now=time;
   my %hash;
-  my $path=$perlvar{'lonDaemons'}.'/tmp';
+  my $path=LONCAPA::tempdir();
   if (tie(%hash,'GDBM_File',
 	  $path.'/tmpstore_'.$stuname.'_'.$domain.'_'.$namespace.'.db',
 	  &GDBM_WRCREAT(),0640)) {
@@ -3799,7 +4390,7 @@ sub tmprestore {
   $namespace=~s/\//\_/g;
   $namespace=~s/\W//g;
   my %hash;
-  my $path=$perlvar{'lonDaemons'}.'/tmp';
+  my $path=LONCAPA::tempdir();
   if (tie(%hash,'GDBM_File',
 	  $path.'/tmpstore_'.$stuname.'_'.$domain.'_'.$namespace.'.db',
 	  &GDBM_READER(),0640)) {
@@ -3936,6 +4527,8 @@ sub restore {
 }
 
 # ---------------------------------------------------------- Course Description
+#
+#  
 
 sub coursedescription {
     my ($courseid,$args)=@_;
@@ -3965,7 +4558,8 @@ sub coursedescription {
 	return %returnhash;
     }
 
-    # get the data agin
+    # get the data again
+
     if (!$args->{'one_time'}) {
 	$envhash{'course.'.$normalid.'.last_cache'}=time;
     }
@@ -3973,6 +4567,10 @@ sub coursedescription {
     if ($chome ne 'no_host') {
        %returnhash=&dump('environment',$cdomain,$cnum);
        if (!exists($returnhash{'con_lost'})) {
+	   my $username = $env{'user.name'}; # Defult username
+	   if(defined $args->{'user'}) {
+	       $username = $args->{'user'};
+	   }
            $returnhash{'home'}= $chome;
 	   $returnhash{'domain'} = $cdomain;
 	   $returnhash{'num'} = $cnum;
@@ -3983,8 +4581,8 @@ sub coursedescription {
                $envhash{'course.'.$normalid.'.'.$name}=$value;
            }
            $returnhash{'url'}=&clutter($returnhash{'url'});
-           $returnhash{'fn'}=$perlvar{'lonDaemons'}.'/tmp/'.
-	       $env{'user.name'}.'_'.$cdomain.'_'.$cnum;
+           $returnhash{'fn'}=LONCAPA::tempdir() .
+	       $username.'_'.$cdomain.'_'.$cnum;
            $envhash{'course.'.$normalid.'.home'}=$chome;
            $envhash{'course.'.$normalid.'.domain'}=$cdomain;
            $envhash{'course.'.$normalid.'.num'}=$cnum;
@@ -4038,95 +4636,127 @@ sub update_released_required {
 
 sub privileged {
     my ($username,$domain)=@_;
-    my $rolesdump=&reply("dump:$domain:$username:roles",
-			&homeserver($username,$domain));
-    if (($rolesdump eq 'con_lost') || ($rolesdump eq '') || 
-        ($rolesdump =~ /^error:/)) {
-        return 0;
-    }
-    my $now=time;
-    if ($rolesdump ne '') {
-        foreach my $entry (split(/&/,$rolesdump)) {
-	    if ($entry!~/^rolesdef_/) {
-		my ($area,$role)=split(/=/,$entry);
-		$area=~s/\_\w\w$//;
-		my ($trole,$tend,$tstart)=split(/_/,$role);
-		if (($trole eq 'dc') || ($trole eq 'su')) {
-		    my $active=1;
-		    if ($tend) {
-			if ($tend<$now) { $active=0; }
-		    }
-		    if ($tstart) {
-			if ($tstart>$now) { $active=0; }
-		    }
-		    if ($active) { return 1; }
-		}
-	    }
+
+    my %rolesdump = &dump("roles", $domain, $username) or return 0;
+    my $now = time;
+
+    for my $role (@rolesdump{grep { ! /^rolesdef_/ } keys %rolesdump}) {
+            my ($trole, $tend, $tstart) = split(/_/, $role);
+            if (($trole eq 'dc') || ($trole eq 'su')) {
+                return 1 unless ($tend && $tend < $now) 
+                    or ($tstart && $tstart > $now);
+            }
 	}
-    }
+
     return 0;
 }
 
 # -------------------------------------------------------- Get user privileges
 
 sub rolesinit {
-    my ($domain,$username,$authhost)=@_;
-    my $now=time;
-    my %userroles = ('user.login.time' => $now);
-    my $extra = &freeze_escape({'skipcheck' => 1});
-    my $rolesdump=reply("dump:$domain:$username:roles:.::$extra",$authhost);
-    if (($rolesdump eq 'con_lost') || ($rolesdump eq '') || 
-        ($rolesdump =~ /^error:/)) {
-        return \%userroles;
+    my ($domain, $username) = @_;
+    my %userroles = ('user.login.time' => time);
+    my %rolesdump = &dump("roles", $domain, $username) or return \%userroles;
+
+    # firstaccess and timerinterval are related to timed maps/resources. 
+    # also, blocking can be triggered by an activating timer
+    # it's saved in the user's %env.
+    my %firstaccess = &dump('firstaccesstimes', $domain, $username);
+    my %timerinterval = &dump('timerinterval', $domain, $username);
+    my (%coursetimerstarts, %firstaccchk, %firstaccenv, %coursetimerintervals,
+        %timerintchk, %timerintenv);
+
+    foreach my $key (keys(%firstaccess)) {
+        my ($cid, $rest) = split(/\0/, $key);
+        $coursetimerstarts{$cid}{$rest} = $firstaccess{$key};
+    }
+
+    foreach my $key (keys(%timerinterval)) {
+        my ($cid,$rest) = split(/\0/,$key);
+        $coursetimerintervals{$cid}{$rest} = $timerinterval{$key};
     }
+
     my %allroles=();
-    my %allgroups=();   
-    my $group_privs;
+    my %allgroups=();
 
-    if ($rolesdump ne '') {
-        foreach my $entry (split(/&/,$rolesdump)) {
-	  if ($entry!~/^rolesdef_/) {
-            my ($area,$role)=split(/=/,$entry);
-	    $area=~s/\_\w\w$//;
-            my ($trole,$tend,$tstart,$group_privs);
-	    if ($role=~/^cr/) { 
-		if ($role=~m|^(cr/$match_domain/$match_username/[a-zA-Z0-9]+)_(.*)$|) {
-		    ($trole,my $trest)=($role=~m|^(cr/$match_domain/$match_username/[a-zA-Z0-9]+)_(.*)$|);
-		    ($tend,$tstart)=split('_',$trest);
-		} else {
-		    $trole=$role;
-		}
-            } elsif ($role =~ m|^gr/|) {
-                ($trole,$tend,$tstart) = split(/_/,$role);
-                ($trole,$group_privs) = split(/\//,$trole);
-                $group_privs = &unescape($group_privs);
-	    } else {
-		($trole,$tend,$tstart)=split(/_/,$role);
-	    }
-	    my %new_role = &set_arearole($trole,$area,$tstart,$tend,$domain,
-					 $username);
-	    @userroles{keys(%new_role)} = @new_role{keys(%new_role)};
-            if (($tend!=0) && ($tend<$now)) { $trole=''; }
-            if (($tstart!=0) && ($tstart>$now)) { $trole=''; }
-            if (($area ne '') && ($trole ne '')) {
-		my $spec=$trole.'.'.$area;
-		my ($tdummy,$tdomain,$trest)=split(/\//,$area);
-		if ($trole =~ /^cr\//) {
-                    &custom_roleprivs(\%allroles,$trole,$tdomain,$trest,$spec,$area);
-                } elsif ($trole eq 'gr') {
-                    &group_roleprivs(\%allgroups,$area,$group_privs,$tend,$tstart);
-		} else {
-                    &standard_roleprivs(\%allroles,$trole,$tdomain,$spec,$trest,$area);
-		}
+    for my $area (grep { ! /^rolesdef_/ } keys %rolesdump) {
+        my $role = $rolesdump{$area};
+        $area =~ s/\_\w\w$//;
+
+        my ($trole, $tend, $tstart, $group_privs);
+
+        if ($role =~ /^cr/) {
+        # Custom role, defined by a user 
+        # e.g., user.role.cr/msu/smith/mynewrole
+            if ($role =~ m|^(cr/$match_domain/$match_username/[a-zA-Z0-9]+)_(.*)$|) {
+                $trole = $1;
+                ($tend, $tstart) = split('_', $2);
+            } else {
+                $trole = $role;
             }
-          }
+        } elsif ($role =~ m|^gr/|) {
+        # Role of member in a group, defined within a course/community
+        # e.g., user.role.gr/msu/04935610a19ee4a5fmsul1/leopards
+            ($trole, $tend, $tstart) = split(/_/, $role);
+            next if $tstart eq '-1';
+            ($trole, $group_privs) = split(/\//, $trole);
+            $group_privs = &unescape($group_privs);
+        } else {
+        # Just a normal role, defined in roles.tab
+            ($trole, $tend, $tstart) = split(/_/,$role);
+        }
+
+        my %new_role = &set_arearole($trole,$area,$tstart,$tend,$domain,
+                 $username);
+        @userroles{keys(%new_role)} = @new_role{keys(%new_role)};
+
+        # role expired or not available yet?
+        $trole = '' if ($tend != 0 && $tend < $userroles{'user.login.time'}) or 
+            ($tstart != 0 && $tstart > $userroles{'user.login.time'});
+
+        next if $area eq '' or $trole eq '';
+
+        my $spec = "$trole.$area";
+        my ($tdummy, $tdomain, $trest) = split(/\//, $area);
+
+        if ($trole =~ /^cr\//) {
+        # Custom role, defined by a user
+            &custom_roleprivs(\%allroles,$trole,$tdomain,$trest,$spec,$area);
+        } elsif ($trole eq 'gr') {
+        # Role of a member in a group, defined within a course/community
+            &group_roleprivs(\%allgroups,$area,$group_privs,$tend,$tstart);
+            next;
+        } else {
+        # Normal role, defined in roles.tab
+            &standard_roleprivs(\%allroles,$trole,$tdomain,$spec,$trest,$area);
+        }
+
+        my $cid = $tdomain.'_'.$trest;
+        unless ($firstaccchk{$cid}) {
+            if (ref($coursetimerstarts{$cid}) eq 'HASH') {
+                foreach my $item (keys(%{$coursetimerstarts{$cid}})) {
+                    $firstaccenv{'course.'.$cid.'.firstaccess.'.$item} = 
+                        $coursetimerstarts{$cid}{$item}; 
+                }
+            }
+            $firstaccchk{$cid} = 1;
+        }
+        unless ($timerintchk{$cid}) {
+            if (ref($coursetimerintervals{$cid}) eq 'HASH') {
+                foreach my $item (keys(%{$coursetimerintervals{$cid}})) {
+                    $timerintenv{'course.'.$cid.'.timerinterval.'.$item} =
+                       $coursetimerintervals{$cid}{$item};
+                }
+            }
+            $timerintchk{$cid} = 1;
         }
-        my ($author,$adv) = &set_userprivs(\%userroles,\%allroles,\%allgroups);
-        $userroles{'user.adv'}    = $adv;
-	$userroles{'user.author'} = $author;
-        $env{'user.adv'}=$adv;
     }
-    return \%userroles;  
+
+    @userroles{'user.author', 'user.adv'} = &set_userprivs(\%userroles,
+        \%allroles, \%allgroups);
+    $env{'user.adv'} = $userroles{'user.adv'};
+
+    return (\%userroles,\%firstaccenv,\%timerintenv);
 }
 
 sub set_arearole {
@@ -4250,7 +4880,7 @@ sub set_userprivs {
             }
         }
         my $thesestr='';
-        foreach my $priv (keys(%thesepriv)) {
+        foreach my $priv (sort(keys(%thesepriv))) {
 	    $thesestr.=':'.$priv.'&'.$thesepriv{$priv};
 	}
         $userroles->{'user.priv.'.$role} = $thesestr;
@@ -4259,7 +4889,7 @@ sub set_userprivs {
 }
 
 sub role_status {
-    my ($rolekey,$then,$refresh,$now,$role,$where,$trolecode,$tstatus,$tstart,$tend) = @_;
+    my ($rolekey,$update,$refresh,$now,$role,$where,$trolecode,$tstatus,$tstart,$tend) = @_;
     my @pwhere = ();
     if (exists($env{$rolekey}) && $env{$rolekey} ne '') {
         (undef,undef,$$role,@pwhere)=split(/\./,$rolekey);
@@ -4268,7 +4898,7 @@ sub role_status {
             $$trolecode=$$role.'.'.$$where;
             ($$tstart,$$tend)=split(/\./,$env{$rolekey});
             $$tstatus='is';
-            if ($$tstart && $$tstart>$then) {
+            if ($$tstart && $$tstart>$update) {
                 $$tstatus='future';
                 if ($$tstart<$now) {
                     if ($$tstart && $$tstart>$refresh) {
@@ -4293,32 +4923,9 @@ sub role_status {
                                 $group_privs = &unescape($group_privs);
                                 &group_roleprivs(\%allgroups,$$where,$group_privs,$$tend,$$tstart);
                                 my %course_roles = &get_my_roles($env{'user.name'},$env{'user.domain'},'userroles',['active'],['cc','co','in','ta','ep','ad','st','cr'],[$tdomain],1);
-                                if (keys(%course_roles) > 0) {
-                                    my ($tnum) = ($trest =~ /^($match_courseid)/);
-                                    if ($tdomain ne '' && $tnum ne '') { 
-                                        foreach my $key (keys(%course_roles)) {
-                                            if ($key =~ /^\Q$tnum\E:\Q$tdomain\E:([^:]+):?([^:]*)/) {
-                                                my $crsrole = $1;
-                                                my $crssec = $2;
-                                                if ($crsrole =~ /^cr/) {
-                                                    unless (grep(/^cr$/,@rolecodes)) {
-                                                        push(@rolecodes,'cr');
-                                                    }
-                                                } else {
-                                                    unless(grep(/^\Q$crsrole\E$/,@rolecodes)) {
-                                                        push(@rolecodes,$crsrole);
-                                                    }
-                                                }
-                                                my $rolekey = $crsrole.'./'.$tdomain.'/'.$tnum;
-                                                if ($crssec ne '') {
-                                                    $rolekey .= '/'.$crssec;
-                                                }
-                                                $rolekey .= './';
-                                                $groups_roles{$rolekey} = \@rolecodes;
-                                            }
-                                        }
-                                    }
-                                }
+                                &get_groups_roles($tdomain,$trest,
+                                                  \%course_roles,\@rolecodes,
+                                                  \%groups_roles);
                             } else {
                                 push(@rolecodes,$$role);
                                 &standard_roleprivs(\%allroles,$$role,$tdomain,$spec,$trest,$$where);
@@ -4332,7 +4939,7 @@ sub role_status {
                 }
             }
             if ($$tend) {
-                if ($$tend<$then) {
+                if ($$tend<$update) {
                     $$tstatus='expired';
                 } elsif ($$tend<$now) {
                     $$tstatus='will_not';
@@ -4342,12 +4949,70 @@ sub role_status {
     }
 }
 
+sub get_groups_roles {
+    my ($cdom,$rest,$cdom_courseroles,$rolecodes,$groups_roles) = @_;
+    return unless((ref($cdom_courseroles) eq 'HASH') && 
+                  (ref($rolecodes) eq 'ARRAY') && 
+                  (ref($groups_roles) eq 'HASH')); 
+    if (keys(%{$cdom_courseroles}) > 0) {
+        my ($cnum) = ($rest =~ /^($match_courseid)/);
+        if ($cdom ne '' && $cnum ne '') {
+            foreach my $key (keys(%{$cdom_courseroles})) {
+                if ($key =~ /^\Q$cnum\E:\Q$cdom\E:([^:]+):?([^:]*)/) {
+                    my $crsrole = $1;
+                    my $crssec = $2;
+                    if ($crsrole =~ /^cr/) {
+                        unless (grep(/^cr$/,@{$rolecodes})) {
+                            push(@{$rolecodes},'cr');
+                        }
+                    } else {
+                        unless(grep(/^\Q$crsrole\E$/,@{$rolecodes})) {
+                            push(@{$rolecodes},$crsrole);
+                        }
+                    }
+                    my $rolekey = "$crsrole./$cdom/$cnum";
+                    if ($crssec ne '') {
+                        $rolekey .= "/$crssec";
+                    }
+                    $rolekey .= './';
+                    $groups_roles->{$rolekey} = $rolecodes;
+                }
+            }
+        }
+    }
+    return;
+}
+
+sub delete_env_groupprivs {
+    my ($where,$courseroles,$possroles) = @_;
+    return unless((ref($courseroles) eq 'HASH') && (ref($possroles) eq 'ARRAY'));
+    my ($dummy,$udom,$uname,$group) = split(/\//,$where);
+    unless (ref($courseroles->{$udom}) eq 'HASH') {
+        %{$courseroles->{$udom}} =
+            &get_my_roles('','','userroles',['active'],
+                          $possroles,[$udom],1);
+    }
+    if (ref($courseroles->{$udom}) eq 'HASH') {
+        foreach my $item (keys(%{$courseroles->{$udom}})) {
+            my ($cnum,$cdom,$crsrole,$crssec) = split(/:/,$item);
+            my $area = '/'.$cdom.'/'.$cnum;
+            my $privkey = "user.priv.$crsrole.$area";
+            if ($crssec ne '') {
+                $privkey .= '/'.$crssec;
+            }
+            $privkey .= ".$area/$group";
+            &Apache::lonnet::delenv($privkey,undef,[$crsrole]);
+        }
+    }
+    return;
+}
+
 sub check_adhoc_privs {
-    my ($cdom,$cnum,$then,$refresh,$now,$checkrole,$caller) = @_;
+    my ($cdom,$cnum,$update,$refresh,$now,$checkrole,$caller) = @_;
     my $cckey = 'user.role.'.$checkrole.'./'.$cdom.'/'.$cnum;
     if ($env{$cckey}) {
         my ($role,$where,$trolecode,$tstart,$tend,$tremark,$tstatus,$tpstart,$tpend);
-        &role_status($cckey,$then,$refresh,$now,\$role,\$where,\$trolecode,\$tstatus,\$tstart,\$tend);
+        &role_status($cckey,$update,$refresh,$now,\$role,\$where,\$trolecode,\$tstatus,\$tstart,\$tend);
         unless (($tstatus eq 'is') || ($tstatus eq 'will_not')) {
             &set_adhoc_privileges($cdom,$cnum,$checkrole,$caller);
         }
@@ -4425,23 +5090,50 @@ sub del {
 
 # -------------------------------------------------------------- dump interface
 
+sub unserialize {
+    my ($rep, $escapedkeys) = @_;
+
+    return {} if $rep =~ /^error/;
+
+    my %returnhash=();
+	foreach my $item (split /\&/, $rep) {
+	    my ($key, $value) = split(/=/, $item, 2);
+	    $key = unescape($key) unless $escapedkeys;
+	    next if $key =~ /^error: 2 /;
+	    $returnhash{$key} = Apache::lonnet::thaw_unescape($value);
+	}
+    #return %returnhash;
+    return \%returnhash;
+}        
+
+# see Lond::dump_with_regexp
+# if $escapedkeys hash keys won't get unescaped.
 sub dump {
-    my ($namespace,$udomain,$uname,$regexp,$range,$extra)=@_;
+    my ($namespace,$udomain,$uname,$regexp,$range,$escapedkeys)=@_;
     if (!$udomain) { $udomain=$env{'user.domain'}; }
     if (!$uname) { $uname=$env{'user.name'}; }
     my $uhome=&homeserver($uname,$udomain);
+
+    my $reply;
+    if (grep { $_ eq $uhome } current_machine_ids()) {
+        # user is hosted on this machine
+        $reply = LONCAPA::Lond::dump_with_regexp(join(":", ($udomain,
+                    $uname, $namespace, $regexp, $range)), $loncaparevs{$uhome});
+        return %{unserialize($reply, $escapedkeys)};
+    }
     if ($regexp) {
 	$regexp=&escape($regexp);
     } else {
 	$regexp='.';
     }
-    my $rep=&reply("dump:$udomain:$uname:$namespace:$regexp:$range:$extra",$uhome);
+    my $rep=&reply("dump:$udomain:$uname:$namespace:$regexp:$range",$uhome);
     my @pairs=split(/\&/,$rep);
     my %returnhash=();
     if (!($rep =~ /^error/ )) {
 	foreach my $item (@pairs) {
 	    my ($key,$value)=split(/=/,$item,2);
-	    $key = &unescape($key);
+        $key = unescape($key) unless $escapedkeys;
+        #$key = &unescape($key);
 	    next if ($key =~ /^error: 2 /);
 	    $returnhash{$key}=&thaw_unescape($value);
 	}
@@ -4454,23 +5146,9 @@ sub dump {
 
 sub dumpstore {
    my ($namespace,$udomain,$uname,$regexp,$range)=@_;
-   if (!$udomain) { $udomain=$env{'user.domain'}; }
-   if (!$uname) { $uname=$env{'user.name'}; }
-   my $uhome=&homeserver($uname,$udomain);
-   if ($regexp) {
-       $regexp=&escape($regexp);
-   } else {
-       $regexp='.';
-   }
-   my $rep=&reply("dump:$udomain:$uname:$namespace:$regexp:$range",$uhome);
-   my @pairs=split(/\&/,$rep);
-   my %returnhash=();
-   foreach my $item (@pairs) {
-       my ($key,$value)=split(/=/,$item,2);
-       next if ($key =~ /^error: 2 /);
-       $returnhash{$key}=&thaw_unescape($value);
-   }
-   return %returnhash;
+   # same as dump but keys must be escaped. They may contain colon separated
+   # lists of values that may themself contain colons (e.g. symbs).
+   return &dump($namespace, $udomain, $uname, $regexp, $range, 1);
 }
 
 # -------------------------------------------------------------- keys interface
@@ -4496,7 +5174,15 @@ sub currentdump {
    $sdom     = $env{'user.domain'}       if (! defined($sdom));
    $sname    = $env{'user.name'}         if (! defined($sname));
    my $uhome = &homeserver($sname,$sdom);
-   my $rep=reply('currentdump:'.$sdom.':'.$sname.':'.$courseid,$uhome);
+   my $rep;
+
+   if (grep { $_ eq $uhome } current_machine_ids()) {
+       $rep = LONCAPA::Lond::dump_profile_database(join(":", ($sdom, $sname, 
+                   $courseid)));
+   } else {
+       $rep = reply('currentdump:'.$sdom.':'.$sname.':'.$courseid,$uhome);
+   }
+
    return if ($rep =~ /^(error:|no_such_host)/);
    #
    my %returnhash=();
@@ -4732,7 +5418,7 @@ sub tmpget {
     return %returnhash;
 }
 
-# ------------------------------------------------------------ tmpget interface
+# ------------------------------------------------------------ tmpdel interface
 sub tmpdel {
     my ($token,$server)=@_;
     if (!defined($server)) { $server = $perlvar{'lonHostID'}; }
@@ -4998,6 +5684,7 @@ sub usertools_access {
         %tools = (
                       aboutme   => 1,
                       blog      => 1,
+                      webdav    => 1,
                       portfolio => 1,
                  );
     }
@@ -5096,7 +5783,7 @@ sub usertools_access {
             }
         }
     } else {
-        if ($context eq 'tools') {
+        if (($context eq 'tools') && ($tool ne 'webdav')) {
             $access = 1;
         } else {
             $access = 0;
@@ -5130,12 +5817,16 @@ sub is_advanced_user {
     my ($udom,$uname) = @_;
     if ($udom ne '' && $uname ne '') {
         if (($udom eq $env{'user.domain'}) && ($uname eq $env{'user.name'})) {
-            return $env{'user.adv'};  
+            if (wantarray) {
+                return ($env{'user.adv'},$env{'user.author'});
+            } else {
+                return $env{'user.adv'};
+            }
         }
     }
     my %roleshash = &get_my_roles($uname,$udom,'userroles',undef,undef,undef,1);
     my %allroles;
-    my $is_adv;
+    my ($is_adv,$is_author);
     foreach my $role (keys(%roleshash)) {
         my ($trest,$tdomain,$trole,$sec) = split(/:/,$role);
         my $area = '/'.$tdomain.'/'.$trest;
@@ -5149,6 +5840,9 @@ sub is_advanced_user {
             } elsif ($trole ne 'gr') {
                 &standard_roleprivs(\%allroles,$trole,$tdomain,$spec,$trest,$area);
             }
+            if ($trole eq 'au') {
+                $is_author = 1;
+            }
         }
     }
     foreach my $role (keys(%allroles)) {
@@ -5163,6 +5857,9 @@ sub is_advanced_user {
             }
         }
     }
+    if (wantarray) {
+        return ($is_adv,$is_author);
+    }
     return $is_adv;
 }
 
@@ -5433,6 +6130,15 @@ sub allowed {
         }
     }
 
+# User who is not author or co-author might still be able to edit
+# resource of an author in the domain (e.g., if Domain Coordinator).
+    if (($priv eq 'eco') && ($thisallowed eq '') && ($env{'request.course.id'}) &&
+        (&allowed('mdc',$env{'request.course.id'}))) {
+        if ($env{"user.priv.cm./$uri/"}=~/\Q$priv\E\&([^\:]*)/) {
+            $thisallowed.=$1;
+        }
+    }
+
 # Course: uri itself is a course
     my $courseuri=$uri;
     $courseuri=~s/\_(\d)/\/$1/;
@@ -5453,7 +6159,12 @@ sub allowed {
         if ($match) {
             if ($env{'user.priv.'.$env{'request.role'}.'./'}
                   =~/\Q$priv\E\&([^\:]*)/) {
-                $thisallowed.=$1;
+                my @blockers = &has_comm_blocking($priv,$symb,$uri);
+                if (@blockers > 0) {
+                    $thisallowed = 'B';
+                } else {
+                    $thisallowed.=$1;
+                }
             }
         } else {
             my $refuri = $env{'httpref.'.$orguri} || $env{'httpref.'.$ver_orguri};
@@ -5464,7 +6175,12 @@ sub allowed {
                     $refuri=&declutter($refuri);
                     my ($match) = &is_on_map($refuri);
                     if ($match) {
-                        $thisallowed='F';
+                        my @blockers = &has_comm_blocking($priv,$symb,$refuri);
+                        if (@blockers > 0) {
+                            $thisallowed = 'B';
+                        } else {
+                            $thisallowed='F';
+                        }
                     }
                 }
             }
@@ -5516,7 +6232,17 @@ sub allowed {
            $statecond=$cond;
            if ($env{'user.priv.'.$env{'request.role'}.'./'.$courseprivid}
                =~/\Q$priv\E\&([^\:]*)/) {
-               $thisallowed.=$1;
+               my $value = $1;
+               if ($priv eq 'bre') {
+                   my @blockers = &has_comm_blocking($priv,$symb,$uri);
+                   if (@blockers > 0) {
+                       $thisallowed = 'B';
+                   } else {
+                       $thisallowed.=$value;
+                   }
+               } else {
+                   $thisallowed.=$value;
+               }
                $checkreferer=0;
            }
        }
@@ -5544,7 +6270,17 @@ sub allowed {
               my $refstatecond=$cond;
               if ($env{'user.priv.'.$env{'request.role'}.'./'.$courseprivid}
                   =~/\Q$priv\E\&([^\:]*)/) {
-                  $thisallowed.=$1;
+                  my $value = $1;
+                  if ($priv eq 'bre') {
+                      my @blockers = &has_comm_blocking($priv,$symb,$refuri);
+                      if (@blockers > 0) {
+                          $thisallowed = 'B';
+                      } else {
+                          $thisallowed.=$value;
+                      }
+                  } else {
+                      $thisallowed.=$value;
+                  }
                   $uri=$refuri;
                   $statecond=$refstatecond;
               }
@@ -5648,7 +6384,7 @@ sub allowed {
        my $unamedom=$env{'user.name'}.':'.$env{'user.domain'};
        if ($env{'course.'.$env{'request.course.id'}.'.'.$priv.'.roles.denied'}
 	   =~/\Q$rolecode\E/) {
-	   if ($priv ne 'pch') { 
+	   if (($priv ne 'pch') && ($priv ne 'plc')) { 
 	       &logthis($env{'user.domain'}.':'.$env{'user.name'}.':'.$env{'user.home'}.':'.
 			'Denied by role: '.$priv.' for '.$uri.' as '.$rolecode.' in '.
 			$env{'request.course.id'});
@@ -5658,7 +6394,7 @@ sub allowed {
 
        if ($env{'course.'.$env{'request.course.id'}.'.'.$priv.'.users.denied'}
 	   =~/\Q$unamedom\E/) {
-	   if ($priv ne 'pch') { 
+	   if (($priv ne 'pch') && ($priv ne 'plc')) { 
 	       &logthis($env{'user.domain'}.':'.$env{'user.name'}.':'.$env{'user.home'}.
 			'Denied by user: '.$priv.' for '.$uri.' as '.$unamedom.' in '.
 			$env{'request.course.id'});
@@ -5672,7 +6408,7 @@ sub allowed {
    if ($thisallowed=~/R/) {
        my $rolecode=(split(/\./,$env{'request.role'}))[0];
        if (&metadata($uri,'roledeny')=~/\Q$rolecode\E/) {
-	   if ($priv ne 'pch') { 
+	   if (($priv ne 'pch') && ($priv ne 'plc')) { 
 	       &logthis($env{'user.domain'}.':'.$env{'user.name'}.':'.$env{'user.home'}.':'.
 			'Denied by role: '.$priv.' for '.$uri.' as '.$rolecode);
 	   }
@@ -5704,6 +6440,184 @@ sub allowed {
    return 'F';
 }
 
+sub get_comm_blocks {
+    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 %commblocks;
+    my $hashid=$cdom.'_'.$cnum;
+    my ($blocksref,$cached)=&is_cached_new('comm_block',$hashid);
+    if ((defined($cached)) && (ref($blocksref) eq 'HASH')) {
+        %commblocks = %{$blocksref};
+    } else {
+        %commblocks = &Apache::lonnet::dump('comm_block',$cdom,$cnum);
+        my $cachetime = 600;
+        &do_cache_new('comm_block',$hashid,\%commblocks,$cachetime);
+    }
+    return %commblocks;
+}
+
+sub has_comm_blocking {
+    my ($priv,$symb,$uri,$blocks) = @_;
+    return unless ($env{'request.course.id'});
+    return unless ($priv eq 'bre');
+    return if ($env{'user.priv.'.$env{'request.role'}} =~/evb\&([^\:]*)/);
+    my %commblocks;
+    if (ref($blocks) eq 'HASH') {
+        %commblocks = %{$blocks};
+    } else {
+        %commblocks = &get_comm_blocks();
+    }
+    return unless (keys(%commblocks) > 0);
+    if (!$symb) { $symb=&symbread($uri,1); }
+    my ($map,$resid,undef)=&decode_symb($symb);
+    my %tocheck = (
+                    maps      => $map,
+                    resources => $symb,
+                  );
+    my @blockers;
+    my $now = time;
+    my $navmap = Apache::lonnavmaps::navmap->new();
+    foreach my $block (keys(%commblocks)) {
+        if ($block =~ /^(\d+)____(\d+)$/) {
+            my ($start,$end) = ($1,$2);
+            if ($start <= $now && $end >= $now) {
+                if (ref($commblocks{$block}{'blocks'}) eq 'HASH') {
+                    if (ref($commblocks{$block}{'blocks'}{'docs'}) eq 'HASH') {
+                        if (ref($commblocks{$block}{'blocks'}{'docs'}{'maps'}) eq 'HASH') {
+                            if ($commblocks{$block}{'blocks'}{'docs'}{'maps'}{$map}) {
+                                unless (grep(/^\Q$block\E$/,@blockers)) {
+                                    push(@blockers,$block);
+                                }
+                            }
+                        }
+                        if (ref($commblocks{$block}{'blocks'}{'docs'}{'resources'}) eq 'HASH') {
+                            if ($commblocks{$block}{'blocks'}{'docs'}{'resources'}{$symb}) {
+                                unless (grep(/^\Q$block\E$/,@blockers)) {  
+                                    push(@blockers,$block);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        } 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 $check_interval;
+                    if (&check_docs_block($commblocks{$block}{'blocks'}{'docs'},\%tocheck)) {
+                        my @interval;
+                        my $type = 'map';
+                        if ($item eq 'course') {
+                            $type = 'course';
+                            @interval=&EXT("resource.0.interval");
+                        } else {
+                            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);
+                                        @to_test = $mapres->retrieveResources($mapres,undef,0,1);
+                                        foreach my $res (@to_test) {
+                                            my $symb = $res->symb();
+                                            next if ($symb eq $mapsymb);
+                                            if ($symb ne '') {
+                                                @interval=&EXT("resource.0.interval",$symb);
+                                                last;
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                        if ($interval[0] =~ /\d+/) {
+                            my $first_access;
+                            if ($type eq 'resource') {
+                                $first_access=&get_first_access($interval[1],$item);
+                            } elsif ($type eq 'map') {
+                                $first_access=&get_first_access($interval[1],undef,$item);
+                            } else {
+                                $first_access=&get_first_access($interval[1]);
+                            }
+                            if ($first_access) {
+                                my $timesup = $first_access+$interval[0];
+                                if ($timesup > $now) {
+                                    foreach my $res (@to_test) {
+                                        if ($res->is_problem()) {
+                                            if ($res->completable()) {
+                                                unless (grep(/^\Q$block\E$/,@blockers)) {
+                                                    push(@blockers,$block);
+                                                }
+                                                last;
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return @blockers;
+}
+
+sub check_docs_block {
+    my ($docsblock,$tocheck) =@_;
+    if ((ref($docsblock) ne 'HASH') || (ref($tocheck) ne 'HASH')) {
+        return;
+    }
+    if (ref($docsblock->{'maps'}) eq 'HASH') {
+        if ($tocheck->{'maps'}) {
+            if ($docsblock->{'maps'}{$tocheck->{'maps'}}) {
+                return 1;
+            }
+        }
+    }
+    if (ref($docsblock->{'resources'}) eq 'HASH') {
+        if ($tocheck->{'resources'}) {
+            if ($docsblock->{'resources'}{$tocheck->{'resources'}}) {
+                return 1;
+            }
+        }
+    }
+    return;
+}
+
+#
+#   Removes the versino from a URI and
+#   splits it in to its filename and path to the filename.
+#   Seems like File::Basename could have done this more clearly.
+#   Parameters:
+#      $uri   - input URI
+#   Returns:
+#     Two element list consisting of 
+#     $pathname  - the URI up to and excluding the trailing /
+#     $filename  - The part of the URI following the last /
+#  NOTE:
+#    Another realization of this is simply:
+#    use File::Basename;
+#    ...
+#    $uri = shift;
+#    $filename = basename($uri);
+#    $path     = dirname($uri);
+#    return ($filename, $path);
+#
+#     The implementation below is probably faster however.
+#
 sub split_uri_for_cond {
     my $uri=&deversion(&declutter(shift));
     my @uriparts=split(/\//,$uri);
@@ -5899,7 +6813,7 @@ sub fetch_enrollment_query {
                 $$replyref{$key} = $value;
             }
         } else {
-            my $pathname = $perlvar{'lonDaemons'}.'/tmp';
+            my $pathname = LONCAPA::tempdir();
             foreach my $line (@responses) {
                 my ($key,$value) = split(/=/,$line);
                 $$replyref{$key} = $value;
@@ -5929,7 +6843,7 @@ sub fetch_enrollment_query {
 
 sub get_query_reply {
     my $queryid=shift;
-    my $replyfile=$perlvar{'lonDaemons'}.'/tmp/'.$queryid;
+    my $replyfile=LONCAPA::tempdir().$queryid;
     my $reply='';
     for (1..100) {
 	sleep 2;
@@ -6025,9 +6939,9 @@ sub auto_get_sections {
 }
 
 sub auto_new_course {
-    my ($cnum,$cdom,$inst_course_id,$owner) = @_;
+    my ($cnum,$cdom,$inst_course_id,$owner,$coowners) = @_;
     my $homeserver = &homeserver($cnum,$cdom);
-    my $response=&unescape(&reply('autonewcourse:'.$inst_course_id.':'.$owner.':'.$cdom,$homeserver));
+    my $response=&unescape(&reply('autonewcourse:'.$inst_course_id.':'.&escape($owner).':'.$cdom.':'.&escape($coowners),$homeserver));
     return $response;
 }
 
@@ -6418,8 +7332,7 @@ sub get_users_groups {
     } else {  
         $grouplist = '';
         my $courseurl = &courseid_to_courseurl($courseid);
-        my $extra = &freeze_escape({'skipcheck' => 1});
-        my %roleshash = &dump('roles',$udom,$uname,$courseurl,undef,$extra);
+        my %roleshash = &dump('roles',$udom,$uname,$courseurl);
         my $access_end = $env{'course.'.$courseid.
                               '.default_enrollment_end_date'};
         my $now = time;
@@ -6599,6 +7512,13 @@ sub assignrole {
                     return 'refused';
                 }
             }
+        } elsif ($role eq 'au') {
+            if ($url ne '/'.$udom.'/') {
+                &logthis('Attempt by '.$env{'user.name'}.':'.$env{'user.domain'}.
+                         ' to assign author role for '.$uname.':'.$udom.
+                         ' in domain: '.$url.' refused (wrong domain).');
+                return 'refused';
+            }
         }
         $mrole=$role;
     }
@@ -7000,14 +7920,16 @@ sub modify_student_enrollment {
         $uid    = $tmp{'id'}         if (!defined($uid)    || $uid  eq '');
     }
     my $fullname = &format_name($first,$middle,$last,$gene,'lastname');
+    my $user = "$uname:$udom";
+    my %old_entry = &Apache::lonnet::get('classlist',[$user],$cdom,$cnum);
     my $reply=cput('classlist',
-		   {"$uname:$udom" => 
+		   {$user => 
 			join(':',$end,$start,$uid,$usec,$fullname,$type,$locktype) },
 		   $cdom,$cnum);
-    unless (($reply eq 'ok') || ($reply eq 'delayed')) {
+    if (($reply eq 'ok') || ($reply eq 'delayed')) {
+        &devalidate_getsection_cache($udom,$uname,$cid);
+    } else { 
 	return 'error: '.$reply;
-    } else {
-	&devalidate_getsection_cache($udom,$uname,$cid);
     }
     # Add student role to user
     my $uurl='/'.$cid;
@@ -7015,7 +7937,16 @@ sub modify_student_enrollment {
     if ($usec) {
 	$uurl.='/'.$usec;
     }
-    return &assignrole($udom,$uname,$uurl,'st',$end,$start,undef,$selfenroll,$context);
+    my $result = &assignrole($udom,$uname,$uurl,'st',$end,$start,undef,
+                             $selfenroll,$context);
+    if ($result ne 'ok') {
+        if ($old_entry{$user} ne '') {
+            $reply = &cput('classlist',\%old_entry,$cdom,$cnum);
+        } else {
+            $reply = &del('classlist',[$user],$cdom,$cnum);
+        }
+    }
+    return $result; 
 }
 
 sub format_name {
@@ -7214,13 +8145,16 @@ sub generate_coursenum {
 }
 
 sub is_course {
-    my ($cdom,$cnum) = @_;
-    my %courses = &courseiddump($cdom,'.',1,'.','.',$cnum,undef,
-				undef,'.');
-    if (exists($courses{$cdom.'_'.$cnum})) {
-        return 1;
-    }
-    return 0;
+    my ($cdom, $cnum) = scalar(@_) == 1 ? 
+         ($_[0] =~ /^($match_domain)_($match_courseid)$/)  :  @_;
+
+    return unless $cdom and $cnum;
+
+    my %courses = &courseiddump($cdom, '.', 1, '.', '.', $cnum, undef, undef,
+        '.');
+
+    return unless exists($courses{$cdom.'_'.$cnum});
+    return wantarray ? ($cdom, $cnum) : $cdom.'_'.$cnum;
 }
 
 sub store_userdata {
@@ -7244,8 +8178,8 @@ sub store_userdata {
                     $namevalue.=&escape($key).'='.&freeze_escape($$storehash{$key}).'&';
                 }
                 $namevalue=~s/\&$//;
-                $result =  &reply("store:$env{'user.domain'}:$env{'user.name'}:".
-                                  "$namespace:$datakey:$namevalue",$uhome);
+                $result =  &reply("store:$udom:$uname:$namespace:$datakey:".
+                                  $namevalue,$uhome);
             }
         } else {
             $result = 'error: data to store was not a hash reference'; 
@@ -7366,7 +8300,7 @@ sub save_selected_files {
 sub clear_selected_files {
     my ($user) = @_;
     my $filename = $user."savedfiles";
-    open (OUT, '>'.$Apache::lonnet::perlvar{'lonDaemons'}.'/tmp/'.$filename);
+    open (OUT, '>'.LONCAPA::tempdir().$filename);
     print (OUT undef);
     close (OUT);
     return ("ok");    
@@ -7376,7 +8310,7 @@ sub files_in_path {
     my ($user, $path) = @_;
     my $filename = $user."savedfiles";
     my %return_files;
-    open (IN, '<'.$Apache::lonnet::perlvar{'lonDaemons'}.'/tmp/'.$filename);
+    open (IN, '<'.LONCAPA::tempdir().$filename);
     while (my $line_in = <IN>) {
         chomp ($line_in);
         my @paths_and_file = split (m!/!, $line_in);
@@ -7398,7 +8332,7 @@ sub files_not_in_path {
     my $filename = $user."savedfiles";
     my @return_files;
     my $path_part;
-    open(IN, '<'.$Apache::lonnet::perlvar{'lonDaemons'}.'/tmp/'.$filename);
+    open(IN, '<'.LONCAPA::.$filename);
     while (my $line = <IN>) {
         #ok, I know it's clunky, but I want it to work
         my @paths_and_file = split(m|/|, $line);
@@ -7746,26 +8680,33 @@ sub dirlist {
 
     if($udom) {
         if($uname) {
+            my $uhome = &homeserver($uname,$udom);
+            if ($uhome eq 'no_host') {
+                return ([],'no_host');
+            }
             $listing = &reply('ls3:'.&escape('/'.$uri).':'.$getpropath.':'
                               .$getuserdir.':'.&escape($dirRoot)
-                              .':'.&escape($uname).':'.&escape($udom),
-                              &homeserver($uname,$udom));
+                              .':'.&escape($uname).':'.&escape($udom),$uhome);
             if ($listing eq 'unknown_cmd') {
-                $listing = &reply('ls2:'.$dirRoot.'/'.$uri,
-                                  &homeserver($uname,$udom));
+                $listing = &reply('ls2:'.$dirRoot.'/'.$uri,$uhome);
             } else {
                 @listing_results = map { &unescape($_); } split(/:/,$listing);
             }
             if ($listing eq 'unknown_cmd') {
-                $listing = &reply('ls:'.$dirRoot.'/'.$uri,
-				  &homeserver($uname,$udom));
+                $listing = &reply('ls:'.$dirRoot.'/'.$uri,$uhome);
                 @listing_results = split(/:/,$listing);
             } else {
                 @listing_results = map { &unescape($_); } split(/:/,$listing);
             }
-            return @listing_results;
+            if (($listing eq 'no_such_host') || ($listing eq 'con_lost') || 
+                ($listing eq 'rejected') || ($listing eq 'refused') ||
+                ($listing eq 'no_such_dir') || ($listing eq 'empty')) {
+                return ([],$listing);
+            } else {
+                return (\@listing_results);
+            }
         } elsif(!$alternateRoot) {
-            my %allusers;
+            my (%allusers,%listerror);
 	    my %servers = &get_servers($udom,'library');
  	    foreach my $tryserver (keys(%servers)) {
                 $listing = &reply('ls3:'.&escape("/res/$udom").':::::'.
@@ -7784,32 +8725,31 @@ sub dirlist {
 		    @listing_results =
 			map { &unescape($_); } split(/:/,$listing);
 		}
-		if ($listing_results[0] ne 'no_such_dir' && 
-		    $listing_results[0] ne 'empty'       &&
-		    $listing_results[0] ne 'con_lost') {
+                if (($listing eq 'no_such_host') || ($listing eq 'con_lost') ||
+                    ($listing eq 'rejected') || ($listing eq 'refused') ||
+                    ($listing eq 'no_such_dir') || ($listing eq 'empty')) {
+                    $listerror{$tryserver} = $listing;
+                } else {
 		    foreach my $line (@listing_results) {
 			my ($entry) = split(/&/,$line,2);
 			$allusers{$entry} = 1;
 		    }
 		}
             }
-            my $alluserstr='';
+            my @alluserslist=();
             foreach my $user (sort(keys(%allusers))) {
-                $alluserstr.=$user.'&user:';
+                push(@alluserslist,$user.'&user');
             }
-            $alluserstr=~s/:$//;
-            return split(/:/,$alluserstr);
+            return (\@alluserslist);
         } else {
-            return ('missing user name');
+            return ([],'missing username');
         }
     } elsif(!defined($getpropath)) {
-        my @all_domains = sort(&all_domains());
-        foreach my $domain (@all_domains) {
-            $domain = $perlvar{'lonDocRoot'}.'/res/'.$domain.'/&domain';
-        }
-        return @all_domains;
+        my $path = $perlvar{'lonDocRoot'}.'/res/'; 
+        my @all_domains = map { $path.$_.'/&domain'; } (sort(&all_domains()));
+        return (\@all_domains);
     } else {
-        return ('missing domain');
+        return ([],'missing domain');
     }
 }
 
@@ -7822,11 +8762,13 @@ sub GetFileTimestamp {
     my ($studentDomain,$studentName,$filename,$getuserdir)=@_;
     $studentDomain = &LONCAPA::clean_domain($studentDomain);
     $studentName   = &LONCAPA::clean_username($studentName);
-    my ($fileStat) = 
-        &Apache::lonnet::dirlist($filename,$studentDomain,$studentName, 
-                                 undef,$getuserdir);
-    my @stats = split('&', $fileStat);
-    if($stats[0] ne 'empty' && $stats[0] ne 'no_such_dir') {
+    my ($fileref,$error) = &dirlist($filename,$studentDomain,$studentName,
+                                    undef,$getuserdir);
+    if (($error eq 'empty') || ($error eq 'no_such_dir')) {
+        return -1;
+    }
+    if (ref($fileref) eq 'ARRAY') {
+        my @stats = split('&',$fileref->[0]);
         # @stats contains first the filename, then the stat output
         return $stats[10]; # so this is 10 instead of 9.
     } else {
@@ -7858,12 +8800,15 @@ sub stat_file {
     if ($file =~ /^userfiles\//) {
         $getpropath = 1;
     }
-    my ($result) = &dirlist($file,$udom,$uname,$getpropath);
-    my @stats = split('&', $result);
-    
-    if($stats[0] ne 'empty' && $stats[0] ne 'no_such_dir') {
-	shift(@stats); #filename is first
-	return @stats;
+    my ($listref,$error) = &dirlist($file,$udom,$uname,$getpropath);
+    if (($error eq 'empty') || ($error eq 'no_such_dir')) {
+        return ();
+    } else {
+        if (ref($listref) eq 'ARRAY') {
+            my @stats = split('&',$listref->[0]);
+	    shift(@stats); #filename is first
+	    return @stats;
+        }
     }
     return ();
 }
@@ -8184,15 +9129,7 @@ sub EXT {
    } elsif ($realm eq 'request') {
 # ------------------------------------------------------------- request.browser
         if ($space eq 'browser') {
-	    if ($qualifier eq 'textremote') {
-		if (&Apache::lonlocal::mt('textual_remote_display') eq 'on') {
-		    return 1;
-		} else {
-		    return 0;
-		}
-	    } else {
-		return $env{'browser.'.$qualifier};
-	    }
+            return $env{'browser.'.$qualifier};
 # ------------------------------------------------------------ request.filename
         } else {
             return $env{'request.'.$spacequalifierrest};
@@ -8473,10 +9410,10 @@ sub metadata {
     if (($uri eq '') || 
 	(($uri =~ m|^/*adm/|) && 
 	     ($uri !~ m|^adm/includes|) && ($uri !~ m|/bulletinboard$|)) ||
-        ($uri =~ m|/$|) || ($uri =~ m|/.meta$|) || ($uri =~ /^\*uploaded\/.+\.sequence$/) || ($uri =~ m{^/*uploaded/$match_domain/$match_courseid/docs/})) {
+        ($uri =~ m|/$|) || ($uri =~ m|/.meta$|) || ($uri =~ m{^/*uploaded/.+\.sequence$})) {
 	return undef;
     }
-    if (($uri =~ /^~/ || $uri =~ m{home/$match_username/public_html/}) 
+    if (($uri =~ /^priv/ || $uri=~/home\/httpd\/html\/priv/) 
 	&& &Apache::lonxml::get_state('target') =~ /^(|meta)$/) {
 	return undef;
     }
@@ -8513,13 +9450,14 @@ sub metadata {
         my %metathesekeys=();
         unless ($filename=~/\.meta$/) { $filename.='.meta'; }
 	my $metastring;
-	if ($uri =~ /^~/ || $uri =~ m{home/$match_username/public_html/}) {
+	if ($uri =~ /^priv/ || $uri=~/home\/httpd\/html\/priv/) {
 	    my $which = &hreflocation('','/'.($liburi || $uri));
 	    $metastring = 
 		&Apache::lonnet::ssi_body($which,
 					  ('grade_target' => 'meta'));
 	    $cachetime = 1; # only want this cached in the child not long term
-	} elsif ($uri !~ m -^(editupload)/-) {
+	} elsif (($uri !~ m -^(editupload)/-) && 
+                 ($uri !~ m{^/*uploaded/$match_domain/$match_courseid/docs/})) {
 	    my $file=&filelocation('',&clutter($filename));
 	    #push(@{$metaentry{$uri.'.file'}},$file);
 	    $metastring=&getfile($file);
@@ -8836,6 +9774,10 @@ sub gettitle {
 	}
 	$title=~s/\&colon\;/\:/gs;
 	if ($title) {
+# Remember both $symb and $title for dynamic metadata
+            $accesshash{$symb.'___crstitle'}=$title;
+            $accesshash{&declutter($map).'___'.&declutter($url).'___usage'}=time;
+# Cache this title and then return it
 	    return &do_cache_new('title',$key,$title,600);
 	}
 	$urlsymb=$url;
@@ -8868,6 +9810,84 @@ sub get_slot {
     }
     return $slotinfo{$which};
 }
+
+sub get_reservable_slots {
+    my ($cnum,$cdom,$uname,$udom) = @_;
+    my $now = time;
+    my $reservable_info;
+    my $key=join("\0",'reservableslots',$cdom,$cnum,$uname,$udom);
+    if (exists($remembered{$key})) {
+        $reservable_info = $remembered{$key};
+    } else {
+        my %resv;
+        ($resv{'now_order'},$resv{'now'},$resv{'future_order'},$resv{'future'}) =
+        &Apache::loncommon::get_future_slots($cnum,$cdom,$now);
+        $reservable_info = \%resv;
+        $remembered{$key} = $reservable_info;
+    }
+    return $reservable_info;
+}
+
+sub get_course_slots {
+    my ($cnum,$cdom) = @_;
+    my $hashid=$cnum.':'.$cdom;
+    my ($result,$cached) = &Apache::lonnet::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 ($tmp) = keys(%slots);
+        if ($tmp !~ /^(con_lost|error|no_such_host)/i) {
+            &Apache::lonnet::do_cache_new('allslots',$hashid,\%slots,600);
+            return %slots;
+        }
+    }
+    return;
+}
+
+sub devalidate_slots_cache {
+    my ($cnum,$cdom)=@_;
+    my $hashid=$cnum.':'.$cdom;
+    &devalidate_cache_new('allslots',$hashid);
+}
+
+sub get_coursechange {
+    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('crschange',$hashid);
+    if ((defined($cached)) && ($change ne '')) {
+        return $change;
+    } else {
+        my %crshash;
+        %crshash = &get('environment',['internal.contentchange'],$cdom,$cnum);
+        if ($crshash{'internal.contentchange'} 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.contentchange'};
+        }
+        my $cachetime = 600;
+        &do_cache_new('crschange',$hashid,$change,$cachetime);
+    }
+    return $change;
+}
+
+sub devalidate_coursechange_cache {
+    my ($cnum,$cdom)=@_;
+    my $hashid=$cnum.':'.$cdom;
+    &devalidate_cache_new('crschange',$hashid);
+}
+
 # ------------------------------------------------- Update symbolic store links
 
 sub symblist {
@@ -8918,8 +9938,9 @@ sub symbverify {
             $thisurl =~ s/\?.+$//;
         }
         my $ids=$bighash{'ids_'.&clutter($thisurl)};
-        unless ($ids) { 
-           $ids=$bighash{'ids_/'.$thisurl};
+        unless ($ids) {
+            my $idkey = 'ids_'.($thisurl =~ m{^/}? '' : '/').$thisurl;  
+            $ids=$bighash{$idkey};
         }
         if ($ids) {
 # ------------------------------------------------------------------- Has ID(s)
@@ -8932,7 +9953,8 @@ sub symbverify {
   &symbclean(&declutter($bighash{'map_id_'.$mapid}).'___'.$resid.'___'.$thisfn)
    eq $symb) { 
 		   if (($env{'request.role.adv'}) ||
-		       $bighash{'encrypted_'.$id} eq $env{'request.enc'}) {
+		       ($bighash{'encrypted_'.$id} eq $env{'request.enc'}) ||
+                       ($thisurl eq '/adm/navmaps')) {
 		       $okay=1; 
 		   }
 	       }
@@ -9010,7 +10032,11 @@ sub deversion {
 sub symbread {
     my ($thisfn,$donotrecurse)=@_;
     my $cache_str='request.symbread.cached.'.$thisfn;
-    if (defined($env{$cache_str})) { return $env{$cache_str}; }
+    if (defined($env{$cache_str})) {
+        if (($thisfn) || ($env{$cache_str} ne '')) {
+            return $env{$cache_str};
+        }
+    }
 # no filename provided? try from environment
     unless ($thisfn) {
         if ($env{'request.symb'}) {
@@ -9207,19 +10233,44 @@ sub getCODE {
     }
     return undef;
 }
-
+#
+#  Determines the random seed for a specific context:
+#
+# parameters:
+#   symb      - in course context the symb for the seed.
+#   course_id - The course id of the form domain_coursenum.
+#   domain    - Domain for the user.
+#   course    - Course for the user.
+#   cenv      - environment of the course.
+#
+# NOTE:
+#   All parameters are picked out of the environment if missing
+#   or not defined.
+#   If a symb cannot be determined the current time is used instead.
+#
+#  For a given well defined symb, courside, domain, username,
+#  and course environment, the seed is reproducible.
+#
 sub rndseed {
-    my ($symb,$courseid,$domain,$username)=@_;
+    my ($symb,$courseid,$domain,$username, $cenv)=@_;
     my ($wsymb,$wcourseid,$wdomain,$wusername)=&whichuser();
     if (!defined($symb)) {
 	unless ($symb=$wsymb) { return time; }
     }
-    if (!$courseid) { $courseid=$wcourseid; }
-    if (!$domain) { $domain=$wdomain; }
-    if (!$username) { $username=$wusername }
-    my $which=&get_rand_alg();
+    if (!defined $courseid) { 
+	$courseid=$wcourseid; 
+    }
+    if (!defined $domain) { $domain=$wdomain; }
+    if (!defined $username) { $username=$wusername }
 
+    my $which;
+    if (defined($cenv->{'rndseed'})) {
+	$which = $cenv->{'rndseed'};
+    } else {
+	$which =&get_rand_alg($courseid);
+    }
     if (defined(&getCODE())) {
+
 	if ($which eq '64bit5') {
 	    return &rndseed_CODE_64bit5($symb,$courseid,$domain,$username);
 	} elsif ($which eq '64bit4') {
@@ -9543,8 +10594,9 @@ sub getfile {
 
 sub repcopy_userfile {
     my ($file)=@_;
-    if ($file =~ m -^/*(uploaded|editupload)/-) { $file=&filelocation("",$file); }
-    if ($file =~ m|^/home/httpd/html/lonUsers/|) { return 'ok'; }
+    my $londocroot = $perlvar{'lonDocRoot'};
+    if ($file =~ m{^/*(uploaded|editupload)/}) { $file=&filelocation("",$file); }
+    if ($file =~ m{^\Q/home/httpd/lonUsers/\E}) { return 'ok'; }
     my ($cdom,$cnum,$filename) = 
 	($file=~m|^\Q$perlvar{'lonDocRoot'}\E/+userfiles/+($match_domain)/+($match_name)/+(.*)|);
     my $uri="/uploaded/$cdom/$cnum/$filename";
@@ -9673,13 +10725,7 @@ sub filelocation {
 	$file=~s-^/adm/coursedocs/showdoc/-/-;
     }
 
-    if ($file=~m:^/~:) { # is a contruction space reference
-        $location = $file;
-        $location =~ s:/~(.*?)/(.*):/home/$1/public_html/$2:;
-    } elsif ($file=~m{^/home/$match_username/public_html/}) {
-	# is a correct contruction space reference
-        $location = $file;
-    } elsif ($file =~ m-^\Q$Apache::lonnet::perlvar{'lonTabDir'}\E/-) {
+    if ($file =~ m-^\Q$Apache::lonnet::perlvar{'lonTabDir'}\E/-) {
         $location = $file;
     } elsif ($file=~/^\/*(uploaded|editupload)/) { # is an uploaded file
         my ($udom,$uname,$filename)=
@@ -9689,7 +10735,7 @@ sub filelocation {
         my @ids=&current_machine_ids();
         foreach my $id (@ids) { if ($id eq $home) { $is_me=1; } }
         if ($is_me) {
-  	    $location=&propath($udom,$uname).'/userfiles/'.$filename;
+  	    $location=propath($udom,$uname).'/userfiles/'.$filename;
         } else {
   	  $location=$Apache::lonnet::perlvar{'lonDocRoot'}.'/userfiles/'.
   	      $udom.'/'.$uname.'/'.$filename;
@@ -9698,11 +10744,12 @@ sub filelocation {
 	$location = $perlvar{'lonDocRoot'}.'/'.$file;
     } else {
         $file=~s/^\Q$perlvar{'lonDocRoot'}\E//;
-        $file=~s:^/res/:/:;
+        $file=~s:^/(res|priv)/:/:;
+        my $space=$1;
         if ( !( $file =~ m:^/:) ) {
             $location = $dir. '/'.$file;
         } else {
-            $location = '/home/httpd/html/res'.$file;
+            $location = $perlvar{'lonDocRoot'}.'/'.$space.$file;
         }
     }
     $location=~s://+:/:g; # remove duplicate /
@@ -9727,11 +10774,9 @@ sub hreflocation {
     }
     if ($file=~m-^\Q$perlvar{'lonDocRoot'}\E-) {
 	$file=~s-^\Q$perlvar{'lonDocRoot'}\E--;
-    } elsif ($file=~m-/home/($match_username)/public_html/-) {
-	$file=~s-^/home/($match_username)/public_html/-/~$1/-;
     } elsif ($file=~m-^\Q$perlvar{'lonUsersDir'}\E-) {
-	$file=~s-^/home/httpd/lonUsers/($match_domain)/./././($match_name)/userfiles/
-	    -/uploaded/$1/$2/-x;
+	$file=~s{^/home/httpd/lonUsers/($match_domain)/./././($match_name)/userfiles/}
+	        {/uploaded/$1/$2/}x;
     }
     if ($file=~ m{^/userfiles/}) {
 	$file =~ s{^/userfiles/}{/uploaded/};
@@ -9739,6 +10784,10 @@ sub hreflocation {
     return $file;
 }
 
+
+
+
+
 sub current_machine_domains {
     return &machine_domains(&hostname($perlvar{'lonHostID'}));
 }
@@ -9804,6 +10853,7 @@ sub declutter {
     $thisfn=~s|^adm/wrapper/||;
     $thisfn=~s|^adm/coursedocs/showdoc/||;
     $thisfn=~s/^res\///;
+    $thisfn=~s/^priv\///;
     unless (($thisfn =~ /^ext/) || ($thisfn =~ /\.(page|sequence)___\d+___ext/)) {
         $thisfn=~s/\?.+$//;
     }
@@ -9927,6 +10977,7 @@ sub get_dns {
     while (%alldns) {
 	my ($dns) = keys(%alldns);
 	my $ua=new LWP::UserAgent;
+        $ua->timeout(30);
 	my $request=new HTTP::Request('GET',"$alldns{$dns}://$dns$url");
 	my $response=$ua->request($request);
         delete($alldns{$dns});
@@ -10011,13 +11062,19 @@ sub get_dns {
     my $loaded;
     my %name_to_host;
     my %internetdom;
+    my %LC_dns_serv;
 
     sub parse_hosts_tab {
 	my ($file) = @_;
 	foreach my $configline (@$file) {
 	    next if ($configline =~ /^(\#|\s*$ )/x);
-	    next if ($configline =~ /^\^/);
-	    chomp($configline);
+            chomp($configline);
+	    if ($configline =~ /^\^/) {
+                if ($configline =~ /^\^([\w.\-]+)/) {
+                    $LC_dns_serv{$1} = 1;
+                }
+                next;
+            }
 	    my ($id,$domain,$role,$name,$protocol,$intdom)=split(/:/,$configline);
 	    $name=~s/\s//g;
 	    if ($id && $domain && $role && $name) {
@@ -10153,6 +11210,14 @@ sub get_dns {
         my ($lonid) = @_;
         return $internetdom{$lonid};
     }
+
+    sub is_LC_dns {
+        &load_hosts_tab() if (!$loaded);
+
+        my ($hostname) = @_;
+        return exists($LC_dns_serv{$hostname});
+    }
+
 }
 
 { 
@@ -10428,9 +11493,25 @@ BEGIN {
     }
 }
 
+# ---------------------------------------------------------- Read managers table
+{
+    if (-e "$perlvar{'lonTabDir'}/managers.tab") {
+        if (open(my $config,"<$perlvar{'lonTabDir'}/managers.tab")) {
+            while (my $configline=<$config>) {
+                chomp($configline);
+                next if ($configline =~ /^\#/);
+                if (($configline =~ /^[\w\-]+$/) || ($configline =~ /^[\w\-]+\:[\w\-]+$/)) {
+                    $managerstab{$configline} = 1;
+                }
+            }
+            close($config);
+        }
+    }
+}
+
 # ------------- set up temporary directory
 {
-    $tmpdir = $perlvar{'lonDaemons'}.'/tmp/';
+    $tmpdir = LONCAPA::tempdir();
 
 }
 
@@ -10691,7 +11772,13 @@ B<idput($udom,%ids)>: store away a list
 
 =item *
 X<rolesinit()>
-B<rolesinit($udom,$username,$authhost)>: get user privileges
+B<rolesinit($udom,$username)>: get user privileges.
+returns user role, first access and timer interval hashes
+
+=item *
+X<privileged()>
+B<privileged($username,$domain)>: returns a true if user has a
+privileged and active role (i.e. su or dc), false otherwise.
 
 =item *
 X<getsection()>
@@ -10924,11 +12011,32 @@ revokecustomrole($udom,$uname,$url,$role
 
 =item *
 
-coursedescription($courseid) : returns a hash of information about the
+coursedescription($courseid,$options) : returns a hash of information about the
 specified course id, including all environment settings for the
 course, the description of the course will be in the hash under the
 key 'description'
 
+$options is an optional parameter that if supplied is a hash reference that controls
+what how this function works.  It has the following key/values:
+
+=over 4
+
+=item freshen_cache
+
+If defined, and the environment cache for the course is valid, it is 
+returned in the returned hash.
+
+=item one_time
+
+If defined, the last cache time is set to _now_
+
+=item user
+
+If defined, the supplied username is used instead of the current user.
+
+
+=back
+
 =item *
 
 resdata($name,$domain,$type,@which) : request for current parameter
@@ -10963,6 +12071,19 @@ createcourse($udom,$description,$url,$co
 
 generate_coursenum($udom,$crstype) : get a unique (unused) course number in domain $udom for course type $crstype (Course or Community).
 
+=item *
+
+is_course($courseid), is_course($cdom, $cnum)
+
+Accepts either a combined $courseid (in the form of domain_courseid) or the
+two component version $cdom, $cnum. It checks if the specified course exists.
+
+Returns:
+    undef if the course doesn't exist, otherwise
+    in scalar context the combined courseid.
+    in list context the two components of the course identifier, domain and 
+    courseid.    
+
 =back
 
 =head2 Resource Subroutines
@@ -11259,7 +12380,82 @@ or lonTabs/domain.tab.
 
 =item *
 
-dirlist($uri) : return directory list based on URI
+dirlist() : return directory list based on URI (first arg).
+
+Inputs: 1 required, 5 optional.
+
+=over
+
+=item 
+$uri - path to file in filesystem (starts: /res or /userfiles/). Required.
+
+=item
+$userdomain - domain of user/course to be listed. Extracted from $uri if absent. 
+
+=item
+$username -  username of user/course to be listed. Extracted from $uri if absent. 
+
+=item
+$getpropath - boolean: 1 if prepend path using &propath(). 
+
+=item
+$getuserdir - boolean: 1 if prepend path for "userfiles".
+
+=item 
+$alternateRoot - path to prepend in place of path from $uri.
+
+=back
+
+Returns: Array of up to two items.
+
+=over
+
+a reference to an array of files/subdirectories
+
+=over
+
+Each element in the array of files/subdirectories is a & separated list of
+item name and the result of running stat on the item.  If dirlist was requested
+for a file instead of a directory, the item name will be ''. For a directory 
+listing, if the item is a metadata file, the element will end &N&M 
+(where N amd M are either 0 or 1, corresponding to obsolete set (1), or
+default copyright set (1).  
+
+=back
+
+a scalar containing error condition (if encountered).
+
+=over
+
+=item 
+no_host (no homeserver identified for $username:$domain).
+
+=item 
+no_such_host (server contacted for listing not identified as valid host).
+
+=item 
+con_lost (connection to remote server failed).
+
+=item 
+refused (invalid $username:$domain received on lond side).
+
+=item 
+no_such_dir (directory at specified path on lond side does not exist). 
+
+=item 
+empty (directory at specified path on lond side is empty).
+
+=over
+
+This is currently not encountered because the &ls3, &ls2, 
+&ls (_handler) routines on the lond side do not filter out
+. and .. from a directory listing. 
+
+=back
+
+=back
+
+=back
 
 =item *
 
@@ -11321,11 +12517,12 @@ splitting on '&', supports elements that
 
 =head2 Logging Routines
 
-=over 4
 
 These routines allow one to make log messages in the lonnet.log and
 lonnet.perm logfiles.
 
+=over 4
+
 =item *
 
 logtouch() : make sure the logfile, lonnet.log, exists
@@ -11341,6 +12538,7 @@ logperm() : append a permanent message t
 file never gets deleted by any automated portion of the system, only
 messages of critical importance should go in here.
 
+
 =back
 
 =head2 General File Helper Routines