diff --git a/htdocs/js/AchievementList/achievementlist.js b/htdocs/js/AchievementList/achievementlist.js new file mode 100644 index 0000000000..54e241e1b0 --- /dev/null +++ b/htdocs/js/AchievementList/achievementlist.js @@ -0,0 +1,61 @@ +(() => { + // Action form validation. + const is_achievement_selected = () => { + const users = document.getElementsByName('selected_achievements'); + for (const achievement of document.getElementsByName('selected_achievements')) { + if (achievement.checked) return true; + } + document.getElementById('select_achievement_err_msg')?.classList.remove('d-none'); + document.getElementById('achievement-table')?.addEventListener('change', e => { + document.getElementById('select_achievement_err_msg')?.classList.add('d-none'); + }, { once : true }); + return false; + }; + + document.getElementById('achievement-list')?.addEventListener('submit', e => { + const action = document.getElementById('current_action')?.value || ''; + if (['edit', 'assign', 'export', 'score'].includes(action)) { + if (!is_achievement_selected()) { + e.preventDefault(); + e.stopPropagation(); + } + } else if (action === 'create') { + const create_text = document.getElementById('create_text'); + if (create_text.value === '') { + e.preventDefault(); + e.stopPropagation(); + document.getElementById('create_file_err_msg')?.classList.remove('d-none'); + create_text.classList.add('is-invalid'); + create_text.addEventListener('change', e => { + document.getElementById('create_file_err_msg')?.classList.add('d-none'); + document.getElementById('create_text')?.classList.remove('is-invalid'); + }, { once : true }); + } else if (document.getElementById('create_select')?.selectedIndex == 1 && !is_achievement_selected()) { + e.preventDefault(); + e.stopPropagation(); + } + } else if (action === 'delete') { + const delete_confirm = document.getElementById('delete_select'); + if (!is_achievement_selected()) { + e.preventDefault(); + e.stopPropagation(); + } else if (delete_confirm.value != 'yes') { + e.preventDefault(); + e.stopPropagation(); + document.getElementById('delete_confirm_err_msg')?.classList.remove('d-none'); + delete_confirm.classList.add('is-invalid'); + delete_confirm.addEventListener('change', e => { + document.getElementById('delete_select')?.classList.remove('is-invalid'); + document.getElementById('delete_confirm_err_msg')?.classList.add('d-none'); + }, { once : true }); + } + } + }); + + // Remove select error message when changing tabs. + for (const tab of document.querySelectorAll('a[data-bs-toggle="tab"]')) { + tab.addEventListener('shown.bs.tab', e => { + document.getElementById('select_achievement_err_msg')?.classList.add('d-none'); + }, { once : true }); + } +})(); diff --git a/htdocs/js/ActionTabs/actiontabs.js b/htdocs/js/ActionTabs/actiontabs.js index 50c43da164..23354b38d0 100644 --- a/htdocs/js/ActionTabs/actiontabs.js +++ b/htdocs/js/ActionTabs/actiontabs.js @@ -45,4 +45,5 @@ }); } } + })(); diff --git a/htdocs/js/ProblemSetList/problemsetlist.js b/htdocs/js/ProblemSetList/problemsetlist.js index 2053823014..ff42dc7b6e 100644 --- a/htdocs/js/ProblemSetList/problemsetlist.js +++ b/htdocs/js/ProblemSetList/problemsetlist.js @@ -1,20 +1,90 @@ (() => { - // Show the filter error message if the 'Filter' button is clicked when matching set IDs without having entered - // a text to filter on. - document.getElementById('take_action')?.addEventListener('click', - (e) => { - const filter_err_msg = document.getElementById('filter_err_msg'); - - if (filter_err_msg && - document.getElementById('current_action')?.value === 'filter' && - document.getElementById('filter_select')?.selectedIndex === 3 && - document.getElementById('filter_text')?.value === '') { - filter_err_msg.classList.remove('d-none'); + // Action form validation. + const is_set_selected = () => { + for (const set of document.getElementsByName('selected_sets')) { + if (set.checked) return true; + } + document.getElementById('select_set_err_msg')?.classList.remove('d-none'); + document.getElementById('set_table_id')?.addEventListener('change', e => { + document.getElementById('select_set_err_msg')?.classList.add('d-none'); + }, { once : true }); + return false; + }; + + document.getElementById('problemsetlist')?.addEventListener('submit', e => { + const action = document.getElementById('current_action')?.value || ''; + if (action === 'filter') { + const filter = document.getElementById('filter_select')?.selectedIndex || 0; + const filter_text = document.getElementById('filter_text'); + if (filter === 2 && !is_set_selected()) { + e.preventDefault(); + e.stopPropagation(); + } else if (filter === 3 && filter_text.value === '') { + e.preventDefault(); + e.stopPropagation(); + document.getElementById('filter_err_msg')?.classList.remove('d-none'); + filter_text.classList.add('is-invalid'); + filter_text.addEventListener('change', e => { + document.getElementById('filter_err_msg')?.classList.add('d-none'); + document.getElementById('filter_text')?.classList.remove('is-invalid'); + }, { once : true }); + } + } else if (['edit', 'publish', 'export', 'save_export', 'score'].includes(action)) { + if (!is_set_selected()) { + e.preventDefault(); + e.stopPropagation(); + } + } else if (action === 'import') { + const import_select = document.getElementById('import_source_select'); + if (!import_select.value.endsWith('.def')) { + e.preventDefault(); + e.stopPropagation(); + document.getElementById('import_file_err_msg')?.classList.remove('d-none'); + import_select.classList.add('is-invalid'); + import_select.addEventListener('change', e => { + document.getElementById('import_source_select')?.classList.remove('is-invalid'); + document.getElementById('import_file_err_msg')?.classList.add('d-none'); + }, { once : true }); + } + } else if (action === 'create') { + const create_text = document.getElementById('create_text'); + if (create_text.value === '') { + e.preventDefault(); + e.stopPropagation(); + document.getElementById('create_file_err_msg')?.classList.remove('d-none'); + create_text.classList.add('is-invalid'); + create_text.addEventListener('change', e => { + document.getElementById('create_file_err_msg')?.classList.add('d-none'); + document.getElementById('create_text')?.classList.remove('is-invalid'); + }, { once : true }); + } else if (document.getElementById('create_select')?.selectedIndex == 1 && !is_set_selected()) { + e.preventDefault(); + e.stopPropagation(); + } + } else if (action === 'delete') { + const delete_confirm = document.getElementById('delete_select'); + if (!is_set_selected()) { + e.preventDefault(); + e.stopPropagation(); + } else if (delete_confirm.value != 'yes') { e.preventDefault(); e.stopPropagation(); + document.getElementById('delete_confirm_err_msg')?.classList.remove('d-none'); + delete_confirm.classList.add('is-invalid'); + delete_confirm.addEventListener('change', e => { + document.getElementById('delete_select')?.classList.remove('is-invalid'); + document.getElementById('delete_confirm_err_msg')?.classList.add('d-none'); + }, { once : true }); } } - ); + }); + + // Remove select error message when changing tabs. + for (const tab of document.querySelectorAll('a[data-bs-toggle="tab"]')) { + tab.addEventListener('shown.bs.tab', e => { + document.getElementById('select_set_err_msg')?.classList.add('d-none'); + }); + } // Toggle the display of the filter elements as the filter select changes. const filter_select = document.getElementById('filter_select'); diff --git a/htdocs/js/UserList/userlist.js b/htdocs/js/UserList/userlist.js index cbda5ab719..43945214e9 100644 --- a/htdocs/js/UserList/userlist.js +++ b/htdocs/js/UserList/userlist.js @@ -24,4 +24,80 @@ export_select_target.addEventListener('change', classlist_add_export_elements); classlist_add_export_elements(); } + + // Action form validation. + const is_user_selected = () => { + for (const user of document.getElementsByName('selected_users')) { + if (user.checked) return true; + } + document.getElementById('select_user_err_msg')?.classList.remove('d-none'); + document.getElementById('classlist-table')?.addEventListener('change', e => { + document.getElementById('select_user_err_msg')?.classList.add('d-none'); + }, { once : true }); + return false; + }; + + document.getElementById('user-list-form')?.addEventListener('submit', e => { + const action = document.getElementById('current_action')?.value || ''; + if (action === 'filter') { + const filter = document.getElementById('filter_select')?.selectedIndex || 0; + const filter_text = document.getElementById('filter_text'); + if (filter === 2 && !is_user_selected()) { + e.preventDefault(); + e.stopPropagation(); + } else if (filter === 3 && filter_text.value === '') { + e.preventDefault(); + e.stopPropagation(); + document.getElementById('filter_err_msg')?.classList.remove('d-none'); + filter_text.classList.add('is-invalid'); + filter_text.addEventListener('change', e => { + document.getElementById('filter_text')?.classList.remove('is-invalid'); + document.getElementById('filter_err_msg')?.classList.add('d-none'); + }, { once : true }); + } + } else if (action === 'edit' || action === 'password') { + if (!is_user_selected()) { + e.preventDefault(); + e.stopPropagation(); + } + } else if (action == 'export') { + const export_filename = document.getElementById('export_filename'); + if (!is_user_selected()) { + e.preventDefault(); + e.stopPropagation(); + } else if (document.getElementById('export_select_target')?.value === 'new' && + export_filename.value === '') { + e.preventDefault(); + e.stopPropagation(); + document.getElementById('export_file_err_msg')?.classList.remove('d-none'); + export_filename.classList.add('is-invalid'); + export_filename.addEventListener('change', e => { + document.getElementById('export_filename')?.classList.remove('is-invalid'); + document.getElementById('export_file_err_msg')?.classList.add('d-none'); + }, { once : true }); + } + } else if (action === 'delete') { + const delete_confirm = document.getElementById('delete_select'); + if (!is_user_selected()) { + e.preventDefault(); + e.stopPropagation(); + } else if (delete_confirm.value != 'yes') { + e.preventDefault(); + e.stopPropagation(); + document.getElementById('delete_confirm_err_msg')?.classList.remove('d-none'); + delete_confirm.classList.add('is-invalid'); + delete_confirm.addEventListener('change', e => { + document.getElementById('delete_select')?.classList.remove('is-invalid'); + document.getElementById('delete_confirm_err_msg')?.classList.add('d-none'); + }, { once : true }); + } + } + }); + + // Remove select error message when changing tabs. + for (const tab of document.querySelectorAll('a[data-bs-toggle="tab"]')) { + tab.addEventListener('shown.bs.tab', e => { + document.getElementById('select_user_err_msg')?.classList.add('d-none'); + }); + } })(); diff --git a/lib/WeBWorK/ContentGenerator/Instructor/AchievementList.pm b/lib/WeBWorK/ContentGenerator/Instructor/AchievementList.pm index 43aec79032..bc8e4b417d 100644 --- a/lib/WeBWorK/ContentGenerator/Instructor/AchievementList.pm +++ b/lib/WeBWorK/ContentGenerator/Instructor/AchievementList.pm @@ -82,6 +82,7 @@ sub initialize ($c) { $c->stash->{formsToShow} = VIEW_FORMS(); $c->stash->{formTitles} = FORM_TITLES(); $c->stash->{achievements} = []; + $c->stash->{axpList} = [ $c->getAxpList ]; # Check permissions return unless $authz->hasPermissions($user, 'edit_achievements'); @@ -146,36 +147,17 @@ sub initialize ($c) { # Handler for editing achievements. Just changes the view mode. sub edit_handler ($c) { - my $result; - - my $scope = $c->param('action.edit.scope'); - if ($scope eq "all") { - $c->{selectedAchievementIDs} = $c->{allAchievementIDs}; - $result = $c->maketext('Editing all achievements.'); - } elsif ($scope eq "selected") { - $result = $c->maketext('Editing selected achievements.'); - } $c->{editMode} = 1; - return (1, $result); + return (1, $c->maketext('Editing selected achievements.')); } # Handler for assigning achievements to users sub assign_handler ($c) { - my $db = $c->db; - my $ce = $c->ce; - - my $scope = $c->param('action.assign.scope'); - my $overwrite = $c->param('action.assign.overwrite') eq 'everything'; - - my @achievementIDs; - my @users = $db->listUsers; - - if ($scope eq "all") { - @achievementIDs = @{ $c->{allAchievementIDs} }; - } else { - @achievementIDs = @{ $c->{selectedAchievementIDs} }; - } + my $db = $c->db; + my $overwrite = $c->param('action.assign.overwrite') eq 'everything'; + my @achievementIDs = @{ $c->{selectedAchievementIDs} }; + my @users = $db->listUsers; # Enable all achievements my @achievements = $db->getAchievements(@achievementIDs); @@ -222,20 +204,10 @@ sub assign_handler ($c) { # Handler for scoring sub score_handler ($c) { - my $ce = $c->ce; - my $db = $c->db; - my $courseName = $c->stash('courseID'); - - my $scope = $c->param('action.score.scope'); - my @achievementsToScore; - - if ($scope eq "none") { - @achievementsToScore = (); - } elsif ($scope eq "all") { - @achievementsToScore = @{ $c->{allAchievementIDs} }; - } elsif ($scope eq "selected") { - @achievementsToScore = $c->param('selected_achievements'); - } + my $ce = $c->ce; + my $db = $c->db; + my $courseName = $c->stash('courseID'); + my @achievementsToScore = $c->param('selected_achievements'); # Define file name my $scoreFileName = $courseName . "_achievement_scores.csv"; @@ -323,16 +295,12 @@ sub score_handler ($c) { # Handler for delete action sub delete_handler ($c) { - my $db = $c->db; + my $db = $c->db; + my $confirm = $c->param('action.delete.confirm'); - my $scope = $c->param('action.delete.scope'); - - my @achievementIDsToDelete = (); - - if ($scope eq "selected") { - @achievementIDsToDelete = @{ $c->{selectedAchievementIDs} }; - } + return (1, $c->maketext('Deleted [quant,_1,achievement].', 0)) unless ($confirm eq 'yes'); + my @achievementIDsToDelete = @{ $c->{selectedAchievementIDs} }; my %allAchievementIDs = map { $_ => 1 } @{ $c->{allAchievementIDs} }; my %selectedAchievementIDs = map { $_ => 1 } @{ $c->{selectedAchievementIDs} }; @@ -348,8 +316,7 @@ sub delete_handler ($c) { $c->{allAchievementIDs} = [ keys %allAchievementIDs ]; $c->{selectedAchievementIDs} = [ keys %selectedAchievementIDs ]; - my $num = @achievementIDsToDelete; - return (1, $c->maketext('Deleted [quant,_1,achievement].', $num)); + return (1, $c->maketext('Deleted [quant,_1,achievement].', scalar @achievementIDsToDelete)); } # Handler for creating an ahcievement @@ -472,19 +439,10 @@ sub import_handler ($c) { # Export handler # This does not actually export any files, rather it sends us to a new page in order to export the files. sub export_handler ($c) { - my $result; - - my $scope = $c->param('action.export.scope'); - if ($scope eq "all") { - $result = $c->maketext('Exporting all achievements.'); - $c->{selectedAchievementIDs} = $c->{allAchievementIDs}; - } elsif ($scope eq "selected") { - $result = $c->maketext('Exporting selected achievements.'); - $c->{selectedAchievementIDs} = [ $c->param('selected_achievements') ]; - } - $c->{exportMode} = 1; + $c->{selectedAchievementIDs} = [ $c->param('selected_achievements') ]; + $c->{exportMode} = 1; - return (1, $result); + return (1, $c->maketext('Exporting selected achievements.')); } # Handler for leaving the export page. diff --git a/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetList.pm b/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetList.pm index 7a7c05c855..3b7da0e7d4 100644 --- a/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetList.pm +++ b/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetList.pm @@ -15,6 +15,7 @@ package WeBWorK::ContentGenerator::Instructor::ProblemSetList; use Mojo::Base 'WeBWorK::ContentGenerator', -signatures; +use WeBWorK::Utils::Instructor qw(getDefList); =head1 NAME @@ -158,18 +159,7 @@ sub pre_header_initialize ($c) { $c->{totalUsers} = $db->countUsers; if (defined $c->param('action') && $c->param('action') eq 'score' && $authz->hasPermissions($user, 'score_sets')) { - my $scope = $c->param('action.score.scope'); - my @setsToScore; - - if ($scope eq 'none') { - return; - } elsif ($scope eq 'all') { - @setsToScore = @{ $c->{allSetIDs} }; - } elsif ($scope eq 'visible') { - @setsToScore = @{ $c->param('visibleSetIDs') }; - } elsif ($scope eq 'selected') { - @setsToScore = $c->param('selected_sets'); - } + my @setsToScore = $c->param('selected_sets'); return unless @setsToScore; @@ -198,6 +188,7 @@ sub initialize ($c) { $c->stash->{fieldTypes} = FIELD_TYPES(); $c->stash->{sortableFields} = SORTABLE_FIELDS(); $c->stash->{sets} = []; + $c->stash->{setDefList} = [ getDefList($ce) ]; # Determine if the user has permisson to do anything here. return unless $authz->hasPermissions($user, 'access_instructor_tools'); @@ -373,62 +364,24 @@ sub sort_handler ($c) { } sub edit_handler ($c) { - my $result; + $c->{visibleSetIDs} = [ $c->param('selected_sets') ]; + $c->{editMode} = 1; - my $scope = $c->param('action.edit.scope'); - if ($scope eq "all") { - $result = $c->maketext('Editing all sets.'); - $c->{visibleSetIDs} = $c->{allSetIDs}; - } elsif ($scope eq "visible") { - $result = $c->maketext('Editing listed sets.'); - # leave visibleSetIDs alone - } elsif ($scope eq "selected") { - $result = $c->maketext('Editing selected sets.'); - $c->{visibleSetIDs} = [ $c->param('selected_sets') ]; - } - $c->{editMode} = 1; - - return (1, $result); + return (1, $c->maketext('Editing selected sets.')); } sub publish_handler ($c) { - my $db = $c->db; - - my @result; - - my $scope = $c->param('action.publish.scope'); - my $value = $c->param('action.publish.value'); - - my @setIDs; - - if ($scope eq "none") { - @setIDs = (); - @result = (0, $c->maketext('No change made to any set.')); - } elsif ($scope eq "all") { - @setIDs = @{ $c->{allSetIDs} }; - @result = - $value - ? (1, $c->maketext('All sets made visible for all students.')) - : (1, $c->maketext('All sets hidden from all students.')); - } elsif ($scope eq "visible") { - @setIDs = @{ $c->{visibleSetIDs} }; - @result = - $value - ? (1, $c->maketext('All listed sets were made visible for all the students.')) - : (1, $c->maketext('All listed sets were hidden from all the students.')); - } elsif ($scope eq "selected") { - @setIDs = $c->param('selected_sets'); - @result = - $value - ? (1, $c->maketext('All selected sets made visible for all students.')) - : (1, $c->maketext('All selected sets hidden from all students.')); - } + my $db = $c->db; + my $value = $c->param('action.publish.value'); + my @setIDs = $c->param('selected_sets'); # Can we use UPDATE here, instead of fetch/change/store? my @sets = $db->getGlobalSets(@setIDs); map { $_->visible($value); $db->putGlobalSet($_); } @sets; - return @result; + return $value + ? (1, $c->maketext('All selected sets made visible for all students.')) + : (1, $c->maketext('All selected sets hidden from all students.')); } sub score_handler ($c) { @@ -438,16 +391,12 @@ sub score_handler ($c) { } sub delete_handler ($c) { - my $db = $c->db; + my $db = $c->db; + my $confirm = $c->param('action.delete.confirm'); - my $scope = $c->param('action.delete.scope'); - - my @setIDsToDelete = (); - - if ($scope eq "selected") { - @setIDsToDelete = @{ $c->{selectedSetIDs} }; - } + return (1, $c->maketext('Deleted [_1] sets.', 0)) unless ($confirm eq 'yes'); + my @setIDsToDelete = @{ $c->{selectedSetIDs} }; my %allSetIDs = map { $_ => 1 } @{ $c->{allSetIDs} }; my %visibleSetIDs = map { $_ => 1 } @{ $c->{visibleSetIDs} }; my %selectedSetIDs = map { $_ => 1 } @{ $c->{selectedSetIDs} }; @@ -463,8 +412,7 @@ sub delete_handler ($c) { $c->{visibleSetIDs} = [ keys %visibleSetIDs ]; $c->{selectedSetIDs} = [ keys %selectedSetIDs ]; - my $num = @setIDsToDelete; - return (1, $c->maketext('Deleted [_1] sets.', $num)); + return (1, $c->maketext('Deleted [_1] sets.', scalar @setIDsToDelete)); } sub create_handler ($c) { @@ -595,22 +543,10 @@ sub import_handler ($c) { # this does not actually export any files, rather it sends us to a new page in order to export the files sub export_handler ($c) { - my $result; + $c->{selectedSetIDs} = $c->{visibleSetIDs} = [ $c->param('selected_sets') ]; + $c->{exportMode} = 1; - my $scope = $c->param('action.export.scope'); - if ($scope eq "all") { - $result = $c->maketext("All sets were selected for export."); - $c->{selectedSetIDs} = $c->{visibleSetIDs} = $c->{allSetIDs}; - } elsif ($scope eq "visible") { - $result = $c->maketext("Visible sets were selected for export."); - $c->{selectedSetIDs} = $c->{visibleSetIDs}; - } elsif ($scope eq "selected") { - $result = $c->maketext("Sets were selected for export."); - $c->{selectedSetIDs} = $c->{visibleSetIDs} = [ $c->param('selected_sets') ]; - } - $c->{exportMode} = 1; - - return (1, $result); + return (1, $c->maketext('Selected sets were exported.')); } sub cancel_export_handler ($c) { diff --git a/lib/WeBWorK/ContentGenerator/Instructor/UserList.pm b/lib/WeBWorK/ContentGenerator/Instructor/UserList.pm index 4138304db3..88c910273a 100644 --- a/lib/WeBWorK/ContentGenerator/Instructor/UserList.pm +++ b/lib/WeBWorK/ContentGenerator/Instructor/UserList.pm @@ -65,6 +65,7 @@ Export users: use WeBWorK::File::Classlist qw(parse_classlist write_classlist); use WeBWorK::Utils qw(cryptPassword x); +use WeBWorK::Utils::Instructor qw(getCSVList); use constant HIDE_USERS_THRESHHOLD => 200; use constant EDIT_FORMS => [qw(save_edit cancel_edit)]; @@ -254,6 +255,7 @@ sub initialize ($c) { $c->stash->{formPerms} = FORM_PERMS(); $c->stash->{fields} = FIELDS(); $c->stash->{fieldProperties} = FIELD_PROPERTIES(); + $c->stash->{CSVList} = [ getCSVList($c->ce) ]; return; } @@ -348,54 +350,28 @@ sub sort_handler ($c) { } sub edit_handler ($c) { - my $result; - my @usersToEdit; - - my $scope = $c->param('action.edit.scope'); - if ($scope eq 'all') { - $result = $c->maketext('Editing all users.'); - @usersToEdit = grep { $c->{userIsEditable}{$_} } @{ $c->{allUserIDs} }; - } elsif ($scope eq 'visible') { - $result = $c->maketext('Editing visible users.'); - @usersToEdit = grep { $c->{userIsEditable}{$_} } (keys %{ $c->{visibleUserIDs} }); - } elsif ($scope eq 'selected') { - $result = $c->maketext('Editing selected users.'); - @usersToEdit = grep { $c->{userIsEditable}{$_} } (keys %{ $c->{selectedUserIDs} }); - } + my @usersToEdit = grep { $c->{userIsEditable}{$_} } (keys %{ $c->{selectedUserIDs} }); $c->{visibleUserIDs} = { map { $_ => 1 } @usersToEdit }; $c->{editMode} = 1; - return $result; + return $c->maketext('Editing selected users.'); } sub password_handler ($c) { - my $result; - my @usersToEdit; - - my $scope = $c->param('action.password.scope'); - if ($scope eq 'all') { - $result = $c->maketext('Giving new passwords to all users.'); - @usersToEdit = grep { $c->{userIsEditable}{$_} } @{ $c->{allUserIDs} }; - } elsif ($scope eq 'visible') { - $result = $c->maketext('Giving new passwords to visible users.'); - @usersToEdit = grep { $c->{userIsEditable}{$_} } (keys %{ $c->{visibleUserIDs} }); - } elsif ($scope eq 'selected') { - $result = $c->maketext('Giving new passwords to selected users.'); - @usersToEdit = grep { $c->{userIsEditable}{$_} } (keys %{ $c->{selectedUserIDs} }); - } + my @usersToEdit = grep { $c->{userIsEditable}{$_} } (keys %{ $c->{selectedUserIDs} }); $c->{visibleUserIDs} = { map { $_ => 1 } @usersToEdit }; $c->{passwordMode} = 1; - return $result; + return $c->maketext('Giving new passwords to selected users.'); } sub delete_handler ($c) { - my $db = $c->db; - my $user = $c->param('user'); - my $scope = $c->param('action.delete.scope'); - my $num = 0; + my $db = $c->db; + my $user = $c->param('user'); + my $confirm = $c->param('action.delete.confirm'); + my $num = 0; - return $c->maketext('Deleted [_1] users.', $num) if ($scope eq 'none'); + return $c->maketext('Deleted [_1] users.', $num) unless ($confirm eq 'yes'); # grep on userIsEditable would still enforce permissions, but no UI feedback my @userIDsToDelete = keys %{ $c->{selectedUserIDs} }; @@ -429,11 +405,14 @@ sub add_handler ($c) { } sub import_handler ($c) { - my $source = $c->param('action.import.source'); - my $add = $c->param('action.import.add'); - my $replace = $c->param('action.import.replace'); + my $fileName = $c->param('action.import.source'); + my $add = $c->param('action.import.add'); + my $replace = $c->param('action.import.replace'); - my $fileName = $source; + unless (defined($fileName) and $fileName =~ /\.lst$/) { + $c->addbadmessage($c->maketext('No class list file provided.')); + return $c->maketext('No users added.'); + } my $createNew = $add eq 'any'; my $replaceExisting; my @replaceList; @@ -475,7 +454,6 @@ sub export_handler ($c) { my $ce = $c->ce; my $dir = $ce->{courseDirs}{templates}; - my $scope = $c->param('action.export.scope'); my $target = $c->param('action.export.target'); my $new = $c->param('action.export.new'); @@ -491,15 +469,7 @@ sub export_handler ($c) { $fileName .= '.lst' unless $fileName =~ m/\.lst$/; - my @userIDsToExport; - if ($scope eq 'all') { - @userIDsToExport = @{ $c->{allUserIDs} }; - } elsif ($scope eq 'visible') { - @userIDsToExport = keys %{ $c->{visibleUserIDs} }; - } elsif ($scope eq 'selected') { - @userIDsToExport = keys %{ $c->{selectedUserIDs} }; - } - + my @userIDsToExport = keys %{ $c->{selectedUserIDs} }; $c->exportUsersToCSV($fileName, @userIDsToExport); return $c->maketext('[_1] users exported to file [_2]', scalar @userIDsToExport, "$dir/$fileName"); diff --git a/templates/ContentGenerator/Instructor/AchievementList.html.ep b/templates/ContentGenerator/Instructor/AchievementList.html.ep index 6b9c8218b2..8e3ca53bca 100644 --- a/templates/ContentGenerator/Instructor/AchievementList.html.ep +++ b/templates/ContentGenerator/Instructor/AchievementList.html.ep @@ -1,8 +1,9 @@ % use WeBWorK::Utils qw(getAssetURL); % % content_for js => begin - <%= javascript getAssetURL($ce, 'js/ActionTabs/actiontabs.js'), defer => undef =%> - <%= javascript getAssetURL($ce, 'js/SelectAll/selectall.js'), defer => undef =%> + <%= javascript getAssetURL($ce, 'js/ActionTabs/actiontabs.js'), defer => undef =%> + <%= javascript getAssetURL($ce, 'js/AchievementList/achievementlist.js'), defer => undef =%> + <%= javascript getAssetURL($ce, 'js/SelectAll/selectall.js'), defer => undef =%> % end % % unless ($authz->hasPermissions(param('user'), 'edit_achievements')) { @@ -26,7 +27,9 @@ % for my $actionID (@$formsToShow) {
<%= maketext('Select achievements to assign to all users.') =%>