From 568c8e6e7618c46b7d83a0a5029d5c89883683c6 Mon Sep 17 00:00:00 2001 From: Jaimos Skriletz Date: Wed, 13 Nov 2024 00:33:52 -0700 Subject: [PATCH] Allow adding multiple initial users when adding a new course. When adding a new course, there is now an 'Add Another Instructor' button which can be used to add more than one new user to the newly created course. Each time this button is hit, the form expands allowing input for a new user. It is possible to set the permission level of each of these users and to keep the form balanced, the student ID can now also be included. This also updates assigning the initial user to sets and achievements. The intention was to assign new instructors to these, so any user (either copied from the admin course or added initially) whose permission is less than admin will be assigned to the sets and achievements. --- lib/WeBWorK/ContentGenerator/CourseAdmin.pm | 161 ++++++++++-------- lib/WeBWorK/Utils/CourseManagement.pm | 10 +- .../CourseAdmin/add_course_form.html.ep | 138 +++++++++------ templates/HelpFiles/AdminAddCourse.html.ep | 14 +- 4 files changed, 191 insertions(+), 132 deletions(-) diff --git a/lib/WeBWorK/ContentGenerator/CourseAdmin.pm b/lib/WeBWorK/ContentGenerator/CourseAdmin.pm index a217800615..f87646c2e5 100644 --- a/lib/WeBWorK/ContentGenerator/CourseAdmin.pm +++ b/lib/WeBWorK/ContentGenerator/CourseAdmin.pm @@ -230,17 +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_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 +258,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 (1 .. $number_of_additional_users) { + 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.', + $_ + ); + } + if ($password ne '' && $password ne $confirmPassword) { + push @errors, $c->maketext('Pasword number [_1] and its password confirmation must match.', $_); + } + } } if ($add_dbLayout eq '') { @@ -283,17 +297,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 +321,17 @@ 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 (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 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 +342,57 @@ 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 (1 .. $number_of_additional_users) { + 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 >= $ce->{userRoles}{professor} } @users; # Include any optional arguments, including a template course and the course title and course institution. my %optional_arguments; @@ -386,11 +412,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 +444,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..efb2ee561d 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}, + % $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 =%> @@ -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 (1 .. $number_of_additional_users) { +
+
+
+ <%= 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. '