diff --git a/lib/WeBWorK/ContentGenerator/CourseAdmin.pm b/lib/WeBWorK/ContentGenerator/CourseAdmin.pm index d4732a0b6e..113e52f233 100644 --- a/lib/WeBWorK/ContentGenerator/CourseAdmin.pm +++ b/lib/WeBWorK/ContentGenerator/CourseAdmin.pm @@ -230,20 +230,18 @@ sub pre_header_initialize ($c) { } sub add_course_form ($c) { + $c->param('number_of_additional_users', ($c->param('number_of_additional_users') // 0) + 1) + if $c->param('add_another_instructor'); + return $c->include('ContentGenerator/CourseAdmin/add_course_form'); } sub add_course_validate ($c) { my $ce = $c->ce; - my $add_courseID = trim_spaces($c->param('new_courseID')) || ''; - my $add_initial_userID = trim_spaces($c->param('add_initial_userID')) || ''; - my $add_initial_password = trim_spaces($c->param('add_initial_password')) || ''; - my $add_initial_confirmPassword = trim_spaces($c->param('add_initial_confirmPassword')) || ''; - my $add_initial_firstName = trim_spaces($c->param('add_initial_firstName')) || ''; - my $add_initial_lastName = trim_spaces($c->param('add_initial_lastName')) || ''; - my $add_initial_email = trim_spaces($c->param('add_initial_email')) || ''; - my $add_dbLayout = trim_spaces($c->param('add_dbLayout')) || ''; + my $add_courseID = trim_spaces($c->param('new_courseID')) || ''; + my $number_of_additional_users = $c->param('number_of_additional_users') || 0; + my $add_dbLayout = trim_spaces($c->param('add_dbLayout')) || ''; my @errors; @@ -260,24 +258,16 @@ sub add_course_validate ($c) { push @errors, $c->maketext('Course ID cannot exceed [_1] characters.', $ce->{maxCourseIdLength}); } - if ($add_initial_userID ne '') { - if ($add_initial_password eq '') { - push @errors, $c->maketext('You must specify a password for the initial instructor.'); - } - if ($add_initial_confirmPassword eq '') { - push @errors, $c->maketext('You must confirm the password for the initial instructor.'); - } - if ($add_initial_password ne $add_initial_confirmPassword) { - push @errors, $c->maketext('The password and password confirmation for the instructor must match.'); - } - if ($add_initial_firstName eq '') { - push @errors, $c->maketext('You must specify a first name for the initial instructor.'); - } - if ($add_initial_lastName eq '') { - push @errors, $c->maketext('You must specify a last name for the initial instructor.'); - } - if ($add_initial_email eq '') { - push @errors, $c->maketext('You must specify an email address for the initial instructor.'); + for (1 .. $number_of_additional_users) { + my $userID = trim_spaces($c->param("add_initial_userID_$_")) || ''; + + unless ($userID =~ /^[\w-.,]*$/) { + push @errors, + $c->maketext( + 'User ID number [_1] may only contain letters, numbers, hyphens, periods, commas, ' + . 'and underscores.', + $_ + ); } } @@ -300,16 +290,10 @@ sub do_add_course ($c) { my $db = $c->db; my $authz = $c->authz; - my $add_courseID = trim_spaces($c->param('new_courseID')) // ''; - my $add_courseTitle = ($c->param('add_courseTitle') // '') =~ s/^\s*|\s*$//gr; - my $add_courseInstitution = ($c->param('add_courseInstitution') // '') =~ s/^\s*|\s\*$//gr; - - my $add_initial_userID = trim_spaces($c->param('add_initial_userID')) // ''; - my $add_initial_password = trim_spaces($c->param('add_initial_password')) // ''; - my $add_initial_confirmPassword = trim_spaces($c->param('add_initial_confirmPassword')) // ''; - my $add_initial_firstName = trim_spaces($c->param('add_initial_firstName')) // ''; - my $add_initial_lastName = trim_spaces($c->param('add_initial_lastName')) // ''; - my $add_initial_email = trim_spaces($c->param('add_initial_email')) // ''; + my $add_courseID = trim_spaces($c->param('new_courseID')) // ''; + my $add_courseTitle = ($c->param('add_courseTitle') // '') =~ s/^\s*|\s*$//gr; + my $add_courseInstitution = ($c->param('add_courseInstitution') // '') =~ s/^\s*|\s\*$//gr; + my $number_of_additional_users = $c->param('number_of_additional_users') || 0; my $copy_from_course = trim_spaces($c->param('copy_from_course')) // ''; @@ -322,49 +306,85 @@ sub do_add_course ($c) { my @users; # copy users from current (admin) course if desired - if ($c->param('add_admin_users')) { - for my $userID ($db->listUsers) { + for my $userID ($c->param('add-admin-users')) { + unless ($db->existsUser($userID)) { + $c->addbadmessage($c->maketext( + 'User "[_1]" will not be copied from the [_2] course as it does not exist.', $userID, + $ce->{admin_course_id} + )); + next; + } + for (1 .. $number_of_additional_users) { + my $add_initial_userID = trim_spaces($c->param("add_initial_userID_$_")) // ''; + if ($userID eq $add_initial_userID) { $c->addbadmessage($c->maketext( - 'User "[_1]" will not be copied from [_2] course as it is the initial instructor.', $userID, - $ce->{admin_course_id} + 'User "[_1]" will not be copied from the [_2] course as it is the same as additional user ' + . 'number [_3].', + $userID, $ce->{admin_course_id}, $_ )); next; } - my $PermissionLevel = $db->newPermissionLevel(); - $PermissionLevel->user_id($userID); - $PermissionLevel->permission($ce->{userRoles}{admin}); - my $User = $db->getUser($userID); - my $Password = $db->getPassword($userID); - $User->status('O'); # Add admin user as an observer. - - push @users, [ $User, $Password, $PermissionLevel ] - if $authz->hasPermissions($userID, 'create_and_delete_courses'); } - } - # add initial instructor if desired - if ($add_initial_userID =~ /\S/) { - my $User = $db->newUser( - user_id => $add_initial_userID, - first_name => $add_initial_firstName, - last_name => $add_initial_lastName, - student_id => $add_initial_userID, - email_address => $add_initial_email, - status => 'O', - ); - my $Password = $db->newPassword( - user_id => $add_initial_userID, - password => cryptPassword($add_initial_password), - ); - my $PermissionLevel = $db->newPermissionLevel( - user_id => $add_initial_userID, - permission => '10', - ); + my $PermissionLevel = $db->getPermissionLevel($userID); + my $User = $db->getUser($userID); + my $Password = $db->getPassword($userID); + + # Enroll student users, and make all other users observers. + $User->status($PermissionLevel->permission == $ce->{userRoles}{student} ? 'C' : 'O'); + push @users, [ $User, $Password, $PermissionLevel ]; } - push @{ $courseOptions{PRINT_FILE_NAMES_FOR} }, map { $_->[0]->user_id } @users; + # add additional instructors if desired + for (1 .. $number_of_additional_users) { + my $userID = trim_spaces($c->param("add_initial_userID_$_")) // ''; + my $password = trim_spaces($c->param("add_initial_password_$_")) // ''; + my $firstName = trim_spaces($c->param("add_initial_firstName_$_")) // ''; + my $lastName = trim_spaces($c->param("add_initial_lastName_$_")) // ''; + my $email = trim_spaces($c->param("add_initial_email_$_")) // ''; + my $permissionLevel = $c->param("add_initial_permission_$_"); + my $add_user = $c->param("add_initial_user_$_") // 0; + + if ($userID =~ /\S/) { + my $User = $db->newUser( + user_id => $userID, + first_name => $firstName, + last_name => $lastName, + email_address => $email, + status => $permissionLevel == $ce->{userRoles}{student} ? 'C' : 'O', + ); + my $Password = $db->newPassword( + user_id => $userID, + password => $password ? cryptPassword($password) : '', + ); + my $PermissionLevel = $db->newPermissionLevel( + user_id => $userID, + permission => $permissionLevel, + ); + push @users, [ $User, $Password, $PermissionLevel ]; + + # Add initial user to admin course if asked. + if ($add_user) { + if ($db->existsUser($userID)) { + $c->addbadmessage($c->maketext( + 'User "[_1]" will not be added to the [_2] course as it already exists.', $userID, + $ce->{admin_course_id} + )); + } else { + $User->status('D'); # By default don't allow user to login. + $db->addUser($User); + $db->addPassword($Password); + $db->addPermissionLevel($PermissionLevel); + $User->status($permissionLevel == $ce->{userRoles}{student} ? 'C' : 'O'); + } + } + } + } + + push @{ $courseOptions{PRINT_FILE_NAMES_FOR} }, + map { $_->[0]->user_id } grep { $_->[2]->permission >= $ce->{userRoles}{professor} } @users; # Include any optional arguments, including a template course and the course title and course institution. my %optional_arguments; @@ -384,11 +404,10 @@ sub do_add_course ($c) { eval { addCourse( - courseID => $add_courseID, - ce => $ce2, - courseOptions => \%courseOptions, - users => \@users, - initial_userID => $add_initial_userID, + courseID => $add_courseID, + ce => $ce2, + courseOptions => \%courseOptions, + users => \@users, %optional_arguments, ); }; @@ -417,11 +436,7 @@ sub do_add_course ($c) { "\tAdded", (defined $add_courseInstitution ? $add_courseInstitution : '(no institution specified)'), (defined $add_courseTitle ? $add_courseTitle : '(no title specified)'), - $add_courseID, - $add_initial_firstName, - $add_initial_lastName, - $add_initial_email, - ) + $add_courseID) ); push( @$output, diff --git a/lib/WeBWorK/ContentGenerator/Instructor/SendMail.pm b/lib/WeBWorK/ContentGenerator/Instructor/SendMail.pm index 723e922758..1b0c86a2d1 100644 --- a/lib/WeBWorK/ContentGenerator/Instructor/SendMail.pm +++ b/lib/WeBWorK/ContentGenerator/Instructor/SendMail.pm @@ -113,8 +113,9 @@ sub initialize ($c) { 'user_id' ); - # Filter out users who don't get included in email - @Users = grep { $ce->status_abbrev_has_behavior($_->status, "include_in_email") } @Users; + # Filter out users who don't get included in email unless in the admin course. + @Users = grep { $ce->status_abbrev_has_behavior($_->status, "include_in_email") } @Users + unless $ce->{courseName} eq $ce->{admin_course_id}; # Cache the user records for later use. $c->{ra_user_records} = \@Users; diff --git a/lib/WeBWorK/Utils/CourseManagement.pm b/lib/WeBWorK/Utils/CourseManagement.pm index f8f6450ef9..a20f797e5f 100644 --- a/lib/WeBWorK/Utils/CourseManagement.pm +++ b/lib/WeBWorK/Utils/CourseManagement.pm @@ -201,7 +201,7 @@ boolean options: =cut sub addCourse { - my (%options) = (initial_userID => '', @_); + my (%options) = @_; for my $key (keys(%options)) { my $value = '####UNDEF###'; @@ -217,7 +217,8 @@ sub addCourse { debug \@users; - my ($initialUser) = grep { $_->[0]{user_id} eq $options{initial_userID} } @users; + my @initialUsers = @users; + my %user_args = map { $_->[0]{user_id} => 1 } @users; # get the database layout out of the options hash my $dbLayoutName = $courseOptions{dbLayoutName}; @@ -359,7 +360,6 @@ sub addCourse { { '!=' => $options{copyConfig} ? $ce0->{userRoles}{student} : $ce->{userRoles}{student} }, user_id => { not_like => 'set_id:%' } })); - my %user_args = map { $_->[0]{user_id} => 1 } @users; for my $user_id (@non_student_ids) { next if $user_args{$user_id}; @@ -402,12 +402,13 @@ sub addCourse { } if ($options{copyNonStudents}) { foreach my $userTriple (@users) { - my $user_id = $userTriple->[0]{user_id}; + my $user_id = $userTriple->[0]{user_id}; + next if $user_args{$user_id}; # Initial users will be assigned to everything below. my @user_sets = $db0->listUserSets($user_id); assignSetsToUsers($db, $ce, \@user_sets, [$user_id]); } } - assignSetsToUsers($db, $ce, \@set_ids, [ $initialUser->[0]{user_id} ]) if $initialUser; + assignSetsToUsers($db, $ce, \@set_ids, [ map { $_->[0]{user_id} } @initialUsers ]) if @initialUsers; } # add achievements @@ -416,16 +417,17 @@ sub addCourse { for my $achievement_id (@achievement_ids) { eval { $db->addAchievement($db0->getAchievement($achievement_id)) }; warn $@ if $@; - if ($initialUser) { + for (@initialUsers) { my $userAchievement = $db->newUserAchievement(); - $userAchievement->user_id($initialUser->[0]{user_id}); + $userAchievement->user_id($_->[0]{user_id}); $userAchievement->achievement_id($achievement_id); $db->addUserAchievement($userAchievement); } } if ($options{copyNonStudents}) { foreach my $userTriple (@users) { - my $user_id = $userTriple->[0]{user_id}; + my $user_id = $userTriple->[0]{user_id}; + next if $user_args{$user_id}; # Initial users were assigned to all achievements above. my @user_achievements = $db0->listUserAchievements($user_id); for my $achievement_id (@user_achievements) { my $userAchievement = $db->newUserAchievement(); diff --git a/templates/ContentGenerator/CourseAdmin/add_course_form.html.ep b/templates/ContentGenerator/CourseAdmin/add_course_form.html.ep index 5d258f7ab9..a822b4b0e7 100644 --- a/templates/ContentGenerator/CourseAdmin/add_course_form.html.ep +++ b/templates/ContentGenerator/CourseAdmin/add_course_form.html.ep @@ -1,23 +1,41 @@ % use WeBWorK::Utils::CourseManagement qw(listCourses); % +% # Create an array of permission values for the permission selects. +% my $permissionLevels = []; +% for my $role (sort { $ce->{userRoles}{$a} <=> $ce->{userRoles}{$b} } keys %{ $ce->{userRoles} }) { + % next if $role eq 'nobody'; + % push( + % @$permissionLevels, + % [ + % $c->maketext($role) => $ce->{userRoles}{$role}, + % $role eq 'professor' ? (selected => undef) : () + % ] + % ); +% } +% my $number_of_additional_users = $c->param('number_of_additional_users') || 0; +%

<%= maketext('Add Course') %> <%= $c->helpMacro('AdminAddCourse') %>

% <%= form_for current_route, method => 'POST', begin =%> <%= $c->hidden_authen_fields =%> <%= $c->hidden_fields('subDisplay') =%> % -
<%= maketext( - 'Specify an ID, title, and institution for the new course. The course ID may contain only letters, ' - . 'numbers, hyphens, and underscores, and may have at most [_1] characters.', - $ce->{maxCourseIdLength}) %> -
-
+
<%= text_field new_courseID => '', id => 'new_courseID', placeholder => '', - class => 'form-control' =%> + class => 'form-control set-id-tooltip', + maxlength => $ce->{maxCourseIdLength}, + data => { + 'bs-placement' => 'top', + 'bs-title' => maketext( + 'Course ID may contain only letters, numbers, hyphens, and underscores, ' + . 'and may have at most [_1] characters.', + $ce->{maxCourseIdLength} + ) + } =%> <%= label_for new_courseID => maketext('Course ID') =%>
@@ -36,71 +54,97 @@
-
+
<%= maketext( - 'To add the WeBWorK administrators to the new course (as administrators) check the box below.') =%> + 'Select users from the [_1] course to add to the new course with indicated permission.', + $ce->{admin_course_id}) =%>
-
- +
+
+ % for my $user ($db->listUsers) { + % my $val = $db->getPermissionLevel($user)->permission; + % my $role = maketext((grep { $ce->{userRoles}{$_} eq $val } keys %{ $ce->{userRoles} })[0]); +
+ +
+ % } +
- <%= maketext( - 'To add an additional instructor to the new course, specify user information below. ' - . 'The user ID may contain only numbers, letters, hyphens, periods (dots), commas,and underscores.' - ) =%> -
-
-
-
- <%= text_field add_initial_userID => '', - id => 'add_initial_userID', - placeholder => '', - class => 'form-control' =%> - <%= label_for add_initial_userID => maketext('User ID') =%> -
-
- <%= password_field 'add_initial_password', - id => 'add_initial_password', - placeholder => '', - class => 'form-control' =%> - <%= label_for add_initial_password => maketext('Password') =%> -
-
- <%= password_field 'add_initial_confirmPassword', - id => 'add_initial_confirmPassword', - placeholder => '', - class => 'form-control' =%> - <%= label_for add_initial_confirmPassword => maketext('Confirm Password') =%> -
+ % for (1 .. $number_of_additional_users) { +
+ <%= maketext('Enter information for additional user number [_1].', $_) %>
-
-
- <%= text_field add_initial_firstName => '', - id => 'add_initial_firstName', - placeholder => '', - class => 'form-control' =%> - <%= label_for add_initial_firstName => maketext('First Name') =%> +
+
+
+ <%= text_field "add_initial_userID_$_" => '', + id => "add_initial_userID_$_", + placeholder => '', + class => 'form-control set-id-tooltip', + data => { + 'bs-placement' => 'top', + 'bs-title' => maketext( + 'User ID may contain only numbers, letters, hyphens, periods, and underscores.' + ) + } =%> + <%= label_for "add_initial_userID_$_" => maketext('User ID') =%> +
+
+ <%= text_field "add_initial_password_$_" => '', + id => "add_initial_password_$_", + placeholder => '', + class => 'form-control' =%> + <%= label_for "add_initial_password_$_" => maketext('Password') =%> +
+
+ <%= select_field "add_initial_permission_$_" => $permissionLevels, + id => "add_initial_role_$_", + class => 'form-select' =%> + <%= label_for "add_initial_permission_$_" => maketext('Permission Level') =%> +
-
- <%= text_field add_initial_lastName => '', - id => 'add_initial_lastName', - placeholder => '', - class => 'form-control' =%> - <%= label_for add_initial_lastName => maketext('Last Name') %> -
-
- <%= text_field add_initial_email => '', - id => 'add_initial_email', - placeholder => '', - class => 'form-control' =%> - <%= label_for add_initial_email => maketext('Email Address') =%> +
+
+ <%= text_field "add_initial_firstName_$_" => '', + id => "add_initial_firstName_$_", + placeholder => '', + class => 'form-control' =%> + <%= label_for "add_initial_firstName_$_" => maketext('First Name') =%> +
+
+ <%= text_field "add_initial_lastName_$_" => '', + id => "add_initial_lastName_$_", + placeholder => '', + class => 'form-control' =%> + <%= label_for "add_initial_lastName_$_" => maketext('Last Name') %> +
+
+ <%= text_field "add_initial_email_$_" => '', + id => "add_initial_email_$_", + placeholder => '', + class => 'form-control' =%> + <%= label_for "add_initial_email_$_" => maketext('Email Address') =%> +
+
+ +
+ % } +
+
+ <%= submit_button maketext('Add Additional User'), name => 'add_another_instructor', + class => 'btn btn-secondary' =%>
<%= maketext('To copy components from an existing course, ' @@ -189,6 +233,8 @@
- <%= hidden_field add_dbLayout => 'sql_single' =%> + <%= hidden_field add_dbLayout => 'sql_single' =%> + <%= hidden_field last_page_was_add_course => 1 =%> + <%= $c->hidden_fields('number_of_additional_users') =%> <%= submit_button maketext('Add Course'), name => 'add_course', class => 'btn btn-primary' =%> <% end =%> diff --git a/templates/ContentGenerator/Instructor/AddUsers.html.ep b/templates/ContentGenerator/Instructor/AddUsers.html.ep index 0cd6147035..a23ce58a5d 100644 --- a/templates/ContentGenerator/Instructor/AddUsers.html.ep +++ b/templates/ContentGenerator/Instructor/AddUsers.html.ep @@ -148,9 +148,11 @@
-

<%= maketext('Select sets below to assign them to the newly-created users.') %>

- % param('assignSets', undef); - <%= select_field assignSets => [ map { [ format_set_name_display($_) => $_ ] } $db->listGlobalSets ], - size => 10, multiple => undef, class => 'form-select w-auto mb-2' =%> + % unless ($ce->{courseName} eq $ce->{admin_course_id}) { +

<%= maketext('Select sets below to assign them to the newly-created users.') %>

+ % param('assignSets', undef); + <%= select_field assignSets => [ map { [ format_set_name_display($_) => $_ ] } $db->listGlobalSets ], + size => 10, multiple => undef, class => 'form-select w-auto mb-2' =%> + % }

<%= submit_button maketext('Add Users'), name => 'addStudents', class => 'btn btn-primary' =%>

<% end =%> diff --git a/templates/HelpFiles/AdminAddCourse.html.ep b/templates/HelpFiles/AdminAddCourse.html.ep index 90414aa727..17f008cfc8 100644 --- a/templates/HelpFiles/AdminAddCourse.html.ep +++ b/templates/HelpFiles/AdminAddCourse.html.ep @@ -22,13 +22,16 @@ . 'on the course home page.') =%>

- <%= maketext('The WeBWorK administrators need to be added to the course in order for them to access the course. ' - . 'Unchecking this option will make it so WeBWorK administrators cannot directly login to the course.') =%> + <%= maketext('Select which users in the [_1] course to copy over to the newly created course. The WeBWorK ' + . 'administrators need to be added to the course in order for them to access the course. Unselecting ' + . 'them will make it so WeBWorK administrators cannot directly login to the course.', + $ce->{admin_course_id}) =%>

- <%= maketext('Enter the details of the course instructor to be added when the course is created. This user ' - . 'will also be added to the admin course with the username "userID_courseID" so you can manage and ' - . 'email the instructors of the courses on the server.') =%> + <%= maketext('Click the "Add Additional User" button to add additional users to the course. The only required ' + . 'field is the user ID. You can also add this user to the [_1] course, so you can copy this user when ' + . 'creating future courses, or manage and email course users. Note, by default these new users will be ' + . '"Dropped" and unable to login to the [_1] course.', $ce->{admin_course_id}) =%>

<%= maketext('You may choose a course to copy components from. Select the course and which components to copy. '