--- loncom/lonnet/perl/lonnet.pm	2009/07/31 02:20:17	1.1008
+++ loncom/lonnet/perl/lonnet.pm	2009/10/08 19:54:31	1.1030
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # TCP networking package
 #
-# $Id: lonnet.pm,v 1.1008 2009/07/31 02:20:17 raeburn Exp $
+# $Id: lonnet.pm,v 1.1030 2009/10/08 19:54:31 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -92,6 +92,7 @@ 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;
 
@@ -784,7 +785,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 +811,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 +963,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 +1051,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);
@@ -1665,12 +1718,14 @@ sub userenvironment {
     }
     $items=~s/\&$//;
     my %returnhash=();
-    my @answer=split(/\&/,
-                &reply('get:'.$udom.':'.$unam.':environment:'.$items,
-                      &homeserver($unam,$udom)));
-    my $i;
-    for ($i=0;$i<=$#what;$i++) {
-	$returnhash{$what[$i]}=&unescape($answer[$i]);
+    my $uhome = &homeserver($unam,$udom);
+    unless ($uhome eq 'no_host') {
+        my @answer=split(/\&/, 
+            &reply('get:'.$udom.':'.$unam.':environment:'.$items,$uhome));
+        my $i;
+        for ($i=0;$i<=$#what;$i++) {
+	    $returnhash{$what[$i]}=&unescape($answer[$i]);
+        }
     }
     return %returnhash;
 }
@@ -2009,9 +2064,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,
@@ -2253,11 +2312,15 @@ sub finishuserfileupload {
 	}
     }
     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+$/)) {
@@ -2968,7 +3031,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=''; }
@@ -2988,7 +3052,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);
@@ -3043,10 +3109,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;
@@ -4669,7 +4735,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'};
     }
@@ -5554,7 +5620,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 {
@@ -5671,6 +5738,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));
     }
@@ -5769,6 +5843,41 @@ sub auto_possible_instcodes {
     return $response;
 }
 
+sub auto_courserequest_checks {
+    my ($dom) = @_;
+    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);
@@ -5957,10 +6066,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\//) {
@@ -5988,7 +6114,18 @@ sub assignrole {
             if ($refused) {
                 if (($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');
+                    if ((grep(/^\Q$role\E$/,@possroles)) && ($env{'user.name'} ne '' && $env{'user.domain'} ne '')) {
+                        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 assignrole: '.$udom.' '.$uname.' '.$url.
                              ' '.$role.' '.$end.' '.$start.' by '.
 	  	             $env{'user.name'}.' at '.$env{'user.domain'});
@@ -6312,28 +6449,57 @@ sub writecoursepref {
 
 sub createcourse {
     my ($udom,$description,$url,$course_server,$nonstandard,$inst_code,
-        $course_owner,$crstype)=@_;
+        $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';
     }
-# ------------------------------------------------------------------- Create ID
-   my $uname=int(1+rand(9)).
-       ('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=substr($$.time,0,5).unpack("H8",pack("I32",time)).
-        unpack("H2",pack("I32",int(rand(255)))).$perlvar{'lonHostID'};
-       $uhome=&homeserver($uname,$udom,'true');       
-       unless (($uhome eq '') || ($uhome eq 'no_host')) {
-           return 'error: unable to generate unique course-ID';
-       } 
-   }
-# ------------------------------------------------ Check supplied server name
+# --------------------------------------------------------------- Get Unique ID
+    my $uname;
+    if ($cnum =~ /^$match_courseid$/) {
+        my $chome=&homeserver($cnum,$udom,'true');
+        if (($chome eq '') || ($chome eq 'no_host')) {
+            $uname = $cnum;
+        } else {
+            $uname = &generate_coursenum($udom);
+        }
+    } else {
+        $uname = &generate_coursenum($udom);
+    }
+    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;
@@ -6342,18 +6508,23 @@ sub createcourse {
     my $reply=&reply('encrypt:makeuser:'.$udom.':'.$uname.':none::',
                       $course_server);
     unless ($reply eq 'ok') { return 'error: '.$reply; }
-    $uhome=&homeserver($uname,$udom,'true');
+    my $uhome=&homeserver($uname,$udom,'true');
     if (($uhome eq '') || ($uhome eq 'no_host')) { 
 	return 'error: no such course';
     }
 # ----------------------------------------------------------------- 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');
@@ -6383,6 +6554,30 @@ ENDINITMAP
     return '/'.$udom.'/'.$uname;
 }
 
+# ------------------------------------------------------------------- Create ID
+sub generate_coursenum {
+    my ($udom) = @_;
+    my $domdesc = &domain($udom);
+    return 'error: invalid domain' if ($domdesc eq '');
+    my $uname=int(1+rand(9)).
+        ('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)).
+               ('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'};
+        $uhome=&homeserver($uname,$udom,'true');
+        unless (($uhome eq '') || ($uhome eq 'no_host')) {
+            return 'error: unable to generate unique course-ID';
+        }
+    }
+    return $uname;
+}
+
 sub is_course {
     my ($cdom,$cnum) = @_;
     my %courses = &courseiddump($cdom,'.',1,'.','.',$cnum,undef,
@@ -6393,6 +6588,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 {
@@ -7882,6 +8110,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 {
@@ -9905,7 +10138,11 @@ database) for a course
 
 =item *
 
-createcourse($udom,$description,$url) : make/modify course
+createcourse($udom,$description,$url,$course_server,$nonstandard,$inst_code,$course_owner,$crstype,$cnum) : make course
+
+=item *
+
+generate_coursenum($udom) : get a unique (unused) course number in domain $udom
 
 =back