--- loncom/lonnet/perl/lonnet.pm	2009/08/08 19:55:24	1.1011
+++ loncom/lonnet/perl/lonnet.pm	2010/03/17 20:22:06	1.1056
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # TCP networking package
 #
-# $Id: lonnet.pm,v 1.1011 2009/08/08 19:55:24 raeburn Exp $
+# $Id: lonnet.pm,v 1.1056 2010/03/17 20:22:06 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -92,14 +92,13 @@ use Time::HiRes qw( gettimeofday tv_inte
 use Cache::Memcached;
 use Digest::MD5;
 use Math::Random;
+use File::MMagic;
 use LONCAPA qw(:DEFAULT :match);
 use LONCAPA::Configuration;
 
 my $readit;
 my $max_connection_retries = 10;     # Or some such value.
 
-my $upload_photo_form = 0; #Variable to check  when user upload a photo 0=not 1=true
-
 require Exporter;
 
 our @ISA = qw (Exporter);
@@ -784,7 +783,8 @@ sub changepass {
     my ($uname,$udom,$currentpass,$newpass,$server,$context)=@_;
     $currentpass = &escape($currentpass);
     $newpass     = &escape($newpass);
-    my $answer = reply("encrypt:passwd:$udom:$uname:$currentpass:$newpass:$context",
+    my $lonhost = $perlvar{'lonHostID'};
+    my $answer = reply("encrypt:passwd:$udom:$uname:$currentpass:$newpass:$context:$lonhost",
 		       $server);
     if (! $answer) {
 	&logthis("No reply on password change request to $server ".
@@ -809,6 +809,9 @@ sub changepass {
     } elsif ($answer =~ "^refused") {
 	&logthis("$server refused to change $uname in $udom password because ".
 		 "it was sent an unencrypted request to change the password.");
+    } elsif ($answer =~ "invalid_client") {
+        &logthis("$server refused to change $uname in $udom password because ".
+                 "it was a reset by e-mail originating from an invalid server.");
     }
     return $answer;
 }
@@ -958,7 +961,21 @@ sub idput {
     }
 }
 
-# ------------------------------------------- get items from domain db files   
+# ------------------------------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;
+}
+
+# ------------------------------------------ get items from domain db files   
 
 sub get_dom {
     my ($namespace,$storearr,$udom,$uhome)=@_;
@@ -1032,6 +1049,40 @@ sub put_dom {
     }
 }
 
+# --------------------- newput for items in db file owned by domainconfig user
+sub newput_dom {
+    my ($namespace,$storehash,$udom) = @_;
+    my $result;
+    if (!$udom) {
+        $udom=$env{'user.domain'};
+    }
+    if ($udom) {
+        my $uname = &get_domainconfiguser($udom);
+        $result = &newput($namespace,$storehash,$udom,$uname);
+    }
+    return $result;
+}
+
+# --------------------- delete for items in db file owned by domainconfig user
+sub del_dom {
+    my ($namespace,$storearr,$udom)=@_;
+    if (ref($storearr) eq 'ARRAY') {
+        if (!$udom) {
+            $udom=$env{'user.domain'};
+        }
+        if ($udom) {
+            my $uname = &get_domainconfiguser($udom); 
+            return &del($namespace,$storearr,$udom,$uname);
+        }
+    }
+}
+
+# ----------------------------------construct domainconfig user for a domain 
+sub get_domainconfiguser {
+    my ($udom) = @_;
+    return $udom.'-domainconfig';
+}
+
 sub retrieve_inst_usertypes {
     my ($udom) = @_;
     my (%returnhash,@order);
@@ -1300,7 +1351,8 @@ sub get_domain_defaults {
     my %domdefaults;
     my %domconfig =
          &Apache::lonnet::get_dom('configuration',['defaults','quotas',
-                                  'requestcourses','inststatus'],$domain);
+                                  'requestcourses','inststatus',
+                                  'coursedefaults'],$domain);
     if (ref($domconfig{'defaults'}) eq 'HASH') {
         $domdefaults{'lang_def'} = $domconfig{'defaults'}{'lang_def'}; 
         $domdefaults{'auth_def'} = $domconfig{'defaults'}{'auth_def'};
@@ -1335,6 +1387,11 @@ sub get_domain_defaults {
             $domdefaults{$item} = $domconfig{'inststatus'}{$item};
         }
     }
+    if (ref($domconfig{'coursedefaults'}) eq 'HASH') {
+        foreach my $item ('canuse_pdfforms') {
+            $domdefaults{$item} = $domconfig{'coursedefaults'}{$item};
+        }
+    }
     &Apache::lonnet::do_cache_new('domdefaults',$domain,\%domdefaults,
                                   $cachetime);
     return %domdefaults;
@@ -1669,6 +1726,9 @@ sub userenvironment {
     unless ($uhome eq 'no_host') {
         my @answer=split(/\&/, 
             &reply('get:'.$udom.':'.$unam.':environment:'.$items,$uhome));
+        if ($#answer==0 && $answer[0] =~ /^(con_lost|error:|no_such_host)/i) {
+            return %returnhash;
+        }
         my $i;
         for ($i=0;$i<=$#what;$i++) {
 	    $returnhash{$what[$i]}=&unescape($answer[$i]);
@@ -2011,9 +2071,13 @@ sub process_coursefile {
             print $fh $env{'form.'.$source};
             close($fh);
             if ($parser eq 'parse') {
-                my $parse_result = &extract_embedded_items($filepath.'/'.$fname,$allfiles,$codebase);
-                unless ($parse_result eq 'ok') {
-                    &logthis('Failed to parse '.$filepath.'/'.$fname.' for embedded media: '.$parse_result);
+                my $mm = new File::MMagic;
+                my $mime_type = $mm->checktype_filename($filepath.'/'.$fname);
+                if ($mime_type eq 'text/html') {
+                    my $parse_result = &extract_embedded_items($filepath.'/'.$fname,$allfiles,$codebase);
+                    unless ($parse_result eq 'ok') {
+                        &logthis('Failed to parse '.$filepath.'/'.$fname.' for embedded media: '.$parse_result);
+                    }
                 }
             }
             $fetchresult= &reply('fetchuserfile:'.$docudom.'/'.$docuname.'/'.$file,
@@ -2090,31 +2154,42 @@ sub clean_filename {
     $fname=~s/\.(\d+)(?=\.)/_$1/g;
     return $fname;
 }
-#This Function check if a Image max 400px width and height 500px. If not then scale the image down
+# This Function checks if an Image's dimensions exceed either $resizewidth (width) 
+# or $resizeheight (height) - both pixels. If so, the image is scaled to produce an 
+# image with the same aspect ratio as the original, but with dimensions which do 
+# not exceed $resizewidth and $resizeheight.
+ 
 sub resizeImage {
-	my($img_url) = @_;	
-	my $ima = Image::Magick->new;                       
-        $ima->Read($img_url);
-	if($ima->Get('width') > 400)
-	{
-		my $factor = $ima->Get('width')/400;
-             	$ima->Scale( width=>400, height=>$ima->Get('height')/$factor );
-	}
-	if($ima->Get('height') > 500)
-        {
-        	my $factor = $ima->Get('height')/500;
-                $ima->Scale( width=>$ima->Get('width')/$factor, height=>500);
-        } 
-		
-	$ima->Write($img_url);
-}
-
-#Wrapper function for userphotoupload
-sub userphotoupload
-{
-	my($formname,$subdir) = @_;
-	$upload_photo_form = 1;
-	return &userfileupload($formname,undef,$subdir);
+    my ($img_path,$resizewidth,$resizeheight) = @_;
+    my $ima = Image::Magick->new;
+    my $resized;
+    if (-e $img_path) {
+        $ima->Read($img_path);
+        if (($resizewidth =~ /^\d+$/) && ($resizeheight > 0)) {
+            my $width = $ima->Get('width');
+            my $height = $ima->Get('height');
+            if ($width > $resizewidth) {
+	        my $factor = $width/$resizewidth;
+                my $newheight = $height/$factor;
+                $ima->Scale(width=>$resizewidth,height=>$newheight);
+                $resized = 1;
+            }
+        }
+        if (($resizeheight =~ /^\d+$/) && ($resizeheight > 0)) {
+            my $width = $ima->Get('width');
+            my $height = $ima->Get('height');
+            if ($height > $resizeheight) {
+                my $factor = $height/$resizeheight;
+                my $newwidth = $width/$factor;
+                $ima->Scale(width=>$newwidth,height=>$resizeheight);
+                $resized = 1;
+            }
+        }
+        if ($resized) {
+            $ima->Write($img_path);
+        }
+    }
+    return;
 }
 
 # --------------- Take an uploaded file and put it into the userfiles directory
@@ -2130,14 +2205,15 @@ sub userphotoupload
 #        $dsetudom - domain for permanaent storage of uploaded file
 #        $thumbwidth - width (pixels) of thumbnail to make for uploaded image 
 #        $thumbheight - height (pixels) of thumbnail to make for uploaded image
+#        $resizewidth - width (pixels) to which to resize uploaded image
+#        $resizeheight - height (pixels) to which to resize uploaded image
 # 
 # output: url of file in userspace, or error: <message> 
 #             or /adm/notfound.html if failure to upload occurse
 
-
 sub userfileupload {
     my ($formname,$coursedoc,$subdir,$parser,$allfiles,$codebase,$destuname,
-        $destudom,$thumbwidth,$thumbheight)=@_;
+        $destudom,$thumbwidth,$thumbheight,$resizewidth,$resizeheight)=@_;
     if (!defined($subdir)) { $subdir='unknown'; }
     my $fname=$env{'form.'.$formname.'.filename'};
     $fname=&clean_filename($fname);
@@ -2187,7 +2263,8 @@ sub userfileupload {
         if ($env{'form.folder'} =~ m/^(default|supplemental)/) {
             return &finishuserfileupload($docuname,$docudom,
 					 $formname,$fname,$parser,$allfiles,
-					 $codebase,$thumbwidth,$thumbheight);
+					 $codebase,$thumbwidth,$thumbheight,
+                                         $resizewidth,$resizeheight);
         } else {
             $fname=$env{'form.folder'}.'/'.$fname;
             return &process_coursefile('uploaddoc',$docuname,$docudom,
@@ -2199,7 +2276,8 @@ sub userfileupload {
         my $docudom=$destudom;
 	return &finishuserfileupload($docuname,$docudom,$formname,$fname,
 				     $parser,$allfiles,$codebase,
-                                     $thumbwidth,$thumbheight);
+                                     $thumbwidth,$thumbheight,
+                                     $resizewidth,$resizeheight);
         
     } else {
         my $docuname=$env{'user.name'};
@@ -2210,13 +2288,14 @@ sub userfileupload {
         }
 	return &finishuserfileupload($docuname,$docudom,$formname,$fname,
 				     $parser,$allfiles,$codebase,
-                                     $thumbwidth,$thumbheight);
+                                     $thumbwidth,$thumbheight,
+                                     $resizewidth,$resizeheight);
     }
 }
 
 sub finishuserfileupload {
     my ($docuname,$docudom,$formname,$fname,$parser,$allfiles,$codebase,
-        $thumbwidth,$thumbheight) = @_;
+        $thumbwidth,$thumbheight,$resizewidth,$resizeheight) = @_;
     my $path=$docudom.'/'.$docuname.'/';
     my $filepath=$perlvar{'lonDocRoot'};
   
@@ -2248,18 +2327,24 @@ sub finishuserfileupload {
 	    return '/adm/notfound.html';
 	}
 	close(FH);
-	if($upload_photo_form==1)
-	{
-		resizeImage($filepath.'/'.$file);		
-		$upload_photo_form = 0;
+        if ($resizewidth && $resizeheight) {
+            my $mm = new File::MMagic;
+            my $mime_type = $mm->checktype_filename($filepath.'/'.$file);
+            if ($mime_type =~ m{^image/}) {
+	        &resizeImage($filepath.'/'.$file,$resizewidth,$resizeheight);
+            }  
 	}
     }
     if ($parser eq 'parse') {
-        my $parse_result = &extract_embedded_items($filepath.'/'.$file,$allfiles,
-						   $codebase);
-        unless ($parse_result eq 'ok') {
-            &logthis('Failed to parse '.$filepath.$file.
-		     ' for embedded media: '.$parse_result); 
+        my $mm = new File::MMagic;
+        my $mime_type = $mm->checktype_filename($filepath.'/'.$file);
+        if ($mime_type eq 'text/html') {
+            my $parse_result = &extract_embedded_items($filepath.'/'.$file,
+                                                       $allfiles,$codebase);
+            unless ($parse_result eq 'ok') {
+                &logthis('Failed to parse '.$filepath.$file.
+	   	         ' for embedded media: '.$parse_result); 
+            }
         }
     }
     if (($thumbwidth =~ /^\d+$/) && ($thumbheight =~ /^\d+$/)) {
@@ -2668,7 +2753,7 @@ sub userrolelog {
     if (($trole=~/^ca/) || ($trole=~/^aa/) ||
         ($trole=~/^in/) || ($trole=~/^cc/) ||
         ($trole=~/^ep/) || ($trole=~/^cr/) ||
-        ($trole=~/^ta/)) {
+        ($trole=~/^ta/) || ($trole=~/^co/)) {
        my (undef,$rudom,$runame,$rsec)=split(/\//,$area);
        $userrolehash
          {$trole.':'.$username.':'.$domain.':'.$runame.':'.$rudom.':'.$rsec}
@@ -2677,7 +2762,8 @@ sub userrolelog {
     if (($env{'request.role'} =~ /dc\./) &&
 	(($trole=~/^au/) || ($trole=~/^in/) ||
 	 ($trole=~/^cc/) || ($trole=~/^ep/) ||
-	 ($trole=~/^cr/) || ($trole=~/^ta/))) {
+	 ($trole=~/^cr/) || ($trole=~/^ta/) ||
+         ($trole=~/^co/))) {
        $userrolehash
          {$trole.':'.$username.':'.$domain.':'.$env{'user.name'}.':'.$env{'user.domain'}.':'}
                     =$tend.':'.$tstart;
@@ -2698,7 +2784,8 @@ sub courserolelog {
     if (($trole eq 'cc') || ($trole eq 'in') ||
         ($trole eq 'ep') || ($trole eq 'ad') ||
         ($trole eq 'ta') || ($trole eq 'st') ||
-        ($trole=~/^cr/) || ($trole eq 'gr')) {
+        ($trole=~/^cr/) || ($trole eq 'gr') ||
+        ($trole eq 'co')) {
         if ($area =~ m-^/($match_domain)/($match_courseid)/?([^/]*)-) {
             my $cdom = $1;
             my $cnum = $2;
@@ -2932,6 +3019,7 @@ sub getannounce {
 
 sub courseidput {
     my ($domain,$storehash,$coursehome,$caller) = @_;
+    return unless (ref($storehash) eq 'HASH');
     my $outcome;
     if ($caller eq 'timeonly') {
         my $cids = '';
@@ -2970,7 +3058,8 @@ sub courseidput {
 sub courseiddump {
     my ($domfilter,$descfilter,$sincefilter,$instcodefilter,$ownerfilter,
         $coursefilter,$hostidflag,$hostidref,$typefilter,$regexp_ok,
-        $selfenrollonly,$catfilter,$showhidden,$caller,$cloner,$cc_clone,$cloneonly)=@_;
+        $selfenrollonly,$catfilter,$showhidden,$caller,$cloner,$cc_clone,
+        $cloneonly,$createdbefore,$createdafter,$creationcontext)=@_;
     my $as_hash = 1;
     my %returnhash;
     if (!$domfilter) { $domfilter=''; }
@@ -2990,7 +3079,9 @@ sub courseiddump {
                          ':'.&escape($regexp_ok).':'.$as_hash.':'.
                          &escape($selfenrollonly).':'.&escape($catfilter).':'.
                          $showhidden.':'.$caller.':'.&escape($cloner).':'.
-                         &escape($cc_clone).':'.$cloneonly,$tryserver);
+                         &escape($cc_clone).':'.$cloneonly.':'.
+                         &escape($createdbefore).':'.&escape($createdafter).':'.
+                         &escape($creationcontext),$tryserver);
                 my @pairs=split(/\&/,$rep);
                 foreach my $item (@pairs) {
                     my ($key,$value)=split(/\=/,$item,2);
@@ -3013,6 +3104,49 @@ sub courseiddump {
     return %returnhash;
 }
 
+sub courselastaccess {
+    my ($cdom,$cnum,$hostidref) = @_;
+    my %returnhash;
+    if ($cdom && $cnum) {
+        my $chome = &homeserver($cnum,$cdom);
+        if ($chome ne 'no_host') {
+            my $rep = &reply('courselastaccess:'.$cdom.':'.$cnum,$chome);
+            &extract_lastaccess(\%returnhash,$rep);
+        }
+    } else {
+        if (!$cdom) { $cdom=''; }
+        my %libserv = &all_library();
+        foreach my $tryserver (keys(%libserv)) {
+            if (ref($hostidref) eq 'ARRAY') {
+                next unless (grep(/^\Q$tryserver\E$/,@{$hostidref}));
+            } 
+            if (($cdom eq '') || (&host_domain($tryserver) eq $cdom)) {
+                my $rep = &reply('courselastaccess:'.&host_domain($tryserver).':',$tryserver);
+                &extract_lastaccess(\%returnhash,$rep);
+            }
+        }
+    }
+    return %returnhash;
+}
+
+sub extract_lastaccess {
+    my ($returnhash,$rep) = @_;
+    if (ref($returnhash) eq 'HASH') {
+        unless ($rep eq 'unknown_command' || $rep eq 'no_such_host' || 
+                $rep eq 'con_lost' || $rep eq 'rejected' || $rep eq 'refused' ||
+                 $rep eq '') {
+            my @pairs=split(/\&/,$rep);
+            foreach my $item (@pairs) {
+                my ($key,$value)=split(/\=/,$item,2);
+                $key = &unescape($key);
+                next if ($key =~ /^error: 2 /);
+                $returnhash->{$key} = &thaw_unescape($value);
+            }
+        }
+    }
+    return;
+}
+
 # ---------------------------------------------------------- DC e-mail
 
 sub dcmailput {
@@ -3045,10 +3179,10 @@ sub dcmaildump {
 
 sub get_domain_roles {
     my ($dom,$roles,$startdate,$enddate)=@_;
-    if (undef($startdate) || $startdate eq '') {
+    if ((!defined($startdate)) || ($startdate eq '')) {
         $startdate = '.';
     }
-    if (undef($enddate) || $enddate eq '') {
+    if ((!defined($enddate)) || ($enddate eq '')) {
         $enddate = '.';
     }
     my $rolelist;
@@ -3738,7 +3872,10 @@ sub privileged {
     my ($username,$domain)=@_;
     my $rolesdump=&reply("dump:$domain:$username:roles",
 			&homeserver($username,$domain));
-    if (($rolesdump eq 'con_lost') || ($rolesdump eq '')) { return 0; }
+    if (($rolesdump eq 'con_lost') || ($rolesdump eq '') || 
+        ($rolesdump =~ /^error:/)) {
+        return 0;
+    }
     my $now=time;
     if ($rolesdump ne '') {
         foreach my $entry (split(/&/,$rolesdump)) {
@@ -3766,13 +3903,15 @@ sub privileged {
 
 sub rolesinit {
     my ($domain,$username,$authhost)=@_;
-    my %userroles;
+    my $now=time;
+    my %userroles = ('user.login.time' => $now);
     my $rolesdump=reply("dump:$domain:$username:roles",$authhost);
-    if (($rolesdump eq 'con_lost') || ($rolesdump eq '')) { return \%userroles; }
+    if (($rolesdump eq 'con_lost') || ($rolesdump eq '') || 
+        ($rolesdump =~ /^error:/)) { 
+        return \%userroles;
+    }
     my %allroles=();
     my %allgroups=();   
-    my $now=time;
-    %userroles = ('user.login.time' => $now);
     my $group_privs;
 
     if ($rolesdump ne '') {
@@ -3838,6 +3977,9 @@ sub custom_roleprivs {
         if (($rdummy ne 'con_lost') && ($roledef ne '')) {
             my ($syspriv,$dompriv,$coursepriv)=split(/\_/,$roledef);
             if (defined($syspriv)) {
+                if ($trest =~ /^$match_community$/) {
+                    $syspriv =~ s/bre\&S//; 
+                }
                 $$allroles{'cm./'}.=':'.$syspriv;
                 $$allroles{$spec.'./'}.=':'.$syspriv;
             }
@@ -3946,8 +4088,8 @@ sub role_status {
             $$tstatus='is';
             if ($$tstart && $$tstart>$then) {
                 $$tstatus='future';
-                if ($$tstart && $$tstart>$refresh) {
-                    if ($$tstart<$now) {
+                if ($$tstart<$now) {
+                    if ($$tstart && $$tstart>$refresh) {
                         if (($$where ne '') && ($$role ne '')) {
                             my (%allroles,%allgroups,$group_privs);
                             my %userroles = (
@@ -3955,13 +4097,6 @@ sub role_status {
                             );
                             my $spec=$$role.'.'.$$where;
                             my ($tdummy,$tdomain,$trest)=split(/\//,$$where);
-                            if ($$role eq 'gr') {
-                                my %rolehash = &get('roles',[$$where.'_'.$$role],$env{'user.domain'},
-                                                    $env{'user.name'})=@_;
-                                my ($trole) = split('_',$role,1);
-                                (undef,my $group_privs) = split(/\//,$trole);
-                                $group_privs = &unescape($group_privs);
-                            }
                             if ($$role =~ /^cr\//) {
                                 &custom_roleprivs(\%allroles,$$role,$tdomain,$trest,$spec,$$where);
                             } elsif ($$role eq 'gr') {
@@ -3977,9 +4112,9 @@ sub role_status {
                             my ($author,$adv)= &set_userprivs(\%userroles,\%allroles,\%allgroups);
                             &appenv(\%userroles,[$$role,'cm']);
                             &log($env{'user.domain'},$env{'user.name'},$env{'user.home'},"Role ".$role);
-                            $$tstatus = 'is';
                         }
                     }
+                    $$tstatus = 'is';
                 }
             }
             if ($$tend) {
@@ -4671,7 +4806,7 @@ sub usertools_access {
         $toolstatus = $env{'environment.'.$context.'.'.$tool};
         $inststatus = $env{'environment.inststatus'};
     } else {
-        my %userenv = &userenvironment($udom,$uname,$context.'.'.$tool);
+        my %userenv = &userenvironment($udom,$uname,$context.'.'.$tool,'inststatus');
         $toolstatus = $userenv{$context.'.'.$tool};
         $inststatus = $userenv{'inststatus'};
     }
@@ -4737,6 +4872,27 @@ sub usertools_access {
     }
 }
 
+sub is_course_owner {
+    my ($cdom,$cnum,$udom,$uname) = @_;
+    if (($udom eq '') || ($uname eq '')) {
+        $udom = $env{'user.domain'};
+        $uname = $env{'user.name'};
+    }
+    unless (($udom eq '') || ($uname eq '')) {
+        if (exists($env{'course.'.$cdom.'_'.$cnum.'.internal.courseowner'})) {
+            if ($env{'course.'.$cdom.'_'.$cnum.'.internal.courseowner'} eq $uname.':'.$udom) {
+                return 1;
+            } else {
+                my %courseinfo = &Apache::lonnet::coursedescription($cdom.'/'.$cnum);
+                if ($courseinfo{'internal.courseowner'} eq $uname.':'.$udom) {
+                    return 1;
+                }
+            }
+        }
+    }
+    return;
+}
+
 sub is_advanced_user {
     my ($udom,$uname) = @_;
     my %roleshash = &get_my_roles($uname,$udom,'userroles',undef,undef,undef,1);
@@ -4772,6 +4928,55 @@ sub is_advanced_user {
     return $is_adv;
 }
 
+sub check_can_request {
+    my ($dom,$can_request,$request_domains) = @_;
+    my $canreq = 0;
+    my ($types,$typename) = &Apache::loncommon::course_types();
+    my @options = ('approval','validate','autolimit');
+    my $optregex = join('|',@options);
+    if ((ref($can_request) eq 'HASH') && (ref($types) eq 'ARRAY')) {
+        foreach my $type (@{$types}) {
+            if (&usertools_access($env{'user.name'},
+                                  $env{'user.domain'},
+                                  $type,undef,'requestcourses')) {
+                $canreq ++;
+                if (ref($request_domains) eq 'HASH') {
+                    push(@{$request_domains->{$type}},$env{'user.domain'});
+                }
+                if ($dom eq $env{'user.domain'}) {
+                    $can_request->{$type} = 1;
+                }
+            }
+            if ($env{'environment.reqcrsotherdom.'.$type} ne '') {
+                my @curr = split(',',$env{'environment.reqcrsotherdom.'.$type});
+                if (@curr > 0) {
+                    foreach my $item (@curr) {
+                        if (ref($request_domains) eq 'HASH') {
+                            my ($otherdom) = ($item =~ /^($match_domain):($optregex)(=?\d*)$/);
+                            if ($otherdom ne '') {
+                                if (ref($request_domains->{$type}) eq 'ARRAY') {
+                                    unless (grep(/^\Q$otherdom\E$/,@{$request_domains->{$type}})) {
+                                        push(@{$request_domains->{$type}},$otherdom);
+                                    }
+                                } else {
+                                    push(@{$request_domains->{$type}},$otherdom);
+                                }
+                            }
+                        }
+                    }
+                    unless($dom eq $env{'user.domain'}) {
+                        $canreq ++;
+                        if (grep(/^\Q$dom\E:($optregex)(=?\d*)$/,@curr)) {
+                            $can_request->{$type} = 1;
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return $canreq;
+}
+
 # ---------------------------------------------- Custom access rule evaluation
 
 sub customaccess {
@@ -4926,17 +5131,68 @@ sub allowed {
     my $statecond=0;
     my $courseprivid='';
 
+    my $ownaccess;
+    # Community Coordinator or Assistant Co-author browsing resource space.
+    if (($priv eq 'bro') && ($env{'user.author'})) {
+        if ($uri eq '') {
+            $ownaccess = 1;
+        } else {
+            if (($env{'user.domain'} ne '') && ($env{'user.name'} ne '')) {
+                my $udom = $env{'user.domain'};
+                my $uname = $env{'user.name'};
+                if ($uri =~ m{^\Q$udom\E/?$}) {
+                    $ownaccess = 1;
+                } elsif ($uri =~ m{^\Q$udom\E/\Q$uname\E/?}) {
+                    unless ($uri =~ m{\.\./}) {
+                        $ownaccess = 1;
+                    }
+                } elsif (($udom ne 'public') && ($uname ne 'public')) {
+                    my $now = time;
+                    if ($uri =~ m{^([^/]+)/?$}) {
+                        my $adom = $1;
+                        foreach my $key (keys(%env)) {
+                            if ($key =~ m{^user\.role\.(ca|aa)/\Q$adom\E}) {
+                                my ($start,$end) = split('.',$env{$key});
+                                if (($now >= $start) && (!$end || $end < $now)) {
+                                    $ownaccess = 1;
+                                    last;
+                                }
+                            }
+                        }
+                    } elsif ($uri =~ m{^([^/]+)/([^/]+)/?}) {
+                        my $adom = $1;
+                        my $aname = $2;
+                        foreach my $role ('ca','aa') { 
+                            if ($env{"user.role.$role./$adom/$aname"}) {
+                                my ($start,$end) =
+                                    split('.',$env{"user.role.$role./$adom/$aname"});
+                                if (($now >= $start) && (!$end || $end < $now)) {
+                                    $ownaccess = 1;
+                                    last;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
 # Course
 
     if ($env{'user.priv.'.$env{'request.role'}.'./'}=~/\Q$priv\E\&([^\:]*)/) {
-       $thisallowed.=$1;
+        unless (($priv eq 'bro') && (!$ownaccess)) {
+            $thisallowed.=$1;
+        }
     }
 
 # Domain
 
     if ($env{'user.priv.'.$env{'request.role'}.'./'.(split(/\//,$uri))[0].'/'}
        =~/\Q$priv\E\&([^\:]*)/) {
-       $thisallowed.=$1;
+        unless (($priv eq 'bro') && (!$ownaccess)) {
+            $thisallowed.=$1;
+        }
     }
 
 # Course: uri itself is a course
@@ -4946,7 +5202,9 @@ sub allowed {
 
     if ($env{'user.priv.'.$env{'request.role'}.'.'.$courseuri}
        =~/\Q$priv\E\&([^\:]*)/) {
-       $thisallowed.=$1;
+        unless (($priv eq 'bro') && (!$ownaccess)) {
+            $thisallowed.=$1;
+        }
     }
 
 # URI is an uploaded document for this course, default permissions don't matter
@@ -5556,7 +5814,8 @@ sub auto_validate_instcode {
     }
     my $response=&unescape(&reply('autovalidateinstcode:'.$cdom.':'.
                            &escape($instcode).':'.&escape($owner),$homeserver));
-    return $response;
+    my ($outcome,$description) = map { &unescape($_); } split('&',$response,2);
+    return ($outcome,$description);
 }
 
 sub auto_create_password {
@@ -5673,6 +5932,13 @@ sub auto_instcode_format {
 		push(@homeservers,$tryserver);
 	    }
         }
+    } elsif ($caller eq 'requests') {
+        if ($codedom =~ /^$match_domain$/) {
+            my $chome = &domain($codedom,'primary');
+            unless ($chome eq 'no_host') {
+                push(@homeservers,$chome);
+            }
+        }
     } else {
         push(@homeservers,&homeserver($caller,$codedom));
     }
@@ -5773,10 +6039,39 @@ sub auto_possible_instcodes {
 
 sub auto_courserequest_checks {
     my ($dom) = @_;
-    my %validations;
+    my ($homeserver,%validations);
+    if ($dom =~ /^$match_domain$/) {
+        $homeserver = &domain($dom,'primary');
+    }
+    unless ($homeserver eq 'no_host') {
+        my $response=&reply('autocrsreqchecks:'.$dom,$homeserver);
+        unless ($response =~ /(con_lost|error|no_such_host|refused)/) {
+            my @items = split(/&/,$response);
+            foreach my $item (@items) {
+                my ($key,$value) = split('=',$item);
+                $validations{&unescape($key)} = &thaw_unescape($value);
+            }
+        }
+    }
     return %validations; 
 }
 
+sub auto_courserequest_validation {
+    my ($dom,$owner,$crstype,$inststatuslist,$instcode,$instseclist) = @_;
+    my ($homeserver,$response);
+    if ($dom =~ /^$match_domain$/) {
+        $homeserver = &domain($dom,'primary');
+    }
+    unless ($homeserver eq 'no_host') {  
+          
+        $response=&unescape(&reply('autocrsreqvalidation:'.$dom.':'.&escape($owner).
+                                    ':'.&escape($crstype).':'.&escape($inststatuslist).
+                                    ':'.&escape($instcode).':'.&escape($instseclist),
+                                    $homeserver));
+    }
+    return $response;
+}
+
 sub auto_validate_class_sec {
     my ($cdom,$cnum,$owners,$inst_class) = @_;
     my $homeserver = &homeserver($cnum,$cdom);
@@ -5929,30 +6224,37 @@ sub devalidate_getgroups_cache {
 
 sub plaintext {
     my ($short,$type,$cid,$forcedefault) = @_;
-    if ($short =~ /^cr/) {
+    if ($short =~ m{^cr/}) {
 	return (split('/',$short))[-1];
     }
     if (!defined($cid)) {
         $cid = $env{'request.course.id'};
     }
-    if (defined($cid) && ($env{'course.'.$cid.'.'.$short.'.plaintext'} ne '')) {
-        unless ($forcedefault) {
-            my $roletext = $env{'course.'.$cid.'.'.$short.'.plaintext'}; 
-            &Apache::lonlocal::mt_escape(\$roletext);
-            return &Apache::lonlocal::mt($roletext);
-        }
-    }
     my %rolenames = (
                       Course    => 'std',
                       Community => 'alt1',
                     );
-    if (defined($type) && 
-         defined($rolenames{$type}) && 
-         defined($prp{$short}{$rolenames{$type}})) {
+    if ($cid ne '') {
+        if ($env{'course.'.$cid.'.'.$short.'.plaintext'} ne '') {
+            unless ($forcedefault) {
+                my $roletext = $env{'course.'.$cid.'.'.$short.'.plaintext'}; 
+                &Apache::lonlocal::mt_escape(\$roletext);
+                return &Apache::lonlocal::mt($roletext);
+            }
+        }
+    }
+    if ((defined($type)) && (defined($rolenames{$type})) &&
+        (defined($rolenames{$type})) && 
+        (defined($prp{$short}{$rolenames{$type}}))) {
         return &Apache::lonlocal::mt($prp{$short}{$rolenames{$type}});
-    } else {
-        return &Apache::lonlocal::mt($prp{$short}{'std'});
+    } elsif ($cid ne '') {
+        my $crstype = $env{'course.'.$cid.'.type'};
+        if (($crstype ne '') && (defined($rolenames{$crstype})) &&
+            (defined($prp{$short}{$rolenames{$crstype}}))) {
+            return &Apache::lonlocal::mt($prp{$short}{$rolenames{$crstype}});
+        }
     }
+    return &Apache::lonlocal::mt($prp{$short}{'std'});
 }
 
 # ----------------------------------------------------------------- Assign Role
@@ -5965,10 +6267,27 @@ sub assignrole {
         my $cwosec=$url;
         $cwosec=~s/^\/($match_domain)\/($match_courseid)\/.*/$1\/$2/;
 	unless (&allowed('ccr',$cwosec)) {
-           &logthis('Refused custom assignrole: '.
-             $udom.' '.$uname.' '.$url.' '.$role.' '.$end.' '.$start.' by '.
-		    $env{'user.name'}.' at '.$env{'user.domain'});
-           return 'refused'; 
+           my $refused = 1;
+           if ($context eq 'requestcourses') {
+               if (($env{'user.name'} ne '') && ($env{'user.domain'} ne '')) {
+                   if ($role =~ m{^cr/($match_domain)/($match_username)/([^/]+)$}) {
+                       if (($1 eq $env{'user.domain'}) && ($2 eq $env{'user.name'})) {
+                           my ($cdom,$cnum) = ($cwosec =~ m{^/?($match_domain)/($match_courseid)$});
+                           my %crsenv = &userenvironment($cdom,$cnum,('internal.courseowner'));
+                           if ($crsenv{'internal.courseowner'} eq
+                               $env{'user.name'}.':'.$env{'user.domain'}) {
+                               $refused = '';
+                           }
+                       }
+                   }
+               }
+           }
+           if ($refused) {
+               &logthis('Refused custom assignrole: '.
+                        $udom.' '.$uname.' '.$url.' '.$role.' '.$end.' '.$start.
+                        ' by '.$env{'user.name'}.' at '.$env{'user.domain'});
+               return 'refused';
+           }
         }
         $mrole='cr';
     } elsif ($role =~ /^gr\//) {
@@ -5994,9 +6313,48 @@ sub assignrole {
                 $refused = 1;
             }
             if ($refused) {
-                if (($selfenroll == 1) && ($role eq 'st') && ($udom eq $env{'user.domain'}) && ($uname eq $env{'user.name'})) {
+                my ($cdom,$cnum) = ($cwosec =~ m{^/?($match_domain)/($match_courseid)$});
+                if (!$selfenroll && $context eq 'course') {
+                    my %crsenv;
+                    if ($role eq 'cc' || $role eq 'co') {
+                        %crsenv = &userenvironment($cdom,$cnum,('internal.courseowner'));
+                        if (($role eq 'cc') && ($cnum !~ /^$match_community$/)) {
+                            if ($env{'request.role'} eq 'cc./'.$cdom.'/'.$cnum) {
+                                if ($crsenv{'internal.courseowner'} eq 
+                                    $env{'user.name'}.':'.$env{'user.domain'}) {
+                                    $refused = '';
+                                }
+                            }
+                        } elsif (($role eq 'co') && ($cnum =~ /^$match_community$/)) { 
+                            if ($env{'request.role'} eq 'co./'.$cdom.'/'.$cnum) {
+                                if ($crsenv{'internal.courseowner'} eq 
+                                    $env{'user.name'}.':'.$env{'user.domain'}) {
+                                    $refused = '';
+                                }
+                            }
+                        }
+                    }
+                } elsif (($selfenroll == 1) && ($role eq 'st') && ($udom eq $env{'user.domain'}) && ($uname eq $env{'user.name'})) {
                     $refused = '';
-                } else {
+                } elsif ($context eq 'requestcourses') {
+                    my @possroles = ('st','ta','ep','in','cc','co');
+                    if ((grep(/^\Q$role\E$/,@possroles)) && ($env{'user.name'} ne '' && $env{'user.domain'} ne '')) {
+                        my $wrongcc;
+                        if ($cnum =~ /^$match_community$/) {
+                            $wrongcc = 1 if ($role eq 'cc');
+                        } else {
+                            $wrongcc = 1 if ($role eq 'co');
+                        }
+                        unless ($wrongcc) {
+                            my %crsenv = &userenvironment($cdom,$cnum,('internal.courseowner'));
+                            if ($crsenv{'internal.courseowner'} eq 
+                                 $env{'user.name'}.':'.$env{'user.domain'}) {
+                                $refused = '';
+                            }
+                        }
+                    }
+                }
+                if ($refused) {
                     &logthis('Refused assignrole: '.$udom.' '.$uname.' '.$url.
                              ' '.$role.' '.$end.' '.$start.' by '.
 	  	             $env{'user.name'}.' at '.$env{'user.domain'});
@@ -6043,10 +6401,97 @@ sub assignrole {
             &Apache::longroup::group_changes($udom,$uname,$url,$role,$origend,
                                              $origstart,$selfenroll,$context);
         }
+        if ($role eq 'cc') {
+            &autoupdate_coowners($url,$end,$start,$uname,$udom);
+        }
     }
     return $answer;
 }
 
+sub autoupdate_coowners {
+    my ($url,$end,$start,$uname,$udom) = @_;
+    my ($cdom,$cnum) = ($url =~ m{^/($match_domain)/($match_courseid)});
+    if (($cdom ne '') && ($cnum ne '')) {
+        my $now = time;
+        my %domdesign = &Apache::loncommon::get_domainconf($cdom);
+        if ($domdesign{$cdom.'.autoassign.co-owners'}) {
+            my %coursehash = &coursedescription($cdom.'_'.$cnum);
+            my $instcode = $coursehash{'internal.coursecode'};
+            if ($instcode ne '') {
+                if (($start && $start <= $now) && ($end == 0) || ($end > $now)) {
+                    unless ($coursehash{'internal.courseowner'} eq $uname.':'.$udom) {
+                        my ($delcoowners,@newcoowners,$putresult,$delresult,$coowners);
+                        my ($result,$desc) = &auto_validate_instcode($cnum,$cdom,$instcode,$uname.':'.$udom);
+                        if ($result eq 'valid') {
+                            if ($coursehash{'internal.co-owners'}) {
+                                foreach my $coowner (split(',',$coursehash{'internal.co-owners'})) {
+                                    push(@newcoowners,$coowner);
+                                }
+                                unless (grep(/^\Q$uname\E:\Q$udom\E$/,@newcoowners)) {
+                                    push(@newcoowners,$uname.':'.$udom);
+                                }
+                                @newcoowners = sort(@newcoowners);
+                            } else {
+                                push(@newcoowners,$uname.':'.$udom);
+                            }
+                        } else {
+                            if ($coursehash{'internal.co-owners'}) {
+                                foreach my $coowner (split(',',$coursehash{'internal.co-owners'})) {
+                                    unless ($coowner eq $uname.':'.$udom) {
+                                        push(@newcoowners,$coowner);
+                                    }
+                                }
+                                unless (@newcoowners > 0) {
+                                    $delcoowners = 1;
+                                    $coowners = '';
+                                }
+                            }
+                        }
+                        if (@newcoowners || $delcoowners) {
+                            &store_coowners($cdom,$cnum,$coursehash{'home'},
+                                            $delcoowners,@newcoowners);
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+sub store_coowners {
+    my ($cdom,$cnum,$chome,$delcoowners,@newcoowners) = @_;
+    my $cid = $cdom.'_'.$cnum;
+    my ($coowners,$delresult,$putresult);
+    if (@newcoowners) {
+        $coowners = join(',',@newcoowners);
+        my %coownershash = (
+                            'internal.co-owners' => $coowners,
+                           );
+        $putresult = &put('environment',\%coownershash,$cdom,$cnum);
+        if ($putresult eq 'ok') {
+            if ($env{'course.'.$cid.'.num'} eq $cnum) {
+                &appenv({'course.'.$cid.'.internal.co-owners' => $coowners});
+            }
+        }
+    }
+    if ($delcoowners) {
+        $delresult = &Apache::lonnet::del('environment',['internal.co-owners'],$cdom,$cnum);
+        if ($delresult eq 'ok') {
+            if ($env{'course.'.$cid.'.internal.co-owners'}) {
+                &Apache::lonnet::delenv('course.'.$cid.'.internal.co-owners');
+            }
+        }
+    }
+    if (($putresult eq 'ok') || ($delresult eq 'ok')) {
+        my %crsinfo =
+            &Apache::lonnet::courseiddump($cdom,'.',1,'.','.',$cnum,undef,undef,'.');
+        if (ref($crsinfo{$cid}) eq 'HASH') {
+            $crsinfo{$cid}{'co-owners'} = \@newcoowners;
+            my $cidput = &Apache::lonnet::courseidput($cdom,\%crsinfo,$chome,'notime');
+        }
+    }
+}
+
 # -------------------------------------------------- Modify user authentication
 # Overrides without validation
 
@@ -6320,10 +6765,41 @@ sub writecoursepref {
 
 sub createcourse {
     my ($udom,$description,$url,$course_server,$nonstandard,$inst_code,
-        $course_owner,$crstype,$cnum)=@_;
+        $course_owner,$crstype,$cnum,$context,$category)=@_;
     $url=&declutter($url);
     my $cid='';
-    unless (&allowed('ccc',$udom)) {
+    if ($context eq 'requestcourses') {
+        my $can_create = 0;
+        my ($ownername,$ownerdom) = split(':',$course_owner);
+        if ($udom eq $ownerdom) {
+            if (&usertools_access($ownername,$ownerdom,$category,undef,
+                                  $context)) {
+                $can_create = 1;
+            }
+        } else {
+            my %userenv = &userenvironment($ownerdom,$ownername,'reqcrsotherdom.'.
+                                           $category);
+            if ($userenv{'reqcrsotherdom.'.$category} ne '') {
+                my @curr = split(',',$userenv{'reqcrsotherdom.'.$category});
+                if (@curr > 0) {
+                    my @options = qw(approval validate autolimit);
+                    my $optregex = join('|',@options);
+                    if (grep(/^\Q$udom\E:($optregex)(=?\d*)$/,@curr)) {
+                        $can_create = 1;
+                    }
+                }
+            }
+        }
+        if ($can_create) {
+            unless ($ownername eq $env{'user.name'} && $ownerdom eq $env{'user.domain'}) {
+                unless (&allowed('ccc',$udom)) {
+                    return 'refused'; 
+                }
+            }
+        } else {
+            return 'refused';
+        }
+    } elsif (!&allowed('ccc',$udom)) {
         return 'refused';
     }
 # --------------------------------------------------------------- Get Unique ID
@@ -6333,16 +6809,24 @@ sub createcourse {
         if (($chome eq '') || ($chome eq 'no_host')) {
             $uname = $cnum;
         } else {
-            $uname = &generate_coursenum($udom);
+            $uname = &generate_coursenum($udom,$crstype);
         }
     } else {
-        $uname = &generate_coursenum($udom);
+        $uname = &generate_coursenum($udom,$crstype);
     }
     return $uname if ($uname =~ /^error/);
 # -------------------------------------------------- Check supplied server name
-    $course_server = $env{'user.homeserver'} if (! defined($course_server));
-    if (! &is_library($course_server)) {
-        return 'error:bad server name '.$course_server;
+    if (!defined($course_server)) {
+        if (defined(&domain($udom,'primary'))) {
+            $course_server = &domain($udom,'primary');
+        } else {
+            $course_server = $env{'user.home'}; 
+        }
+    }
+    my %host_servers =
+        &Apache::lonnet::get_servers($udom,'library');
+    unless ($host_servers{$course_server}) {
+        return 'error: invalid home server for course: '.$course_server;
     }
 # ------------------------------------------------------------- Make the course
     my $reply=&reply('encrypt:makeuser:'.$udom.':'.$uname.':none::',
@@ -6354,12 +6838,17 @@ sub createcourse {
     }
 # ----------------------------------------------------------------- Course made
 # log existence
+    my $now = time;
     my $newcourse = {
                     $udom.'_'.$uname => {
                                      description => $description,
                                      inst_code   => $inst_code,
                                      owner       => $course_owner,
                                      type        => $crstype,
+                                     creator     => $env{'user.name'}.':'.
+                                                    $env{'user.domain'},
+                                     created     => $now,
+                                     context     => $context,
                                                 },
                     };
     &courseidput($udom,$newcourse,$uhome,'notime');
@@ -6384,24 +6873,40 @@ ENDINITMAP
     }
 # ----------------------------------------------------------- Write preferences
     &writecoursepref($udom.'_'.$uname,
-                     ('description' => $description,
-                      'url'         => $topurl));
+                     ('description'              => $description,
+                      'url'                      => $topurl,
+                      'internal.creator'         => $env{'user.name'}.':'.
+                                                    $env{'user.domain'},
+                      'internal.created'         => $now,
+                      'internal.creationcontext' => $context)
+                    );
     return '/'.$udom.'/'.$uname;
 }
 
 # ------------------------------------------------------------------- Create ID
 sub generate_coursenum {
-    my ($udom) = @_;
+    my ($udom,$crstype) = @_;
     my $domdesc = &domain($udom);
     return 'error: invalid domain' if ($domdesc eq '');
-    my $uname=int(1+rand(9)).
+    my $first;
+    if ($crstype eq 'Community') {
+        $first = '0';
+    } else {
+        $first = int(1+rand(9)); 
+    } 
+    my $uname=$first.
         ('a'..'z','A'..'Z','0'..'9')[int(rand(62))].
         substr($$.time,0,5).unpack("H8",pack("I32",time)).
         unpack("H2",pack("I32",int(rand(255)))).$perlvar{'lonHostID'};
 # ----------------------------------------------- Make sure that does not exist
     my $uhome=&homeserver($uname,$udom,'true');
     unless (($uhome eq '') || ($uhome eq 'no_host')) {
-        $uname=int(1+rand(9)).
+        if ($crstype eq 'Community') {
+            $first = '0';
+        } else {
+            $first = int(1+rand(9));
+        }
+        $uname=$first.
                ('a'..'z','A'..'Z','0'..'9')[int(rand(62))].
                substr($$.time,0,5).unpack("H8",pack("I32",time)).
                unpack("H2",pack("I32",int(rand(255)))).$perlvar{'lonHostID'};
@@ -6423,6 +6928,39 @@ sub is_course {
     return 0;
 }
 
+sub store_userdata {
+    my ($storehash,$datakey,$namespace,$udom,$uname) = @_;
+    my $result;
+    if ($datakey ne '') {
+        if (ref($storehash) eq 'HASH') {
+            if ($udom eq '' || $uname eq '') {
+                $udom = $env{'user.domain'};
+                $uname = $env{'user.name'};
+            }
+            my $uhome=&homeserver($uname,$udom);
+            if (($uhome eq '') || ($uhome eq 'no_host')) {
+                $result = 'error: no_host';
+            } else {
+                $storehash->{'ip'} = $ENV{'REMOTE_ADDR'};
+                $storehash->{'host'} = $perlvar{'lonHostID'};
+
+                my $namevalue='';
+                foreach my $key (keys(%{$storehash})) {
+                    $namevalue.=&escape($key).'='.&freeze_escape($$storehash{$key}).'&';
+                }
+                $namevalue=~s/\&$//;
+                $result =  &reply("store:$env{'user.domain'}:$env{'user.name'}:".
+                                  "$namespace:$datakey:$namevalue",$uhome);
+            }
+        } else {
+            $result = 'error: data to store was not a hash reference'; 
+        }
+    } else {
+        $result= 'error: invalid requestkey'; 
+    }
+    return $result;
+}
+
 # ---------------------------------------------------------- Assign Custom Role
 
 sub assigncustomrole {
@@ -7912,6 +8450,11 @@ sub devalidate_title_cache {
     &devalidate_cache_new('title',$key);
 }
 
+# ------------------------------------------------- Get the title of a course
+
+sub current_course_title {
+    return $env{ 'course.' . $env{'request.course.id'} . '.description' };
+}
 # ------------------------------------------------- Get the title of a resource
 
 sub gettitle {
@@ -8015,6 +8558,9 @@ sub symbverify {
 
     if (tie(%bighash,'GDBM_File',$env{'request.course.fn'}.'.db',
                             &GDBM_READER(),0640)) {
+        if (($thisurl =~ m{^/adm/wrapper/ext/}) || ($thisurl =~ m{^ext/})) {
+            $thisurl =~ s/\?.+$//;
+        }
         my $ids=$bighash{'ids_'.&clutter($thisurl)};
         unless ($ids) { 
            $ids=$bighash{'ids_/'.$thisurl};
@@ -8023,6 +8569,9 @@ sub symbverify {
 # ------------------------------------------------------------------- Has ID(s)
 	    foreach my $id (split(/\,/,$ids)) {
 	       my ($mapid,$resid)=split(/\./,$id);
+               if ($thisfn =~ m{^/adm/wrapper/ext/}) {
+                   $symb =~ s/\?.+$//;
+               }
                if (
   &symbclean(&declutter($bighash{'map_id_'.$mapid}).'___'.$resid.'___'.$thisfn)
    eq $symb) { 
@@ -8899,7 +9448,9 @@ sub declutter {
     $thisfn=~s|^adm/wrapper/||;
     $thisfn=~s|^adm/coursedocs/showdoc/||;
     $thisfn=~s/^res\///;
-    $thisfn=~s/\?.+$//;
+    unless (($thisfn =~ /^ext/) || ($thisfn =~ /\.(page|sequence)___\d+___ext/)) {
+        $thisfn=~s/\?.+$//;
+    }
     return $thisfn;
 }
 
@@ -8911,8 +9462,8 @@ sub clutter {
 	|| $thisfn =~ m{^/adm/(includes|pages)} ) { 
        $thisfn='/res'.$thisfn; 
     }
-    if ($thisfn !~m|/adm|) {
-	if ($thisfn =~ m|/ext/|) {
+    if ($thisfn !~m|^/adm|) {
+	if ($thisfn =~ m|^/ext/|) {
 	    $thisfn='/adm/wrapper'.$thisfn;
 	} else {
 	    my ($ext) = ($thisfn =~ /\.(\w+)$/);
@@ -9939,7 +10490,7 @@ createcourse($udom,$description,$url,$co
 
 =item *
 
-generate_coursenum($udom) : get a unique (unused) course number in domain $udom
+generate_coursenum($udom,$crstype) : get a unique (unused) course number in domain $udom for course type $crstype (Course or Community).
 
 =back