--- loncom/lonnet/perl/lonnet.pm	2006/06/22 14:48:40	1.756
+++ loncom/lonnet/perl/lonnet.pm	2006/09/15 07:55:05	1.780
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # TCP networking package
 #
-# $Id: lonnet.pm,v 1.756 2006/06/22 14:48:40 albertel Exp $
+# $Id: lonnet.pm,v 1.780 2006/09/15 07:55:05 albertel Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -293,8 +293,10 @@ sub error {
 }
 
 # ------------------------------------------- Transfer profile into environment
-
+my $env_loaded;
 sub transfer_profile_to_env {
+    if ($env_loaded) { return; } 
+
     my ($lonidsdir,$handle)=@_;
     if (!defined($lonidsdir)) {
 	$lonidsdir = $perlvar{'lonIDsDir'};
@@ -325,6 +327,7 @@ sub transfer_profile_to_env {
         }
     }
     $env{'user.environment'} = "$lonidsdir/$handle.id";
+    $env_loaded=1;
     foreach my $expired_key (keys(%Remove)) {
         &delenv($expired_key);
     }
@@ -344,6 +347,11 @@ sub appenv {
             $env{$key}=$newenv{$key};
         }
     }
+    foreach my $key (keys(%newenv)) {
+	my $value = &escape($newenv{$key});
+	delete($newenv{$key});
+	$newenv{&escape($key)}=$value;
+    }
 
     my $lockfh;
     unless (open($lockfh,"$env{'user.environment'}")) {
@@ -369,8 +377,6 @@ sub appenv {
         chomp($oldenv[$i]);
         if ($oldenv[$i] ne '') {
 	    my ($name,$value)=split(/=/,$oldenv[$i],2);
-	    $name=&unescape($name);
-	    $value=&unescape($value);
 	    unless (defined($newenv{$name})) {
 		$newenv{$name}=$value;
 	    }
@@ -383,7 +389,7 @@ sub appenv {
 	}
 	my $newname;
 	foreach $newname (keys %newenv) {
-	    print $fh &escape($newname).'='.&escape($newenv{$newname})."\n";
+	    print $fh $newname.'='.$newenv{$newname}."\n";
 	}
 	close($fh);
     }
@@ -891,6 +897,7 @@ sub save_cache {
     &purge_remembered();
     #&Apache::loncommon::validate_page();
     undef(%env);
+    undef($env_loaded);
 }
 
 my $to_remember=-1;
@@ -1176,7 +1183,7 @@ sub ssi_body {
     }
     my $output=($filelink=~/^http\:/?&externalssi($filelink):
                                      &ssi($filelink,%form));
-    $output=~s|//(\s*<!--)? BEGIN LON-CAPA Internal.+// END LON-CAPA Internal\s*(-->)?\s||gs;
+    $output=~s|//(\s*<!--)? BEGIN LON-CAPA Internal.+?// END LON-CAPA Internal\s*(-->)?\s||gs;
     $output=~s/^.*?\<body[^\>]*\>//si;
     $output=~s/(.*)\<\/body\s*\>.*?$/$1/si;
     return $output;
@@ -1885,9 +1892,6 @@ sub get_course_adv_roles {
 	    (!$nothide{$username.':'.$domain})) { next; }
 	if ($role eq 'cr') { next; }
         my $key=&plaintext($role);
-	if ($role =~ /^cr/) {
-	    $key=(split('/',$role))[3];
-	}
         if ($section) { $key.=' (Sec/Grp '.$section.')'; }
         if ($returnhash{$key}) {
 	    $returnhash{$key}.=','.$username.':'.$domain;
@@ -2848,7 +2852,7 @@ sub set_userprivs {
     if (keys(%{$allgroups}) > 0) {
         foreach my $role (keys %{$allroles}) {
             my ($trole,$area,$sec,$extendedarea);
-            if ($role =~ m|^(\w+)\.(/\w+/\w+)(/?\w*)|) {
+            if ($role =~ m-^(\w+|cr/\w+/\w+/\w+)\.(/\w+/\w+)(/?\w*)-) {
                 $trole = $1;
                 $area = $2;
                 $sec = $3;
@@ -3220,6 +3224,218 @@ sub tmpdel {
     return &reply("tmpdel:$token",$server);
 }
 
+# -------------------------------------------------- portfolio access checking
+
+sub portfolio_access {
+    my ($requrl) = @_;
+    my (undef,$udom,$unum,$file_name,$group) = &parse_portfolio_url($requrl);
+    my $result = &get_portfolio_access($udom,$unum,$file_name,$group);
+    if ($result eq 'ok') {
+       return 'F';
+    } elsif ($result =~ /^[^:]+:guest_/) {
+       return 'A';
+    }
+    return '';
+}
+
+sub get_portfolio_access {
+    my ($udom,$unum,$file_name,$group,$access_hash) = @_;
+
+    if (!ref($access_hash)) {
+	my $current_perms = &get_portfile_permissions($udom,$unum);
+	my %access_controls = &get_access_controls($current_perms,$group,
+						   $file_name);
+	$access_hash = $access_controls{$file_name};
+    }
+
+    my ($public,$guest,@domains,@users,@courses,@groups);
+    my $now = time;
+    if (ref($access_hash) eq 'HASH') {
+        foreach my $key (keys(%{$access_hash})) {
+            my ($num,$scope,$end,$start) = ($key =~ /^([^:]+):([a-z]+)_(\d*)_?(\d*)$/);
+            if ($start > $now) {
+                next;
+            }
+            if ($end && $end<$now) {
+                next;
+            }
+            if ($scope eq 'public') {
+                $public = $key;
+                last;
+            } elsif ($scope eq 'guest') {
+                $guest = $key;
+            } elsif ($scope eq 'domains') {
+                push(@domains,$key);
+            } elsif ($scope eq 'users') {
+                push(@users,$key);
+            } elsif ($scope eq 'course') {
+                push(@courses,$key);
+            } elsif ($scope eq 'group') {
+                push(@groups,$key);
+            }
+        }
+        if ($public) {
+            return 'ok';
+        }
+        if ($env{'user.name'} eq 'public' && $env{'user.domain'} eq 'public') {
+            if ($guest) {
+                return $guest;
+            }
+        } else {
+            if (@domains > 0) {
+                foreach my $domkey (@domains) {
+                    if (ref($access_hash->{$domkey}{'dom'}) eq 'ARRAY') {
+                        if (grep(/^\Q$env{'user.domain'}\E$/,@{$access_hash->{$domkey}{'dom'}})) {
+                            return 'ok';
+                        }
+                    }
+                }
+            }
+            if (@users > 0) {
+                foreach my $userkey (@users) {
+                    if (exists($access_hash->{$userkey}{'users'}{$env{'user.name'}.':'.$env{'user.domain'}})) {
+                        return 'ok';
+                    }
+                }
+            }
+            my %roleshash;
+            my @courses_and_groups = @courses;
+            push(@courses_and_groups,@groups); 
+            if (@courses_and_groups > 0) {
+                my (%allgroups,%allroles); 
+                my ($start,$end,$role,$sec,$group);
+                foreach my $envkey (%env) {
+                    if ($envkey =~ m-^user\.role\.(gr|cc|in|ta|ep|st)\./([^/]+)/([^/]+)/?([^/]*)$-) {
+                        my $cid = $2.'_'.$3; 
+                        if ($1 eq 'gr') {
+                            $group = $4;
+                            $allgroups{$cid}{$group} = $env{$envkey};
+                        } else {
+                            if ($4 eq '') {
+                                $sec = 'none';
+                            } else {
+                                $sec = $4;
+                            }
+                            $allroles{$cid}{$1}{$sec} = $env{$envkey};
+                        }
+                    } elsif ($envkey =~ m-^user\.role\./cr/(\w+/\w+/\w*)./([^/]+)/([^/]+)/?([^/]*)$-) {
+                        my $cid = $2.'_'.$3;
+                        if ($4 eq '') {
+                            $sec = 'none';
+                        } else {
+                            $sec = $4;
+                        }
+                        $allroles{$cid}{$1}{$sec} = $env{$envkey};
+                    }
+                }
+                if (keys(%allroles) == 0) {
+                    return;
+                }
+                foreach my $key (@courses_and_groups) {
+                    my %content = %{$$access_hash{$key}};
+                    my $cnum = $content{'number'};
+                    my $cdom = $content{'domain'};
+                    my $cid = $cdom.'_'.$cnum;
+                    if (!exists($allroles{$cid})) {
+                        next;
+                    }    
+                    foreach my $role_id (keys(%{$content{'roles'}})) {
+                        my @sections = @{$content{'roles'}{$role_id}{'section'}};
+                        my @groups = @{$content{'roles'}{$role_id}{'group'}};
+                        my @status = @{$content{'roles'}{$role_id}{'access'}};
+                        my @roles = @{$content{'roles'}{$role_id}{'role'}};
+                        foreach my $role (keys(%{$allroles{$cid}})) {
+                            if ((grep/^all$/,@roles) || (grep/^\Q$role\E$/,@roles)) {
+                                foreach my $sec (keys(%{$allroles{$cid}{$role}})) {
+                                    if (&course_group_datechecker($allroles{$cid}{$role}{$sec},$now,\@status) eq 'ok') {
+                                        if (grep/^all$/,@sections) {
+                                            return 'ok';
+                                        } else {
+                                            if (grep/^$sec$/,@sections) {
+                                                return 'ok';
+                                            }
+                                        }
+                                    }
+                                }
+                                if (keys(%{$allgroups{$cid}}) == 0) {
+                                    if (grep/^none$/,@groups) {
+                                        return 'ok';
+                                    }
+                                } else {
+                                    if (grep/^all$/,@groups) {
+                                        return 'ok';
+                                    } 
+                                    foreach my $group (keys(%{$allgroups{$cid}})) {
+                                        if (grep/^$group$/,@groups) {
+                                            return 'ok';
+                                        }
+                                    }
+                                } 
+                            }
+                        }
+                    }
+                }
+            }
+            if ($guest) {
+                return $guest;
+            }
+        }
+    }
+    return;
+}
+
+sub course_group_datechecker {
+    my ($dates,$now,$status) = @_;
+    my ($start,$end) = split(/\./,$dates);
+    if (!$start && !$end) {
+        return 'ok';
+    }
+    if (grep/^active$/,@{$status}) {
+        if (((!$start) || ($start && $start <= $now)) && ((!$end) || ($end && $end >= $now))) {
+            return 'ok';
+        }
+    }
+    if (grep/^previous$/,@{$status}) {
+        if ($end > $now ) {
+            return 'ok';
+        }
+    }
+    if (grep/^future$/,@{$status}) {
+        if ($start > $now) {
+            return 'ok';
+        }
+    }
+    return; 
+}
+
+sub parse_portfolio_url {
+    my ($url) = @_;
+
+    my ($type,$udom,$unum,$group,$file_name);
+    
+    if ($url =~  m-^/*uploaded/([^/]+)/([^/]+)/portfolio(/.+)$-) {
+	$type = 1;
+        $udom = $1;
+        $unum = $2;
+        $file_name = $3;
+    } elsif ($url =~ m-^/*uploaded/([^/]+)/([^/]+)/groups/([^/]+)/portfolio/(.+)$-) {
+	$type = 2;
+        $udom = $1;
+        $unum = $2;
+        $group = $3;
+        $file_name = $3.'/'.$4;
+    }
+    if (wantarray) {
+	return ($type,$udom,$unum,$file_name,$group);
+    }
+    return $type;
+}
+
+sub is_portfolio_url {
+    my ($url) = @_;
+    return scalar(&parse_portfolio_url($url));
+}
+
 # ---------------------------------------------- Custom access rule evaluation
 
 sub customaccess {
@@ -3265,8 +3481,9 @@ sub allowed {
     
     if (defined($env{'allowed.'.$priv})) { return $env{'allowed.'.$priv}; }
 # Free bre access to adm and meta resources
-    if (((($uri=~/^adm\//) && ($uri !~ m|/bulletinboard$|)) 
-	 || ($uri=~/\.meta$/)) && ($priv eq 'bre')) {
+    if (((($uri=~/^adm\//) && ($uri !~ m{/(?:smppg|bulletinboard)$})) 
+	 || (($uri=~/\.meta$/) && ($uri!~m|^uploaded/|) )) 
+	&& ($priv eq 'bre')) {
 	return 'F';
     }
 
@@ -3277,7 +3494,7 @@ sub allowed {
         return 'F';
     }
 
-# bre access to group if user has rgf priv for this group and course.
+# bre access to group portfolio for rgf priv in group, or mdg or vcg in course.
     if (($space=~/^(uploaded|editupload)$/) && ($dir[0] eq 'groups') 
          && ($dir[2] eq 'portfolio') && ($priv eq 'bre')) {
         if (exists($env{'request.course.id'})) {
@@ -3289,6 +3506,14 @@ sub allowed {
                 if ($env{'user.priv.'.$env{'request.role'}.'./'.$courseprivid
                     .'/'.$dir[1]} =~/rgf\&([^\:]*)/) {
                     return $1; 
+                } else {
+                    if ($env{'request.course.sec'}) {
+                        $courseprivid.='/'.$env{'request.course.sec'};
+                    }
+                    if ($env{'user.priv.'.$env{'request.role'}.'./'.
+                        $courseprivid} =~/(mdg|vcg)\&([^\:]*)/) {
+                        return $2;
+                    }
                 }
             }
         }
@@ -3357,14 +3582,6 @@ sub allowed {
        $thisallowed.=$1;
     }
 
-# Group: uri itself is a group
-    my $groupuri=$uri;
-    $groupuri=~s/^([^\/])/\/$1/;
-    if ($env{'user.priv.'.$env{'request.role'}.'.'.$groupuri}
-       =~/\Q$priv\E\&([^\:]*)/) {
-       $thisallowed.=$1;
-    }
-
 # URI is an uploaded document for this course, default permissions don't matter
 # not allowing 'edit' access (editupload) to uploaded course docs
     if (($priv eq 'bre') && ($uri=~m|^uploaded/|)) {
@@ -3391,6 +3608,13 @@ sub allowed {
         }
     }
 
+    if ($priv eq 'bre'
+	&& $thisallowed ne 'F' 
+	&& $thisallowed ne '2'
+	&& &is_portfolio_url($uri)) {
+	$thisallowed = &portfolio_access($uri);
+    }
+    
 # Full access at system, domain or course-wide level? Exit.
 
     if ($thisallowed=~/F/) {
@@ -3541,7 +3765,11 @@ sub allowed {
 #
 
     unless ($env{'request.course.id'}) {
-       return '1';
+	if ($thisallowed eq 'A') {
+	    return 'A';
+	} else {
+	    return '1';
+	}
     }
 
 #
@@ -3604,6 +3832,9 @@ sub allowed {
       }
    }
 
+    if ($thisallowed eq 'A') {
+	return 'A';
+    }
    return 'F';
 }
 
@@ -3850,7 +4081,7 @@ sub auto_run {
     my $response = &reply('autorun:'.$cdom,$homeserver);
     return $response;
 }
-                                                                                   
+
 sub auto_get_sections {
     my ($cnum,$cdom,$inst_coursecode) = @_;
     my $homeserver = &homeserver($cnum,$cdom);
@@ -3861,21 +4092,21 @@ sub auto_get_sections {
     }
     return @secs;
 }
-                                                                                   
+
 sub auto_new_course {
     my ($cnum,$cdom,$inst_course_id,$owner) = @_;
     my $homeserver = &homeserver($cnum,$cdom);
     my $response=&unescape(&reply('autonewcourse:'.$inst_course_id.':'.$owner.':'.$cdom,$homeserver));
     return $response;
 }
-                                                                                   
+
 sub auto_validate_courseID {
     my ($cnum,$cdom,$inst_course_id) = @_;
     my $homeserver = &homeserver($cnum,$cdom);
     my $response=&unescape(&reply('autovalidatecourse:'.$inst_course_id.':'.$cdom,$homeserver));
     return $response;
 }
-                                                                                   
+
 sub auto_create_password {
     my ($cnum,$cdom,$authparam) = @_;
     my $homeserver = &homeserver($cnum,$cdom); 
@@ -3969,33 +4200,49 @@ sub auto_photoupdate {
 sub auto_instcode_format {
     my ($caller,$codedom,$instcodes,$codes,$codetitles,$cat_titles,$cat_order) = @_;
     my $courses = '';
-    my $homeserver;
+    my @homeservers;
     if ($caller eq 'global') {
         foreach my $tryserver (keys %libserv) {
             if ($hostdom{$tryserver} eq $codedom) {
-                $homeserver = $tryserver;
-                last;
+                if (!grep/^\Q$tryserver\E$/,@homeservers) {
+                    push(@homeservers,$tryserver);
+                }
             }
         }
-        if (($env{'user.name'}) && ($env{'user.domain'} eq $codedom)) {
-            $homeserver = &homeserver($env{'user.name'},$codedom);
-        }
     } else {
-        $homeserver = &homeserver($caller,$codedom);
+        push(@homeservers,&homeserver($caller,$codedom));
     }
     foreach (keys %{$instcodes}) {
         $courses .= &escape($_).'='.&escape($$instcodes{$_}).'&';
     }
     chop($courses);
-    my $response=&reply('autoinstcodeformat:'.$codedom.':'.$courses,$homeserver);
-    unless ($response =~ /(con_lost|error|no_such_host|refused)/) {
-        my ($codes_str,$codetitles_str,$cat_titles_str,$cat_order_str) = split/:/,$response;
-        %{$codes} = &str2hash($codes_str);
-        @{$codetitles} = &str2array($codetitles_str);
-        %{$cat_titles} = &str2hash($cat_titles_str);
-        %{$cat_order} = &str2hash($cat_order_str);
+    my $ok_response = 0;
+    my $response;
+    while (@homeservers > 0 && $ok_response == 0) {
+        my $server = shift(@homeservers); 
+        $response=&reply('autoinstcodeformat:'.$codedom.':'.$courses,$server);
+        if ($response !~ /(con_lost|error|no_such_host|refused)/) {
+            my ($codes_str,$codetitles_str,$cat_titles_str,$cat_order_str) = 
+                                                            split/:/,$response;
+            %{$codes} = (%{$codes},&str2hash($codes_str));
+            push(@{$codetitles},&str2array($codetitles_str));
+            %{$cat_titles} = (%{$cat_titles},&str2hash($cat_titles_str));
+            %{$cat_order} = (%{$cat_order},&str2hash($cat_order_str));
+            $ok_response = 1;
+        }
+    }
+    if ($ok_response) {
         return 'ok';
+    } else {
+        return $response;
     }
+}
+
+sub auto_validate_class_sec {
+    my ($cdom,$cnum,$owner,$inst_class) = @_;
+    my $homeserver = &homeserver($cnum,$cdom);
+    my $response=&reply('autovalidateclass_sec:'.$inst_class.':'.
+                        &escape($owner).':'.$cdom,$homeserver);
     return $response;
 }
 
@@ -4113,6 +4360,9 @@ sub devalidate_getgroups_cache {
 
 sub plaintext {
     my ($short,$type,$cid) = @_;
+    if ($short =~ /^cr/) {
+	return (split('/',$short))[-1];
+    }
     if (!defined($cid)) {
         $cid = $env{'request.course.id'};
     }
@@ -4582,6 +4832,14 @@ sub is_locked {
     }
 }
 
+sub declutter_portfile {
+    my ($file) = @_;
+    &logthis("got $file");
+    $file =~ s-^(/portfolio/|portfolio/)-/-;
+    &logthis("ret $file");
+    return $file;
+}
+
 # ------------------------------------------------------------- Mark as Read Only
 
 sub mark_as_readonly {
@@ -4590,6 +4848,7 @@ sub mark_as_readonly {
     my ($tmp)=keys(%current_permissions);
     if ($tmp=~/^error:/) { undef(%current_permissions); }
     foreach my $file (@{$files}) {
+	$file = &declutter_portfile($file);
         push(@{$current_permissions{$file}},$what);
     }
     &put('file_permissions',\%current_permissions,$domain,$user);
@@ -4679,11 +4938,13 @@ sub get_portfile_permissions {
 
 sub get_access_controls {
     my ($current_permissions,$group,$file) = @_;
-    my %access; 
+    my %access;
+    my $real_file = $file;
+    $file =~ s/\.meta$//;
     if (defined($file)) {
         if (ref($$current_permissions{$file."\0".'accesscontrol'}) eq 'HASH') {
             foreach my $control (keys(%{$$current_permissions{$file."\0".'accesscontrol'}})) {
-                $access{$file}{$control} = $$current_permissions{$file."\0".$control};
+                $access{$real_file}{$control} = $$current_permissions{$file."\0".$control};
             }
         }
     } else {
@@ -4706,67 +4967,6 @@ sub get_access_controls {
     return %access;
 }
 
-sub parse_access_controls {
-    my ($access_item) = @_;
-    my %content;
-    my $role_id;
-    my $user;
-    my $usercount;
-    my $token;
-    my $parser=HTML::TokeParser->new(\$access_item);
-    while ($token=$parser->get_token) {
-        if ($token->[0] eq 'S')  {
-            my $entry=$token->[1];
-            if ($entry eq 'scope') {
-                my $type = $token->[2]{'type'};
-                if (($type eq 'course') || ($type eq 'group')) {
-                    $content{'roles'} = {};
-                }
-            } elsif ($entry eq 'roles') {
-                $role_id = $token->[2]{id};
-		$content{$entry}{$role_id} = {
-		                                 role => [],
-                                                 access => [],
-                                                 section => [],
-                                                 group => [],
-                                             };
-            } elsif ($entry eq 'users') {
-                $content{'users'} = {};
-                $usercount = 0;
-            } elsif ($entry eq 'user') {
-                $user = '';
-            } else {
-                my $value=$parser->get_text('/'.$entry);
-                if ($entry eq 'uname') {
-                    $user = $value;
-                } elsif ($entry eq 'udom') {
-                    $user .= ':'.$value;
-                    $content{'users'}{$user} = $usercount;
-                } elsif ($entry eq 'role' ||
-                    $entry eq 'access' ||
-                    $entry eq 'section' ||
-                    $entry eq 'group') {
-                    if ($role_id ne '') {
-                        push(@{$content{'roles'}{$role_id}{$entry}},$value);
-                    }
-                } elsif ($entry eq 'dom') {
-                    push(@{$content{$entry}},$value);
-                } else {
-                    $content{$entry}=$value;
-                }
-            }
-        } elsif ($token->[0] eq 'E') {
-            if ($token->[1] eq 'user') {
-                $user = '';
-                $usercount ++;
-            } elsif ($token->[1] eq 'roles') {
-                $role_id = '';
-            }
-        }
-    }
-    return %content;
-}
-
 sub modify_access_controls {
     my ($file_name,$changes,$domain,$user)=@_;
     my ($outcome,$deloutcome);
@@ -4872,7 +5072,7 @@ sub get_marked_as_readonly {
         if (ref($value) eq "ARRAY"){
             foreach my $stored_what (@{$value}) {
                 my $cmp2=$stored_what;
-                if (ref($stored_what eq 'ARRAY')) {
+                if (ref($stored_what) eq 'ARRAY') {
                     $cmp2=join('',@{$stored_what});
                 }
                 if ($cmp1 eq $cmp2) {
@@ -4924,6 +5124,7 @@ sub unmark_as_readonly {
     # unmarks $file_name (if $file_name is defined), or all files locked by $what 
     # for portfolio submissions, $what contains [$symb,$crsid] 
     my ($domain,$user,$what,$file_name,$group) = @_;
+    $file_name = &declutter_portfile($file_name);
     my $symb_crs = $what;
     if (ref($what)) { $symb_crs=join('',@$what); }
     my %current_permissions = &dump('file_permissions',$domain,$user,$group);
@@ -4931,7 +5132,8 @@ sub unmark_as_readonly {
     if ($tmp=~/^error:/) { undef(%current_permissions); }
     my @readonly_files = &get_marked_as_readonly($domain,$user,$what,$group);
     foreach my $file (@readonly_files) {
-	if (defined($file_name) && ($file_name ne $file)) { next; }
+	my $clean_file = &declutter_portfile($file);
+	if (defined($file_name) && ($file_name ne $clean_file)) { next; }
 	my $current_locks = $current_permissions{$file};
         my @new_locks;
         my @del_keys;
@@ -5205,6 +5407,7 @@ sub devalidatecourseresdata {
     &devalidate_cache_new('courseres',$hashid);
 }
 
+
 # --------------------------------------------------- Course Resourcedata Query
 
 sub get_courseresdata {
@@ -5557,6 +5760,9 @@ sub EXT {
 	if (($uname eq $env{'user.name'})&&($udom eq $env{'user.domain'})) {
 	    return $env{'environment.'.$spacequalifierrest};
 	} else {
+	    if ($uname eq 'anonymous' && $udom eq '') {
+		return '';
+	    }
 	    my %returnhash=&userenvironment($udom,$uname,
 					    $spacequalifierrest);
 	    return $returnhash{$spacequalifierrest};
@@ -5707,7 +5913,7 @@ sub metadata {
         my %metathesekeys=();
         unless ($filename=~/\.meta$/) { $filename.='.meta'; }
 	my $metastring;
-	if ($uri !~ m -^(uploaded|editupload)/-) {
+	if ($uri !~ m -^(editupload)/-) {
 	    my $file=&filelocation('',&clutter($filename));
 	    #push(@{$metaentry{$uri.'.file'}},$file);
 	    $metastring=&getfile($file);
@@ -5926,6 +6132,17 @@ sub metadata_generate_part0 {
     }
 }
 
+# ------------------------------------------------------ Devalidate title cache
+
+sub devalidate_title_cache {
+    my ($url)=@_;
+    if (!$env{'request.course.id'}) { return; }
+    my $symb=&symbread($url);
+    if (!$symb) { return; }
+    my $key=$env{'request.course.id'}."\0".$symb;
+    &devalidate_cache_new('title',$key);
+}
+
 # ------------------------------------------------- Get the title of a resource
 
 sub gettitle {
@@ -7332,6 +7549,7 @@ actions
  '': forbidden
  1: user needs to choose course
  2: browse allowed
+ A: passphrase authentication needed
 
 =item *
 
@@ -8059,15 +8277,6 @@ Internal notes:
  
  Locks on files (resulting from submission of portfolio file to a homework problem stored in array of arrays.
 
-parse_access_controls():
-
-Parses XML of an access control record
-Args
-1. Text string (XML) of access comtrol record
-
-Returns:
-1. Hash of access control settings. 
-
 modify_access_controls():
 
 Modifies access controls for a portfolio file