diff --git a/lib/WeBWorK/ContentGenerator/CourseAdmin.pm b/lib/WeBWorK/ContentGenerator/CourseAdmin.pm index a217800615..7b17d3bc3d 100644 --- a/lib/WeBWorK/ContentGenerator/CourseAdmin.pm +++ b/lib/WeBWorK/ContentGenerator/CourseAdmin.pm @@ -230,17 +230,19 @@ sub pre_header_initialize ($c) { } sub add_course_form ($c) { + if ($c->param('add_another_instructor')) { + my $n = $c->param('number_of_additional_users') || 0; + $c->param('number_of_additional_users', $n + 1); + } 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_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; @@ -257,11 +259,24 @@ sub add_course_validate ($c) { push @errors, $c->maketext('Course ID cannot exceed [_1] characters.', $ce->{maxCourseIdLength}); } - if ($add_initial_userID ne '' - && $add_initial_password ne '' - && $add_initial_password ne $add_initial_confirmPassword) - { - push @errors, $c->maketext('The password and password confirmation for the instructor must match.'); + for (0 .. $number_of_additional_users - 1) { + my $userID = trim_spaces($c->param("add_initial_userID_$_")) || ''; + my $password = trim_spaces($c->param("add_initial_password_$_")) || ''; + my $confirmPassword = trim_spaces($c->param("add_initial_confirmPassword_$_")) || ''; + + if ($userID ne '') { + unless ($userID =~ /^[\w-.,]*$/) { + push @errors, + $c->maketext( + 'User ID number [_1] may only contain letters, numbers, hyphens, periods, commas, ' + . 'and underscores.', + $_ + 1 + ); + } + if ($password ne '' && $password ne $confirmPassword) { + push @errors, $c->maketext('Pasword number [_1] and its password confirmation must match.', $_ + 1); + } + } } if ($add_dbLayout eq '') { @@ -283,17 +298,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_initial_user = $c->param('add_initial_user') // 0; + 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')) // ''; @@ -314,12 +322,18 @@ sub do_add_course ($c) { )); next; } - if ($userID eq $add_initial_userID) { - $c->addbadmessage($c->maketext( - 'User "[_1]" will not be copied from the [_2] course as it is the initial instructor.', $userID, - $ce->{admin_course_id} - )); - next; + for (0 .. $number_of_additional_users - 1) { + 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 the [_2] course as it is the same as additional user ' + . 'number [_3].', + $userID, + $ce->{admin_course_id} + )); + next; + } } my $PermissionLevel = $db->getPermissionLevel($userID); @@ -330,43 +344,56 @@ sub do_add_course ($c) { push @users, [ $User, $Password, $PermissionLevel ]; } - # 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, - email_address => $add_initial_email, - status => 'O', - ); - my $Password = $db->newPassword( - user_id => $add_initial_userID, - password => $add_initial_password ? cryptPassword($add_initial_password) : '', - ); - my $PermissionLevel = $db->newPermissionLevel( - user_id => $add_initial_userID, - permission => '10', - ); - push @users, [ $User, $Password, $PermissionLevel ]; - - # Add initial user to admin course if asked. - if ($add_initial_user) { - if ($db->existsUser($add_initial_userID)) { - $c->addbadmessage($c->maketext( - 'User "[_1]" will not be added to the [_2] course as it already exists.', $add_initial_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('O'); + # add additional instructors if desired + for (0 .. $number_of_additional_users - 1) { + my $userID = trim_spaces($c->param("add_initial_userID_$_")) // ''; + my $password = trim_spaces($c->param("add_initial_password_$_")) // ''; + my $confirmPassword = trim_spaces($c->param("add_initial_confirmPassword_$_")) // ''; + 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 $studentID = trim_spaces($c->param("add_initial_studentID_$_")) // ''; + my $permissionLevel = trim_spaces($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, + student_id => $studentID, + status => '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('O'); + } } } } - push @{ $courseOptions{PRINT_FILE_NAMES_FOR} }, map { $_->[0]->user_id } @users; + push @{ $courseOptions{PRINT_FILE_NAMES_FOR} }, map { $_->[0]->user_id } grep { $_->[2]->permission >= 10 } @users; # Include any optional arguments, including a template course and the course title and course institution. my %optional_arguments; @@ -386,11 +413,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, ); }; @@ -419,11 +445,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/Utils/CourseManagement.pm b/lib/WeBWorK/Utils/CourseManagement.pm index f8f6450ef9..6ea4eac5db 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,7 @@ sub addCourse { debug \@users; - my ($initialUser) = grep { $_->[0]{user_id} eq $options{initial_userID} } @users; + my @initialUsers = grep { $_->[2]->permission < $ce->{userRoles}{admin} } @users; # get the database layout out of the options hash my $dbLayoutName = $courseOptions{dbLayoutName}; @@ -407,7 +407,7 @@ sub addCourse { 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,9 +416,9 @@ 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); } diff --git a/templates/ContentGenerator/CourseAdmin/add_course_form.html.ep b/templates/ContentGenerator/CourseAdmin/add_course_form.html.ep index fab6ac3c91..c875a9c4be 100644 --- a/templates/ContentGenerator/CourseAdmin/add_course_form.html.ep +++ b/templates/ContentGenerator/CourseAdmin/add_course_form.html.ep @@ -1,5 +1,19 @@ % 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}, + % $ce->{userRoles}{$role} == 10 ? (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 =%> @@ -60,64 +74,85 @@
<%= 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.' + 'To add additional instructor(s) 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', - autocomplete => 'new-password' =%> - <%= label_for add_initial_password => maketext('Password') =%> +
+ % for (0 .. $number_of_additional_users - 1) { +
+
+
+ <%= 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', + autocomplete => 'new-password' =%> + <%= 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') =%> +
+
+ <%= select_field "add_initial_permission_$_" => $permissionLevels, + id => "add_initial_role_$_", + class => 'form-select' =%> + <%= label_for "add_initial_permission_$_" => maketext('Permission Level') =%> +
-
- <%= password_field 'add_initial_confirmPassword', - id => 'add_initial_confirmPassword', - placeholder => '', - class => 'form-control' =%> - <%= label_for add_initial_confirmPassword => maketext('Confirm Password') =%> +
+
+ <%= 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') =%> +
+
+ <%= text_field "add_initial_studentID_$_" => '', + id => "add_initial_studentID_$_", + placeholder => '', + class => 'form-control' =%> + <%= label_for "add_initial_studentID_$_" => maketext('Student ID') =%> +
-
-
- <%= 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 Another Instructor'), name => 'add_another_instructor', + class => 'btn btn-primary' =%>
<%= maketext('To copy components from an existing course, ' @@ -208,5 +243,6 @@ <%= 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/HelpFiles/AdminAddCourse.html.ep b/templates/HelpFiles/AdminAddCourse.html.ep index c01fc1e330..734ecae554 100644 --- a/templates/HelpFiles/AdminAddCourse.html.ep +++ b/templates/HelpFiles/AdminAddCourse.html.ep @@ -28,12 +28,14 @@ $ce->{admin_course_id}) =%>

- <%= maketext('Enter the details of a new course instructor to be added when the course is created. The only ' - . 'required field is the user ID of the this user. Optionally, you can add this user to the [_1] course, ' - . 'so you can copy this user when creating future courses, or manage and email course instructors. Note, ' - . 'by default these new users will be "Dropped" and unable to login to the [_1] course. You can change ' - . 'this on the "Accounts Manager" page. Additionally you can control access to the [_1] course by setting ' - . q/$permissionLevels{login}='admin' in course.conf./, $ce->{admin_course_id}) =%> + <%= maketext('Optionally, to add additional instructors click the "Add Another Instructor" button, then enter ' + . 'the details of a new course instructor to be added when the course is created. The only required field ' + . 'is the user ID of the this user. Optionally, you can add this user to the [_1] course, so you can copy ' + . 'this user when creating future courses, or manage and email course instructors. Click the "Add Another ' + . 'Instructor" button again to add multiple additional users. Note, by default these new users will be ' + . '"Dropped" and unable to login to the [_1] course. You can change this on the "Accounts Manager" page. ' + . q/Additionally you can control access to the [_1] course by setting $permissionLevels{login}='admin' in / + . 'course.conf.', $ce->{admin_course_id}) =%>

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