--- loncom/interface/lonuserutils.pm 2014/02/14 17:44:00 1.162 +++ loncom/interface/lonuserutils.pm 2015/08/05 18:47:21 1.171 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Utility functions for managing LON-CAPA user accounts # -# $Id: lonuserutils.pm,v 1.162 2014/02/14 17:44:00 bisitz Exp $ +# $Id: lonuserutils.pm,v 1.171 2015/08/05 18:47:21 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -450,6 +450,7 @@ sub javascript_validations { if (($mode eq 'upload') && ($context eq 'domain')) { $alert{'inststatus'} = &mt('The optional affiliation field was not specified'); } + &js_escape(\%alert); my $function_name = <<"END"; $setsections_js @@ -642,8 +643,9 @@ sub upload_manager_javascript_forward_as $numbuttons ++; } if (!$can_assign->{'int'}) { - my $warning = &mt('You may not specify an initial password for each user, as this is only available when new users use LON-CAPA internal authentication.').'\n'. + my $warning = &mt('You may not specify an initial password for each user, as this is only available when new users use LON-CAPA internal authentication.')."\n". &mt('Your current role does not have rights to create users with that authentication type.'); + &js_escape(\$warning); $auth_update = <<"END"; // Currently the initial password field is only supported for internal auth // (see bug 6368). @@ -781,6 +783,7 @@ sub upload_manager_javascript_reverse_as if (!$can_assign->{'int'}) { my $warning = &mt('You may not specify an initial password, as this is only available when new users use LON-CAPA internal authentication.\n'). &mt('Your current role does not have rights to create users with that authentication type.'); + &js_escape(\$warning); $auth_update = <<"END"; // Currently the initial password field is only supported for internal auth // (see bug 6368). @@ -1081,14 +1084,13 @@ sub forceid_change { my ($context) = @_; my $output = '
'."\n" - .&mt('(only do if you know what you are doing.)')."\n"; + .&mt('Force change of existing ID') + .''.&Apache::loncommon::help_open_topic('ForceIDChange')."\n"; if ($context eq 'domain') { - $output .= '
'."\n"; + $output .= + '
' + .''."\n"; } return $output; } @@ -3174,6 +3176,10 @@ sub bulkaction_javascript { my $noaction = &mt("You need to select an action to take for the user(s) you have selected"); my $singconfirm = &mt(' for a single user?'); my $multconfirm = &mt(' for multiple users?'); + &js_escape(\$alert); + &js_escape(\$noaction); + &js_escape(\$singconfirm); + &js_escape(\$multconfirm); my $output = <<"ENDJS"; function verify_action (field) { var numchecked = 0; @@ -4291,7 +4297,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,%idinst_results,%alerts,%checkuname); + my $counter = -1; foreach my $line (@userdata) { + $counter ++; my @secs; my %entries=&Apache::loncommon::record_sep($line); # Determine user name @@ -4323,23 +4332,20 @@ sub upfile_drop_add { if ($entries{$fields{'username'}} =~ /\s/) { $nowhitespace = ' - '.&mt('usernames may not contain spaces.'); } - $r->print( - '
'. + $disallow{$counter} = &mt('Unacceptable username [_1] for user [_2] [_3] [_4] [_5]', - '"'.$entries{$fields{'username'}}.'"', - $fname,$mname,$lname,$gen). - $nowhitespace); + '"'.$entries{$fields{'username'}}.'"', + $fname,$mname,$lname,$gen).$nowhitespace; next; } else { $entries{$fields{'domain'}} =~ s/^\s+|\s+$//g; if ($entries{$fields{'domain'}} ne &LONCAPA::clean_domain($entries{$fields{'domain'}})) { - $r->print( - '
'. + $disallow{$counter} = &mt('Unacceptable domain [_1] for user [_2] [_3] [_4] [_5]', - '"'.$entries{$fields{'domain'}}.'"', - $fname,$mname,$lname,$gen)); - next; + '"'.$entries{$fields{'domain'}}.'"', + $fname,$mname,$lname,$gen); + next; } my $username = $entries{$fields{'username'}}; my $userdomain = $entries{$fields{'domain'}}; @@ -4351,10 +4357,15 @@ sub upfile_drop_add { $entries{$fields{'sec'}} =~ s/\W//g; my $item = $entries{$fields{'sec'}}; if ($item eq "none" || $item eq 'all') { - $r->print('
'.&mt('[_1]: Unable to enroll user [_2] [_3] [_4] [_5] in a section named "[_6]" - this is a reserved word.',''.$username.'',$fname,$mname,$lname,$gen,$item)); + $disallow{$counter} = + &mt('[_1]: Unable to enroll user [_2] [_3] [_4] [_5] in a section named "[_6]" - this is a reserved word.', + ''.$username.'',$fname,$mname,$lname,$gen,$item); next; } elsif (exists($curr_groups{$item})) { - $r->print('
'.&mt('[_1]: Unable to enroll user [_2] [_3] [_4] [_5] in a section named "[_6]" - this is a course group.',''.$username.'',$fname,$mname,$lname,$gen,$item).' '.&mt('Section names and group names must be distinct.')); + $disallow{$counter} = + &mt('[_1]: Unable to enroll user [_2] [_3] [_4] [_5] in a section named "[_6]" - this is a course group.', + ''.$username.'',$fname,$mname,$lname,$gen,$item).' '. + &mt('Section names and group names must be distinct.'); next; } else { push(@secs,$item); @@ -4366,14 +4377,21 @@ sub upfile_drop_add { if (ref($userlist{$username.':'.$userdomain}) eq 'ARRAY') { my $currsec = $userlist{$username.':'.$userdomain}[$secidx]; if ($currsec ne $env{'request.course.sec'}) { - $r->print('
'.&mt('[_1]: Unable to enroll user [_2] [_3] [_4] [_5] in a section named "[_6]".',''.$username.'',$fname,$mname,$lname,$gen,$secs[0]).'
'); + $disallow{$counter} = + &mt('[_1]: Unable to enroll user [_2] [_3] [_4] [_5] in a section named "[_6]".', + ''.$username.'',$fname,$mname,$lname,$gen,$secs[0]); if ($currsec eq '') { - $r->print(&mt('This user already has an active/future student role in the course, unaffiliated to any section.')); + $disallow{$counter} .= + &mt('This user already has an active/future student role in the course, unaffiliated to any section.'); } else { - $r->print(&mt('This user already has an active/future role in section "[_1]" of the course.',$currsec)); + $disallow{$counter} .= + &mt('This user already has an active/future role in section "[_1]" of the course.',$currsec); } - $r->print('
'.&mt('Although your current role has privileges to add students to section "[_1]", you do not have privileges to modify existing enrollments in other sections.',$secs[0]).'
'); + $disallow{$counter} .= + '
'. + &mt('Although your current role has privileges to add students to section "[_1]", you do not have privileges to modify existing enrollments in other sections.', + $secs[0]); next; } } @@ -4425,13 +4443,12 @@ sub upfile_drop_add { } if ($role eq '') { my $rolestr = join(', ',@permitted_roles); - $r->print('
' - .&mt('[_1]: You do not have permission to add the requested role [_2] for the user.' - ,''.$entries{$fields{'username'}}.'' - ,$entries{$fields{'role'}}) - .'
' - .&mt('Allowable role(s) is/are: [_1].',$rolestr)."\n" - ); + $disallow{$counter} = + &mt('[_1]: You do not have permission to add the requested role [_2] for the user.' + ,''.$entries{$fields{'username'}}.'' + ,$entries{$fields{'role'}}) + .'
' + .&mt('Allowable role(s) is/are: [_1].',$rolestr); next; } } @@ -4461,55 +4478,36 @@ sub upfile_drop_add { # check against rules my $checkid = 0; my $newuser = 0; - my (%rulematch,%inst_results,%idinst_results); my $uhome=&Apache::lonnet::homeserver($username,$userdomain); if ($uhome eq 'no_host') { if ($userdomain ne $newuserdom) { if ($context eq 'course') { - $r->print('
'. - &mt('[_1]: The domain specified ([_2]) is different to that of the course.', - ''.$username.'',$userdomain).'
'); + $disallow{$counter} = + &mt('[_1]: The domain specified ([_2]) is different to that of the course.', + ''.$username.'',$userdomain); } elsif ($context eq 'author') { - $r->print(&mt('[_1]: The domain specified ([_2]) is different to that of the author.', - ''.$username.'',$userdomain).'
'); + $disallow{$counter} = + &mt('[_1]: The domain specified ([_2]) is different to that of the author.', + ''.$username.'',$userdomain); } else { - $r->print(&mt('[_1]: The domain specified ([_2]) is different to that of your current role.', - ''.$username.'',$userdomain).'
'); + $disallow{$counter} = + &mt('[_1]: The domain specified ([_2]) is different to that of your current role.', + ''.$username.'',$userdomain); } - $r->print(&mt('The user does not already exist, and you may not create a new user in a different domain.')); + $disallow{$counter} .= + &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') { + $disallow{$counter} = + &mt('[_1]: This is a new user but no default password was provided, and the authentication type requires one.', + ''.$username.''); + next; + } } $checkid = 1; $newuser = 1; - my $user = $username.':'.$newuserdom; - my $checkhash; - my $checks = { 'username' => 1 }; - $checkhash->{$username.':'.$newuserdom} = { 'newuser' => 1, }; - &Apache::loncommon::user_rule_check($checkhash,$checks, - \%alerts,\%rulematch,\%inst_results,\%curr_rules, - \%got_rules); - if (ref($alerts{'username'}) eq 'HASH') { - if (ref($alerts{'username'}{$newuserdom}) eq 'HASH') { - if ($alerts{'username'}{$newuserdom}{$username}) { - $r->print('
'. - &mt('[_1]: matches the username format at your institution, but is not known to your directory service.',''.$username.'').'
'. - &mt('Consequently, the user was not created.')); - next; - } - } - } - my $usertype = 'unofficial'; - if (ref($rulematch{$user}) eq 'HASH') { - if ($rulematch{$user}{'username'}) { - $usertype = 'official'; - } - } - unless ($cancreate{$usertype}) { - my $showtype = $longtypes{$usertype}; - $r->print('
'. - &mt('[_1]: The user does not exist, and you are not permitted to create users of type: [_2].',''.$username.'',$showtype)); - next; - } + $checkuname{$username.':'.$newuserdom} = { 'newuser' => 1, 'id' => 1 }; } else { if ($context eq 'course' || $context eq 'author') { if ($userdomain eq $domain ) { @@ -4542,77 +4540,165 @@ sub upfile_drop_add { } } } + if ($id) { + $existinguser{$userdomain}{$username} = $id; + } } - if ($id ne '') { - if (!$newuser) { - my %idhash = &Apache::lonnet::idrget($userdomain,($username)); - if ($idhash{$username} ne $id) { - $checkid = 1; + $userinfo{$counter} = { + username => $username, + domain => $userdomain, + fname => $fname, + mname => $mname, + lname => $lname, + gen => $gen, + email => $email, + id => $id, + password => $password, + inststatus => $inststatus, + role => $role, + sections => \@secs, + credits => $credits, + newuser => $newuser, + checkid => $checkid, + }; + } + } + } # end of foreach (@userdata) + if ($counter > -1) { + my $total = $counter + 1; + my %prog_state = &Apache::lonhtmlcommon::Create_PrgWin($r,$total); + my %checkids; + if (keys(%existinguser)) { + foreach my $dom (keys(%existinguser)) { + if (ref($existinguser{$dom}) eq 'HASH') { + my %idhash = &Apache::lonnet::idrget($dom,keys(%{$existinguser{$dom}})); + foreach my $username (keys(%{$existinguser{$dom}})) { + if ($idhash{$username} ne $existinguser{$dom}{$username}) { + $checkids{$username.':'.$dom} = { 'id' => $existinguser{$dom}{$username} }; + } + } + if (keys(%checkids)) { + &Apache::loncommon::user_rule_check(\%checkids,{ 'id' => 1 }, + \%alerts,\%rulematch, + \%idinst_results,\%curr_rules, + \%got_rules); + } + } + } + } + if (keys(%checkuname)) { + &Apache::loncommon::user_rule_check(\%checkuname,{ 'username' => 1 }, + \%alerts,\%rulematch,\%inst_results, + \%curr_rules,\%got_rules); + } + $r->print(''); + } # end of loop + &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); + } # Flush the course logs so reverse user roles immediately updated $r->register_cleanup(\&Apache::lonnet::flushcourselogs); $r->print("

\n

\n".&mt('Processed [quant,_1,user].',$counts{'user'}). @@ -4751,11 +4825,12 @@ sub user_change_result { my ($r,$userresult,$authresult,$roleresult,$idresult,$counts,$flushc, $username,$userdomain,$userchg) = @_; my $okresult = 0; + my @status; if ($userresult ne 'ok') { if ($userresult =~ /^error:(.+)$/) { my $error = $1; - $r->print('
'. - &mt('[_1]: Unable to add/modify: [_2]',''.$username.':'.$userdomain.'',$error)); + push(@status, + &mt('[_1]: Unable to add/modify: [_2]',''.$username.':'.$userdomain.'',$error)); } } else { $counts->{'user'} ++; @@ -4764,8 +4839,8 @@ sub user_change_result { if ($authresult ne 'ok') { if ($authresult =~ /^error:(.+)$/) { my $error = $1; - $r->print('
'. - &mt('[_1]: Unable to modify authentication: [_2]',''.$username.':'.$userdomain.'',$error)); + push(@status, + &mt('[_1]: Unable to modify authentication: [_2]',''.$username.':'.$userdomain.'',$error)); } } else { $counts->{'auth'} ++; @@ -4774,8 +4849,8 @@ sub user_change_result { if ($roleresult ne 'ok') { if ($roleresult =~ /^error:(.+)$/) { my $error = $1; - $r->print('
'. - &mt('[_1]: Unable to add role: [_2]',''.$username.':'.$userdomain.'',$error)); + push(@status, + &mt('[_1]: Unable to add role: [_2]',''.$username.':'.$userdomain.'',$error)); } } else { $counts->{'role'} ++; @@ -4784,14 +4859,16 @@ sub user_change_result { if ($okresult) { $flushc++; $userchg->{$username.':'.$userdomain}=1; - $r->print('. '); if ($flushc>15) { $r->rflush; $flushc=0; } } if ($idresult) { - $r->print($idresult); + push(@status,$idresult); + } + if (@status) { + $r->print('

  • '.join('
    ',@status).'
  • '); } return $flushc; } @@ -5216,18 +5293,25 @@ sub active_student_roles { sub section_check_js { my $groupslist= &get_groupslist(); + my %js_lt = &Apache::lonlocal::texthash( + mayn => 'may not be used as the name for a section, as it is a reserved word.', + plch => 'Please choose a different section name.', + mnot => 'may not be used as a section name, as it is the name of a course group.', + secn => 'Section names and group names must be distinct. Please choose a different section name.', + ); + &js_escape(\%js_lt); return <<"END"; function validate(caller) { var groups = new Array($groupslist); var secname = caller.value; if ((secname == 'all') || (secname == 'none')) { - alert("'"+secname+"' may not be used as the name for a section, as it is a reserved word.\\nPlease choose a different section name."); + alert("'"+secname+"' $js_lt{'mayn'}\\n$js_lt{'plch'}"); return 'error'; } if (secname != '') { for (var k=0; k $b} keys %{$sections_count}); + my @sections = (sort {$a <=> $b} keys(%{$sections_count})); my $numsec = scalar(@sections); my $is_selected = ' selected="selected"'; if ($numsec <= 1) { @@ -5352,7 +5436,7 @@ sub setsections_javascript { } $rolecode = "var match = str.split('_'); var role = match[3];\n"; - } elsif ($formname eq 'enrollstudent') { + } elsif (($formname eq 'enrollstudent') || ($formname eq 'selfenroll')) { $checkincluded = 'formname.name == "'.$formname.'"'; if ($checkauth) { $finish = "var authcheck = auth_check();\n". @@ -5386,7 +5470,8 @@ sub setsections_javascript { mnot => 'may not be used as a section name, as it is the name of a course group.', secn => 'Section names and group names must be distinct. Please choose a different section name.', nonw => 'Section names may only contain letters or numbers.', - ); + ); + &js_escape(\%alerts); $setsection_js .= <<"ENDSECCODE"; function setSections(formname,crstype) { @@ -5397,6 +5482,9 @@ function setSections(formname,crstype) { var groups = new Array($groupslist); for (var i=0;i $env{'course.'.$env{'request.course.id'}.'.internal.selfenrollmgrdc'}, + 'internal.selfenrollmgrcc' => $env{'course.'.$env{'request.course.id'}.'.internal.selfenrollmgrcc'}, + 'internal.coursecode' => $env{'course.'.$env{'request.course.id'}.'.internal.coursecode'}, + 'internal.textbook' =>$env{'course.'.$env{'request.course.id'}.'.internal.textbook'}, + ); + my ($managed_by_cc,$managed_by_dc) = &selfenrollment_administration($cdom,$cnum,$crstype,\%coursehash); + if (ref($managed_by_cc) eq 'ARRAY') { + if (@{$managed_by_cc}) { + $permission{'selfenrolladmin'} = 1; + } + } + } } elsif ($context eq 'author') { $permission{'cusr'} = &authorpriv($env{'user.name'},$env{'request.role.domain'}); $permission{'view'} = $permission{'cusr'}; @@ -6004,6 +6108,7 @@ sub sectioncheck_alerts { thwa => 'There was a problem with your course selection', thwc => 'There was a problem with your community selection', ); + &js_escape(\%alerts); return %alerts; } @@ -6014,6 +6119,7 @@ sub authcheck_alerts { krb => 'You need to specify the Kerberos domain.', ipass => 'You need to specify the initial password.', ); + &js_escape(\%alerts); return %alerts; } @@ -6032,5 +6138,126 @@ sub is_courseowner { return; } +sub get_selfenroll_titles { + my @row = ('types','registered','enroll_dates','access_dates','section', + 'approval','limit'); + my %lt = &Apache::lonlocal::texthash ( + types => 'Users allowed to self-enroll', + registered => 'Registration status (official courses)' , + enroll_dates => 'Dates self-enrollment available', + access_dates => 'Access dates for self-enrolling users', + section => "Self-enrolling users' section", + approval => 'Processing of requests', + limit => 'Enrollment limit', + ); + return (\@row,\%lt); +} + +sub selfenroll_default_descs { + my %desc = ( + types => { + dom => &mt('Course domain'), + all => &mt('Any domain'), + '' => &mt('None'), + }, + limit => { + none => &mt('No limit'), + allstudents => &mt('Limit by total students'), + selfenrolled => &mt('Limit by total self-enrolled'), + }, + approval => { + '0' => &mt('Processed automatically'), + '1' => &mt('Queued for approval'), + '2' => &mt('Queued, pending validation'), + }, + registered => { + 0 => 'No registration required', + 1 => 'Registered students only', + }, + ); + return %desc; +} + +sub selfenroll_validation_types { + my @items = ('url','fields','button','markup'); + my %names = &Apache::lonlocal::texthash ( + url => 'Web address of validation server/script', + fields => 'Form fields to send to validator', + button => 'Text for validation button', + markup => 'Validation description (HTML)', + ); + my @fields = ('username','domain','uniquecode','course','coursetype','description'); + return (\@items,\%names,\@fields); +} + +sub get_extended_type { + my ($cdom,$cnum,$crstype,$current) = @_; + my $type = 'unofficial'; + my %settings; + if (ref($current) eq 'HASH') { + %settings = %{$current}; + } else { + %settings = &Apache::lonnet::get('environment',['internal.coursecode','internal.textbook'],$cdom,$cnum); + } + if ($crstype eq 'Community') { + $type = 'community'; + } elsif ($settings{'internal.coursecode'}) { + $type = 'official'; + } elsif ($settings{'internal.textbook'}) { + $type = 'textbook'; + } + return $type; +} + +sub selfenrollment_administration { + my ($cdom,$cnum,$crstype,$coursehash) = @_; + my %settings; + if (ref($coursehash) eq 'HASH') { + %settings = %{$coursehash}; + } else { + %settings = &Apache::lonnet::get('environment', + ['internal.selfenrollmgrdc','internal.selfenrollmgrcc', + 'internal.coursecode','internal.textbook'],$cdom,$cnum); + } + my ($possconfigs) = &get_selfenroll_titles(); + my %domdefaults = &Apache::lonnet::get_domain_defaults($cdom); + my $selfenrolltype = &get_extended_type($cdom,$cnum,$crstype,\%settings); + + my (@in_course,@in_domain); + if ($settings{'internal.selfenrollmgrcc'} ne '') { + @in_course = split(/,/,$settings{'internal.selfenrollmgrcc'}); + my @diffs = &Apache::loncommon::compare_arrays($possconfigs,\@in_course); + unless (@diffs) { + return (\@in_course,\@in_domain); + } + } + if ($settings{'internal.selfenrollmgrdc'} ne '') { + my @in_domain = split(/,/,$settings{'internal.selfenrollmgrdc'}); + my @diffs = &Apache::loncommon::compare_arrays(\@in_domain,$possconfigs); + unless (@diffs) { + return (\@in_course,\@in_domain); + } + } + my @combined = @in_course; + push(@combined,@in_domain); + my @diffs = &Apache::loncommon::compare_arrays(\@combined,$possconfigs); + unless (@diffs) { + return (\@in_course,\@in_domain); + } + if ($domdefaults{$selfenrolltype.'selfenrolladmdc'} eq '') { + push(@in_course,@diffs); + } else { + my @defaultdc = split(/,/,$domdefaults{$selfenrolltype.'selfenrolladmdc'}); + foreach my $item (@diffs) { + if (grep(/^\Q$item\E$/,@defaultdc)) { + push(@in_domain,$item); + } else { + push(@in_course,$item); + } + } + } + return (\@in_course,\@in_domain); +} + 1;