From 03799c8b4fd4201f9961b527ef69fdc322387013 Mon Sep 17 00:00:00 2001 From: Jaimos Skriletz Date: Sun, 31 Dec 2023 14:27:10 -0700 Subject: [PATCH] Remove action scope and add action form validation. On the UserList, ProblemSetList, and AchievementList managers, remove the scope option that helps determine which items to act on, instead users will always select which items to act on and can use filters to change the list of items to select from. This address #1991. In addition add javascript form validation that will inform the user if the form is missing information, such as no items are selected, a text string is not provided, a valid file is not selected, and so on. Last, disable the import tab if no valid files are found to import from. --- htdocs/js/AchievementList/achievementlist.js | 61 ++++++++++ htdocs/js/ActionTabs/actiontabs.js | 1 + htdocs/js/ProblemSetList/problemsetlist.js | 94 ++++++++++++++-- htdocs/js/UserList/userlist.js | 76 +++++++++++++ .../Instructor/AchievementList.pm | 78 +++---------- .../Instructor/ProblemSetList.pm | 104 ++++-------------- .../ContentGenerator/Instructor/UserList.pm | 68 ++++-------- .../Instructor/AchievementList.html.ep | 15 ++- .../AchievementList/assign_form.html.ep | 13 +-- .../AchievementList/create_form.html.ep | 3 + .../AchievementList/default_table.html.ep | 3 + .../AchievementList/delete_form.html.ep | 11 +- .../AchievementList/edit_form.html.ep | 11 +- .../AchievementList/export_form.html.ep | 11 +- .../AchievementList/import_form.html.ep | 2 +- .../AchievementList/score_form.html.ep | 12 +- .../Instructor/ProblemSetList.html.ep | 5 +- .../ProblemSetList/create_form.html.ep | 3 + .../ProblemSetList/delete_form.html.ep | 11 +- .../ProblemSetList/edit_form.html.ep | 10 +- .../ProblemSetList/export_form.html.ep | 12 +- .../ProblemSetList/filter_form.html.ep | 2 +- .../ProblemSetList/import_form.html.ep | 11 +- .../ProblemSetList/publish_form.html.ep | 15 +-- .../ProblemSetList/score_form.html.ep | 11 +- .../ProblemSetList/set_list_table.html.ep | 3 + .../Instructor/UserList.html.ep | 5 +- .../Instructor/UserList/delete_form.html.ep | 11 +- .../Instructor/UserList/edit_form.html.ep | 10 +- .../Instructor/UserList/export_form.html.ep | 15 +-- .../Instructor/UserList/filter_form.html.ep | 3 + .../Instructor/UserList/import_form.html.ep | 4 +- .../Instructor/UserList/password_form.html.ep | 11 +- .../Instructor/UserList/user_list.html.ep | 3 + .../InstructorAchievementList.html.ep | 5 +- .../HelpFiles/InstructorUserList.html.ep | 7 +- 36 files changed, 359 insertions(+), 361 deletions(-) create mode 100644 htdocs/js/AchievementList/achievementlist.js 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) {