--- loncom/lonnet/perl/lonnet.pm	2010/01/16 13:46:05	1.1049
+++ loncom/lonnet/perl/lonnet.pm	2010/03/26 00:47:25	1.1060
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # TCP networking package
 #
-# $Id: lonnet.pm,v 1.1049 2010/01/16 13:46:05 raeburn Exp $
+# $Id: lonnet.pm,v 1.1060 2010/03/26 00:47:25 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -99,8 +99,6 @@ 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);
@@ -2156,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
@@ -2196,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);
@@ -2253,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,
@@ -2265,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'};
@@ -2276,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'};
   
@@ -2314,10 +2327,12 @@ 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') {
@@ -3004,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 = '';
@@ -3088,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 {
@@ -3148,7 +3207,7 @@ sub get_domain_roles {
     return %personnel;
 }
 
-# ----------------------------------------------------------- Check out an item
+# ----------------------------------------------------------- Interval timing 
 
 sub get_first_access {
     my ($type,$argsymb)=@_;
@@ -3184,91 +3243,6 @@ sub set_first_access {
     return 'already_set';
 }
 
-sub checkout {
-    my ($symb,$tuname,$tudom,$tcrsid)=@_;
-    my $now=time;
-    my $lonhost=$perlvar{'lonHostID'};
-    my $infostr=&escape(
-                 'CHECKOUTTOKEN&'.
-                 $tuname.'&'.
-                 $tudom.'&'.
-                 $tcrsid.'&'.
-                 $symb.'&'.
-		 $now.'&'.$ENV{'REMOTE_ADDR'});
-    my $token=&reply('tmpput:'.$infostr,$lonhost);
-    if ($token=~/^error\:/) { 
-        &logthis("<font color=\"blue\">WARNING: ".
-                "Checkout tmpput failed ".$tudom.' - '.$tuname.' - '.$symb.
-                 "</font>");
-        return ''; 
-    }
-
-    $token=~s/^(\d+)\_.*\_(\d+)$/$1\*$2\*$lonhost/;
-    $token=~tr/a-z/A-Z/;
-
-    my %infohash=('resource.0.outtoken' => $token,
-                  'resource.0.checkouttime' => $now,
-                  'resource.0.outremote' => $ENV{'REMOTE_ADDR'});
-
-    unless (&cstore(\%infohash,$symb,$tcrsid,$tudom,$tuname) eq 'ok') {
-       return '';
-    } else {
-        &logthis("<font color=\"blue\">WARNING: ".
-                "Checkout cstore failed ".$tudom.' - '.$tuname.' - '.$symb.
-                 "</font>");
-    }    
-
-    if (&log($tudom,$tuname,&homeserver($tuname,$tudom),
-                         &escape('Checkout '.$infostr.' - '.
-                                                 $token)) ne 'ok') {
-	return '';
-    } else {
-        &logthis("<font color=\"blue\">WARNING: ".
-                "Checkout log failed ".$tudom.' - '.$tuname.' - '.$symb.
-                 "</font>");
-    }
-    return $token;
-}
-
-# ------------------------------------------------------------ Check in an item
-
-sub checkin {
-    my $token=shift;
-    my $now=time;
-    my ($ta,$tb,$lonhost)=split(/\*/,$token);
-    $lonhost=~tr/A-Z/a-z/;
-    my $dtoken=$ta.'_'.&hostname($lonhost).'_'.$tb;
-    $dtoken=~s/\W/\_/g;
-    my ($dummy,$tuname,$tudom,$tcrsid,$symb,$chtim,$rmaddr)=
-                 split(/\&/,&unescape(&reply('tmpget:'.$dtoken,$lonhost)));
-
-    unless (($tuname) && ($tudom)) {
-        &logthis('Check in '.$token.' ('.$dtoken.') failed');
-        return '';
-    }
-    
-    unless (&allowed('mgr',$tcrsid)) {
-        &logthis('Check in '.$token.' ('.$dtoken.') unauthorized: '.
-                 $env{'user.name'}.' - '.$env{'user.domain'});
-        return '';
-    }
-
-    my %infohash=('resource.0.intoken' => $token,
-                  'resource.0.checkintime' => $now,
-                  'resource.0.inremote' => $ENV{'REMOTE_ADDR'});
-
-    unless (&cstore(\%infohash,$symb,$tcrsid,$tudom,$tuname) eq 'ok') {
-       return '';
-    }    
-
-    if (&log($tudom,$tuname,&homeserver($tuname,$tudom),
-                         &escape('Checkin - '.$token)) ne 'ok') {
-	return '';
-    }
-
-    return ($symb,$tuname,$tudom,$tcrsid);    
-}
-
 # --------------------------------------------- Set Expire Date for Spreadsheet
 
 sub expirespread {
@@ -4565,7 +4539,7 @@ sub get_portfolio_access {
                 my (%allgroups,%allroles); 
                 my ($start,$end,$role,$sec,$group);
                 foreach my $envkey (%env) {
-                    if ($envkey =~ m-^user\.role\.(gr|cc|in|ta|ep|st)\./($match_domain)/($match_courseid)/?([^/]*)$-) {
+                    if ($envkey =~ m-^user\.role\.(gr|cc|co|in|ta|ep|ad|st)\./($match_domain)/($match_courseid)/?([^/]*)$-) {
                         my $cid = $2.'_'.$3; 
                         if ($1 eq 'gr') {
                             $group = $4;
@@ -4813,6 +4787,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);
@@ -6321,10 +6316,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
 
@@ -6357,12 +6439,18 @@ sub modifyuser {
     my ($udom,    $uname, $uid,
         $umode,   $upass, $first,
         $middle,  $last,  $gene,
-        $forceid, $desiredhome, $email, $inststatus)=@_;
+        $forceid, $desiredhome, $email, $inststatus, $candelete)=@_;
     $udom= &LONCAPA::clean_domain($udom);
     $uname=&LONCAPA::clean_username($uname);
+    my $showcandelete = 'none';
+    if (ref($candelete) eq 'ARRAY') {
+        if (@{$candelete} > 0) {
+            $showcandelete = join(', ',@{$candelete});
+        }
+    }
     &logthis('Call to modify user '.$udom.', '.$uname.', '.$uid.', '.
              $umode.', '.$first.', '.$middle.', '.
-	     $last.', '.$gene.'(forceid: '.$forceid.')'.
+	     $last.', '.$gene.'(forceid: '.$forceid.'; candelete: '.$showcandelete.')'.
              (defined($desiredhome) ? ' desiredhome = '.$desiredhome :
                                      ' desiredhome not specified'). 
              ' by '.$env{'user.name'}.' at '.$env{'user.domain'}.
@@ -6427,9 +6515,33 @@ sub modifyuser {
         %names = @tmp;
     }
 #
-# Make sure to not trash student environment if instructor does not bother
-# to supply name and email information
-#
+# If name, email and/or uid are blank (e.g., because an uploaded file
+# of users did not contain them), do not overwrite existing values
+# unless field is in $candelete array ref.  
+#
+
+    my @fields = ('firstname','middlename','lastname','generation',
+                  'permanentemail','id');
+    my %newvalues;
+    if (ref($candelete) eq 'ARRAY') {
+        foreach my $field (@fields) {
+            if (grep(/^\Q$field\E$/,@{$candelete})) {
+                if ($field eq 'firstname') {
+                    $names{$field} = $first;
+                } elsif ($field eq 'middlename') {
+                    $names{$field} = $middle;
+                } elsif ($field eq 'lastname') {
+                    $names{$field} = $last;
+                } elsif ($field eq 'generation') { 
+                    $names{$field} = $gene;
+                } elsif ($field eq 'permanentemail') {
+                    $names{$field} = $email;
+                } elsif ($field eq 'id') {
+                    $names{$field}  = $uid;
+                }
+            }
+        }
+    }
     if ($first)  { $names{'firstname'}  = $first; }
     if (defined($middle)) { $names{'middlename'} = $middle; }
     if ($last)   { $names{'lastname'}   = $last; }
@@ -6649,9 +6761,17 @@ sub createcourse {
     }
     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::',
@@ -6698,8 +6818,13 @@ 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;
 }
 
@@ -10143,9 +10268,16 @@ modifyuserauth($udom,$uname,$umode,$upas
 
 =item *
 
-modifyuser($udom,$uname,$uid,$umode,$upass,$first,$middle,$last,$gene,
-           $forceid,$desiredhome,$email,$inststatus) : 
-modify user
+modifyuser($udom,$uname,$uid,$umode,$upass,$first,$middle,$last, $gene,
+           $forceid,$desiredhome,$email,$inststatus,$candelete) :
+
+will update user information (firstname,middlename,lastname,generation,
+permanentemail), and if forceid is true, student/employee ID also.
+A user's institutional affiliation(s) can also be updated.
+User information fields will not be overwritten with empty entries 
+unless the field is included in the $candelete array reference.
+This array is included when a single user is modified via "Manage Users",
+or when Autoupdate.pl is run by cron in a domain.
 
 =item *