--- loncom/interface/lonuserutils.pm	2017/07/07 02:50:41	1.184
+++ loncom/interface/lonuserutils.pm	2025/01/13 01:00:58	1.225
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Utility functions for managing LON-CAPA user accounts
 #
-# $Id: lonuserutils.pm,v 1.184 2017/07/07 02:50:41 raeburn Exp $
+# $Id: lonuserutils.pm,v 1.225 2025/01/13 01:00:58 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -50,6 +50,7 @@ use strict;
 use Apache::lonnet;
 use Apache::loncommon();
 use Apache::lonhtmlcommon;
+use Apache::loncoursequeueadmin;
 use Apache::lonlocal;
 use Apache::longroup;
 use HTML::Entities;
@@ -103,7 +104,7 @@ sub modifystudent {
 sub modifyuserrole {
     my ($context,$setting,$changeauth,$cid,$udom,$uname,$uid,$umode,$upass,
         $first,$middle,$last,$gene,$sec,$forceid,$desiredhome,$email,$role,
-        $end,$start,$checkid,$inststatus) = @_;
+        $end,$start,$checkid,$inststatus,$emptyok) = @_;
     my ($scope,$userresult,$authresult,$roleresult,$idresult);
     if ($setting eq 'course' || $context eq 'course') {
         $scope = '/'.$cid;
@@ -114,7 +115,11 @@ sub modifyuserrole {
     } elsif ($context eq 'domain') {
         $scope = '/'.$env{'request.role.domain'}.'/';
     } elsif ($context eq 'author') {
-        $scope =  '/'.$env{'user.domain'}.'/'.$env{'user.name'};
+        if ($env{'request.role'} =~ m{^ca\.(/$match_domain/$match_username)$}) {
+            $scope = $1;
+        } else {
+            $scope =  '/'.$env{'user.domain'}.'/'.$env{'user.name'};
+        }
     }
     if ($context eq 'domain') {
         my $uhome = &Apache::lonnet::homeserver($uname,$udom);
@@ -134,6 +139,25 @@ sub modifyuserrole {
                                   generation => $gene,
                                   id         => $uid,
                                  );
+
+                # When "Update ID in user's course(s)" and "Force change of existing ID"
+                # checkboxes both checked, prevent replacement of name information
+                # in classlist.db file(s) for the user's course(s) with blank(s),
+                # in the case where the uploaded csv file was without column(s) for
+                # the particular field. Fields are: First Name, Middle Names/Initials,
+                # Last Name (or the composite: Last Name, First Names), and Generation.
+
+                my %emptyallowed;
+                if ((ref($emptyok) eq 'HASH') && (keys(%{$emptyok}) > 0)) {
+                    %emptyallowed = %{$emptyok};
+                }
+                foreach my $field (keys(%userupdate)) {
+                    if ($userupdate{$field} eq '') {
+                        unless ($emptyallowed{$field}) {
+                            delete($userupdate{$field});
+                        }
+                    }
+                }
                 $idresult = &propagate_id_change($uname,$udom,\%userupdate);
             }
         }
@@ -153,6 +177,560 @@ sub modifyuserrole {
     return ($userresult,$authresult,$roleresult,$idresult);
 }
 
+sub role_approval {
+    my ($dom,$context,$process_by,$notifydc) = @_;
+    if (ref($process_by) eq 'HASH') {
+        my %domconfig = &Apache::lonnet::get_dom('configuration',['privacy'],$dom);
+        if (ref($domconfig{'privacy'}) eq 'HASH') {
+            if (ref($notifydc) eq 'ARRAY') {
+                if ($domconfig{'privacy'}{'notify'} ne '') {
+                    @{$notifydc} = split(/,/,$domconfig{'privacy'}{'notify'});
+                }
+            }
+            if (ref($domconfig{'privacy'}{'approval'}) eq 'HASH') {
+                my %approvalconf = %{$domconfig{'privacy'}{'approval'}};
+                foreach my $key ('instdom','extdom') {
+                    if (ref($approvalconf{$key}) eq 'HASH') {
+                        if (keys(%{$approvalconf{$key}})) {
+                            $process_by->{$key} = $approvalconf{$key}{$context};
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return;
+}
+
+sub get_instdoms {
+    my ($udom,$instdoms) = @_;
+    return unless (ref($instdoms) eq 'ARRAY');
+    my @intdoms;
+    my %iphost = &Apache::lonnet::get_iphost();
+    my $primary_id = &Apache::lonnet::domain($udom,'primary');
+    my $primary_ip = &Apache::lonnet::get_host_ip($primary_id);
+    if (ref($iphost{$primary_ip}) eq 'ARRAY') {
+        foreach my $id (@{$iphost{$primary_ip}}) {
+            my $intdom = &Apache::lonnet::internet_dom($id);
+            unless(grep(/^\Q$intdom\E$/,@intdoms)) {
+                push(@intdoms,$intdom);
+            }
+        }
+    }
+    foreach my $ip (keys(%iphost)) {
+        if (ref($iphost{$ip}) eq 'ARRAY') {
+            foreach my $id (@{$iphost{$ip}}) {
+                my $location = &Apache::lonnet::internet_dom($id);
+                if ($location) {
+                    if (grep(/^\Q$location\E$/,@intdoms)) {
+                        my $dom = &Apache::lonnet::host_domain($id);
+                        unless (grep(/^\Q$dom\E/,@{$instdoms})) {
+                            push(@{$instdoms},$dom);
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return;
+}
+
+sub restricted_dom {
+    my ($context,$key,$udom,$uname,$role,$start,$end,$cdom,$cnum,$csec,$credits,
+        $process_by,$instdoms,$got_role_approvals,$got_instdoms,$reject,$pending,
+        $notifydc,$status,$unauthorized,$currqueued) = @_;
+    return if ($udom eq $cdom);
+    return unless ((ref($process_by) eq 'HASH') && (ref($instdoms) eq 'HASH') &&
+                   (ref($got_role_approvals) eq 'HASH') && (ref($got_instdoms) eq 'HASH') &&
+                   (ref($reject) eq 'HASH') && (ref($pending) eq 'HASH') &&
+                   (ref($notifydc) eq 'HASH') && (ref($status) eq 'HASH') &&
+                   (ref($unauthorized) eq 'HASH') && (ref($currqueued) eq 'HASH'));
+    my (%approval,@notify,$gotdata,$skip);
+    if (ref($got_role_approvals->{$context}) eq 'HASH') {
+        if ($got_role_approvals->{$context}{$udom}) {
+            $gotdata = 1;
+            if (ref($process_by->{$context}{$udom}) eq 'HASH') {
+                %approval = %{$process_by->{$context}{$udom}};
+            }
+        }
+    }
+    unless ($gotdata) {
+        &role_approval($udom,$context,\%approval,\@notify);
+        $process_by->{$context} = {
+                                    $udom => \%approval,
+                                  };
+        $got_role_approvals->{$context} = {
+                                            $udom => 1,
+                                          };
+        $notifydc->{$udom} = \@notify;
+    }
+    if (ref($process_by->{$context}) eq 'HASH') {
+        if (ref($process_by->{$context}{$udom}) eq 'HASH') {
+            my @inst;
+            if ($got_instdoms->{$udom}) {
+                if (ref($instdoms->{$udom}) eq 'ARRAY') {
+                    @inst = @{$instdoms->{$udom}};
+                }
+            } else {
+                &get_instdoms(\@inst);
+                $instdoms->{$udom} = \@inst;
+                $got_instdoms->{$udom} = 1;
+            }
+            if (grep(/^\Q$cdom\E$/,@inst)) {
+                if (exists($approval{'instdom'})) {
+                    my $rule = $approval{'instdom'};
+                    if (($rule eq 'none') || ($rule eq 'user') || ($rule eq 'domain')) {
+                        my ($id,$currstatus,$curradj) = &get_othdomreq_status($key,$uname,$udom,$role,$cdom,$cnum,$csec);
+                        if (($currstatus ne '') && ($curradj eq $rule)) {
+                            $status->{$key}->{$uname.':'.$udom} = $currstatus;
+                        }
+                        if ($rule eq 'none') {
+                             $reject->{$key}->{$uname.':'.$udom} = {
+                                                                     cdom  => $cdom,
+                                                                     cnum  => $cnum,
+                                                                     csec  => $csec,
+                                                                     udom  => $udom,
+                                                                     uname => $uname,
+                                                                     role  => $role,
+                                                                   };
+                            $skip = 1;
+                        } elsif (($rule eq 'user') || ($rule eq 'domain')) {
+                            if ($curradj eq $rule) {
+                                unless ($currstatus eq 'approved') {
+                                    if ($currstatus eq 'rejected') {
+                                        $unauthorized->{$key}->{$uname.':'.$udom} = {
+                                                                                      cdom  => $cdom,
+                                                                                      cnum  => $cnum,
+                                                                                      csec  => $csec,
+                                                                                      udom  => $udom,
+                                                                                      uname => $uname,
+                                                                                      role  => $role,
+                                                                                    };
+                                    } elsif ($currstatus eq 'pending') {
+                                        $currqueued->{$key}->{$uname.':'.$udom} = {
+                                                                                    cdom  => $cdom,
+                                                                                    cnum  => $cnum,
+                                                                                    csec  => $csec,
+                                                                                    udom  => $udom,
+                                                                                    uname => $uname,
+                                                                                    role  => $role,
+                                                                                    adj   => $rule,
+                                                                       };
+                                    }
+                                    $skip = 1;
+                                }
+                            } else {
+                                $pending->{$key}->{$uname.':'.$udom} = {
+                                                                         cdom  => $cdom,
+                                                                         cnum  => $cnum,
+                                                                         csec  => $csec,
+                                                                         udom  => $udom,
+                                                                         uname => $uname,
+                                                                         role  => $role,
+                                                                         start => $start,
+                                                                         end   => $end,
+                                                                         adj   => $rule,
+                                                                       };
+                                if (($role eq 'st') && ($credits ne '')) {
+                                    $pending->{$key}->{$uname.':'.$udom}->{'credits'} = $credits;
+                                }
+                                $skip = 1;
+                            }
+                        }
+                    }
+                }
+            } elsif (exists($approval{'extdom'})) {
+                my $rule = $approval{'extdom'};
+                if (($rule eq 'none') || ($rule eq 'user') || ($rule eq 'domain')) {
+                    my ($id,$currstatus,$curradj) = &get_othdomreq_status($key,$uname,$udom,$role,$cdom,$cnum,$csec);
+                    if (($currstatus ne '') && ($curradj eq $rule)) {
+                        $status->{$key}->{$uname.':'.$udom} = $currstatus;
+                    }
+                    if ($rule eq 'none') {
+                        $reject->{$key}->{$uname.':'.$udom} = {
+                                                                cdom  => $cdom,
+                                                                cnum  => $cnum,
+                                                                csec  => $csec,
+                                                                udom  => $udom,
+                                                                uname => $uname,
+                                                                role  => $role,
+                                                              };
+                        $skip = 1;
+                    } elsif (($rule eq 'user') || ($rule eq 'domain')) {
+                        if ($curradj eq $rule) {
+                            unless ($currstatus eq 'approved') {
+                                if ($currstatus eq 'rejected') {
+                                    $unauthorized->{$key}->{$uname.':'.$udom} = {
+                                                                                  cdom  => $cdom,
+                                                                                  cnum  => $cnum,
+                                                                                  csec  => $csec,
+                                                                                  udom  => $udom,
+                                                                                  uname => $uname,
+                                                                                  role  => $role,
+                                                                                };
+                                } elsif ($currstatus eq 'pending') {
+                                    $currqueued->{$key}->{$uname.':'.$udom} = {
+                                                                                cdom  => $cdom,
+                                                                                cnum  => $cnum,
+                                                                                csec  => $csec,
+                                                                                udom  => $udom,
+                                                                                uname => $uname,
+                                                                                role  => $role,
+                                                                                adj   => $rule,
+                                                                       };
+                                }
+                                $skip = 1;
+                            }
+                        } else {
+                            $pending->{$key}->{$uname.':'.$udom} = {
+                                                                     cdom  => $cdom,
+                                                                     cnum  => $cnum,
+                                                                     csec  => $csec,
+                                                                     udom  => $udom,
+                                                                     uname => $uname,
+                                                                     role  => $role,
+                                                                     start => $start,
+                                                                     end   => $end,
+                                                                     adj   => $rule,
+                                                                   };
+                            if (($role eq 'st') && ($credits ne '')) {
+                                $pending->{$key}->{$uname.':'.$udom}->{'credits'} = $credits;
+                            }
+                            $skip = 1;
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return $skip;
+}
+
+sub get_othdomreq_status {
+    my ($key,$uname,$udom,$role,$cdom,$cnum,$csec) = @_;
+    my $id = $uname.':'.$udom.':'.$role; 
+    my ($dbnum,$currstatus,$curradj);
+    if (($role eq 'ca') || ($role eq 'aa')) {
+        $dbnum = $cnum;
+    } elsif ($key eq $cdom.'_'.$role) {
+        $dbnum = &Apache::lonnet::get_domainconfiguser($cdom);
+    } else {
+        $id .= ':'.$csec;
+        $dbnum = $cnum;
+    }
+    my $statusid = 'status&'.$id;
+    my %curr = &Apache::lonnet::get('nohist_othdomqueued',[$id,$statusid],$cdom,$dbnum);
+    if (ref($curr{$id}) eq 'HASH') {
+        $curradj = $curr{$id}{'adj'};
+    }
+    $currstatus = $curr{$statusid};
+    return ($id,$currstatus,$curradj);
+}
+
+sub print_roles_rejected {
+    my ($context,$reject,$unauthorized) = @_;
+    return unless ((ref($reject) eq 'HASH') || (ref($unauthorized) eq 'HASH'));
+    my $output;
+    if (keys(%{$reject}) > 0) {
+        $output = '<p class="LC_warning">'.
+                  &mt("The following roles could not be assigned because the user is from another domain, and that domain's policies disallow it").'<ul>';
+        foreach my $key (sort(keys(%{$reject}))) {
+            if (ref($reject->{$key}) eq 'HASH') {
+                foreach my $user (sort(keys(%{$reject->{$key}}))) {
+                    if (ref($reject->{$key}->{$user}) eq 'HASH') {
+                        my ($crstype,$role,$cdom,$cnum,$csec,$title,$plainrole);
+                        $role = $reject->{$key}->{$user}{'role'};
+                        $cdom = $reject->{$key}->{$user}{'cdom'};
+                        $cnum = $reject->{$key}->{$user}{'cnum'};
+                        $csec = $reject->{$key}->{$user}{'csec'};
+                        if (($context eq 'domain') && ($cnum ne '')) {
+                            if (($role eq 'ca') || ($role eq 'aa')) {
+                                $title = &Apache::loncommon::plainname($cnum,$cdom);
+                            } else {
+                                if (&Apache::lonnet::is_course($cdom,$cnum)) {
+                                    my %coursedata = &Apache::lonnet::coursedescription($cdom.'_'.$cnum);
+                                    $crstype = $coursedata{'type'};
+                                    $title = $coursedata{'description'};
+                                }
+                            }
+                        } elsif ($context eq 'course') {
+                            $crstype = &Apache::loncommon::course_type();
+                        }
+                        my $plainrole = &Apache::lonnet::plaintext($role,$crstype);
+                        $output .= '<li>'.&mt('User: [_1]',$reject->{$key}->{$user}{'uname'}).' | '.
+                                          &mt('Domain: [_1]',$reject->{$key}->{$user}{'udom'}).' | '.
+                                          &mt('Role: [_1]',$plainrole);
+                        if ($crstype) {
+                            if ($csec ne'') {
+                                $output .= ' | '.&mt('Section: [_1]',$csec);
+                            }
+                        } elsif (($context eq 'domain') && (($role eq 'ca') || ($role eq 'aa'))) {
+                            $output .= ' | '.&mt('Authoring Space belonging to: [_1]',$title);
+                                                
+                        }
+                        if (($context eq 'domain') && ($crstype)) {
+                            $output .= ' | '.&mt("$crstype: [_1]",$title);
+                        }
+                        $output .= '</li>';
+                    }
+                }
+            }
+        }
+        $output .= '</ul></p>';
+    }
+    if (keys(%{$unauthorized}) > 0) {
+        $output = '<p class="LC_warning">'.
+                  &mt("The following roles could not be assigned because the user is from another domain, and that domain's policies require approval by the user themselves or by a domain coordinator in that domain, and approval has been withheld.").'<ul>';
+        foreach my $key (sort(keys(%{$unauthorized}))) {
+            if (ref($unauthorized->{$key}) eq 'HASH') {
+                foreach my $user (sort(keys(%{$unauthorized->{$key}}))) {
+                    if (ref($unauthorized->{$key}->{$user}) eq 'HASH') {
+                        my ($crstype,$role,$cdom,$cnum,$csec,$title,$plainrole);
+                        $role = $unauthorized->{$key}->{$user}{'role'};
+                        $cdom = $unauthorized->{$key}->{$user}{'cdom'};
+                        $cnum = $unauthorized->{$key}->{$user}{'cnum'};
+                        $csec = $unauthorized->{$key}->{$user}{'csec'};
+                        if (($context eq 'domain') && ($cnum ne '')) {
+                            if (($role eq 'ca') || ($role eq 'aa')) {
+                                $title = &mt('Authoring Space belonging to: [_1]',
+                                             &Apache::loncommon::plainname($cnum,$cdom));
+                            } else {
+                                if (&Apache::lonnet::is_course($cdom,$cnum)) {
+                                    my %coursedata = &Apache::lonnet::coursedescription($cdom.'_'.$cnum);
+                                    $crstype = $coursedata{'type'};
+                                    $title = &mt("$crstype: [_1]",$coursedata{'description'});
+                                }
+                            }
+                        } elsif ($context eq 'course') {
+                            $crstype = &Apache::loncommon::course_type();
+                        }
+                        $plainrole = &Apache::lonnet::plaintext($role,$crstype);
+                        $output .= '<li>'.&mt('User: [_1]',$unauthorized->{$key}->{$user}{'uname'}).' | '.
+                                          &mt('Domain: [_1]',$unauthorized->{$key}->{$user}{'udom'}).' | '.
+                                          &mt('Role: [_1]',$plainrole);
+                        if ($crstype) {
+                            if ($csec ne'') {
+                                $output .= ' | '.&mt('Section: [_1]',$csec);
+                            }
+                        }
+                        if ($title ne '') {
+                            $output .= ' | '.$title;
+                        }
+                        $output .= '</li>';
+                    }
+                }
+            }
+        }
+        $output .= '</ul></p>'; 
+    }
+    return $output;
+}
+
+sub print_roles_queued {
+    my ($context,$pending,$notifydc,$currqueued) = @_;
+    return unless ((ref($pending) eq 'HASH') && (ref($notifydc) eq 'HASH') &&
+                   (ref($currqueued) eq 'HASH'));
+    my $output;
+    if (keys(%{$pending}) > 0) {
+        my $now = time;
+        $output = '<p class="LC_warning">'.
+                  &mt("The following role assignments have been queued because the user is from another domain, and that domain's policies require approval by the user themselves or by a domain coordinator in that domain").'<ul>';
+        my (%todom,%touser,%crsqueue,%caqueue,%domqueue);
+        my $requester = $env{'user.name'}.':'.$env{'user.domain'};
+        foreach my $key (sort(keys(%{$pending}))) {
+            if (ref($pending->{$key}) eq 'HASH') {
+                foreach my $user (sort(keys(%{$pending->{$key}}))) {
+                    if (ref($pending->{$key}->{$user}) eq 'HASH') {
+                        my $role = $pending->{$key}->{$user}{'role'};
+                        my $uname = $pending->{$key}->{$user}{'uname'};
+                        my $udom = $pending->{$key}->{$user}{'udom'};
+                        my $csec = $pending->{$key}->{$user}{'csec'};
+                        my $cdom = $pending->{$key}->{$user}{'cdom'};
+                        my $cnum = $pending->{$key}->{$user}{'cnum'};
+                        my $adj = $pending->{$key}->{$user}{'adj'};
+                        my $start = $pending->{$key}->{$user}{'start'};
+                        my $end = $pending->{$key}->{$user}{'end'};
+                        my $credits = $pending->{$key}->{$user}{'credits'};
+                        my $now = time;
+                        my ($crstype,$title,$plainrole,$extent,$id,$status);
+                        if ($context eq 'course') {
+                            $crstype = &Apache::loncommon::course_type();
+                            $title = $env{'course.'.$env{'request.course.id'}.'.description'};
+                        } elsif ($context eq 'domain') {
+                            if (&Apache::lonnet::is_course($cdom,$cnum)) {
+                                my %coursedata = &Apache::lonnet::coursedescription($cdom.'_'.$cnum);
+                                $crstype = $coursedata{'type'};
+                                $title = $coursedata{'description'};
+                            } elsif (($role eq 'ca') || ($role eq 'aa')) {
+                                $title = &Apache::loncommon::plainname($cnum,$cdom);
+                            }
+                        }
+                        $plainrole = &Apache::lonnet::plaintext($role,$crstype);
+                        $extent = "/$cdom/$cnum";
+                        $id = $uname.':'.$udom.':'.$role;
+                        if (($context eq 'course') || ($crstype)) {
+                            $id .= ':'.$csec;
+                        }
+                        $output .= '<li>'.&mt('User: [_1]',$uname).' | '.
+                                          &mt('Domain: [_1]',$udom).' | '.
+                                          &mt('Role: [_1]',$plainrole);
+                        if ($crstype) {
+                            if ($csec ne'') {
+                                $output .= ' | '.&mt('Section: [_1]',$csec);
+                            }
+                        } elsif (($context eq 'domain') && (($role eq 'ca') || ($role eq 'aa'))) {
+                            $output .= ' | '.&mt('Authoring Space belonging to: [_1]',$title);
+                        }
+                        if (($context eq 'domain') && ($crstype)) {
+                            $output .= ' | '.&mt("$crstype: [_1]",$title);
+                        }
+                        if (($crstype) && ($csec ne '')) {
+                            $extent .= "/$csec";
+                        }
+                        if ($adj eq 'user') {
+                            $output .= '<br />'.&mt('Message sent to user for approval');
+                            $touser{$uname.':'.$udom}{'pending:'.$extent.':'.$role} = {
+                                                                                        timestamp => $now,
+                                                                                        requester => $requester,
+                                                                                        start     => $start,
+                                                                                        end       => $end,
+                                                                                        credits   => $credits,
+                                                                                        context   => $context,
+                                                                                      };
+                        } elsif ($adj eq 'domain') {
+                            $output .= '<br />'.&mt("Message sent to user's domain coordinator for approval");
+                            $todom{$udom}{'pending:'.$uname.':'.$extent.':'.$role} = {
+                                                                                       timestamp => $now,
+                                                                                       requester => $requester,
+                                                                                       start     => $start,
+                                                                                       end       => $end,
+                                                                                       credits   => $credits,
+                                                                                       context   => $context,
+                                                                                     };
+                        }
+                        $output .= '</li>';
+                        if (($context eq 'course') || ($crstype)) {
+                            $crsqueue{$cdom.'_'.$cnum}{$id} = {
+                                                                timestamp => $now,
+                                                                requester => $requester,
+                                                                adj       => $adj,
+                                                              };
+                            $crsqueue{$cdom.'_'.$cnum}{'status&'.$id} = 'pending';
+                        } elsif (($context eq 'author') ||
+                                 (($context eq 'domain') && (($role eq 'ca') || ($role eq 'aa')))) {
+                            $caqueue{$cnum.':'.$cdom}{$id} = {
+                                                               timestamp => $now,
+                                                               requester => $requester,
+                                                               adj       => $adj,
+                                                             };
+                            $caqueue{$cnum.':'.$cdom}{'status&'.$id} = 'pending';
+                        } elsif ($context eq 'domain') {
+                            $domqueue{$id} = {
+                                               timestamp => $now,
+                                               requester => $requester,
+                                               adj       => $adj,
+                                             };
+                            $domqueue{'status&'.$id} = 'pending';
+                        }
+                    }
+                }
+            }
+        }
+        $output .= '</ul></p>';
+        if (keys(%touser)) {
+            foreach my $key (keys(%touser)) {
+                my ($uname,$udom) = split(/:/,$key);
+                if (&Apache::lonnet::put('nohist_queuedrolereqs',$touser{$key},$udom,$uname) eq 'ok') {
+                    my $owndomdesc = &Apache::lonnet::domain($udom);
+                    &Apache::loncoursequeueadmin::send_selfserve_notification($uname.':'.$udom,
+                        '','',$owndomdesc,$now,'othdomroleuser',$requester);
+                }
+            }
+        }
+        if (keys(%todom)) {
+            foreach my $dom (keys(%todom)) {
+                if (ref($todom{$dom}) eq 'HASH') {
+                    my $confname = &Apache::lonnet::get_domainconfiguser($dom);
+                    if (&Apache::lonnet::put('nohist_queuedrolereqs',$todom{$dom},$dom,$confname) eq 'ok') {
+                        if (ref($notifydc->{$dom}) eq 'ARRAY') {
+                            if (@{$notifydc->{$dom}} > 0) {
+                                my $notifylist = join(',',@{$notifydc->{$dom}});
+                                &Apache::loncoursequeueadmin::send_selfserve_notification($notifylist,
+                                    '','','',$now,'othdomroledc',$requester);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        if (keys(%crsqueue)) {
+            foreach my $key (keys(%crsqueue)) {
+                my ($cdom,$cnum) = split(/_/,$key);
+                if (ref($crsqueue{$key}) eq 'HASH') {
+                    &Apache::lonnet::put('nohist_othdomqueued',$crsqueue{$key},$cdom,$cnum);
+                }
+            }
+        }
+        if (keys(%caqueue)) {
+            foreach my $key (keys(%caqueue)) {
+                my ($auname,$audom) = split(/:/,$key);
+                if (ref($caqueue{$key}) eq 'HASH') {
+                    &Apache::lonnet::put('nohist_othdomqueued',$caqueue{$key},$audom,$auname);
+                }
+            }
+        }
+        if (keys(%domqueue)) {
+            my $confname = &Apache::lonnet::get_domainconfiguser($env{'request.role.domain'});
+            &Apache::lonnet::put('nohist_othdomqueued',\%domqueue,$env{'request.role.domain'},$confname);
+        }
+    }
+    if (keys(%{$currqueued}) > 0) {
+        $output = '<p class="LC_warning">'.
+                  &mt("The following role assignments were already queued because the user is from another domain, and that domain's policies require approval by the user themselves or by a domain coordinator in that domain").'<ul>';
+        my $requester = $env{'user.name'}.':'.$env{'user.domain'};
+        foreach my $key (sort(keys(%{$currqueued}))) {
+            if (ref($currqueued->{$key}) eq 'HASH') {
+                foreach my $user (sort(keys(%{$currqueued->{$key}}))) {
+                    if (ref($currqueued->{$key}->{$user}) eq 'HASH') {
+                        my $role = $currqueued->{$key}->{$user}{'role'};
+                        my $csec = $currqueued->{$key}->{$user}{'csec'};
+                        my $cdom = $currqueued->{$key}->{$user}{'cdom'};
+                        my $cnum = $currqueued->{$key}->{$user}{'cnum'};
+                        my ($crstype,$title,$plainrole);
+                        if ($context eq 'course') {
+                            $crstype = &Apache::loncommon::course_type();
+                        } elsif (($context eq 'domain') && ($cnum ne '')) {
+                            if (($role eq 'ca') || ($role eq 'aa')) {
+                                $title = &mt('Authoring Space belonging to: [_1]',
+                                             &Apache::loncommon::plainname($cnum,$cdom));
+                            } elsif (&Apache::lonnet::is_course($cdom,$cnum)) {
+                                my %coursedata = &Apache::lonnet::coursedescription($cdom.'_'.$cnum);
+                                $crstype = $coursedata{'type'};
+                                $title = &mt("$crstype: [_1]",$coursedata{'description'});
+                            }
+                        }
+                        $plainrole = &Apache::lonnet::plaintext($role,$crstype);
+                        $output .= '<li>'.&mt('User: [_1]',$currqueued->{$key}->{$user}{'uname'}).' | '.
+                                          &mt('Domain: [_1]',$currqueued->{$key}->{$user}{'udom'}).' | '.
+                                          &mt('Role: [_1]',$plainrole);
+                        if ($title ne '') {
+                            $output .= ' | '.$title;
+                        }
+                        if ($crstype) {
+                            if ($csec ne '') {
+                                $output .= ' | '.&mt('Section: [_1]',$csec);
+                            }
+                        }
+                        $output .= '</li>';
+                    }
+                }
+            }
+        }
+        $output .= '</ul></p>';
+    }
+    return $output;
+}
+
 sub propagate_id_change {
     my ($uname,$udom,$user) = @_;
     my (@types,@roles);
@@ -438,7 +1016,7 @@ sub javascript_validations {
             } elsif ($context eq 'domain') {
                 $setsection_call = 'setCourse()';
                 $setsections_js = &dc_setcourse_js($param{'formname'},$mode,
-                                                   $context,$showcredits);
+                                                   $context,$showcredits,$domain);
             }
             $finish = "  var checkSec = $setsection_call\n".
                       "  if (checkSec == 'ok') {\n".
@@ -510,7 +1088,7 @@ END
 ";
     } elsif ($mode eq 'modifycourse') {
         $auth_checks .= "
-    if (vf.elements[current.argfield].value == null || vf.elements[current.argfield].value == '') {
+    if ((current.argfield !== null) && (current.argfield !== undefined) && (current.argfield !== '') && (vf.elements[current.argfield].value == null || vf.elements[current.argfield].value == '')) {
 ";
     }
     if ( ($mode eq 'createcourse') || ($mode eq 'modifycourse') ) {
@@ -531,21 +1109,28 @@ END
 /* regexp here to check for non \d \. in credits */
 END
     } else {
+        my ($numrules,$intargjs) =
+            &Apache::loncommon::passwd_validation_js('vf.elements[current.argfield].value',$domain);
         $auth_checks .= (<<END);
     foundatype=1;
     if (current.argfield == null || current.argfield == '') {
+        // The login radiobutton checked does not have an associated textbox
+    } else if (vf.elements[current.argfield].value == '') {
         var alertmsg = '';
         switch (current.radiovalue) {
             case 'krb':
                 alertmsg = '$alert{'krb'}';
                 break;
-            case 'loc':
-            case 'fsys':
+            case 'int':
                 alertmsg = '$alert{'ipass'}';
                 break;
             case 'fsys':
+                alertmsg = '$alert{'ipass'}';
+                break;
+            case 'loc':
                 alertmsg = '';
                 break;
+            case 'lti':
             default:
                 alertmsg = '';
         }
@@ -553,6 +1138,10 @@ END
             alert(alertmsg);
             return;
         }
+    } else if (current.radiovalue == 'int') {
+        if ($numrules > 0) {
+$intargjs
+        }
     }
 END
     }
@@ -641,6 +1230,7 @@ END
                  $section_checks.$authheader;
     return $result;
 }
+
 ###############################################################
 ###############################################################
 sub upload_manager_javascript_forward_associate {
@@ -898,6 +1488,7 @@ sub print_upload_manager_footer {
     my $krbform = &Apache::loncommon::authform_kerberos(%param);
     my $intform = &Apache::loncommon::authform_internal(%param);
     my $locform = &Apache::loncommon::authform_local(%param);
+    my $ltiform = &Apache::loncommon::authform_lti(%param);
     my $date_table = &date_setting_table(undef,undef,$context,undef,
                                          $formname,$permission,$crstype);
 
@@ -926,7 +1517,7 @@ sub print_upload_manager_footer {
             &Apache::loncommon::help_open_topic('Auth_Options').
             "</p>\n";
     }
-    $Str .= &set_login($defdom,$krbform,$intform,$locform);
+    $Str .= &set_login($defdom,$krbform,$intform,$locform,$ltiform);
 
     my ($home_server_pick,$numlib) =
         &Apache::loncommon::home_server_form_item($defdom,'lcserver',
@@ -943,8 +1534,14 @@ sub print_upload_manager_footer {
                 &Apache::lonhtmlcommon::row_closure();
     }
 
+    my ($trusted,$untrusted);
+    if ($context eq 'course') {
+        ($trusted,$untrusted) = &Apache::lonnet::trusted_domains('enroll',$defdom);
+    } elsif ($context eq 'author') {
+        ($trusted,$untrusted) = &Apache::lonnet::trusted_domains('othcoau',$defdom);
+    }
     $Str .= &Apache::lonhtmlcommon::row_title(&mt('Default domain'))
-           .&Apache::loncommon::select_dom_form($defdom,'defaultdomain',undef,1)
+           .&Apache::loncommon::select_dom_form($defdom,'defaultdomain',undef,1,undef,$trusted,$untrusted)
            .&Apache::lonhtmlcommon::row_closure();
 
     $Str .= &Apache::lonhtmlcommon::row_title(&mt('Starting and Ending Dates'))
@@ -1121,8 +1718,15 @@ sub print_upload_manager_form {
     if (!$env{'form.datatoken'}) {
         $datatoken=&Apache::loncommon::upfile_store($r);
     } else {
-        $datatoken=$env{'form.datatoken'};
-        &Apache::loncommon::load_tmp_file($r);
+        $datatoken=&Apache::loncommon::valid_datatoken($env{'form.datatoken'});
+        if ($datatoken ne '') {
+            &Apache::loncommon::load_tmp_file($r,$datatoken);
+        }
+    }
+    if ($datatoken eq '') {
+        $r->print('<p class="LC_error">'.&mt('Error').': '.
+                  &mt('Invalid datatoken').'</p>');
+        return 'missingdata';
     }
     my @records=&Apache::loncommon::upfile_record_sep();
     if($env{'form.noFirstLine'}){
@@ -1206,6 +1810,7 @@ sub print_upload_manager_form {
     }
     &print_upload_manager_footer($r,$i,$keyfields,$defdom,$today,$halfyear,
                                  $context,$permission,$crstype,$showcredits);
+    return 'ok';
 }
 
 sub setup_date_selectors {
@@ -1466,6 +2071,14 @@ sub construction_space_roles {
         foreach my $role (@allroles) {
             if (&Apache::lonnet::allowed('c'.$role,$env{'user.domain'}.'/'.$env{'user.name'})) { 
                 push(@roles,$role); 
+            } elsif ($env{'request.role'} =~ m{^ca\./($match_domain)/($match_username)$}) {
+                my ($audom,$auname) = ($1,$2);
+                if (($role eq 'ca') || ($role eq 'aa')) {
+                    if ((&Apache::lonnet::allowed('v'.$role,,$audom.'/'.$auname)) &&
+                        ($env{"environment.internal.manager./$audom/$auname"})) {
+                        push(@roles,$role);
+                    }
+                }
             }
         }
         return @roles;
@@ -1553,10 +2166,11 @@ sub my_custom_roles {
     my %rolehash=&Apache::lonnet::dump('roles',$udom,$uname);
     foreach my $key (keys(%rolehash)) {
         if ($key=~/^rolesdef\_(\w+)$/) {
+            my $role = $1;
             if ($crstype eq 'Community') {
                 next if ($rolehash{$key} =~ /bre\&S/); 
             }
-            $returnhash{$1}=$1;
+            $returnhash{$role}=$role;
         }
     }
     return %returnhash;
@@ -1569,6 +2183,24 @@ sub print_userlist {
     if (! exists($env{'form.sortby'})) {
         $env{'form.sortby'} = 'username';
     }
+    my ($showstart,$showend);
+    if (($env{'form.Status'} eq '') && ($env{'form.phase'} eq '') &&
+        ($env{'form.showrole'} eq '') && ($context eq 'course') &&
+        ($env{'request.course.id'} ne '') &&
+        ($env{'course.'.$env{'request.course.id'}.'.internal.coursecode'} ne '')) {
+        my $now = time;
+        my $startaccess = $env{'course.'.$env{'request.course.id'}.'.default_enrollment_start_date'};
+        my $endaccess = $env{'course.'.$env{'request.course.id'}.'.default_enrollment_end_date'};
+        if (($startaccess) && ($startaccess > $now)) {
+            $env{'form.Status'} = 'Future';
+            $showstart = 1;
+        }
+        if (($endaccess ne '') && ($endaccess != 0) && ($endaccess < $now)) {
+            $env{'form.Status'} = 'Expired';
+            undef($showstart);
+            $showend = 1;
+        }
+    }
     if ($env{'form.Status'} !~ /^(Any|Expired|Active|Future)$/) {
         $env{'form.Status'} = 'Active';
     }
@@ -1636,7 +2268,7 @@ sub print_userlist {
         $r->print(&section_group_filter($cnum,$cdom));
     }
     $r->print('</div><div class="LC_left_float">'.
-              &column_checkboxes($context,$mode,$formname,$showcredits).
+              &column_checkboxes($context,$mode,$formname,$showcredits,$showstart,$showend).
               '</div>');
     if ($env{'form.phase'} eq '') {
         $r->print('<br clear="all" />'.
@@ -1722,12 +2354,31 @@ sub print_userlist {
     } else {
         my (%cstr_roles,%dom_roles);
         if ($context eq 'author') {
-            # List co-authors and assistant co-authors
             my @possroles = &roles_by_context($context);
-            %cstr_roles = &Apache::lonnet::get_my_roles(undef,undef,undef,
-                                              \@statuses,\@possroles);
-            &gather_userinfo($context,$format,\%userlist,$indexhash,\%userinfo,
-                             \%cstr_roles,$permission);
+            my @allowedroles;
+            # List co-authors and assistant co-authors
+            my ($auname,$audom);
+            if ($env{'request.role'} =~ m{^ca\./($match_domain)/($match_username)$}) {
+                ($audom,$auname) = ($1,$2);
+                foreach my $role (@possroles) {
+                    if ((&Apache::lonnet::allowed('v'.$role,"$audom/$auname")) ||
+                        (&Apache::lonnet::allowed('c'.$role,"$audom/$auname"))) {
+                        push(@allowedroles,$role);
+                    }
+                }
+            } elsif ($env{'request.role'} =~ m{^au\./($match_domain)/}) {
+                if ($1 eq $env{'user.domain'}) {
+                    $auname = $env{'user.name'};
+                    $audom = $env{'user.domain'};
+                }
+                @allowedroles = @possroles;
+            }
+            if (($auname ne '') && ($audom ne '')) {
+                %cstr_roles = &Apache::lonnet::get_my_roles($auname,$audom,undef,
+                                                            \@statuses,\@allowedroles);
+                &gather_userinfo($context,$format,\%userlist,$indexhash,\%userinfo,
+                                 \%cstr_roles,$permission);
+            }
         } elsif ($context eq 'domain') {
             if ($env{'form.roletype'} eq 'domain') {
                 if (grep(/^authorusage$/,@cols)) {
@@ -2018,14 +2669,17 @@ sub get_cols_array {
             push(@cols,'photo');
         }
         if ($context eq 'domain') {
-            push (@cols,('authorusage','authorquota','extent'));
+            push(@cols,('authorusage','authorquota','extent'));
+        }
+        if ($context eq 'author') {
+            push(@cols,'manager');
         }
     }
     return @cols;
 }
 
 sub column_checkboxes {
-    my ($context,$mode,$formname,$showcredits) = @_;
+    my ($context,$mode,$formname,$showcredits,$showstart,$showend) = @_;
     my @cols = &get_cols_array($context,$mode,$showcredits);
     my @showncols = &Apache::loncommon::get_env_multiple('form.showcol');
     my (%disabledchk,%unchecked);
@@ -2038,11 +2692,21 @@ sub column_checkboxes {
             if ($showcredits) {
                 $unchecked{'credits'} = 1;
             }
-        } elsif ($context eq 'domain') { 
+            my %curr_groups = &Apache::longroup::coursegroups();
+            unless (keys(%curr_groups)) {
+                $unchecked{'groups'} = 1;
+            }
+        } elsif ($context eq 'domain') {
             $unchecked{'extent'} = 1; 
         }
-        $unchecked{'start'} = 1;
-        $unchecked{'end'} = 1;
+        if ($showstart) {
+            $unchecked{'lastlogin'} = 1;
+        } else {
+            $unchecked{'start'} = 1;
+        }
+        unless ($showend) {
+            $unchecked{'end'} = 1;
+        }
     } else {
         if ($env{'form.Status'} ne 'Any') {
             $disabledchk{'status'} = 1;
@@ -2059,6 +2723,11 @@ sub column_checkboxes {
             } elsif ($env{'form.roletype'} eq 'domain') {
                 $disabledchk{'extent'} = 1; 
             }
+        } elsif ($context eq 'author') {
+            if (($env{'form.Status'} eq 'Expired') ||
+                ($env{'form.showrole'} eq 'aa')) {
+                $disabledchk{'manager'} = 1;
+            }
         }
     }
     my $numposs = scalar(@cols);
@@ -2150,6 +2819,7 @@ sub get_column_names {
         'ca'         => "check all",
         'ua'         => "uncheck all",
         'clicker'    => "clicker-ID",
+        'manager'    => "co-author manager",
     );
     if ($context eq 'domain' && $env{'form.roletype'} eq 'course') {
         $lt{'extent'} = &mt('course(s): description, section(s), status');
@@ -2174,6 +2844,7 @@ sub gather_userinfo {
             ($userdata{'username'},$userdata{'domain'},$userdata{'role'}) =
                 split(/:/,$item);
             ($userdata{'start'},$userdata{'end'})=split(/:/,$rolehash->{$item});
+            next if (($userdata{'username'} eq '') && ($userdata{'domain'} eq ''));
             &build_user_record($context,\%userdata,$userinfo,$indexhash,
                                $item,$userlist);
         } elsif ($context eq 'course') {
@@ -2279,7 +2950,6 @@ sub build_user_record {
 
 sub courses_selector {
     my ($cdom,$formname) = @_;
-    my %coursecodes = ();
     my %codes = ();
     my @codetitles = ();
     my %cat_titles = ();
@@ -2292,14 +2962,15 @@ sub courses_selector {
     my $jscript = '';
 
     my $totcodes = 0;
-    $totcodes =
-        &Apache::courseclassifier::retrieve_instcodes(\%coursecodes,
-                                                      $cdom,$totcodes);
-    if ($totcodes > 0) {
-        $format_reply =
-             &Apache::lonnet::auto_instcode_format($caller,$cdom,\%coursecodes,
-                                \%codes,\@codetitles,\%cat_titles,\%cat_order);
-        if ($format_reply eq 'ok') {
+    my $instcats = &Apache::lonnet::get_dom_instcats($cdom);
+    if (ref($instcats) eq 'HASH') {
+        if ((ref($instcats->{'codetitles'}) eq 'ARRAY') && (ref($instcats->{'codes'}) eq 'HASH') &&
+            (ref($instcats->{'cat_titles'}) eq 'HASH') && (ref($instcats->{'cat_order'}) eq 'HASH')) {
+            %codes = %{$instcats->{'codes'}};
+            @codetitles = @{$instcats->{'codetitles'}};
+            %cat_titles = %{$instcats->{'cat_titles'}};
+            %cat_order = %{$instcats->{'cat_order'}};
+            $totcodes = scalar(keys(%codes));
             my $numtypes = @codetitles;
             &Apache::courseclassifier::build_code_selections(\%codes,\@codetitles,\%cat_titles,\%cat_order,\%idlist,\%idnums,\%idlist_titles);
             my ($scripttext,$longtitles) = &Apache::courseclassifier::javascript_definitions(\@codetitles,\%idlist,\%idlist_titles,\%idnums,\%cat_titles);
@@ -2336,7 +3007,8 @@ function setCourseCat(formname) {
     }
     courseSet('$codetitles[1]');
     for (var j=0; j<formname.Department.length; j++) {
-        if (formname.Department.options[j].value == "$env{'form.Department'}") {            formname.Department.options[j].selected = true;
+        if (formname.Department.options[j].value == "$env{'form.Department'}") {
+            formname.Department.options[j].selected = true;
         }
     }
     if (formname.Department.options[formname.Department.selectedIndex].value == -1) {
@@ -2433,6 +3105,7 @@ sub make_keylist_array {
     $index->{'instsec'} = &Apache::loncoursedata::CL_INSTSEC();
     $index->{'authorquota'} = &Apache::loncoursedata::CL_AUTHORQUOTA();
     $index->{'authorusage'} = &Apache::loncoursedata::CL_AUTHORUSAGE();
+    $index->{'manager'} = &Apache::loncoursedata::CL_CAMANAGER();
     foreach my $key (keys(%{$index})) {
         $keylist->[$index->{$key}] = $key;
     }
@@ -2507,6 +3180,9 @@ sub show_users_list {
             (($env{'form.showrole'} eq 'Any') || ($env{'form.showrole'} eq 'au'))) {
             push(@sortable,('authorusage','authorquota'));
         }
+        if ($context eq 'author') {
+            push(@sortable,'manager');
+        }
     }
     if ($mode eq 'pickauthor') {
         @sortable = ('username','fullname','email','status');
@@ -2824,6 +3500,7 @@ END
     foreach my $idx (@$keylist) {
         $index{$idx} = $i++;
     }
+    my $now = time;
     my $usercount = 0;
     my ($secfilter,$grpfilter);
     if ($context eq 'course') {
@@ -2841,13 +3518,28 @@ END
                                                 Future  => 'Future',
                                                 Expired => 'Expired',
                                                );
-    # If this is for a single course get last course "log-in".
-    my %crslogins;
+    my (%crslogins,%camanagers,%othdoms);
     if ($context eq 'course') {
+        # If this is for a single course get last course "log-in".
         %crslogins=&Apache::lonnet::dump('nohist_crslastlogin',$cdom,$cnum);
+    } elsif ($context eq 'author') {
+        my $authormanagers;
+        if ($env{'request.role'} =~ m{^(?:ca|aa)\./($match_domain)/($match_username)$}) {
+            my %envhash = &Apache::lonnet::userenvironment($1,$2,'authormanagers');
+            $authormanagers = $envhash{'authormanagers'};
+        } else {
+            $authormanagers = $env{'environment.authormanagers'};
+        }
+        if ($authormanagers ne '') {
+            map { $camanagers{$_.':ca'} = 1; } split(/,/,$authormanagers);
+        }
     }
     # Get groups, role, permanent e-mail so we can sort on them if
     # necessary.
+    # Compare user's domain with $env{'request.role.dom'}, and if
+    # different add to the domains for which to retrieve data on
+    # viewable user information, by institutional status, for users
+    # from "other" domains.
     foreach my $user (keys(%{$userlist})) {
         if ($user eq '' ) {
             delete($userlist->{$user});
@@ -2944,6 +3636,16 @@ END
                 }
             }
         }
+        if ($context eq 'author') {
+            if (($camanagers{$user}) &&
+                ((!defined($userlist->{$user}->[$index{'end'}])) ||
+                 ($userlist->{$user}->[$index{'end'}] == 0) ||
+                 ($userlist->{$user}->[$index{'end'}] > $now))) {
+                $userlist->{$user}->[$index{'manager'}] = &mt('Yes');
+            } else {
+                $userlist->{$user}->[$index{'manager'}] = &mt('No');
+            }
+        }
         my %emails   = &Apache::loncommon::getemails($uname,$udom);
         if ($emails{'permanentemail'} =~ /\S/) {
             $userlist->{$user}->[$index{'email'}] = $emails{'permanentemail'};
@@ -2968,6 +3670,9 @@ END
                 $userlist->{$user}->[$index{'authorquota'}] = sprintf("%.2f",$disk_quota);
             }
         }
+        unless ($env{'request.role.domain'} eq $udom) {
+            $othdoms{$udom} = 1;
+        }
         $usercount ++;
     }
     my $autocount = 0;
@@ -3009,16 +3714,83 @@ END
             $disabled = ' disabled="disabled"';
         }
     }
+    my (%shownfields_by_dom,%checkshown);
+    if (keys(%othdoms)) {
+        my @userinfo = ('firstname','middlename','lastname','generation',
+                        'permanentemail','id');
+        foreach my $dom (keys(%othdoms)) {
+            my %shownfields = &get_othdom_shownfields($dom,\@userinfo);
+            $shownfields_by_dom{$dom} = \%shownfields;
+        }
+        foreach my $item (@userinfo) {
+            if ($item eq 'id') {
+                if (grep(/^\Q$item\E$/,@cols)) {
+                    $checkshown{$item} = 1;
+                }
+            } elsif ($item eq 'permamentemail') {
+                if (grep(/^email$/,@cols)) {
+                    $checkshown{$item} = 1;
+                }
+            } elsif (grep(/^fullname$/,@cols)) {
+                $checkshown{$item} = 1;
+            }
+        }
+    }
     foreach my $user (@sorted_users) {
         my %in;
         my $sdata = $userlist->{$user};
-        $rowcount ++; 
+        $rowcount ++;
         foreach my $item (@{$keylist}) {
             $in{$item} = $sdata->[$index{$item}];
         }
-        my $clickers = (&Apache::lonnet::userenvironment($in{'domain'},$in{'username'},'clickers'))[1];
-        if ($clickers!~/\w/) { $clickers='-'; }
-        $in{'clicker'} = $clickers;
+        if ((grep(/^clicker$/,@cols)) || (keys(%checkshown) && (exists($othdoms{$in{'domain'}})))) {
+            my %info = &Apache::lonnet::userenvironment($in{'domain'},$in{'username'},'clickers','inststatus',
+                                                        'firstname','middlename','lastname','generation');
+            if (grep(/^clicker$/,@cols)) {
+                if ($info{'clickers'} !~/\w/) { $info{'clickers'} = '-'; }
+                $in{'clicker'} = $info{'clickers'};
+            }
+            if (keys(%checkshown) && exists($othdoms{$in{'domain'}})) {
+                my @statuses;
+                if ($info{'inststatus'} ne '') {
+                    @statuses = split(/:/,$info{'inststatus'});
+                } else {
+                    @statuses = ('default');
+                }
+                if (ref($shownfields_by_dom{$in{'domain'}}) eq 'HASH') {
+                    my (%shown,$rebuildname,%names);
+                    foreach my $type (@statuses) {
+                        if (ref($shownfields_by_dom{$in{'domain'}}{$type}) eq 'HASH') {
+                            foreach my $key (keys(%{$shownfields_by_dom{$in{'domain'}}{$type}})) {
+                                $shown{$key} = 1;
+                            }
+                        }
+                    }
+                    foreach my $item ('id','permanentemail') {
+                        if ($checkshown{$item} && !$shown{$item}) {
+                            if ($item eq 'permanentemail') {
+                                $in{'email'} = &mt('not shown');
+                            } else {
+                                $in{$item} = &mt('not shown');
+                            }
+                        }
+                    }
+                    foreach my $item ('firstname','middlename','lastname','generation') {
+                        if ($checkshown{$item} && !$shown{$item}) {
+                            $rebuildname = 1;
+                        } else {
+                            $names{$item} = $info{$item};
+                        }
+                    }
+                    if ($rebuildname) {
+                        $in{'fullname'} = &Apache::lonnet::format_name($names{'firstname'},
+                                                                       $names{'middlename'},
+                                                                       $names{'lastname'},
+                                                                       $names{'generation'},'lastname');
+                    }
+                }
+            }
+        }
 	my $role = $in{'role'};
         $in{'role'}=&Apache::lonnet::plaintext($sdata->[$index{'role'}],$crstype);
         unless ($mode eq 'excel') {
@@ -3140,13 +3912,10 @@ END
                 } elsif ($item eq 'clicker') {
                     if (($context eq 'course') && ($mode ne 'autoenroll')) {
                         if ($env{'form.showrole'} eq 'st' || $env{'form.showrole'} eq 'Any') {
-                            my $clickers =
-                   (&Apache::lonnet::userenvironment($in{'domain'},$in{'username'},'clickers'))[1];
-                            if ($clickers!~/\w/) { $clickers='-'; }
-                            $r->print('<td>'.$clickers.'</td>');
+                            $r->print('<td>'.$in{'clicker'}.'</td>');
                         } else {
-                             $r->print('<td>&nbsp;</td>'."\n");
-                        } 
+                            $r->print('<td>&nbsp;</td>'."\n");
+                        }
                     }
                 } elsif (($item eq 'authorquota') || ($item eq 'authorusage')) {
                     $r->print('<td align="right">'.$in{$item}.'</td>'."\n");
@@ -3512,6 +4281,8 @@ END
         setSections(formname,'$crstype');
         if (seccheck == 'ok') {
             opener.document.$callingform.newsecs.value = formname.sections.value;
+        } else {
+            return;
         }
 END
     } else {
@@ -3760,10 +4531,16 @@ sub results_header_row {
             $description .= ' ('.$constraint.')';
         } 
     } elsif ($context eq 'author') {
+        my ($auname,$audom);
+        if ($env{'request.role'} =~ m{^(?:ca|aa)\./($match_domain)/($match_username)$}) {
+            ($audom,$auname) = ($1,$2);
+        } else {
+            ($audom,$auname) = ($env{'user.domain'},$env{'user.name'});
+        }
         $description = 
             &mt('Author space for [_1]'
                 ,'<span class="LC_cusr_emph">'
-                .&Apache::loncommon::plainname($env{'user.name'},$env{'user.domain'})
+                .&Apache::loncommon::plainname($auname,$audom)
                 .'</span>')
             .':&nbsp;&nbsp;';
         if ($statusmode eq 'Expired') {
@@ -4096,7 +4873,7 @@ sub print_first_users_upload_form {
            .&Apache::lonhtmlcommon::end_pick_box();
 
     $str .= '<p>'
-           .'<input type="submit" name="fileupload" value="'.&mt('Next').'"'
+           .'<input type="button" name="fileupload" value="'.&mt('Next').'"'
            .' onclick="javascript:checkUpload(this.form);" />'
            .'</p>';
 
@@ -4107,7 +4884,10 @@ sub print_first_users_upload_form {
 # ================================================= Drop/Add from uploaded file
 sub upfile_drop_add {
     my ($r,$context,$permission,$showcredits) = @_;
-    &Apache::loncommon::load_tmp_file($r);
+    my $datatoken = &Apache::loncommon::valid_datatoken($env{'form.datatoken'});
+    if ($datatoken ne '') {
+        &Apache::loncommon::load_tmp_file($r,$datatoken);
+    }
     my @userdata=&Apache::loncommon::upfile_record_sep();
     if($env{'form.noFirstLine'}){shift(@userdata);}
     my @keyfields = split(/\,/,$env{'form.keyfields'});
@@ -4121,10 +4901,6 @@ sub upfile_drop_add {
             $fields{$env{'form.f'.$i}}=$keyfields[$i];
         }
     }
-    if ($env{'form.fullup'} ne 'yes') {
-        $r->print('<form name="studentform" method="post" action="/adm/createuser">'."\n".
-                  '<input type="hidden" name="action" value="'.$env{'form.action'}.'" />');
-    }
     #
     # Store the field choices away
     my @storefields = qw/username names fname mname lname gen id 
@@ -4138,17 +4914,42 @@ sub upfile_drop_add {
         $fieldstype{$field.'_choice'} = 'scalar';
     }
     &Apache::loncommon::store_course_settings('enrollment_upload',\%fieldstype);
-    my ($cid,$crstype,$setting);
+    my ($cid,$crstype,$setting,$crsdom,$crsnum,$oldcrsuserdoms,%emptyok);
     if ($context eq 'domain') {
         $setting = $env{'form.roleaction'};
+        if (exists($fields{'names'})) {
+            map { $emptyok{$_} = 1; } ('lastname','firstname','middlename');
+        } else {
+            if (exists($fields{'lname'})) {
+                $emptyok{'lastname'} = 1;
+            }
+            if (exists($fields{'fname'})) {
+                $emptyok{'firstname'} = 1;
+            }
+            if (exists($fields{'mname'})) {
+                $emptyok{'middlename'} = 1;
+            }
+        }
+        if (exists($fields{'gen'})) {
+            $emptyok{'generation'} = 1;
+        }
     }
     if ($env{'request.course.id'} ne '') {
         $cid = $env{'request.course.id'};
         $crstype = &Apache::loncommon::course_type();
+        $crsdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+        $crsnum = $env{'course.'.$env{'request.course.id'}.'.num'};
     } elsif ($setting eq 'course') {
         if (&Apache::lonnet::is_course($env{'form.dcdomain'},$env{'form.dccourse'})) {
             $cid = $env{'form.dcdomain'}.'_'.$env{'form.dccourse'};
             $crstype = &Apache::loncommon::course_type($cid);
+            $crsdom = $env{'form.dcdomain'};
+            $crsnum = $env{'form.dccourse'};
+            if (exists($env{'course.'.$cid.'.internal.userdomains'})) {
+                $oldcrsuserdoms = 1;
+            }
+            my %coursedesc = &Apache::lonnet::coursedescription($cid,{ one_time => 1 });
+            $env{'course.'.$cid.'.internal.userdomains'} = $coursedesc{'internal.userdomains'};
         }
     }
     my ($startdate,$enddate) = &get_dates_from_form();
@@ -4159,7 +4960,47 @@ sub upfile_drop_add {
     my $defdom=$env{'request.role.domain'};
     my $domain;
     if ($env{'form.defaultdomain'} ne '') {
-        $domain = $env{'form.defaultdomain'};
+        if (($context eq 'course') || ($setting eq 'course')) {
+            if ($env{'form.defaultdomain'} eq $crsdom) {
+                $domain = $env{'form.defaultdomain'};
+            } else {
+                if (&Apache::lonnet::will_trust('enroll',$crsdom,$env{'form.defaultdomain'})) {
+                    $domain = $env{'form.defaultdomain'};
+                } else {
+                    $r->print('<span class="LC_error">'.&mt('Error').': '.
+                              &mt('Enrollment of users not permitted for specified default domain: [_1].',
+                                  &Apache::lonnet::domain($env{'form.defaultdomain'},'description')).'</span>');
+                    return 'untrusted';
+                }
+            }
+        } elsif ($context eq 'author') {
+            if ($env{'form.defaultdomain'} eq $defdom) {
+                $domain = $env{'form.defaultdomain'}; 
+            } else {
+                if ((&Apache::lonnet::will_trust('othcoau',$defdom,$env{'form.defaultdomain'})) &&
+                    (&Apache::lonnet::will_trust('coaurem',$env{'form.defaultdomain'},$defdom))) {
+                    $domain = $env{'form.defaultdomain'};
+                } else {
+                    $r->print('<span class="LC_error">'.&mt('Error').': '.
+                              &mt('Addition of users not permitted for specified default domain: [_1].',
+                                  &Apache::lonnet::domain($env{'form.defaultdomain'},'description')).'</span>');
+                    return 'untrusted';
+                }
+            }
+        } elsif (($context eq 'domain') && ($setting eq 'domain')) {
+            if ($env{'form.defaultdomain'} eq $defdom) {
+                $domain = $env{'form.defaultdomain'};
+            } else {
+                if (&Apache::lonnet::will_trust('domroles',$defdom,$env{'form.defaultdomain'})) {
+                    $domain = $env{'form.defaultdomain'};
+                } else {
+                    $r->print('<span class="LC_error">'.&mt('Error').': '.
+                              &mt('Addition of users not permitted for specified default domain: [_1].',
+                                  &Apache::lonnet::domain($env{'form.defaultdomain'},'description')).'</span>');
+                    return 'untrusted';
+                }
+            }
+        }
     } else {
         $domain = $defdom;
     }
@@ -4169,10 +5010,9 @@ sub upfile_drop_add {
     } else {
         my %home_servers = &Apache::lonnet::get_servers($defdom,'library');
         if (! exists($home_servers{$desiredhost})) {
-            $r->print('<span class="LC_error">'.&mt('Error').
-                      &mt('Invalid home server specified').'</span>');
-            $r->print(&Apache::loncommon::end_page());
-            return;
+            $r->print('<p class="LC_error">'.&mt('Error').': '.
+                      &mt('Invalid home server specified').'</p>');
+            return 'invalidhome';
         }
     }
     # Determine authentication mechanism
@@ -4182,6 +5022,7 @@ sub upfile_drop_add {
     }
     my $amode  = '';
     my $genpwd = '';
+    my @genpwdfail;
     if ($env{'form.login'} eq 'krb') {
         $amode='krb';
         $amode.=$env{'form.krbver'};
@@ -4190,12 +5031,16 @@ sub upfile_drop_add {
         $amode='internal';
         if ((defined($env{'form.intarg'})) && ($env{'form.intarg'})) {
             $genpwd=$env{'form.intarg'};
+            @genpwdfail =
+                &Apache::loncommon::check_passwd_rules($domain,$genpwd);
         }
     } elsif ($env{'form.login'} eq 'loc') {
         $amode='localauth';
         if ((defined($env{'form.locarg'})) && ($env{'form.locarg'})) {
             $genpwd=$env{'form.locarg'};
         }
+    } elsif ($env{'form.login'} eq 'lti') {
+        $amode='lti';
     }
     if ($amode =~ /^krb/) {
         if (! defined($genpwd) || $genpwd eq '') {
@@ -4268,10 +5113,14 @@ sub upfile_drop_add {
                                                   \@statuses,\@poss_roles);
                 &gather_userinfo($context,'view',\%userlist,$indexhash,\%info,
                              \%cstr_roles,$permission);
-
             }
         }
     }
+    if ($datatoken eq '') {
+        $r->print('<p class="LC_error">'.&mt('Error').': '.
+                  &mt('Invalid datatoken').'</p>');
+        return 'missingdata';
+    }
     if ( $domain eq &LONCAPA::clean_domain($domain)
         && ($amode ne '')) {
         #######################################
@@ -4285,6 +5134,8 @@ sub upfile_drop_add {
             $r->print('<h3>'.&mt('Adding/Modifying Users')."</h3>\n<p>\n");
         }
         $r->rflush;
+        my (%got_role_approvals,%got_instdoms,%process_by,%instdoms,
+            %pending,%reject,%notifydc,%status,%unauthorized,%currqueued);
 
         my %counts = (
                        user => 0,
@@ -4341,8 +5192,10 @@ sub upfile_drop_add {
         my $newuserdom = $env{'request.role.domain'};
         map { $cancreate{$_} = &can_create_user($newuserdom,$context,$_); } keys(%longtypes);
         # Get new users list
-        my (%existinguser,%userinfo,%disallow,%rulematch,%inst_results,%alerts,%checkuname);
+        my (%existinguser,%userinfo,%disallow,%rulematch,%inst_results,%alerts,%checkuname,
+            %showpasswdrules,$haspasswdmap);
         my $counter = -1;
+        my (%willtrust,%trustchecked);
         foreach my $line (@userdata) {
             $counter ++;
             my @secs;
@@ -4390,6 +5243,28 @@ sub upfile_drop_add {
                                 '"<b>'.$entries{$fields{'domain'}}.'</b>"',
                                 $fname,$mname,$lname,$gen);
                         next;
+                    } elsif ($entries{$fields{'domain'}} ne $domain) {
+                        my $possdom = $entries{$fields{'domain'}};
+                        if ($context eq 'course' || $setting eq 'course') {
+                            unless ($trustchecked{$possdom}) {
+                                $willtrust{$possdom} = &Apache::lonnet::will_trust('enroll',$domain,$possdom);
+                                $trustchecked{$possdom} = 1;
+                            }
+                        } elsif ($context eq 'author') {
+                            unless ($trustchecked{$possdom}) {
+                                $willtrust{$possdom} = &Apache::lonnet::will_trust('othcoau',$domain,$possdom);
+                            }
+                            if ($willtrust{$possdom}) {
+                                $willtrust{$possdom} = &Apache::lonnet::will_trust('coaurem',$possdom,$domain); 
+                            }
+                        }
+                        unless ($willtrust{$possdom}) {
+                            $disallow{$counter} =
+                                &mt('Unacceptable domain [_1] for user [_2] [_3] [_4] [_5]',
+                                    '"<b>'.$possdom.'</b>"',
+                                    $fname,$mname,$lname,$gen);
+                            next;
+                        }
                     }
                     my $username = $entries{$fields{'username'}};
                     my $userdomain = $entries{$fields{'domain'}};
@@ -4469,11 +5344,43 @@ sub upfile_drop_add {
                         }
                     }
                     # determine user password
-                    my $password = $genpwd;
+                    my $password;
+                    my $passwdfromfile;
                     if (defined($fields{'ipwd'})) {
                         if ($entries{$fields{'ipwd'}}) {
                             $password=$entries{$fields{'ipwd'}};
+                            $passwdfromfile = 1;
+                            if ($env{'form.login'} eq 'int') {
+                                my $uhome=&Apache::lonnet::homeserver($username,$userdomain);
+                                if (($uhome eq 'no_host') || ($changeauth)) {
+                                    my @brokepwdrules =
+                                        &Apache::loncommon::check_passwd_rules($domain,$password);
+                                    if (@brokepwdrules) {
+                                        $disallow{$counter} = &mt('[_1]: Password included in file for this user did not meet requirements.',
+                                                                  '<b>'.$username.'</b>');
+                                        map { $showpasswdrules{$_} = 1; } @brokepwdrules;
+                                        next;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    unless ($passwdfromfile) {
+                        if ($env{'form.login'} eq 'int') {
+                            if (@genpwdfail) {
+                                my $uhome=&Apache::lonnet::homeserver($username,$userdomain);
+                                if (($uhome eq 'no_host') || ($changeauth)) {
+                                    $disallow{$counter} = &mt('[_1]: No specific password in file for this user; default password did not meet requirements',
+                                                              '<b>'.$username.'</b>');
+                                    unless ($haspasswdmap) {
+                                        map { $showpasswdrules{$_} = 1; } @genpwdfail;
+                                        $haspasswdmap = 1;
+                                    }
+                                }
+                                next;
+                            }
                         }
+                        $password = $genpwd;
                     }
                     # determine user role
                     my $role = '';
@@ -4542,7 +5449,7 @@ sub upfile_drop_add {
                                 &mt('The user does not already exist, and you may not create a new user in a different domain.');
                             next;
                         } else {
-                            unless ($password || $env{'form.login'} eq 'loc') {
+                            unless (($password ne '') || ($env{'form.login'} eq 'loc') || ($env{'form.login'} eq 'lti')) {
                                 $disallow{$counter} =
                                     &mt('[_1]: This is a new user but no default password was provided, and the authentication type requires one.',
                                         '<b>'.$username.'</b>');
@@ -4745,12 +5652,33 @@ sub upfile_drop_add {
                     my (%userres,%authres,%roleres,%idres);
                     my $singlesec = '';
                     if ($role eq 'st') {
+                        if (($context eq 'domain') && ($changeauth eq 'Yes') && (!$newuser)) {
+                            if ((&Apache::lonnet::allowed('mau',$userdomain)) &&
+                                (&Apache::lonnet::homeserver($username,$userdomain) ne 'no_host')) {
+                                if ((($amode =~ /^krb4|krb5|internal$/) && $password ne '') ||
+                                     ($amode eq 'localauth')) {
+                                    $authresult =
+                                        &Apache::lonnet::modifyuserauth($userdomain,$username,$amode,$password);
+                                }
+                            }
+                        }
                         my $sec;
                         if (ref($userinfo{$i}{'sections'}) eq 'ARRAY') {
                             if (@secs > 0) {
                                 $sec = $secs[0];
                             }
                         }
+                        if ($userdomain ne $env{'request.role.domain'}) {
+                            my $item = "/$crsdom/$crsnum" ;
+                            if ($sec ne '') {
+                                $item .= "/$sec";
+                            }
+                            $item .= '_st';
+                            next if (&restricted_dom($context,$item,$userdomain,$username,$role,$startdate,
+                                                     $enddate,$crsdom,$crsnum,$sec,$credits,\%process_by,
+                                                     \%instdoms,\%got_role_approvals,\%got_instdoms,\%reject,
+                                                     \%pending,\%notifydc,\%status,\%unauthorized,\%currqueued));
+                        }
                         &modifystudent($userdomain,$username,$cid,$sec,
                                        $desiredhost,$context);
                         $roleresult =
@@ -4762,16 +5690,34 @@ sub upfile_drop_add {
                                  '',$context,$inststatus,$credits);
                         $userresult = $roleresult;
                     } else {
-                        if ($role ne '') { 
+                        my $possrole;
+                        if ($role ne '') {
                             if ($context eq 'course' || $setting eq 'course') {
                                 if ($customroles{$role}) {
                                     $role = 'cr_'.$env{'user.domain'}.'_'.
                                             $env{'user.name'}.'_'.$role;
                                 }
-                                if (($role ne 'cc') && ($role ne 'co')) { 
+                                $possrole = $role;
+                                if ($possrole =~ /^cr_/) {
+                                    $possrole =~ s{_}{/}g;
+                                }
+                                if (($role ne 'cc') && ($role ne 'co')) {
                                    if (@secs > 1) {
                                         $multiple = 1;
+                                        my $prefix = "/$crsdom/$crsnum";
                                         foreach my $sec (@secs) {
+                                            if ($userdomain ne $env{'request.role.domain'}) {
+                                                my $item = $prefix;
+                                                if ($sec ne '') {
+                                                    $item .= "/$sec";
+                                                }
+                                                $item .= '_'.$possrole;
+                                                next if (&restricted_dom($context,$item,$userdomain,$username,$possrole,
+                                                                         $startdate,$enddate,$crsdom,$crsnum,$sec,
+                                                                         $credits,\%process_by,\%instdoms,\%got_role_approvals,
+                                                                         \%got_instdoms,\%reject,\%pending,\%notifydc,
+                                                                         \%status,\%unauthorized,\%currqueued));
+                                            }
                                             ($userres{$sec},$authres{$sec},$roleres{$sec},$idres{$sec}) =
                                             &modifyuserrole($context,$setting,
                                                 $changeauth,$cid,$userdomain,$username,
@@ -4785,18 +5731,31 @@ sub upfile_drop_add {
                                         $singlesec = $secs[0];
                                     }
                                 }
-                            }
-                            if (!$multiple) {
-                                ($userresult,$authresult,$roleresult,$idresult) = 
-                                    &modifyuserrole($context,$setting,
-                                                    $changeauth,$cid,$userdomain,$username, 
-                                                    $id,$amode,$password,$fname,
-                                                    $mname,$lname,$gen,$singlesec,
-                                                    $env{'form.forceid'},$desiredhost,
-                                                    $email,$role,$enddate,$startdate,
-                                                    $checkid,$inststatus);
+                            } else {
+                                $possrole = $role;
                             }
                         }
+                        if (!$multiple) {
+                            if (($userdomain ne $env{'request.role.domain'}) && ($role ne '')) {
+                                my $item = "/$crsdom/$crsnum";
+                                if ($singlesec ne '') {
+                                    $item .= "/$singlesec";
+                                }
+                                $item .= '_'.$possrole;
+                                next if (&restricted_dom($context,$item,$userdomain,$username,$possrole,$startdate,$enddate,
+                                                         $crsdom,$crsnum,$singlesec,$credits,\%process_by,\%instdoms,
+                                                         \%got_role_approvals,\%got_instdoms,\%reject,\%pending,\%notifydc,
+                                                         \%status,\%unauthorized,\%currqueued));
+                            }
+                            ($userresult,$authresult,$roleresult,$idresult) = 
+                                &modifyuserrole($context,$setting,
+                                                $changeauth,$cid,$userdomain,$username, 
+                                                $id,$amode,$password,$fname,
+                                                $mname,$lname,$gen,$singlesec,
+                                                $env{'form.forceid'},$desiredhost,
+                                                $email,$role,$enddate,$startdate,
+                                                $checkid,$inststatus,\%emptyok);
+                        }
                     }
                     if ($multiple) {
                         foreach my $sec (sort(keys(%userres))) {
@@ -4817,6 +5776,13 @@ sub upfile_drop_add {
             } # end of loop
             $r->print('</ul>');
             &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
+            if (($context eq 'domain') && ($setting eq 'course')) {
+                unless ($oldcrsuserdoms) {
+                    if (exists($env{'course.'.$cid.'.internal.userdomains'})) {
+                        delete($env{'course.'.$cid.'.internal.userdomains'});
+                    }
+                }
+            }
         }
         # Flush the course logs so reverse user roles immediately updated
         $r->register_cleanup(\&Apache::lonnet::flushcourselogs);
@@ -4824,7 +5790,9 @@ sub upfile_drop_add {
                   "</p>\n");
         if ($counts{'role'} > 0) {
             $r->print("<p>\n".
-                      &mt('Roles added for [quant,_1,user].',$counts{'role'}).' '.&mt('If a user is currently logged-in to LON-CAPA, any new roles which are active will be available when the user next logs in.')."</p>\n");
+                      &mt('Roles added for [quant,_1,user].',$counts{'role'}).' '.
+                      &mt('If a user is currently logged-in to LON-CAPA, any new roles which are active will be available when the user next logs in.').
+                      "</p>\n");
         } else {
             $r->print('<p>'.&mt('No roles added').'</p>');
         }
@@ -4834,6 +5802,13 @@ sub upfile_drop_add {
                           $counts{'auth'})."</p>\n");
         }
         $r->print(&print_namespacing_alerts($domain,\%alerts,\%curr_rules));
+        $r->print(&passwdrule_alerts($domain,\%showpasswdrules));
+        if ((keys(%reject)) || (keys(%unauthorized))) {
+            $r->print(&print_roles_rejected($context,\%reject,\%unauthorized));
+        }
+        if ((keys(%pending)) || (keys(%currqueued))) {
+            $r->print(&print_roles_queued($context,\%pending,\%notifydc,\%currqueued));
+        }
         #####################################
         # Display list of students to drop  #
         #####################################
@@ -4842,10 +5817,9 @@ sub upfile_drop_add {
             #  Get current classlist
             my $classlist = &Apache::loncoursedata::get_classlist();
             if (! defined($classlist)) {
-                $r->print('<form name="studentform" method="post" action="/adm/createuser">'.
-                          '<input type="hidden" name="action" value="'.$env{'form.action'}.'" />'.
-                          '<p class="LC_info">'.&mt('There are no students with current/future access to the course.').'</p>'.
-                          '</form>'."\n");
+                $r->print('<p class="LC_info">'.
+                          &mt('There are no students with current/future access to the course.').
+                          '</p>'."\n");
             } elsif (ref($classlist) eq 'HASH') {
                 # Remove the students we just added from the list of students.
                 foreach my $line (@userdata) {
@@ -4861,9 +5835,7 @@ sub upfile_drop_add {
             }
         }
     } # end of unless
-    if ($env{'form.fullup'} ne 'yes') {
-        $r->print('</form>');
-    }
+    return 'ok';
 }
 
 sub print_namespacing_alerts {
@@ -4906,6 +5878,42 @@ sub print_namespacing_alerts {
     }
 }
 
+sub passwdrule_alerts {
+    my ($domain,$passwdrules) = @_;
+    my $warning;
+    if (ref($passwdrules) eq 'HASH') {
+        my %showrules = %{$passwdrules};
+        if (keys(%showrules)) {
+            my %passwdconf = &Apache::lonnet::get_passwdconf($domain);
+            $warning = '<b>'.&mt('Password requirement(s) unmet for one or more users:').'</b><ul>';
+            if ($showrules{'min'}) {
+                my $min = $passwdconf{'min'};
+                if ($min eq '') {
+                    $min = $Apache::lonnet::passwdmin;
+                }
+                $warning .= '<li>'.&mt('minimum [quant,_1,character]',$min).'</li>';
+            }
+            if ($showrules{'max'}) {
+                $warning .= '<li>'.&mt('maximum [quant,_1,character]',$passwdconf{'max'}).'</li>';
+            }
+            if ($showrules{'uc'}) {
+                $warning .= '<li>'.&mt('contain at least one upper case letter').'</li>';
+            }
+            if ($showrules{'lc'}) {
+                $warning .= '<li>'.&mt('contain at least one lower case letter').'</li>';
+            }
+            if ($showrules{'num'}) {
+                $warning .= '<li>'.&mt('contain at least one number').'</li>';
+            }
+            if ($showrules{'spec'}) {
+                $warning .= '<li>'.&mt('contain at least one non-alphanumeric').'</li>';
+            }
+            $warning .= '</ul>';
+        }
+    }
+    return $warning;
+}
+
 sub user_change_result {
     my ($r,$userresult,$authresult,$roleresult,$idresult,$counts,$flushc,
         $username,$userdomain,$userchg) = @_;
@@ -4994,7 +6002,8 @@ sub update_user_list {
     if ($context eq 'course') {
         $crstype = &Apache::loncommon::course_type();
     }
-    my @changelist;
+    my (@changelist,%got_role_approvals,%got_instdoms,%process_by,%instdoms,
+        %pending,%reject,%notifydc,%status,%unauthorized,%currqueued);
     if ($choice eq 'drop') {
         @changelist = &Apache::loncommon::get_env_multiple('form.droplist');
     } else {
@@ -5024,7 +6033,7 @@ sub update_user_list {
     foreach my $item (@changelist) {
         my ($role,$uname,$udom,$cid,$sec,$scope,$result,$type,$locktype,
             @sections,$scopestem,$singlesec,$showsecs,$warn_singlesec,
-            $nothingtodo,$keepnosection,$credits,$instsec);
+            $nothingtodo,$keepnosection,$credits,$instsec,$cdom,$cnum);
         if ($choice eq 'drop') {
             ($uname,$udom,$sec) = split(/:/,$item,-1);
             $role = 'st';
@@ -5041,6 +6050,8 @@ sub update_user_list {
                 split(/\:/,$item,8);
             $instsec = &unescape($instsec);
             $cid = $env{'request.course.id'};
+            $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+            $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
             $scopestem = '/'.$cid;
             $scopestem =~s/\_/\//g;
             if ($sec eq '') {
@@ -5051,15 +6062,20 @@ sub update_user_list {
         } elsif ($context eq 'author') {
             ($uname,$udom,$role) = split(/\:/,$item,-1);
             $scope = '/'.$env{'user.domain'}.'/'.$env{'user.name'};
+            $cdom = $env{'user.domain'};
+            $cnum = $env{'user.name'};
         } elsif ($context eq 'domain') {
             if ($setting eq 'domain') {
                 ($role,$uname,$udom) = split(/\:/,$item,-1);
                 $scope = '/'.$env{'request.role.domain'}.'/';
+                $cdom = $env{'request.role.domain'};
             } elsif ($setting eq 'author') { 
                 ($uname,$udom,$role,$scope) = split(/\:/,$item);
+                (undef,$cdom,$cnum) = split(/\//,$scope);
             } elsif ($setting eq 'course') {
                 ($uname,$udom,$role,$cid,$sec,$type,$locktype,$credits,$instsec) = 
                     split(/\:/,$item,9);
+                ($cdom,$cnum) = split('_',$cid);
                 $instsec = &unescape($instsec);
                 $scope = '/'.$cid;
                 $scope =~s/\_/\//g;
@@ -5101,7 +6117,12 @@ sub update_user_list {
                 $start = $startdate; 
                 $end = $enddate;
             }
+            my $id = $scope.'_'.$role;
             if ($choice eq 'reenable') {
+                next if (&restricted_dom($context,$id,$udom,$uname,$role,$now,$end,$cdom,$cnum,
+                                         $sec,$credits,\%process_by,\%instdoms,\%got_role_approvals,
+                                         \%got_instdoms,\%reject,\%pending,\%notifydc,
+                                         \%status,\%unauthorized,\%currqueued));
                 if ($role eq 'st') {
                     $result = &Apache::lonnet::modify_student_enrollment($udom,$uname,undef,undef,undef,undef,undef,$sec,$end,$start,$type,$locktype,$cid,'',$context,$credits,$instsec);
                 } else {
@@ -5110,6 +6131,10 @@ sub update_user_list {
                                                     $now,'','',$context);
                 }
             } elsif ($choice eq 'activate') {
+                next if (&restricted_dom($context,$id,$udom,$uname,$role,$now,$end,$cdom,$cnum,
+                                         $sec,$credits,\%process_by,\%instdoms,\%got_role_approvals,
+                                         \%got_instdoms,\%reject,\%pending,\%notifydc,
+                                         \%status,\%unauthorized,\%currqueued));
                 if ($role eq 'st') {
                     $result = &Apache::lonnet::modify_student_enrollment($udom,$uname,undef,undef,undef,undef,undef,$sec,$end,$start,$type,$locktype,$cid,'',$context,$credits,$instsec);
                 } else {
@@ -5117,6 +6142,10 @@ sub update_user_list {
                                             $now,'','',$context);
                 }
             } elsif ($choice eq 'chgdates') {
+                next if (&restricted_dom($context,$id,$udom,$uname,$role,$start,$end,$cdom,$cnum,
+                                         $sec,$credits,\%process_by,\%instdoms,\%got_role_approvals,
+                                         \%got_instdoms,\%reject,\%pending,\%notifydc,
+                                         \%status,\%unauthorized,\%currqueued));
                 if ($role eq 'st') {
                     $result = &Apache::lonnet::modify_student_enrollment($udom,$uname,undef,undef,undef,undef,undef,$sec,$end,$start,$type,$locktype,$cid,'',$context,$credits,$instsec);
                 } else {
@@ -5326,6 +6355,12 @@ sub update_user_list {
             $r->print(&make_dates_default($startdate,$enddate,$context,$crstype));
         }
     }
+    if ((keys(%reject)) || (keys(%unauthorized))) {
+        $r->print(&print_roles_rejected($context,\%reject,\%unauthorized));
+    }
+    if ((keys(%pending)) || (keys(%currqueued))) {
+        $r->print(&print_roles_queued($context,\%pending,\%notifydc,\%currqueued));
+    }
     my $linktext = &mt('Display User Lists');
     if ($choice eq 'drop') {
         $linktext = &mt('Display current class roster');
@@ -5409,7 +6444,7 @@ END
 }
 
 sub set_login {
-    my ($dom,$authformkrb,$authformint,$authformloc) = @_;
+    my ($dom,$authformkrb,$authformint,$authformloc,$authformlti) = @_;
     my %domconfig = &Apache::lonnet::get_dom('configuration',['usercreation'],$dom);
     my $response;
     my ($authnum,%can_assign) =
@@ -5431,6 +6466,11 @@ sub set_login {
                          '<td>'.$authformloc.'</td>'.
                          &Apache::loncommon::end_data_table_row()."\n";
         }
+        if ($can_assign{'lti'}) {
+            $response .= &Apache::loncommon::start_data_table_row().
+                         '<td>'.$authformlti.'</td>'.
+                         &Apache::loncommon::end_data_table_row()."\n";
+        }
         $response .= &Apache::loncommon::end_data_table();
     }
     return $response;
@@ -5721,12 +6761,20 @@ sub can_modify_userinfo {
     my %domconfig =
        &Apache::lonnet::get_dom('configuration',['usermodification'],
                                 $dom);
+    if (($context eq 'author') &&
+        ($env{'request.role'} =~ m{^(ca|aa)\./$match_domain/$match_username$})) {
+        $context = 'coauthor';
+    }
     my %canmodify;
     if (ref($fields) eq 'ARRAY') {
         foreach my $field (@{$fields}) {
             $canmodify{$field}  = 0;
             if (&Apache::lonnet::allowed('mau',$dom)) {
                 $canmodify{$field} = 1;
+            } elsif (($context ne 'selfcreate') &&
+                     ($env{'request.role.dom'} ne $dom) &&
+                     ($env{'user.domain'} ne $dom)) {
+                $canmodify{$field} = 0;
             } else {
                 if (ref($domconfig{'usermodification'}) eq 'HASH') {
                     if (ref($domconfig{'usermodification'}{$context}) eq 'HASH') {
@@ -5775,6 +6823,70 @@ sub can_modify_userinfo {
     return %canmodify;
 }
 
+sub can_change_internalpass {
+    my ($uname,$udom,$crstype,$permission) = @_;
+    my $canchange;
+    if (&Apache::lonnet::allowed('mau',$udom)) {
+        $canchange = 1;
+    } elsif ((ref($permission) eq 'HASH') && ($permission->{'mip'}) &&
+             ($udom eq $env{'request.role.domain'})) {
+        unless ($env{'course.'.$env{'request.course.id'}.'.internal.nopasswdchg'}) {
+            my ($cnum,$cdom) = &get_course_identity();
+            if ((&Apache::lonnet::is_course_owner($cdom,$cnum)) && ($udom eq $env{'user.domain'})) {
+                my @userstatuses = ('default');
+                my %userenv = &Apache::lonnet::userenvironment($udom,$uname,'inststatus');
+                if ($userenv{'inststatus'} ne '') {
+                    @userstatuses =  split(/:/,$userenv{'inststatus'});
+                }
+                my $noupdate = 1;
+                my %passwdconf = &Apache::lonnet::get_passwdconf($cdom);
+                if (ref($passwdconf{'crsownerchg'}) eq 'HASH') {
+                    if (ref($passwdconf{'crsownerchg'}{'for'}) eq 'ARRAY') {
+                        foreach my $status (@userstatuses) {
+                            if (grep(/^\Q$status\E$/,@{$passwdconf{'crsownerchg'}{'for'}})) {
+                                undef($noupdate);
+                                last;
+                            }
+                        }
+                    }
+                }
+                if ($noupdate) {
+                    return;
+                }
+                my %owned = &Apache::lonnet::courseiddump($cdom,'.',1,'.',
+                                                          $env{'user.name'}.':'.$env{'user.domain'},
+                                                          undef,undef,undef,'.');
+                my %roleshash = &Apache::lonnet::get_my_roles($uname,$udom,'userroles',
+                                                              ['active','future']);
+                foreach my $key (keys(%roleshash)) {
+                    my ($name,$domain,$role) = split(/:/,$key);
+                    if ($role eq 'st') {
+                        next if (($name eq $cnum) && ($domain eq $cdom));
+                        if ($owned{$domain.'_'.$name}) {
+                            if (ref($owned{$domain.'_'.$name}) eq 'HASH') {
+                                if ($owned{$domain.'_'.$name}{'nopasswdchg'}) {
+                                    $noupdate = 1;
+                                    last;
+                                }
+                            }
+                        } else {
+                            $noupdate = 1;
+                            last;
+                        }
+                    } else {
+                        $noupdate = 1;
+                        last;
+                    }
+                }
+                unless ($noupdate) {
+                    $canchange = 1;
+                }
+            }
+        }
+    }
+    return $canchange;
+}
+
 sub check_usertype {
     my ($dom,$uname,$rules,$curr_rules,$got_rules) = @_;
     my $usertype;
@@ -5895,12 +7007,21 @@ sub get_permission {
                     $permission{'selfenrolladmin'} = 1;
                 }
             }
+            unless ($permission{'selfenrolladmin'}) {
+                $permission{'selfenrollview'} = 1;
+            }
         }
         if ($env{'request.course.id'}) {
-            my $user = $env{'user.name'}.':'.$env{'user.domain'};
+            my $user;
+            if (($env{'user.name'} ne '') && ($env{'user.domain'} ne '')) {
+                $user = $env{'user.name'}.':'.$env{'user.domain'};
+            }
             if (($user ne '') && ($env{'course.'.$env{'request.course.id'}.'.internal.courseowner'} eq
                                   $user)) {
                 $permission{'owner'} = 1;
+                if (&Apache::lonnet::allowed('mip',$env{'request.course.id'})) {
+                    $permission{'mip'} = 1;
+                }
             } elsif (($user ne '') && ($env{'course.'.$env{'request.course.id'}.'.internal.co-owners'} ne '')) {
                 if (grep(/^\Q$user\E$/,split(/,/,$env{'course.'.$env{'request.course.id'}.'.internal.co-owners'}))) {
                     $permission{'co-owner'} = 1;
@@ -5908,8 +7029,23 @@ sub get_permission {
             }
         }
     } elsif ($context eq 'author') {
-        $permission{'cusr'} = &authorpriv($env{'user.name'},$env{'request.role.domain'});
-        $permission{'view'} = $permission{'cusr'};
+        my $audom = $env{'request.role.domain'};
+        my $auname = $env{'user.name'};
+        if ((&Apache::lonnet::allowed('cca',"$audom/$auname")) ||
+            (&Apache::lonnet::allowed('caa',"$audom/$auname"))) {
+            $permission{'author'} = 1;
+            $permission{'cusr'} = 1;
+            $permission{'view'} = 1;
+        }
+    } elsif ($context eq 'coauthor') {
+        my ($audom,$auname) = ($env{'request.role'} =~ m{^ca\./($match_domain)/($match_username)$});
+        if ((&Apache::lonnet::allowed('vca',"$audom/$auname")) ||
+            (&Apache::lonnet::allowed('vaa',"$audom/$auname"))) {
+            if ($env{"environment.internal.manager./$audom/$auname"}) {
+                $permission{'cusr'} = 1;
+                $permission{'view'} = 1;
+            }
+        }
     } else {
         my @allroles = &roles_by_context($context);
         foreach my $role (@allroles) {
@@ -5937,8 +7073,9 @@ sub get_permission {
         }
     }
     my $allowed = 0;
-    foreach my $perm (values(%permission)) {
-        if ($perm) { $allowed=1; last; }
+    foreach my $key (keys(%permission)) {
+        next if (($key eq 'owner') || ($key eq 'co-owner') || ($key eq 'author'));
+        if ($permission{$key}) { $allowed=1; last; }
     }
     return (\%permission,$allowed);
 }
@@ -5951,6 +7088,18 @@ sub authorpriv {
          || (&Apache::lonnet::allowed('caa',$audom.'/'.$auname))) { return ''; }    return 1;
 }
 
+sub coauthorpriv {
+    my ($auname,$audom)=@_;
+    my $uname = $env{'user.name'};
+    my $udom = $env{'user.domain'};
+    if (((&Apache::lonnet::allowed('vca',"$udom/$uname")) ||
+         (&Apache::lonnet::allowed('vaa',"$udom/$uname"))) &&
+         ($env{"environment.internal.manager./$audom/$auname"})) {
+        return 1;
+    }
+    return '';
+}
+
 sub roles_on_upload {
     my ($context,$setting,$crstype,%customroles) = @_;
     my (@possible_roles,@permitted_roles);
@@ -5987,7 +7136,7 @@ sub get_course_identity {
 }
 
 sub dc_setcourse_js {
-    my ($formname,$mode,$context,$showcredits) = @_;
+    my ($formname,$mode,$context,$showcredits,$domain) = @_;
     my ($dc_setcourse_code,$authen_check);
     my $cctext = &Apache::lonnet::plaintext('cc');
     my $cotext = &Apache::lonnet::plaintext('co');
@@ -5996,7 +7145,7 @@ sub dc_setcourse_js {
     if ($mode eq 'upload') {
         $role = 'courserole';
     } else {
-        $authen_check = &verify_authen($formname,$context);
+        $authen_check = &verify_authen($formname,$context,$domain);
     }
     $dc_setcourse_code = (<<"SCRIPTTOP");
 $authen_check
@@ -6140,12 +7289,14 @@ ENDSCRIPT
 }
 
 sub verify_authen {
-    my ($formname,$context) = @_;
+    my ($formname,$context,$domain) = @_;
     my %alerts = &authcheck_alerts();
     my $finish = "return 'ok';";
     if ($context eq 'author') {
         $finish = "document.$formname.submit();";
     }
+    my ($numrules,$intargjs) =
+        &Apache::loncommon::passwd_validation_js('argpicked',$domain);
     my $outcome = <<"ENDSCRIPT";
 
 function auth_check() {
@@ -6179,6 +7330,7 @@ function auth_check() {
                 break;
             case 'int':
                 alertmsg = '$alerts{'ipass'}';
+                break;
             case 'fsys':
                 alertmsg = '$alerts{'ipass'}';
                 break;
@@ -6192,6 +7344,11 @@ function auth_check() {
             alert(alertmsg);
             return;
         }
+    } else if (logintype == 'int') {
+        var numrules = $numrules;
+        if (numrules > 0) {
+$intargjs
+        }
     }
     $finish
 }
@@ -6343,7 +7500,7 @@ sub selfenrollment_administration {
         }
     }
     if ($settings{'internal.selfenrollmgrdc'} ne '') {
-        my @in_domain = split(/,/,$settings{'internal.selfenrollmgrdc'});
+        @in_domain = split(/,/,$settings{'internal.selfenrollmgrdc'});
         my @diffs = &Apache::loncommon::compare_arrays(\@in_domain,$possconfigs);
         unless (@diffs) {
             return (\@in_course,\@in_domain);
@@ -6811,6 +7968,27 @@ sub adhoc_staff {
     return $output;
 }
 
+sub get_othdom_shownfields {
+    my ($dom,$userinfo) = @_;
+    return unless (ref($userinfo) eq 'ARRAY');
+    my %domdefaults = &Apache::lonnet::get_domain_defaults($dom);
+    my @insttypes;
+    if (ref($domdefaults{'inststatustypes'}) eq 'HASH') {
+        @insttypes = keys(%{$domdefaults{'inststatustypes'}});
+    }
+    push(@insttypes,'default');
+    my %shownfields;
+    if (ref($domdefaults{'privacyothdom'}) eq 'HASH') {
+        %shownfields = %{$domdefaults{'privacyothdom'}};
+    } else {
+        foreach my $type (@insttypes) {
+            foreach my $field (@{$userinfo}) {
+                $shownfields{$type}{$field} = 1;
+            }
+        }
+    }
+    return %shownfields;
+}
 
 1;