From e9c4625ea434b6d663d54338b3a17227e78649c0 Mon Sep 17 00:00:00 2001 From: Jaimos Skriletz Date: Thu, 23 Jan 2025 11:12:01 -0700 Subject: [PATCH] Move achievement item use form to ProblemSet page. Move the forms to use achievement items to a modal on the problem set page. If any achievement item is usable on the current set, a button to "Use Achievement Reward" will be available, and open a modal which lists all achievement items that can be applied. The Achievements page still lists all rewards the student has earned, and provides instructions to use them from the problem assignment they wish to use the achievement on. --- .../js/AchievementItems/achievementitems.js | 33 ----- lib/WeBWorK/AchievementItems.pm | 130 ++++++++++++----- lib/WeBWorK/AchievementItems/AddNewTestGW.pm | 75 ++++------ lib/WeBWorK/AchievementItems/DoubleProb.pm | 112 +++++---------- lib/WeBWorK/AchievementItems/DoubleSet.pm | 76 ++++------ lib/WeBWorK/AchievementItems/DuplicateProb.pm | 135 ++++++------------ lib/WeBWorK/AchievementItems/ExtendDueDate.pm | 86 +++++------ .../AchievementItems/ExtendDueDateGW.pm | 102 +++++-------- .../AchievementItems/ExtendReducedDate.pm | 96 +++++-------- .../AchievementItems/FullCreditProb.pm | 114 ++++++--------- lib/WeBWorK/AchievementItems/FullCreditSet.pm | 70 ++++----- .../AchievementItems/HalfCreditProb.pm | 130 +++++++---------- lib/WeBWorK/AchievementItems/HalfCreditSet.pm | 84 +++++------ lib/WeBWorK/AchievementItems/NoReducedCred.pm | 88 +++++------- lib/WeBWorK/AchievementItems/ReducedCred.pm | 99 +++++++------ .../ResetIncorrectAttempts.pm | 116 ++++++--------- lib/WeBWorK/AchievementItems/ResurrectGW.pm | 84 ++++------- lib/WeBWorK/AchievementItems/ResurrectHW.pm | 102 ++++++------- .../AchievementItems/SuperExtendDueDate.pm | 86 +++++------ .../SuperExtendReducedDate.pm | 96 +++++-------- lib/WeBWorK/AchievementItems/Surprise.pm | 9 +- lib/WeBWorK/ContentGenerator/Achievements.pm | 50 +------ lib/WeBWorK/ContentGenerator/Problem.pm | 1 + lib/WeBWorK/ContentGenerator/ProblemSet.pm | 37 ++--- .../ContentGenerator/Achievements.html.ep | 6 +- .../Achievements/achievement_items.html.ep | 63 ++------ templates/ContentGenerator/ProblemSet.html.ep | 1 + .../ProblemSet/use_achievement_items.html.ep | 53 +++++++ 28 files changed, 841 insertions(+), 1293 deletions(-) delete mode 100644 htdocs/js/AchievementItems/achievementitems.js create mode 100644 templates/ContentGenerator/ProblemSet/use_achievement_items.html.ep diff --git a/htdocs/js/AchievementItems/achievementitems.js b/htdocs/js/AchievementItems/achievementitems.js deleted file mode 100644 index 267d27f495..0000000000 --- a/htdocs/js/AchievementItems/achievementitems.js +++ /dev/null @@ -1,33 +0,0 @@ -(() => { - for (const setSelect of document.querySelectorAll('select[data-problems]')) { - setSelect.addEventListener('change', () => { - const problemIds = JSON.parse( - Array.from(setSelect.querySelectorAll('option')).find((option) => option.value === setSelect.value) - ?.dataset.problemIds ?? '[]' - ); - - const problemSelect = document.getElementById(setSelect.dataset.problems); - if (problemSelect) { - for (const option of problemSelect.querySelectorAll('option')) option.remove(); - for (const id of problemIds) { - const option = document.createElement('option'); - option.value = id; - option.text = id; - problemSelect.add(option); - } - } - - // This is only used by the "Box of Transmogrification". - const problemSelect2 = document.getElementById(setSelect.dataset.problems2); - if (problemSelect2) { - for (const option of problemSelect2.querySelectorAll('option')) option.remove(); - for (const id of problemIds) { - const option = document.createElement('option'); - option.value = id; - option.text = id; - problemSelect2.add(option); - } - } - }); - } -})(); diff --git a/lib/WeBWorK/AchievementItems.pm b/lib/WeBWorK/AchievementItems.pm index 7585142b5d..f0f0eea827 100644 --- a/lib/WeBWorK/AchievementItems.pm +++ b/lib/WeBWorK/AchievementItems.pm @@ -16,7 +16,7 @@ package WeBWorK::AchievementItems; use Mojo::Base -signatures; -use WeBWorK::Utils qw(thaw_base64); +use WeBWorK::Utils qw(nfreeze_base64 thaw_base64); # List of available achievement items. Make sure to add any new items to this list. Furthermore, the elements in this # list have to match the class name of the achievement item classes loaded below. @@ -44,65 +44,125 @@ use constant ITEMS => [ qw( =head2 NAME -This is the base class for achievement times. This defines an interface for all of the achievement items. Each -achievement item will have a name, a description, a method for creating an html form to get its inputs called print_form -and a method for applying those inputs called use_item. +This is the base class for achievement times. This defines an interface for all of the achievement items. +Each achievement item will have an id, a name, a description, and the three methods can_use (checks if the +item can be used on the given set), print_form (prints the form to use the item), and use_item. Note: the ID has to match the name of the class. +The global method UserItems returns an array of all achievement items available to the given user. If no +set is included, a list of all earned achievement items is return. If provided a set and corresponding problem +or test version records, a list of items usable on the current set and records paired with an input form to +use the item is returned. This method will also process any posts to use the achievement item. + =cut -sub id ($c) { return $c->{id}; } -sub name ($c) { return $c->{name}; } -sub description ($c) { return $c->{description}; } +sub id ($self) { return $self->{id}; } +sub name ($self) { return $self->{name}; } +sub count ($self) { return $self->{count}; } +sub description ($self) { return $self->{description}; } + +# Method to find all achievement items available to the given user. +# If $set is undefined return an array reference of all earned items. +# If $set is defined, return an array reference of the usable items +# for the given $set and problem or test versions records. Each item +# is paired with its input form to use the item. +sub UserItems ($c, $userName, $set, $records) { + my $db = $c->db; -# This is a global method that returns all of the provided users items. -sub UserItems ($userName, $db, $ce) { - # return unless the user has global achievement data - my $globalUserAchievement = $db->getGlobalUserAchievement($userName); + # When acting as another user, achievement items can be listed but not used. + return if $set && $userName ne $c->param('user'); - return unless ($globalUserAchievement->frozen_hash); + # Return unless the user has global achievement data. + my $globalUserAchievement = $c->{globalData} // $db->getGlobalUserAchievement($userName); + return unless $globalUserAchievement && $globalUserAchievement->frozen_hash; - my $globalData = thaw_base64($globalUserAchievement->frozen_hash); + my $globalData = thaw_base64($globalUserAchievement->frozen_hash); + my $use_item_id = $c->param('use_achievement_item_id') // ''; my @items; - # Get a new item object for each type of item. for my $item (@{ +ITEMS }) { - push(@items, [ "WeBWorK::AchievementItems::$item"->new, $globalData->{$item} ]) - if ($globalData->{$item}); + next unless $globalData->{$item}; + my $achievementItem = "WeBWorK::AchievementItems::$item"->new; + $achievementItem->{count} = $globalData->{$item}; + + # Return list of achievements items if $set is not defined. + unless ($set) { + push(@items, $achievementItem); + next; + } + next unless $achievementItem->can_use($set, $records); + + # Use the achievement item. + if ($use_item_id eq $item) { + my $message = $achievementItem->use_item($set, $records, $c); + if ($message) { + $globalData->{$item}--; + $achievementItem->{count}--; + $globalUserAchievement->frozen_hash(nfreeze_base64($globalData)); + $db->putGlobalUserAchievement($globalUserAchievement); + $c->addgoodmessage($c->maketext('[_1] succesffuly used. [_2]', $achievementItem->name, $message)); + } + } + + push(@items, [ $achievementItem, $use_item_id ? '' : $achievementItem->print_form($set, $records, $c) ]); } + # If an achievement item has been used, double check if the achievement items can still be used + # since the item count could now be zero or an achievement item has altered the set/records. + # Input forms are also built here to account for any possible change. + if ($set && $use_item_id) { + my @new_items; + for (@items) { + my $item = $_->[0]; + next unless $item->{count} && $item->can_use($set, $records); + push(@new_items, [ $item, $item->print_form($set, $records, $c) ]); + } + return \@new_items; + } return \@items; } +# Method that returns a string with the achievement name and number of remaining items. +sub remaining_title ($self, $c) { + if ($self->count > 1) { + return $c->maketext('[_1] ([_2] remaining)', $c->maketext($self->name), $self->count); + } elsif ($self->count < 0) { + return $c->maketext('[_1] (unlimited reusability)', $c->maketext($self->name)); + } else { + return $c->maketext('[_1] (1 remains)', $c->maketext($self->name)); + } +} + # Utility method for outputing a form row with a label and popup menu. # The id, label_text, and values are required parameters. sub form_popup_menu_row ($c, %options) { my %params = ( - id => '', - label_text => '', - label_attr => {}, - values => [], - menu_attr => {}, - menu_container_attr => {}, - add_container => 1, + id => '', + first_item => '', + label_text => '', + label_attr => {}, + values => [], + menu_attr => {}, + add_container => 1, %options ); - $params{label_attr}{class} //= 'col-4 col-form-label'; - $params{menu_attr}{class} //= 'form-select'; - $params{menu_container_attr}{class} //= 'col-8'; + $params{label_attr}{class} //= 'col-form-label'; + $params{menu_attr}{class} //= 'form-select'; - my $row_contents = $c->c( - $c->label_for($params{id} => $params{label_text}, %{ $params{label_attr} }), - $c->tag( - 'div', - %{ $params{menu_container_attr} }, - $c->select_field($params{id} => $params{values}, id => $params{id}, %{ $params{menu_attr} }) - ) - )->join(''); + unshift(@{ $params{values} }, [ $params{first_item} => '' ]) if $params{first_item}; + + my $row_contents = $c->tag( + 'div', + class => 'form-floating', + $c->c( + $c->select_field($params{id} => $params{values}, %{ $params{menu_attr} }), + $c->label_for($params{id} => $params{label_text}, %{ $params{label_attr} }) + )->join('') + ); - return $params{add_container} ? $c->tag('div', class => 'row mb-3', $row_contents) : $row_contents; + return $params{add_container} ? $c->tag('div', class => 'my-3', $row_contents) : $row_contents; } END { diff --git a/lib/WeBWorK/AchievementItems/AddNewTestGW.pm b/lib/WeBWorK/AchievementItems/AddNewTestGW.pm index 07b1d4e813..ebfefeef4b 100644 --- a/lib/WeBWorK/AchievementItems/AddNewTestGW.pm +++ b/lib/WeBWorK/AchievementItems/AddNewTestGW.pm @@ -18,9 +18,8 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures; # Item to allow students to take an additional version of a test within its test version interval -use WeBWorK::Utils qw(x nfreeze_base64 thaw_base64); -use WeBWorK::Utils::DateTime qw(before between); -use WeBWorK::Utils::Sets qw(format_set_name_display); +use WeBWorK::Utils qw(x); +use WeBWorK::Utils::DateTime qw(between); sub new ($class) { return bless { @@ -33,60 +32,34 @@ sub new ($class) { }, $class; } -sub print_form ($self, $sets, $setProblemIds, $c) { - my $db = $c->db; - - my @openGateways; - - # Find the template sets of open gateway quizzes. - for my $set (@$sets) { - push(@openGateways, [ format_set_name_display($set->set_id) => $set->set_id ]) - if $set->assignment_type =~ /gateway/ - && $set->set_id !~ /,v\d+$/ - && between($set->open_date, $set->due_date); - } - - return unless @openGateways; +sub can_use ($self, $set, $records) { + return + $set->assignment_type =~ /gateway/ + && $set->set_id !~ /,v\d+$/ + && between($set->open_date, $set->due_date) + && $set->versions_per_interval > 0; +} - return $c->c( - $c->tag('p', $c->maketext('Add a new version for which test?')), - WeBWorK::AchievementItems::form_popup_menu_row( - $c, - id => 'adtgw_gw_id', - label_text => $c->maketext('Test Name'), - values => \@openGateways, - menu_attr => { dir => 'ltr' } +sub print_form ($self, $set, $records, $c) { + return $c->tag( + 'p', + $c->maketext( + 'Increase the number of versions from [_1] to [_2] for this test.', + $set->versions_per_interval, + $set->versions_per_interval + 1 ) - )->join(''); + ); } -sub use_item ($self, $userName, $c) { - my $db = $c->db; - my $ce = $c->ce; - - # Validate data - my $globalUserAchievement = $db->getGlobalUserAchievement($userName); - return 'No achievement data?!?!?!' unless $globalUserAchievement->frozen_hash; - - my $globalData = thaw_base64($globalUserAchievement->frozen_hash); - return "You are $self->{id} trying to use an item you don't have" unless $globalData->{ $self->{id} }; - - my $setID = $c->param('adtgw_gw_id'); - return 'You need to input a Test Name' unless defined $setID; - - my $set = $db->getMergedSet($userName, $setID); - my $userSet = $db->getUserSet($userName, $setID); - return q{Couldn't find that set!} unless $set && $userSet; - - # Add an additional version per interval to the set. - $userSet->versions_per_interval($set->versions_per_interval + 1) unless $set->versions_per_interval == 0; +sub use_item ($self, $set, $records, $c) { + # Increase the number of versions per interval by 1. + my $db = $c->db; + my $userSet = $db->getUserSet($set->user_id, $set->set_id); + $set->versions_per_interval($set->versions_per_interval + 1); + $userSet->versions_per_interval($set->versions_per_interval); $db->putUserSet($userSet); - $globalData->{ $self->{id} }--; - $globalUserAchievement->frozen_hash(nfreeze_base64($globalData)); - $db->putGlobalUserAchievement($globalUserAchievement); - - return; + return $c->maketext('One additional test version added to this test.'); } 1; diff --git a/lib/WeBWorK/AchievementItems/DoubleProb.pm b/lib/WeBWorK/AchievementItems/DoubleProb.pm index a23b294061..3e82bea2c7 100644 --- a/lib/WeBWorK/AchievementItems/DoubleProb.pm +++ b/lib/WeBWorK/AchievementItems/DoubleProb.pm @@ -18,11 +18,8 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures; # Item to make a problem worth double. -use Mojo::JSON qw(encode_json); - -use WeBWorK::Utils qw(x nfreeze_base64 thaw_base64); +use WeBWorK::Utils qw(x); use WeBWorK::Utils::DateTime qw(after); -use WeBWorK::Utils::Sets qw(format_set_name_display); sub new ($class) { return bless { @@ -32,84 +29,49 @@ sub new ($class) { }, $class; } -sub print_form ($self, $sets, $setProblemIds, $c) { - # Construct a dropdown with open sets and another with problems. - # Javascript ensures the appropriate problems are shown for the selected set. - - my (@openSets, @initialProblemIDs); - - for my $i (0 .. $#$sets) { - if (after($sets->[$i]->open_date) - && $sets->[$i]->assignment_type eq 'default' - && @{ $setProblemIds->{ $sets->[$i]->set_id } }) - { - push( - @openSets, - [ - format_set_name_display($sets->[$i]->set_id) => $sets->[$i]->set_id, - data => { problem_ids => encode_json($setProblemIds->{ $sets->[$i]->set_id }) } - ] - ); - @initialProblemIDs = @{ $setProblemIds->{ $sets->[$i]->set_id } } unless @initialProblemIDs; - } - } - - return unless @openSets; - - return $c->c( - $c->tag( - 'p', - $c->maketext( - 'Please choose the set name and problem number of the question which should have its weight doubled.') - ), - WeBWorK::AchievementItems::form_popup_menu_row( - $c, - id => 'dbp_set_id', - label_text => $c->maketext('Set Name'), - values => \@openSets, - menu_attr => { dir => 'ltr', data => { problems => 'dbp_problem_id' } } - ), - WeBWorK::AchievementItems::form_popup_menu_row( - $c, - id => 'dbp_problem_id', - label_text => $c->maketext('Problem Number'), - values => \@initialProblemIDs, - menu_container_attr => { class => 'col-3' } - ) - )->join(''); +sub can_use ($self, $set, $records) { + return $set->assignment_type eq 'default' && after($set->open_date); } -sub use_item ($self, $userName, $c) { - my $db = $c->db; - my $ce = $c->ce; - - # Validate data - - my $globalUserAchievement = $db->getGlobalUserAchievement($userName); - return 'No achievement data?!?!?!' unless $globalUserAchievement->frozen_hash; - - my $globalData = thaw_base64($globalUserAchievement->frozen_hash); - return "You are $self->{id} trying to use an item you don't have" unless $globalData->{ $self->{id} }; - - my $setID = $c->param('dbp_set_id'); - return 'You need to input a Set Name' unless defined $setID; +sub print_form ($self, $set, $records, $c) { + return WeBWorK::AchievementItems::form_popup_menu_row( + $c, + id => 'dbp_problem_id', + label_text => $c->maketext('Problem Number'), + first_item => $c->maketext('Choose problem to double.'), + values => [ + map { [ $c->maketext('Problem [_1] ([_2] to [_3])', $_->problem_id, $_->value, 2 * $_->value) => + $_->problem_id ] } @$records + ], + ); +} +sub use_item ($self, $set, $records, $c) { my $problemID = $c->param('dbp_problem_id'); - return 'You need to input a Problem Number' unless $problemID; + unless ($problemID) { + $c->addbadmessage($c->maketext('Select problem to double with the [_1].', $self->name)); + return ''; + } - my $globalproblem = $db->getMergedProblem($userName, $setID, $problemID); - my $problem = $db->getUserProblem($userName, $setID, $problemID); - return 'There was an error accessing that problem.' unless $globalproblem && $problem; + my $problem; + for (@$records) { + if ($_->problem_id == $problemID) { + $problem = $_; + last; + } + } + return '' unless $problem; # Double the value of the problem. - $problem->value($globalproblem->value * 2); - $db->putUserProblem($problem); - - $globalData->{ $self->{id} }--; - $globalUserAchievement->frozen_hash(nfreeze_base64($globalData)); - $db->putGlobalUserAchievement($globalUserAchievement); - - return; + my $db = $c->db; + my $userProblem = $db->getUserProblem($problem->user_id, $problem->set_id, $problem->problem_id); + my $orig_value = $problem->value; + $problem->value($orig_value * 2); + $userProblem->value($problem->value); + $db->putUserProblem($userProblem); + + return $c->maketext('Problem [_1] increased from [_2] points to [_3] points.', + $problemID, $orig_value, $problem->value); } 1; diff --git a/lib/WeBWorK/AchievementItems/DoubleSet.pm b/lib/WeBWorK/AchievementItems/DoubleSet.pm index 680ffc2672..f6c3213982 100644 --- a/lib/WeBWorK/AchievementItems/DoubleSet.pm +++ b/lib/WeBWorK/AchievementItems/DoubleSet.pm @@ -18,9 +18,8 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures; # Item to make a homework set worth twice as much -use WeBWorK::Utils qw(x nfreeze_base64 thaw_base64); +use WeBWorK::Utils qw(x); use WeBWorK::Utils::DateTime qw(after); -use WeBWorK::Utils::Sets qw(format_set_name_display); sub new ($class) { return bless { @@ -30,62 +29,35 @@ sub new ($class) { }, $class; } -sub print_form ($self, $sets, $setProblemIds, $c) { - my @openSets; +sub can_use ($self, $set, $records) { + return $set->assignment_type eq 'default' && after($set->open_date); +} - for my $i (0 .. $#$sets) { - push(@openSets, [ format_set_name_display($sets->[$i]->set_id) => $sets->[$i]->set_id ]) - if (after($sets->[$i]->open_date) && $sets->[$i]->assignment_type eq 'default'); +sub print_form ($self, $set, $records, $c) { + my $total = 0; + for my $problem (@$records) { + $total += $problem->value; } - - return unless @openSets; - - return $c->c( - $c->tag('p', $c->maketext('Choose the set which you would like to be worth twice as much.')), - WeBWorK::AchievementItems::form_popup_menu_row( - $c, - id => 'dub_set_id', - label_text => $c->maketext('Set Name'), - values => \@openSets, - menu_attr => { dir => 'ltr' } - ) - )->join(''); + return $c->tag('p', + $c->maketext(q(Increase this assignment's total number of points from [_1] to [_2].), $total, 2 * $total)); } -sub use_item ($self, $userName, $c) { - my $db = $c->db; - my $ce = $c->ce; - - # Validate data - - my $globalUserAchievement = $db->getGlobalUserAchievement($userName); - return 'No achievement data?!?!?!' unless $globalUserAchievement->frozen_hash; - - my $globalData = thaw_base64($globalUserAchievement->frozen_hash); - return "You are $self->{id} trying to use an item you don't have" unless $globalData->{ $self->{id} }; - - my $setID = $c->param('dub_set_id'); - return 'You need to input a Set Name' unless defined $setID; - - my $set = $db->getMergedSet($userName, $setID); - return q{Couldn't find that set!} unless $set; - - my @probIDs = $db->listUserProblems($userName, $setID); - - for my $probID (@probIDs) { - my $globalproblem = $db->getMergedProblem($userName, $setID, $probID); - my $problem = $db->getUserProblem($userName, $setID, $probID); - - # Double the problem value. - $problem->value($globalproblem->value * 2); - $db->putUserProblem($problem); +sub use_item ($self, $set, $records, $c) { + my $db = $c->db; + my $old_value = 0; + my $new_value = 0; + + my @userProblems = $db->getUserProblemsWhere({ user_id => $set->user_id, set_id => $set->set_id }, 'problem_id'); + for my $n (0 .. $#userProblems) { + $old_value += $records->[$n]->value; + $records->[$n]->value($records->[$n]->value * 2); + $userProblems[$n]->value($records->[$n]->value); + $new_value += $userProblems[$n]->value; + $db->putUserProblem($userProblems[$n]); } - $globalData->{ $self->{id} }--; - $globalUserAchievement->frozen_hash(nfreeze_base64($globalData)); - $db->putGlobalUserAchievement($globalUserAchievement); - - return; + return $c->maketext(q(Assignment's total point value increased from [_1] points to [_2] points), + $old_value, $new_value); } 1; diff --git a/lib/WeBWorK/AchievementItems/DuplicateProb.pm b/lib/WeBWorK/AchievementItems/DuplicateProb.pm index 054154cce7..18765951d4 100644 --- a/lib/WeBWorK/AchievementItems/DuplicateProb.pm +++ b/lib/WeBWorK/AchievementItems/DuplicateProb.pm @@ -18,11 +18,8 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures; # Item to turn one problem into another problem -use Mojo::JSON qw(encode_json); - -use WeBWorK::Utils qw(x nfreeze_base64 thaw_base64); +use WeBWorK::Utils qw(x); use WeBWorK::Utils::DateTime qw(between); -use WeBWorK::Utils::Sets qw(format_set_name_display); sub new ($class) { return bless { @@ -32,109 +29,57 @@ sub new ($class) { }, $class; } -sub print_form ($self, $sets, $setProblemIds, $c) { - # Show open sets and allow for a choice of two problems from the set. - # Javascript ensures the appropriate problems are shown for the selected set. - - my (@openSets, @initialProblemIDs); - - for my $i (0 .. $#$sets) { - if (between($sets->[$i]->open_date, $sets->[$i]->due_date) - && $sets->[$i]->assignment_type eq 'default' - && @{ $setProblemIds->{ $sets->[$i]->set_id } }) - { - push( - @openSets, - [ - format_set_name_display($sets->[$i]->set_id) => $sets->[$i]->set_id, - data => { problem_ids => encode_json($setProblemIds->{ $sets->[$i]->set_id }) } - ] - ); - @initialProblemIDs = @{ $setProblemIds->{ $sets->[$i]->set_id } } unless @initialProblemIDs; - } - } - - return unless @openSets; +sub can_use ($self, $set, $records) { + return $set->assignment_type eq 'default' && between($set->open_date, $set->due_date); +} +sub print_form ($self, $set, $records, $c) { return $c->c( - $c->tag( - 'p', - $c->maketext( - 'Please choose the set, the problem you would like to copy, ' - . 'and the problem you would like to copy it to.' - ) + $c->tag('p', $c->maketext('Replaces the second problem with a copy of the first.')), + WeBWorK::AchievementItems::form_popup_menu_row( + $c, + id => 'clone_source_problem_id', + label_text => $c->maketext('Problem Number'), + first_item => $c->maketext('Choose problem to copy from.'), + values => [ map { [ $c->maketext('Problem [_1]', $_->problem_id) => $_->problem_id ] } @$records ], ), WeBWorK::AchievementItems::form_popup_menu_row( $c, - id => 'tran_set_id', - label_text => $c->maketext('Set Name'), - values => \@openSets, - menu_attr => { - dir => 'ltr', - data => { problems => 'tran_problem_id', problems2 => 'tran_problem_id2' } - } + id => 'clone_dest_problem_id', + label_text => $c->maketext('Problem Number'), + first_item => $c->maketext('Choose problem to replace.'), + values => [ map { [ $c->maketext('Problem [_1]', $_->problem_id) => $_->problem_id ] } @$records ], ), - $c->tag( - 'div', - class => 'row mb-3', - $c->c( - WeBWorK::AchievementItems::form_popup_menu_row( - $c, - id => 'tran_problem_id', - values => \@initialProblemIDs, - label_text => $c->maketext('Copy this Problem'), - menu_container_attr => { class => 'col-2 ps-0' }, - add_container => 0 - ), - WeBWorK::AchievementItems::form_popup_menu_row( - $c, - id => 'tran_problem_id2', - values => \@initialProblemIDs, - label_text => $c->maketext('To this Problem'), - menu_container_attr => { class => 'col-2 ps-0' }, - add_container => 0 - ) - )->join('') - ) )->join(''); } -sub use_item ($self, $userName, $c) { - my $db = $c->db; - my $ce = $c->ce; - - # Validate data - - my $globalUserAchievement = $db->getGlobalUserAchievement($userName); - return 'No achievement data?!?!?!' unless $globalUserAchievement->frozen_hash; - - my $globalData = thaw_base64($globalUserAchievement->frozen_hash); - return "You are $self->{id} trying to use an item you don't have" unless $globalData->{ $self->{id} }; - - my $setID = $c->param('tran_set_id'); - return 'You need to input a Set Name' unless defined $setID; - - my $problemID = $c->param('tran_problem_id'); - return 'You need to input a Problem Number' unless $problemID; - - my $problemID2 = $c->param('tran_problem_id2'); - return 'You need to input a Problem Number' unless $problemID2; - - return 'You need to pick 2 different problems!' if $problemID == $problemID2; - - my $problem = $db->getMergedProblem($userName, $setID, $problemID); - my $problem2 = $db->getUserProblem($userName, $setID, $problemID2); - return 'There was an error accessing those problems.' unless $problem && $problem2; +sub use_item ($self, $set, $records, $c) { + my $sourceID = $c->param('clone_source_problem_id'); + my $destID = $c->param('clone_dest_problem_id'); + unless ($sourceID) { + $c->addbadmessage($c->maketext('Select problem to clone with the [_1].', $self->name)); + return ''; + } + unless ($destID) { + $c->addbadmessage($c->maketext('Select problem to replace with the [_1].', $self->name)); + return ''; + } - # Set the source of the second problem to that of the first problem. - $problem2->source_file($problem->source_file); - $db->putUserProblem($problem2); + my ($sourceProblem, $destProblem); + for (@$records) { + $sourceProblem = $_ if $_->problem_id == $sourceID; + $destProblem = $_ if $_->problem_id == $destID; + last if $sourceProblem && $destProblem; + } + return '' unless $sourceProblem && $destProblem; - $globalData->{ $self->{id} }--; - $globalUserAchievement->frozen_hash(nfreeze_base64($globalData)); - $db->putGlobalUserAchievement($globalUserAchievement); + my $db = $c->db; + my $userProblem = $db->getUserProblem($destProblem->user_id, $destProblem->set_id, $destProblem->problem_id); + $destProblem->source_file($sourceProblem->source_file); + $userProblem->source_file($destProblem->source_file); + $db->putUserProblem($userProblem); - return; + return $c->maketext("Problem [_1] replaced with problem [_2].", $destID, $sourceID); } 1; diff --git a/lib/WeBWorK/AchievementItems/ExtendDueDate.pm b/lib/WeBWorK/AchievementItems/ExtendDueDate.pm index f645c59734..6ff82ea546 100644 --- a/lib/WeBWorK/AchievementItems/ExtendDueDate.pm +++ b/lib/WeBWorK/AchievementItems/ExtendDueDate.pm @@ -18,9 +18,8 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures; # Item to extend a close date by 24 hours. -use WeBWorK::Utils qw(x nfreeze_base64 thaw_base64); +use WeBWorK::Utils qw(x); use WeBWorK::Utils::DateTime qw(after between); -use WeBWorK::Utils::Sets qw(format_set_name_display); use constant ONE_DAY => 86400; @@ -35,69 +34,56 @@ sub new ($class) { }, $class; } -sub print_form ($self, $sets, $setProblemIds, $c) { - my @openSets; - - for my $i (0 .. $#$sets) { - push(@openSets, [ format_set_name_display($sets->[$i]->set_id) => $sets->[$i]->set_id ]) - if (between($sets->[$i]->open_date, $sets->[$i]->due_date + ONE_DAY) - && $sets->[$i]->assignment_type eq 'default'); - } - - return unless @openSets; +sub can_use ($self, $set, $records) { + return $set->assignment_type eq 'default' && between($set->open_date, $set->due_date + ONE_DAY); +} - return $c->c( - $c->tag('p', $c->maketext('Choose the set whose close date you would like to extend.')), - WeBWorK::AchievementItems::form_popup_menu_row( - $c, - id => 'ext_set_id', - label_text => $c->maketext('Set Name'), - values => \@openSets, - menu_attr => { dir => 'ltr' } +sub print_form ($self, $set, $records, $c) { + my $randomization_statement = after($set->due_date) ? $c->maketext('All problems will be rerandomized.') : ''; + return $c->tag( + 'p', + $c->maketext( + 'Extend the close date of this assignment to [_1] (an additional 24 hours). [_2]', + $c->formatDateTime($set->due_date + ONE_DAY, $c->ce->{studentDateDisplayFormat}), + $randomization_statement ) - )->join(''); + ); } -sub use_item ($self, $userName, $c) { - my $db = $c->db; - my $ce = $c->ce; - - # Validate data - - my $globalUserAchievement = $db->getGlobalUserAchievement($userName); - return 'No achievement data?!?!?!' unless $globalUserAchievement->frozen_hash; - - my $globalData = thaw_base64($globalUserAchievement->frozen_hash); - return "You are $self->{id} trying to use an item you don't have" unless $globalData->{ $self->{id} }; - - my $setID = $c->param('ext_set_id'); - return 'You need to input a Set Name' unless defined $setID; - - my $set = $db->getMergedSet($userName, $setID); - my $userSet = $db->getUserSet($userName, $setID); - return q{Couldn't find that set!} unless $set && $userSet; +sub use_item ($self, $set, $records, $c) { + my $db = $c->db; + my $userSet = $db->getUserSet($set->user_id, $set->set_id); # Change the seed for all of the problems if the set is currently closed. if (after($set->due_date)) { - for my $problem ($db->getUserProblemsWhere({ user_id => $userName, set_id => $setID })) { - $problem->problem_seed($problem->problem_seed % 2**31 + 1); - $db->putUserProblem($problem); + my @userProblems = + $db->getUserProblemsWhere({ user_id => $set->user_id, set_id => $set->set_id }, 'problem_id'); + for my $n (0 .. $#userProblems) { + $userProblems[$n]->problem_seed($userProblems[$n]->problem_seed % 2**31 + 1); + $records->[$n]->problem_seed($userProblems[$n]->problem_seed); + $db->putUserProblem($userProblems[$n]); } } # Add time to the reduced scoring date if it was defined in the first place - $userSet->reduced_scoring_date($set->reduced_scoring_date + ONE_DAY) if $set->reduced_scoring_date; + if ($set->reduced_scoring_date) { + $set->reduced_scoring_date($set->reduced_scoring_date + ONE_DAY); + $userSet->reduced_scoring_date($set->reduced_scoring_date); + } # Add time to the close date - $userSet->due_date($set->due_date + ONE_DAY); + $set->due_date($set->due_date + ONE_DAY); + $userSet->due_date($set->due_date); # This may require also extending the answer date. - $userSet->answer_date($userSet->due_date) if $userSet->due_date > $set->answer_date; + if ($set->due_date > $set->answer_date) { + $set->answer_date($set->due_date); + $userSet->answer_date($set->answer_date); + } $db->putUserSet($userSet); - $globalData->{ $self->{id} }--; - $globalUserAchievement->frozen_hash(nfreeze_base64($globalData)); - $db->putGlobalUserAchievement($globalUserAchievement); - - return; + return $c->maketext( + 'Closing date of this assignment extended by 24 hours to [_1].', + $c->formatDateTime($set->due_date, $c->ce->{studentDateDisplayFormat}) + ); } 1; diff --git a/lib/WeBWorK/AchievementItems/ExtendDueDateGW.pm b/lib/WeBWorK/AchievementItems/ExtendDueDateGW.pm index cccefc6f12..0f215cf929 100644 --- a/lib/WeBWorK/AchievementItems/ExtendDueDateGW.pm +++ b/lib/WeBWorK/AchievementItems/ExtendDueDateGW.pm @@ -18,9 +18,8 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures; # Item to extend the close date on a test -use WeBWorK::Utils qw(x nfreeze_base64 thaw_base64); +use WeBWorK::Utils qw(x); use WeBWorK::Utils::DateTime qw(between); -use WeBWorK::Utils::Sets qw(format_set_name_display); use constant ONE_DAY => 86400; @@ -33,76 +32,53 @@ sub new ($class) { }, $class; } -sub print_form ($self, $sets, $setProblemIds, $c) { - my $db = $c->db; - - my @openGateways; - - # Find the template sets for open tests. - for my $set (@$sets) { - push(@openGateways, [ format_set_name_display($set->set_id) => $set->set_id ]) - if $set->assignment_type =~ /gateway/ - && $set->set_id !~ /,v\d+$/ - && between($set->open_date, $set->due_date); - } - - return unless @openGateways; +sub can_use ($self, $set, $records) { + return + $set->assignment_type =~ /gateway/ + && $set->set_id !~ /,v\d+$/ + && between($set->open_date, $set->due_date + ONE_DAY); +} - return $c->c( - $c->tag('p', $c->maketext('Extend the close date for which test?')), - WeBWorK::AchievementItems::form_popup_menu_row( - $c, - id => 'eddgw_gw_id', - label_text => $c->maketext('Test Name'), - values => \@openGateways, - menu_attr => { dir => 'ltr' } +sub print_form ($self, $set, $records, $c) { + return $c->tag( + 'p', + $c->maketext( + 'Extend the close date of this test to [_1] (an additional 24 hours).', + $c->formatDateTime($set->due_date + ONE_DAY, $c->ce->{studentDateDisplayFormat}) ) - )->join(''); + ); } -sub use_item ($self, $userName, $c) { - my $db = $c->db; - my $ce = $c->ce; - - # Validate data - - my $globalUserAchievement = $db->getGlobalUserAchievement($userName); - return 'No achievement data?!?!?!' unless $globalUserAchievement->frozen_hash; - - my $globalData = thaw_base64($globalUserAchievement->frozen_hash); - return "You are $self->{id} trying to use an item you don't have" unless $globalData->{ $self->{id} }; - - my $setID = $c->param('eddgw_gw_id'); - return 'You need to input a Test Name' unless defined $setID; - - my $set = $db->getMergedSet($userName, $setID); - my $userSet = $db->getUserSet($userName, $setID); - return q{Couldn't find that set!} unless $set && $userSet; +sub use_item ($self, $set, $records, $c) { + my $db = $c->db; + my $userSet = $db->getUserSet($set->user_id, $set->set_id); # Add time to the reduced scoring date, due date, and answer date. - $userSet->reduced_scoring_date($set->reduced_scoring_date() + ONE_DAY) - if defined($set->reduced_scoring_date()) && $set->reduced_scoring_date(); - $userSet->due_date($set->due_date() + ONE_DAY); - $userSet->answer_date($set->answer_date() + ONE_DAY); + if ($set->reduced_scoring_date) { + $set->reduced_scoring_date($set->reduced_scoring_date + ONE_DAY); + $userSet->reduced_scoring_date($set->reduced_scoring_date); + } + $set->due_date($set->due_date + ONE_DAY); + $userSet->due_date($set->due_date); + $set->answer_date($set->answer_date + ONE_DAY); + $userSet->answer_date($set->answer_date); $db->putUserSet($userSet); + # FIXME: Should we add time to each test version, as adding 24 hours to a 1 hour long test + # isn't reasonable. Disabling this for now, will revisit later. # Add time to the reduced scoring date, due date, and answer date for all versions. - my @versions = $db->listSetVersions($userName, $setID); - - for my $version (@versions) { - $set = $db->getSetVersion($userName, $setID, $version); - $set->reduced_scoring_date($set->reduced_scoring_date() + ONE_DAY) - if defined($set->reduced_scoring_date()) && $set->reduced_scoring_date(); - $set->due_date($set->due_date() + ONE_DAY); - $set->answer_date($set->answer_date() + ONE_DAY); - $db->putSetVersion($set); - } - - $globalData->{ $self->{id} }--; - $globalUserAchievement->frozen_hash(nfreeze_base64($globalData)); - $db->putGlobalUserAchievement($globalUserAchievement); - - return; + #my @versions = $db->listSetVersions($userName, $setID); + #for my $version (@versions) { + # $set = $db->getSetVersion($userName, $setID, $version); + # $set->reduced_scoring_date($set->reduced_scoring_date() + ONE_DAY) + # if defined($set->reduced_scoring_date()) && $set->reduced_scoring_date(); + # $set->due_date($set->due_date() + ONE_DAY); + # $set->answer_date($set->answer_date() + ONE_DAY); + # $db->putSetVersion($set); + #} + + return $c->maketext('Close date of this test change to [_1].', + $c->formatDateTime($set->due_date, $c->ce->{studentDateDisplayFormat})); } 1; diff --git a/lib/WeBWorK/AchievementItems/ExtendReducedDate.pm b/lib/WeBWorK/AchievementItems/ExtendReducedDate.pm index f3d78333ae..dd929e9ded 100644 --- a/lib/WeBWorK/AchievementItems/ExtendReducedDate.pm +++ b/lib/WeBWorK/AchievementItems/ExtendReducedDate.pm @@ -18,9 +18,8 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures; # Item to extend a close date by 24 hours. -use WeBWorK::Utils qw(x nfreeze_base64 thaw_base64); +use WeBWorK::Utils qw(x); use WeBWorK::Utils::DateTime qw(between); -use WeBWorK::Utils::Sets qw(format_set_name_display); use constant ONE_DAY => 86400; @@ -36,75 +35,50 @@ sub new ($class) { }, $class; } -sub print_form ($self, $sets, $setProblemIds, $c) { - my @openSets; +sub can_use ($self, $set, $records) { + return 0 + unless $set->assignment_type eq 'default' + && $set->enable_reduced_scoring + && $set->reduced_scoring_date + && $set->reduced_scoring_date < $set->due_date; - # Nothing to do if reduced scoring is not enabled. - return unless $c->{ce}->{pg}{ansEvalDefaults}{enableReducedScoring}; - - for my $i (0 .. $#$sets) { - my $new_date = 0; - if ($sets->[$i]->reduced_scoring_date() && $sets->[$i]->reduced_scoring_date() < $sets->[$i]->due_date()) { - $new_date = $sets->[$i]->reduced_scoring_date() + ONE_DAY; - $new_date = $sets->[$i]->due_date() if $sets->[$i]->due_date() < $new_date; - } - push(@openSets, [ format_set_name_display($sets->[$i]->set_id) => $sets->[$i]->set_id ]) - if ($new_date - && between($sets->[$i]->open_date, $new_date) - && $sets->[$i]->assignment_type eq 'default' - && $sets->[$i]->enable_reduced_scoring); - } + $self->{new_date} = $set->reduced_scoring_date + ONE_DAY; + $self->{new_date} = $set->due_date if $set->due_date < $self->{new_date}; + return between($set->open_date, $self->{new_date}); +} - return unless @openSets; +sub print_form ($self, $set, $records, $c) { + return $c->tag( + 'p', + $c->maketext( + q{This item won't work unless your instructor enables the reduced scoring feature. } + . 'Let your instructor know that you recieved this message.' + ) + ) unless $c->{ce}->{pg}{ansEvalDefaults}{enableReducedScoring}; - return $c->c( - $c->tag( - 'p', - $c->maketext('Choose the assignment whose reduced scoring date you would like to extend by 24 hours.') - ), - WeBWorK::AchievementItems::form_popup_menu_row( - $c, - id => 'ext_reduced_set_id', - label_text => $c->maketext('Assignment Name'), - values => \@openSets, - menu_attr => { dir => 'ltr' } + return $c->tag( + 'p', + $c->maketext( + 'Extend the reduced scoring date to [_1] (an additional 24 hours).', + $c->formatDateTime($self->{new_date}, $c->ce->{studentDateDisplayFormat}) ) - )->join(''); + ); } -sub use_item ($self, $userName, $c) { - my $db = $c->db; - my $ce = $c->ce; - - # Validate data - - # Nothing to do if reduced scoring is not enabled. - return 'Reduced scoring disabled.' unless $c->{ce}->{pg}{ansEvalDefaults}{enableReducedScoring}; +sub use_item ($self, $set, $records, $c) { + return '' unless $c->{ce}->{pg}{ansEvalDefaults}{enableReducedScoring}; - my $globalUserAchievement = $db->getGlobalUserAchievement($userName); - return 'No achievement data?!?!?!' unless $globalUserAchievement->frozen_hash; + my $db = $c->db; + my $userSet = $db->getUserSet($set->user_id, $set->set_id); - my $globalData = thaw_base64($globalUserAchievement->frozen_hash); - return "You are $self->{id} trying to use an item you don't have" unless $globalData->{ $self->{id} }; - - my $setID = $c->param('ext_reduced_set_id'); - return 'You need to input a Set Name' unless defined $setID; - - my $set = $db->getMergedSet($userName, $setID); - my $userSet = $db->getUserSet($userName, $setID); - return q{Couldn't find that set!} unless $set && $userSet; - - # Add time to the reduced scoring date, keeping in mind this cannot extend past the due date. - my $new_date = $set->reduced_scoring_date() + ONE_DAY; - $new_date = $set->due_date() if $set->due_date() < $new_date; - $userSet->reduced_scoring_date($new_date); + $set->reduced_scoring_date($self->{new_date}); + $userSet->reduced_scoring_date($set->reduced_scoring_date); $db->putUserSet($userSet); - $globalData->{ $self->{id} }--; - $globalUserAchievement->frozen_hash(nfreeze_base64($globalData)); - $db->putGlobalUserAchievement($globalUserAchievement); - - return; + return $c->maketext( + 'Reduced scoring date of this assignment exted by 24 hours to [_1].', + $c->formatDateTime($self->{new_date}, $c->ce->{studentDateDisplayFormat}) + ); } 1; diff --git a/lib/WeBWorK/AchievementItems/FullCreditProb.pm b/lib/WeBWorK/AchievementItems/FullCreditProb.pm index 8cf759d30f..02593b938d 100644 --- a/lib/WeBWorK/AchievementItems/FullCreditProb.pm +++ b/lib/WeBWorK/AchievementItems/FullCreditProb.pm @@ -18,11 +18,8 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures; # Item to give full credit on a single problem -use Mojo::JSON qw(encode_json); - -use WeBWorK::Utils qw(x nfreeze_base64 thaw_base64); +use WeBWorK::Utils qw(x wwRound); use WeBWorK::Utils::DateTime qw(after); -use WeBWorK::Utils::Sets qw(format_set_name_display); sub new ($class) { return bless { @@ -32,84 +29,57 @@ sub new ($class) { }, $class; } -sub print_form ($self, $sets, $setProblemIds, $c) { - # Construct a dropdown with open sets and another with problems. - # Javascript ensures the appropriate problems are shown for the selected set. - - my (@openSets, @initialProblemIDs); +sub can_use ($self, $set, $records) { + return 0 + unless $set->assignment_type eq 'default' + && after($set->open_date); - for my $i (0 .. $#$sets) { - if (after($sets->[$i]->open_date) - && $sets->[$i]->assignment_type eq 'default' - && @{ $setProblemIds->{ $sets->[$i]->set_id } }) - { - push( - @openSets, - [ - format_set_name_display($sets->[$i]->set_id) => $sets->[$i]->set_id, - data => { problem_ids => encode_json($setProblemIds->{ $sets->[$i]->set_id }) } - ] - ); - @initialProblemIDs = @{ $setProblemIds->{ $sets->[$i]->set_id } } unless @initialProblemIDs; - } - } + my @problems = grep { $_->status < 1 } @$records; + return 0 unless @problems; - return unless @openSets; - - return $c->c( - $c->tag( - 'p', - $c->maketext( - 'Please choose the set name and problem number of the question which should be given full credit.') - ), - WeBWorK::AchievementItems::form_popup_menu_row( - $c, - id => 'fcp_set_id', - label_text => $c->maketext('Set Name'), - values => \@openSets, - menu_attr => { dir => 'ltr', data => { problems => 'fcp_problem_id' } } - ), - WeBWorK::AchievementItems::form_popup_menu_row( - $c, - id => 'fcp_problem_id', - label_text => $c->maketext('Problem Number'), - values => \@initialProblemIDs, - menu_container_attr => { class => 'col-3' } - ) - )->join(''); + $self->{usableProblems} = \@problems; + return 1; } -sub use_item ($self, $userName, $c) { - my $db = $c->db; - my $ce = $c->ce; - - # Validate data - - my $globalUserAchievement = $db->getGlobalUserAchievement($userName); - return 'No achievement data?!?!?!' unless $globalUserAchievement->frozen_hash; - - my $globalData = thaw_base64($globalUserAchievement->frozen_hash); - return "You are $self->{id} trying to use an item you don't have" unless $globalData->{ $self->{id} }; - - my $setID = $c->param('fcp_set_id'); - return 'You need to input a Set Name' unless defined $setID; +sub print_form ($self, $set, $records, $c) { + return WeBWorK::AchievementItems::form_popup_menu_row( + $c, + id => 'full_cred_problem_id', + label_text => $c->maketext('Problem Number'), + first_item => $c->maketext('Choose problem to give full credit.'), + values => [ + map { [ $c->maketext('Problem [_1] ([_2]% to 100%)', $_->problem_id, 100 * wwRound(2, $_->status)) => + $_->problem_id ] } @{ $self->{usableProblems} } + ], + ); +} - my $problemID = $c->param('fcp_problem_id'); - return 'You need to input a Problem Number' unless $problemID; +sub use_item ($self, $set, $records, $c) { + my $problemID = $c->param('full_cred_problem_id'); + unless ($problemID) { + $c->addbadmessage($c->maketext('Select problem to give 100% to the [_1].', $self->name)); + return ''; + } - my $problem = $db->getUserProblem($userName, $setID, $problemID); - return 'There was an error accessing that problem.' unless $problem; + my $problem; + for (@$records) { + if ($_->problem_id == $problemID) { + $problem = $_; + last; + } + } + return '' unless $problem; - # Set the status and sub_status of the problem to one. + # Increase status to 100%. + my $db = $c->db; + my $userProblem = $db->getUserProblem($problem->user_id, $problem->set_id, $problem->problem_id); $problem->status(1); $problem->sub_status(1); - $db->putUserProblem($problem); - - $globalData->{ $self->{id} }--; - $globalUserAchievement->frozen_hash(nfreeze_base64($globalData)); - $db->putGlobalUserAchievement($globalUserAchievement); + $userProblem->status(1); + $userProblem->sub_status(1); + $db->putUserProblem($userProblem); - return; + return $c->maketext('Problem number [_1] increased to 100%.', $problemID); } 1; diff --git a/lib/WeBWorK/AchievementItems/FullCreditSet.pm b/lib/WeBWorK/AchievementItems/FullCreditSet.pm index 21bc077285..0437357cdd 100644 --- a/lib/WeBWorK/AchievementItems/FullCreditSet.pm +++ b/lib/WeBWorK/AchievementItems/FullCreditSet.pm @@ -18,9 +18,8 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures; # Item to give half credit on all problems in a homework set. -use WeBWorK::Utils qw(x nfreeze_base64 thaw_base64); +use WeBWorK::Utils qw(x wwRound); use WeBWorK::Utils::DateTime qw(after); -use WeBWorK::Utils::Sets qw(format_set_name_display); sub new ($class) { return bless { @@ -30,59 +29,38 @@ sub new ($class) { }, $class; } -sub print_form ($self, $sets, $setProblemIds, $c) { - my @openSets; +sub can_use ($self, $set, $records) { + return 0 + unless $set->assignment_type eq 'default' + && after($set->open_date); - for my $i (0 .. $#$sets) { - push(@openSets, [ format_set_name_display($sets->[$i]->set_id) => $sets->[$i]->set_id ]) - if (after($sets->[$i]->open_date) && $sets->[$i]->assignment_type eq 'default'); + my $total = 0; + my $grade = 0; + for my $problem (@$records) { + $grade += $problem->status * $problem->value; + $total += $problem->value; } + $self->{old_grade} = 100 * wwRound(2, $grade / $total); + return $self->{old_grade} == 100 ? 0 : 1; +} - return unless @openSets; - - return $c->c( - $c->tag('p', $c->maketext('Please choose the set for which all problems should be given full credit.')), - WeBWorK::AchievementItems::form_popup_menu_row( - $c, - id => 'fcs_set_id', - label_text => $c->maketext('Set Name'), - values => \@openSets, - menu_attr => { dir => 'ltr' } - ) - )->join(''); +sub print_form ($self, $set, $records, $c) { + return $c->tag('p', $c->maketext(q(Increase this assignment's grade from [_1]% to 100%.), $self->{old_grade})); } -sub use_item ($self, $userName, $c) { +sub use_item ($self, $set, $records, $c) { my $db = $c->db; - my $ce = $c->ce; - - # Validate data - - my $globalUserAchievement = $db->getGlobalUserAchievement($userName); - return 'No achievement data?!?!?!' unless $globalUserAchievement->frozen_hash; - - my $globalData = thaw_base64($globalUserAchievement->frozen_hash); - return "You are $self->{id} trying to use an item you don't have" unless $globalData->{ $self->{id} }; - my $setID = $c->param('fcs_set_id'); - return 'You need to input a Set Name' unless defined $setID; - - my @probIDs = $db->listUserProblems($userName, $setID); - - for my $probID (@probIDs) { - my $problem = $db->getUserProblem($userName, $setID, $probID); - - # Set status and sub_status to 1. - $problem->status(1); - $problem->sub_status(1); - $db->putUserProblem($problem); + my @userProblems = $db->getUserProblemsWhere({ user_id => $set->user_id, set_id => $set->set_id }, 'problem_id'); + for my $n (0 .. $#userProblems) { + $records->[$n]->status(1); + $records->[$n]->sub_status(1); + $userProblems[$n]->status(1); + $userProblems[$n]->sub_status(1); + $db->putUserProblem($userProblems[$n]); } - $globalData->{ $self->{id} }--; - $globalUserAchievement->frozen_hash(nfreeze_base64($globalData)); - $db->putGlobalUserAchievement($globalUserAchievement); - - return; + return $c->maketext(q(Assignment's grade increased from [_1]% to 100%.), $self->{old_grade}); } 1; diff --git a/lib/WeBWorK/AchievementItems/HalfCreditProb.pm b/lib/WeBWorK/AchievementItems/HalfCreditProb.pm index f607a8bb01..80ae83b80d 100644 --- a/lib/WeBWorK/AchievementItems/HalfCreditProb.pm +++ b/lib/WeBWorK/AchievementItems/HalfCreditProb.pm @@ -18,11 +18,8 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures; # Item to give half credit on a single problem. -use Mojo::JSON qw(encode_json); - -use WeBWorK::Utils qw(x nfreeze_base64 thaw_base64); +use WeBWorK::Utils qw(x wwRound); use WeBWorK::Utils::DateTime qw(after); -use WeBWorK::Utils::Sets qw(format_set_name_display); sub new ($class) { return bless { @@ -32,87 +29,60 @@ sub new ($class) { }, $class; } -sub print_form ($self, $sets, $setProblemIds, $c) { - # Construct a dropdown with open sets and another with problems. - # Javascript ensures the appropriate problems are shown for the selected set. - - my (@openSets, @initialProblemIDs); - - for my $i (0 .. $#$sets) { - if (after($sets->[$i]->open_date) - && $sets->[$i]->assignment_type eq 'default' - && @{ $setProblemIds->{ $sets->[$i]->set_id } }) - { - push( - @openSets, - [ - format_set_name_display($sets->[$i]->set_id) => $sets->[$i]->set_id, - data => { problem_ids => encode_json($setProblemIds->{ $sets->[$i]->set_id }) } - ] - ); - @initialProblemIDs = @{ $setProblemIds->{ $sets->[$i]->set_id } } unless @initialProblemIDs; - } - } +sub can_use($self, $set, $records) { + return 0 + unless $set->assignment_type eq 'default' + && after($set->open_date); - return unless @openSets; - - return $c->c( - $c->tag( - 'p', - $c->maketext( - 'Please choose the assignment name and problem number of the question to add half credit to.') - ), - WeBWorK::AchievementItems::form_popup_menu_row( - $c, - id => 'hcp_set_id', - label_text => $c->maketext('Set Name'), - values => \@openSets, - menu_attr => { dir => 'ltr', data => { problems => 'hcp_problem_id' } } - ), - WeBWorK::AchievementItems::form_popup_menu_row( - $c, - id => 'hcp_problem_id', - values => \@initialProblemIDs, - label_text => $c->maketext('Problem Number'), - menu_container_attr => { class => 'col-3' } - ) - )->join(''); + $self->{unfinishedProblems} = [ grep { $_->status < 1 } @$records ]; + return @{ $self->{unfinishedProblems} } ? 1 : 0; } -sub use_item ($self, $userName, $c) { - my $db = $c->db; - my $ce = $c->ce; - - # Validate data - - my $globalUserAchievement = $db->getGlobalUserAchievement($userName); - return 'No achievement data?!?!?!' unless $globalUserAchievement->frozen_hash; - - my $globalData = thaw_base64($globalUserAchievement->frozen_hash); - return "You are $self->{id} trying to use an item you don't have" unless $globalData->{ $self->{id} }; - - my $setID = $c->param('hcp_set_id'); - return 'You need to input a Set Name' unless defined $setID; - - my $problemID = $c->param('hcp_problem_id'); - return 'You need to input a Problem Number' unless $problemID; - - my $problem = $db->getUserProblem($userName, $setID, $problemID); - return 'There was an error accessing that problem.' unless $problem; - - # Add .5 to grade with max of 1 - my $new_status = $problem->status + 0.5; - $new_status = 1 if $new_status > 1; - $problem->status($new_status); - $problem->sub_status($new_status); - - $db->putUserProblem($problem); +sub print_form ($self, $set, $records, $c) { + return WeBWorK::AchievementItems::form_popup_menu_row( + $c, + id => 'half_cred_problem_id', + label_text => $c->maketext('Problem Number'), + first_item => $c->maketext('Choose problem to increase 50%.'), + values => [ + map { [ + $c->maketext( + 'Problem [_1] ([_2]% to [_3]%)', + $_->problem_id, + 100 * wwRound(2, $_->status), + 100 * wwRound(2, $_->status < 0.5 ? $_->status + 0.5 : 1) + ) => $_->problem_id + ] } @{ $self->{unfinishedProblems} } + ], + ); +} - $globalData->{ $self->{id} }--; - $globalUserAchievement->frozen_hash(nfreeze_base64($globalData)); - $db->putGlobalUserAchievement($globalUserAchievement); +sub use_item ($self, $set, $records, $c) { + my $problemID = $c->param('half_cred_problem_id'); + unless ($problemID) { + $c->addbadmessage($c->maketext('Select problem to add 50% with the [_1].', $self->name)); + return ''; + } - return; + my $problem; + for (@$records) { + if ($_->problem_id == $problemID) { + $problem = $_; + last; + } + } + return '' unless $problem; + + # Increase status to 100%. + my $db = $c->db; + my $userProblem = $db->getUserProblem($problem->user_id, $problem->set_id, $problem->problem_id); + $problem->status($problem->status > 0.5 ? 1 : $problem->status + 0.5); + $problem->sub_status($problem->status); + $userProblem->status($problem->status); + $userProblem->sub_status($problem->status); + $db->putUserProblem($userProblem); + + return $c->maketext('Problem number [_1] increased to [_2]%.', $problemID, 100 * wwRound(2, $problem->status)); } 1; diff --git a/lib/WeBWorK/AchievementItems/HalfCreditSet.pm b/lib/WeBWorK/AchievementItems/HalfCreditSet.pm index c70cfa73c4..a93ef346e0 100644 --- a/lib/WeBWorK/AchievementItems/HalfCreditSet.pm +++ b/lib/WeBWorK/AchievementItems/HalfCreditSet.pm @@ -18,9 +18,8 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures; # Item to give half credit on all problems in a homework set. -use WeBWorK::Utils qw(x nfreeze_base64 thaw_base64); +use WeBWorK::Utils qw(x wwRound); use WeBWorK::Utils::DateTime qw(after); -use WeBWorK::Utils::Sets qw(format_set_name_display); sub new ($class) { return bless { @@ -30,64 +29,47 @@ sub new ($class) { }, $class; } -sub print_form ($self, $sets, $setProblemIds, $c) { - my @openSets; - - for my $i (0 .. $#$sets) { - push(@openSets, [ format_set_name_display($sets->[$i]->set_id) => $sets->[$i]->set_id ]) - if (after($sets->[$i]->open_date) && $sets->[$i]->assignment_type eq 'default'); +sub can_use($self, $set, $records) { + return 0 + unless $set->assignment_type eq 'default' + && after($set->open_date); + + my $total = 0; + my $old_grade = 0; + my $new_grade = 0; + for my $problem (@$records) { + $old_grade += $problem->status * $problem->value; + $new_grade += ($problem->status > 0.5 ? 1 : $problem->status + 0.5) * $problem->value; + $total += $problem->value; } + $self->{old_grade} = 100 * wwRound(2, $old_grade / $total); + $self->{new_grade} = 100 * wwRound(2, $new_grade / $total); + return $self->{old_grade} == 100 ? 0 : 1; +} - return unless @openSets; - - return $c->c( - $c->tag( - 'p', $c->maketext('Please choose the assignment for which all problems should have half credit added.') - ), - WeBWorK::AchievementItems::form_popup_menu_row( - $c, - id => 'hcs_set_id', - label_text => $c->maketext('Set Name'), - values => \@openSets, - menu_attr => { dir => 'ltr' } +sub print_form ($self, $set, $records, $c) { + return $c->tag( + 'p', + $c->maketext( + q(Increase this assignment's grade from [_1]% to [_2]%.), + $self->{old_grade}, $self->{new_grade} ) - )->join(''); + ); } -sub use_item ($self, $userName, $c) { +sub use_item ($self, $set, $records, $c) { my $db = $c->db; - my $ce = $c->ce; - - # Validate data - - my $globalUserAchievement = $db->getGlobalUserAchievement($userName); - return 'No achievement data?!?!?!' unless $globalUserAchievement->frozen_hash; - - my $globalData = thaw_base64($globalUserAchievement->frozen_hash); - return "You are $self->{id} trying to use an item you don't have" unless $globalData->{ $self->{id} }; - my $setID = $c->param('hcs_set_id'); - return 'You need to input a Set Name' unless defined $setID; - - my @probIDs = $db->listUserProblems($userName, $setID); - - for my $probID (@probIDs) { - my $problem = $db->getUserProblem($userName, $setID, $probID); - - # Add .5 to grade with max of 1. - my $new_status = $problem->status + 0.5; - $new_status = 1 if $new_status > 1; - $problem->status($new_status); - $problem->sub_status($new_status); - - $db->putUserProblem($problem); + my @userProblems = $db->getUserProblemsWhere({ user_id => $set->user_id, set_id => $set->set_id }, 'problem_id'); + for my $n (0 .. $#userProblems) { + $records->[$n]->status($records->[$n]->status > 0.5 ? 1 : $records->[$n]->status + 0.5); + $records->[$n]->sub_status($records->[$n]->status); + $userProblems[$n]->status($records->[$n]->status); + $userProblems[$n]->sub_status($records->[$n]->status); + $db->putUserProblem($userProblems[$n]); } - $globalData->{ $self->{id} }--; - $globalUserAchievement->frozen_hash(nfreeze_base64($globalData)); - $db->putGlobalUserAchievement($globalUserAchievement); - - return; + return $c->maketext(q(Assignment's grade increased from [_1] to [_2].), $self->{old_grade}, $self->{new_grade}); } 1; diff --git a/lib/WeBWorK/AchievementItems/NoReducedCred.pm b/lib/WeBWorK/AchievementItems/NoReducedCred.pm index cc302dcd0b..01a5db47d6 100644 --- a/lib/WeBWorK/AchievementItems/NoReducedCred.pm +++ b/lib/WeBWorK/AchievementItems/NoReducedCred.pm @@ -19,9 +19,8 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures; # Item to remove reduce credit scoring period from a set. # Reduced scoring needs to be enabled for this item to be useful. -use WeBWorK::Utils qw(x nfreeze_base64 thaw_base64); +use WeBWorK::Utils qw(x); use WeBWorK::Utils::DateTime qw(between); -use WeBWorK::Utils::Sets qw(format_set_name_display); sub new ($class) { return bless { @@ -34,67 +33,48 @@ sub new ($class) { }, $class; } -sub print_form ($self, $sets, $setProblemIds, $c) { - my @openSets; - - # Nothing to do if reduced scoring is not enabled. - return unless $c->{ce}->{pg}{ansEvalDefaults}{enableReducedScoring}; - - # Only show open sets that have reduced scoring enabled. - for my $i (0 .. $#$sets) { - push(@openSets, [ format_set_name_display($sets->[$i]->set_id) => $sets->[$i]->set_id ]) - if (between($sets->[$i]->open_date, $sets->[$i]->due_date) - && $sets->[$i]->assignment_type eq 'default' - && $sets->[$i]->enable_reduced_scoring); - } - - return unless @openSets; +sub can_use ($self, $set, $records) { + return 0 + unless $set->assignment_type eq 'default' + && $set->enable_reduced_scoring + && $set->reduced_scoring_date + && $set->reduced_scoring_date < $set->due_date + && between($set->open_date, $set->due_date); +} - return $c->c( - $c->tag('p', $c->maketext('Choose the assignment to remove the reduced scoring pentaly from.')), - WeBWorK::AchievementItems::form_popup_menu_row( - $c, - id => 'no_reduce_set_id', - label_text => $c->maketext('Assignment Name'), - values => \@openSets, - menu_attr => { dir => 'ltr' } +sub print_form ($self, $set, $records, $c) { + return $c->tag( + 'p', + $c->maketext( + q{This item won't work unless your instructor enables the reduced scoring feature. } + . 'Let your instructor know that you recieved this message.' + ) + ) unless $c->{ce}->{pg}{ansEvalDefaults}{enableReducedScoring}; + + return $c->tag( + 'p', + $c->maketext( + 'Remove the reduced scoring pentaly from this assignment. Problems submitted before ' + . 'the close date on [_1] will earn full credit. Any problems that have already been ' + . 'penalized will have to be resubmitted for full credit.', + $c->formatDateTime($set->due_date, $c->ce->{studentDateDisplayFormat}) ) - )->join(''); + ); } -sub use_item ($self, $userName, $c) { - my $db = $c->db; - my $ce = $c->ce; - - # Validate data +sub use_item ($self, $set, $records, $c) { + return '' unless $c->{ce}->{pg}{ansEvalDefaults}{enableReducedScoring}; - return q{This item won't work unless your instructor enables the reduced scoring feature. } - . 'Let your instructor know that you received this message.' - unless $ce->{pg}{ansEvalDefaults}{enableReducedScoring}; + my $db = $c->db; + my $userSet = $db->getUserSet($set->user_id, $set->set_id); - my $globalUserAchievement = $db->getGlobalUserAchievement($userName); - return "No achievement data?!?!?!" unless $globalUserAchievement->frozen_hash; - - my $globalData = thaw_base64($globalUserAchievement->frozen_hash); - return "You are $self->{id} trying to use an item you don't have" unless $globalData->{ $self->{id} }; - - my $setID = $c->param('no_reduce_set_id'); - return "You need to input a Set Name" unless defined $setID; - - my $set = $db->getMergedSet($userName, $setID); - my $userSet = $db->getUserSet($userName, $setID); - return "Couldn't find that set!" unless $set && $userSet; - - # Remove reduced scoring from the set and set the reduced scoring date to be the due date. + $set->enable_reduced_scoring(0); + $set->reduced_scoring_date($set->due_date); $userSet->enable_reduced_scoring(0); - $userSet->reduced_scoring_date($set->due_date()); + $userSet->reduced_scoring_date($set->due_date); $db->putUserSet($userSet); - $globalData->{ $self->{id} }--; - $globalUserAchievement->frozen_hash(nfreeze_base64($globalData)); - $db->putGlobalUserAchievement($globalUserAchievement); - - return; + return $c->maketext('Reduced scoring pentaly removed.'); } 1; diff --git a/lib/WeBWorK/AchievementItems/ReducedCred.pm b/lib/WeBWorK/AchievementItems/ReducedCred.pm index 297c275d7e..6d70a6f687 100644 --- a/lib/WeBWorK/AchievementItems/ReducedCred.pm +++ b/lib/WeBWorK/AchievementItems/ReducedCred.pm @@ -19,9 +19,8 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures; # Item to extend a close date by 24 hours for reduced credit # Reduced scoring needs to be enabled for this item to work. -use WeBWorK::Utils qw(x nfreeze_base64 thaw_base64); +use WeBWorK::Utils qw(x); use WeBWorK::Utils::DateTime qw(after between); -use WeBWorK::Utils::Sets qw(format_set_name_display); use constant ONE_DAY => 86400; @@ -37,75 +36,73 @@ sub new ($class) { }, $class; } -sub print_form ($self, $sets, $setProblemIds, $c) { - my @openSets; - - for my $i (0 .. $#$sets) { - push(@openSets, [ format_set_name_display($sets->[$i]->set_id) => $sets->[$i]->set_id ]) - if (between($sets->[$i]->open_date, $sets->[$i]->due_date + ONE_DAY) - && $sets->[$i]->assignment_type eq 'default'); - } +sub can_use ($self, $set, $records) { + return $set->assignment_type eq 'default' && between($set->open_date, $set->due_date + ONE_DAY); +} - return unless @openSets; +sub print_form ($self, $set, $records, $c) { + my $ce = $c->ce; - return $c->c( - $c->tag('p', $c->maketext('Choose the set which you would like to enable partial credit for.')), - WeBWorK::AchievementItems::form_popup_menu_row( - $c, - id => 'red_set_id', - label_text => $c->maketext('Set Name'), - values => \@openSets, - menu_attr => { dir => 'ltr' } + return $c->tag( + 'p', + $c->maketext( + q{This item won't work unless your instructor enables the reduced scoring feature. } + . 'Let your instructor know that you received this message.' ) - )->join(''); + ) unless $ce->{pg}{ansEvalDefaults}{enableReducedScoring}; + + my $randomization_statement = after($set->due_date) ? $c->maketext('All problems will be rerandomized.') : ''; + return $c->tag( + 'p', + $c->maketext( + 'Extend the close date of this assignment to [_1] (an additional 24 hours). Any submissions during ' + . 'this additional time will be reducend and are worth [_2]% of their full value. [_3]', + $c->formatDateTime($set->due_date + ONE_DAY, $ce->{studentDateDisplayFormat}), + 100 * $ce->{pg}{ansEvalDefaults}{reducedScoringValue}, + $randomization_statement + ) + ); } -sub use_item ($self, $userName, $c) { - my $db = $c->db; +sub use_item ($self, $set, $records, $c) { my $ce = $c->ce; + my $db = $c->db; - # Validate data - - return q{This item won't work unless your instructor enables the reduced scoring feature. } - . 'Let your instructor know that you received this message.' - unless $ce->{pg}{ansEvalDefaults}{reducedScoringPeriod}; - - my $globalUserAchievement = $db->getGlobalUserAchievement($userName); - return "No achievement data?!?!?!" unless $globalUserAchievement->frozen_hash; - - my $globalData = thaw_base64($globalUserAchievement->frozen_hash); - return "You are $self->{id} trying to use an item you don't have" unless $globalData->{ $self->{id} }; - - my $setID = $c->param('red_set_id'); - return "You need to input a Set Name" unless defined $setID; + # Still need to double check reduced scoring is enabled. + return '' unless $ce->{pg}{ansEvalDefaults}{enableReducedScoring}; - my $set = $db->getMergedSet($userName, $setID); - my $userSet = $db->getUserSet($userName, $setID); - return "Couldn't find that set!" unless $set && $userSet; + my $userSet = $db->getUserSet($set->user_id, $set->set_id); # Change the seed for all of the problems if the set is currently closed. if (after($set->due_date)) { - for my $problem ($db->getUserProblemsWhere({ user_id => $userName, set_id => $setID })) { - $problem->problem_seed($problem->problem_seed % 2**31 + 1); - $db->putUserProblem($problem); + my @userProblems = + $db->getUserProblemsWhere({ user_id => $set->user_id, set_id => $set->set_id }, 'problem_id'); + for my $n (0 .. $#userProblems) { + $userProblems[$n]->problem_seed($userProblems[$n]->problem_seed % 2**31 + 1); + $records->[$n]->problem_seed($userProblems[$n]->problem_seed); + $db->putUserProblem($userProblems[$n]); } } # Either there is already a valid reduced scoring date, or set the reduced scoring date to the close date. - $userSet->reduced_scoring_date($set->due_date) - unless ($set->reduced_scoring_date && ($set->reduced_scoring_date < $set->due_date)); + unless ($set->reduced_scoring_date && $set->reduced_scoring_date < $set->due_date) { + $set->reduced_scoring_date($set->due_date); + $userSet->reduced_scoring_date($set->reduced_scoring_date); + } + $set->enable_reduced_scoring(1); $userSet->enable_reduced_scoring(1); # Add time to the close date - $userSet->due_date($set->due_date + ONE_DAY); + $set->due_date($set->due_date + ONE_DAY); + $userSet->due_date($set->due_date); # This may require also extending the answer date. - $userSet->answer_date($userSet->due_date) if ($userSet->due_date > $set->answer_date); + if ($set->due_date > $set->answer_date) { + $set->answer_date($set->due_date); + $userSet->answer_date($set->answer_date); + } $db->putUserSet($userSet); - $globalData->{ $self->{id} }--; - $globalUserAchievement->frozen_hash(nfreeze_base64($globalData)); - $db->putGlobalUserAchievement($globalUserAchievement); - - return; + return $c->maketext('Close date changed by 24 hours to [_1].', + $c->formatDateTime($set->due_date, $ce->{studentDateDisplayFormat})); } 1; diff --git a/lib/WeBWorK/AchievementItems/ResetIncorrectAttempts.pm b/lib/WeBWorK/AchievementItems/ResetIncorrectAttempts.pm index 6462fbae9a..df919aaaca 100644 --- a/lib/WeBWorK/AchievementItems/ResetIncorrectAttempts.pm +++ b/lib/WeBWorK/AchievementItems/ResetIncorrectAttempts.pm @@ -18,11 +18,8 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures; # Item to reset number of incorrect attempts. -use Mojo::JSON qw(encode_json); - -use WeBWorK::Utils qw(x nfreeze_base64 thaw_base64); +use WeBWorK::Utils qw(x); use WeBWorK::Utils::DateTime qw(between); -use WeBWorK::Utils::Sets qw(format_set_name_display); sub new ($class) { return bless { @@ -32,85 +29,56 @@ sub new ($class) { }, $class; } -sub print_form ($self, $sets, $setProblemIds, $c) { - # Construct a dropdown with open sets and another with problems. - # Javascript ensures the appropriate problems are shown for the selected set. - - my (@openSets, @initialProblemIDs); - - for my $i (0 .. $#$sets) { - if (between($sets->[$i]->open_date, $sets->[$i]->due_date) - && $sets->[$i]->assignment_type eq 'default' - && @{ $setProblemIds->{ $sets->[$i]->set_id } }) - { - push( - @openSets, - [ - format_set_name_display($sets->[$i]->set_id) => $sets->[$i]->set_id, - data => { problem_ids => encode_json($setProblemIds->{ $sets->[$i]->set_id }) } - ] - ); - @initialProblemIDs = @{ $setProblemIds->{ $sets->[$i]->set_id } } unless @initialProblemIDs; - } - } - - return unless @openSets; +sub can_use ($self, $set, $records) { + return 0 + unless $set->assignment_type eq 'default' + && between($set->open_date, $set->due_date); - return $c->c( - $c->tag( - 'p', - $c->maketext( - 'Please choose the set name and problem number of the question which ' - . 'should have its incorrect attempt count reset.' - ) - ), - WeBWorK::AchievementItems::form_popup_menu_row( - $c, - id => 'ria_set_id', - label_text => $c->maketext('Set Name'), - values => \@openSets, - menu_attr => { dir => 'ltr', data => { problems => 'ria_problem_id' } } - ), - WeBWorK::AchievementItems::form_popup_menu_row( - $c, - id => 'ria_problem_id', - label_text => $c->maketext('Problem Number'), - values => \@initialProblemIDs, - menu_container_attr => { class => 'col-3' } - ) - )->join(''); + $self->{usableProblems} = [ grep { $_->max_attempts > 0 && $_->num_incorrect > 0 && $_->status < 1 } @$records ]; + return @{ $self->{usableProblems} } ? 1 : 0; } -sub use_item ($self, $userName, $c) { - my $db = $c->db; - my $ce = $c->ce; - - # Validate data - - my $globalUserAchievement = $db->getGlobalUserAchievement($userName); - return 'No achievement data?!?!?!' unless $globalUserAchievement->frozen_hash; - - my $globalData = thaw_base64($globalUserAchievement->frozen_hash); - return "You are $self->{id} trying to use an item you don't have" unless $globalData->{ $self->{id} }; - - my $setID = $c->param('ria_set_id'); - return 'You need to input a Set Name' unless defined $setID; +sub print_form ($self, $set, $records, $c) { + return WeBWorK::AchievementItems::form_popup_menu_row( + $c, + id => 'reset_attempts_problem_id', + label_text => $c->maketext('Problem Number'), + first_item => $c->maketext('Choose problem to reset incorrect attempts.'), + values => [ + map { [ + $c->maketext('Problem [_1] ([_2] of [_3] used)', + $_->problem_id, $_->num_incorrect, $_->max_attempts) => $_->problem_id + ] } @{ $self->{usableProblems} } + ], + ); +} - my $problemID = $c->param('ria_problem_id'); - return 'You need to input a Problem Number' unless $problemID; +# use_item is called after print_form returns a non-empty form. +# So we can assume that $set and $records have already been validated. +sub use_item ($self, $set, $records, $c) { + my $problemID = $c->param('reset_attempts_problem_id'); + unless ($problemID) { + $c->addbadmessage($c->maketext('Select problem to reset with the [_1].', $self->name)); + return ''; + } - my $problem = $db->getUserProblem($userName, $setID, $problemID); - return 'There was an error accessing that problem.' unless $problem; + my $problem; + for (@$records) { + if ($_->problem_id == $problemID) { + $problem = $_; + last; + } + } + return '' unless $problem; # Set the number of incorrect attempts to zero. + my $db = $c->db; + my $userProblem = $db->getUserProblem($problem->user_id, $problem->set_id, $problem->problem_id); $problem->num_incorrect(0); - $db->putUserProblem($problem); - - $globalData->{ $self->{id} }--; - $globalUserAchievement->frozen_hash(nfreeze_base64($globalData)); - $db->putGlobalUserAchievement($globalUserAchievement); + $userProblem->num_incorrect(0); + $db->putUserProblem($userProblem); - return; + return $c->maketext('Reset the number of attempts on problem [_1].', $problemID); } 1; diff --git a/lib/WeBWorK/AchievementItems/ResurrectGW.pm b/lib/WeBWorK/AchievementItems/ResurrectGW.pm index eb7286f3ec..6d47348914 100644 --- a/lib/WeBWorK/AchievementItems/ResurrectGW.pm +++ b/lib/WeBWorK/AchievementItems/ResurrectGW.pm @@ -18,9 +18,10 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures; # Item to extend the due date on a gateway -use WeBWorK::Utils qw(x nfreeze_base64 thaw_base64); -use WeBWorK::Utils::DateTime qw(after); -use WeBWorK::Utils::Sets qw(format_set_name_display); +use WeBWorK::Utils qw(x); +use WeBWorK::Utils::DateTime qw(between); + +use constant ONE_DAY => 86400; sub new ($class) { return bless { @@ -33,63 +34,38 @@ sub new ($class) { }, $class; } -sub print_form ($self, $sets, $setProblemIds, $c) { - my $db = $c->db; - - my @closed_gateway_sets; - - # Find the template sets of gateway quizzes. - for my $set (@$sets) { - push(@closed_gateway_sets, [ format_set_name_display($set->set_id) => $set->set_id ]) - if $set->assignment_type =~ /gateway/ - && $set->set_id !~ /,v\d+$/ - && (after($set->due_date) - || ($set->reduced_scoring_date && after($set->reduced_scoring_date))); - } - - return unless @closed_gateway_sets; +sub can_use($self, $set, $records) { + return $set->assignment_type =~ /gateway/ && between($set->due_date, $set->due_date + ONE_DAY); + # TODO: Check if a new version can be created, and only allow using this reward in that case. +} - return $c->c( - $c->tag('p', $c->maketext('Resurrect which test?')), - WeBWorK::AchievementItems::form_popup_menu_row( - $c, - id => 'resgw_gw_id', - label_text => $c->maketext('Test Name'), - values => \@closed_gateway_sets, - menu_attr => { dir => 'ltr' } +sub print_form ($self, $set, $records, $c) { + return $c->tag( + 'p', + $c->maketext( + 'Reopen this test for the next 24 hours. This item does not allow you to take any additional ' + . 'versions of the test.' ) - )->join(''); + ); } -sub use_item ($self, $userName, $c) { - my $db = $c->db; - my $ce = $c->ce; - - # Validate data - - my $globalUserAchievement = $db->getGlobalUserAchievement($userName); - return 'No achievement data?!?!?!' unless $globalUserAchievement->frozen_hash; - - my $globalData = thaw_base64($globalUserAchievement->frozen_hash); - return "You are $self->{id} trying to use an item you don't have" unless $globalData->{ $self->{id} }; - - my $setID = $c->param('resgw_gw_id'); - return 'You need to input a Test Name' unless defined $setID; - - my $set = $db->getUserSet($userName, $setID); - return q{Couldn't find that set!} unless $set; +sub use_item ($self, $set, $records, $c) { + my $db = $c->db; + my $userSet = $db->getUserSet($set->user_id, $set->set_id); # Add time to the reduced scoring date, due date, and answer date. - $set->reduced_scoring_date(time + 86400) if defined($set->reduced_scoring_date()) && $set->reduced_scoring_date(); - $set->due_date(time + 86400); - $set->answer_date(time + 86400); - $db->putUserSet($set); - - $globalData->{ $self->{id} }--; - $globalUserAchievement->frozen_hash(nfreeze_base64($globalData)); - $db->putGlobalUserAchievement($globalUserAchievement); - - return; + if ($set->reduced_scoring_date) { + $set->reduced_scoring_date($set->reduced_scoring_date + ONE_DAY); + $userSet->reduced_scoring_date($set->reduced_scoring_date); + } + $set->due_date($set->due_date + ONE_DAY); + $userSet->due_date($set->due_date); + $set->answer_date($set->answer_date + ONE_DAY); + $userSet->answer_date($set->answer_date); + $db->putUserSet($userSet); + + return $c->maketext('Close date of this test extended 24 hours to [_1].', + $c->formatDateTime($set->due_date, $c->ce->{studentDateDisplayFormat})); } 1; diff --git a/lib/WeBWorK/AchievementItems/ResurrectHW.pm b/lib/WeBWorK/AchievementItems/ResurrectHW.pm index 323c66020d..63a0f76f6b 100644 --- a/lib/WeBWorK/AchievementItems/ResurrectHW.pm +++ b/lib/WeBWorK/AchievementItems/ResurrectHW.pm @@ -18,9 +18,10 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures; # Item to resurrect a homework for 24 hours -use WeBWorK::Utils qw(x nfreeze_base64 thaw_base64); -use WeBWorK::Utils::DateTime qw(after); -use WeBWorK::Utils::Sets qw(format_set_name_display); +use WeBWorK::Utils qw(x); +use WeBWorK::Utils::DateTime qw(between); + +use constant ONE_DAY => 86400; sub new ($class) { return bless { @@ -30,70 +31,49 @@ sub new ($class) { }, $class; } -sub print_form ($self, $sets, $setProblemIds, $c) { - # List all of the sets that are closed or past their reduced scoring date. - - my @closedSets; - - for my $i (0 .. $#$sets) { - push(@closedSets, [ format_set_name_display($sets->[$i]->set_id) => $sets->[$i]->set_id ]) - if $sets->[$i]->assignment_type eq 'default' - && (after($sets->[$i]->due_date) - || ($sets->[$i]->reduced_scoring_date && after($sets->[$i]->reduced_scoring_date))); - } - - return unless @closedSets; - - return $c->c( - $c->tag('p', $c->maketext('Choose the set which you would like to resurrect.')), - WeBWorK::AchievementItems::form_popup_menu_row( - $c, - id => 'res_set_id', - label_text => $c->maketext('Set Name'), - values => \@closedSets, - menu_attr => { dir => 'ltr' } - ) - )->join(''); +sub can_use($self, $set, $records) { + return $set->assignment_type eq 'default' && between($set->due_date, $set->due_date + ONE_DAY); } -sub use_item ($self, $userName, $c) { - my $db = $c->db; - my $ce = $c->ce; - - # Validate data - - my $globalUserAchievement = $db->getGlobalUserAchievement($userName); - return 'No achievement data?!?!?!' unless $globalUserAchievement->frozen_hash; - - my $globalData = thaw_base64($globalUserAchievement->frozen_hash); - return "You are $self->{id} trying to use an item you don't have" unless $globalData->{ $self->{id} }; - - my $setID = $c->param('res_set_id'); - return 'You need to input a Set Name' unless defined $setID; - - my $set = $db->getUserSet($userName, $setID); - return q{Couldn't find that set!} unless $set; - - # Set a new reduced scoring date, close date, and answer date for the student. - $set->reduced_scoring_date(time + 86400); - $set->due_date(time + 86400); - $set->answer_date(time + 86400); - $db->putUserSet($set); - - my @probIDs = $db->listUserProblems($userName, $setID); +sub print_form ($self, $set, $records, $c) { + return $c->tag('p', + $c->maketext('Reopen this homework assignment for the next 24 hours. All problems will be rerandomized.')); +} - # Change the seed for all of the problems in the set. - for my $probID (@probIDs) { - my $problem = $db->getUserProblem($userName, $setID, $probID); - $problem->problem_seed($problem->problem_seed + 100); - $db->putUserProblem($problem); +sub use_item ($self, $set, $records, $c) { + my $db = $c->db; + my $userSet = $db->getUserSet($set->user_id, $set->set_id); + + # Change the seed for all of the problems if the set is currently closed. + if (after($set->due_date)) { + my @userProblems = + $db->getUserProblemsWhere({ user_id => $set->user_id, set_id => $set->set_id }, 'problem_id'); + for my $n (0 .. $#userProblems) { + $userProblems[$n]->problem_seed($userProblems[$n]->problem_seed % 2**31 + 1); + $records->[$n]->problem_seed($userProblems[$n]->problem_seed); + $db->putUserProblem($userProblems[$n]); + } } - $globalData->{ $self->{id} }--; - $globalUserAchievement->frozen_hash(nfreeze_base64($globalData)); - $db->putGlobalUserAchievement($globalUserAchievement); + # Add time to the reduced scoring date if it was defined in the first place + if ($set->reduced_scoring_date) { + $set->reduced_scoring_date($set->reduced_scoring_date + ONE_DAY); + $userSet->reduced_scoring_date($set->reduced_scoring_date); + } + # Add time to the close date + $set->due_date($set->due_date + ONE_DAY); + $userSet->due_date($set->due_date); + # This may require also extending the answer date. + if ($set->due_date > $set->answer_date) { + $set->answer_date($set->due_date); + $userSet->answer_date($set->answer_date); + } + $db->putUserSet($userSet); - return; + return $c->maketext( + 'Closing date of this assignment extended by 24 hours to [_1].', + $c->formatDateTime($set->due_date, $c->ce->{studentDateDisplayFormat}) + ); } 1; diff --git a/lib/WeBWorK/AchievementItems/SuperExtendDueDate.pm b/lib/WeBWorK/AchievementItems/SuperExtendDueDate.pm index ccf960321d..9e299445b8 100644 --- a/lib/WeBWorK/AchievementItems/SuperExtendDueDate.pm +++ b/lib/WeBWorK/AchievementItems/SuperExtendDueDate.pm @@ -18,9 +18,8 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures; # Item to extend a close date by 48 hours. -use WeBWorK::Utils qw(x nfreeze_base64 thaw_base64); +use WeBWorK::Utils qw(x); use WeBWorK::Utils::DateTime qw(after between); -use WeBWorK::Utils::Sets qw(format_set_name_display); use constant TWO_DAYS => 172800; @@ -35,69 +34,56 @@ sub new ($class) { }, $class; } -sub print_form ($self, $sets, $setProblemIds, $c) { - my @openSets; - - for my $i (0 .. $#$sets) { - push(@openSets, [ format_set_name_display($sets->[$i]->set_id) => $sets->[$i]->set_id ]) - if (between($sets->[$i]->open_date, $sets->[$i]->due_date + TWO_DAYS) - && $sets->[$i]->assignment_type eq 'default'); - } - - return unless @openSets; +sub can_use ($self, $set, $records) { + return $set->assignment_type eq 'default' && between($set->open_date, $set->due_date + TWO_DAYS); +} - return $c->c( - $c->tag('p', $c->maketext('Choose the set whose close date you would like to extend.')), - WeBWorK::AchievementItems::form_popup_menu_row( - $c, - id => 'super_ext_set_id', - label_text => $c->maketext('Set Name'), - values => \@openSets, - menu_attr => { dir => 'ltr' } +sub print_form ($self, $set, $records, $c) { + my $randomization_statement = after($set->due_date) ? $c->maketext('All problems will be rerandomized.') : ''; + return $c->tag( + 'p', + $c->maketext( + 'Extend the close date of this assignment to [_1] (an additional 48 hours). [_2]', + $c->formatDateTime($set->due_date + TWO_DAYS, $c->ce->{studentDateDisplayFormat}), + $randomization_statement ) - )->join(''); + ); } -sub use_item ($self, $userName, $c) { - my $db = $c->db; - my $ce = $c->ce; - - # Validate data - - my $globalUserAchievement = $db->getGlobalUserAchievement($userName); - return 'No achievement data?!?!?!' unless $globalUserAchievement->frozen_hash; - - my $globalData = thaw_base64($globalUserAchievement->frozen_hash); - return "You are $self->{id} trying to use an item you don't have" unless $globalData->{ $self->{id} }; - - my $setID = $c->param('super_ext_set_id'); - return 'You need to input a Set Name' unless defined $setID; - - my $set = $db->getMergedSet($userName, $setID); - my $userSet = $db->getUserSet($userName, $setID); - return q{Couldn't find that set!} unless $set && $userSet; +sub use_item ($self, $set, $records, $c) { + my $db = $c->db; + my $userSet = $db->getUserSet($set->user_id, $set->set_id); # Change the seed for all of the problems if the set is currently closed. if (after($set->due_date)) { - for my $problem ($db->getUserProblemsWhere({ user_id => $userName, set_id => $setID })) { - $problem->problem_seed($problem->problem_seed % 2**31 + 1); - $db->putUserProblem($problem); + my @userProblems = + $db->getUserProblemsWhere({ user_id => $set->user_id, set_id => $set->set_id }, 'problem_id'); + for my $n (0 .. $#userProblems) { + $userProblems[$n]->problem_seed($userProblems[$n]->problem_seed % 2**31 + 1); + $records->[$n]->problem_seed($userProblems[$n]->problem_seed); + $db->putUserProblem($userProblems[$n]); } } # Add time to the reduced scoring date if it was defined in the first place - $userSet->reduced_scoring_date($set->reduced_scoring_date + TWO_DAYS) if $set->reduced_scoring_date; + if ($set->reduced_scoring_date) { + $set->reduced_scoring_date($set->reduced_scoring_date + TWO_DAYS); + $userSet->reduced_scoring_date($set->reduced_scoring_date); + } # Add time to the close date - $userSet->due_date($set->due_date + TWO_DAYS); + $set->due_date($set->due_date + TWO_DAYS); + $userSet->due_date($set->due_date); # This may require also extending the answer date. - $userSet->answer_date($userSet->due_date) if ($userSet->due_date > $set->answer_date); + if ($set->due_date > $set->answer_date) { + $set->answer_date($set->due_date); + $userSet->answer_date($set->answer_date); + } $db->putUserSet($userSet); - $globalData->{ $self->{id} }--; - $globalUserAchievement->frozen_hash(nfreeze_base64($globalData)); - $db->putGlobalUserAchievement($globalUserAchievement); - - return; + return $c->maketext( + 'Closing date of this assignment extended by 48 hours too [_1].', + $c->formatDateTime($set->due_date, $c->ce->{studentDateDisplayFormat}) + ); } 1; diff --git a/lib/WeBWorK/AchievementItems/SuperExtendReducedDate.pm b/lib/WeBWorK/AchievementItems/SuperExtendReducedDate.pm index 5996b58984..3071340d11 100644 --- a/lib/WeBWorK/AchievementItems/SuperExtendReducedDate.pm +++ b/lib/WeBWorK/AchievementItems/SuperExtendReducedDate.pm @@ -18,9 +18,8 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures; # Item to extend a close date by 48 hours. -use WeBWorK::Utils qw(x nfreeze_base64 thaw_base64); +use WeBWorK::Utils qw(x); use WeBWorK::Utils::DateTime qw(between); -use WeBWorK::Utils::Sets qw(format_set_name_display); use constant TWO_DAYS => 172800; @@ -36,75 +35,50 @@ sub new ($class) { }, $class; } -sub print_form ($self, $sets, $setProblemIds, $c) { - my @openSets; +sub can_use ($self, $set, $records) { + return 0 + unless $set->assignment_type eq 'default' + && $set->enable_reduced_scoring + && $set->reduced_scoring_date + && $set->reduced_scoring_date < $set->due_date; - # Nothing to do if reduced scoring is not enabled. - return unless $c->{ce}->{pg}{ansEvalDefaults}{enableReducedScoring}; - - for my $i (0 .. $#$sets) { - my $new_date = 0; - if ($sets->[$i]->reduced_scoring_date() && $sets->[$i]->reduced_scoring_date() < $sets->[$i]->due_date()) { - $new_date = $sets->[$i]->reduced_scoring_date() + TWO_DAYS; - $new_date = $sets->[$i]->due_date() if $sets->[$i]->due_date() < $new_date; - } - push(@openSets, [ format_set_name_display($sets->[$i]->set_id) => $sets->[$i]->set_id ]) - if ($new_date - && between($sets->[$i]->open_date, $new_date) - && $sets->[$i]->assignment_type eq 'default' - && $sets->[$i]->enable_reduced_scoring); - } + $self->{new_date} = $set->reduced_scoring_date + TWO_DAYS; + $self->{new_date} = $set->due_date if $set->due_date < $self->{new_date}; + return between($set->open_date, $self->{new_date}); +} - return unless @openSets; +sub print_form ($self, $set, $records, $c) { + return $c->tag( + 'p', + $c->maketext( + q{This item won't work unless your instructor enables the reduced scoring feature. } + . 'Let your instructor know that you recieved this message.' + ) + ) unless $c->{ce}->{pg}{ansEvalDefaults}{enableReducedScoring}; - return $c->c( - $c->tag( - 'p', - $c->maketext('Choose the assignment whose reduced scoring date you would like to extend by 48 hours.') - ), - WeBWorK::AchievementItems::form_popup_menu_row( - $c, - id => 'super_ext_reduced_set_id', - label_text => $c->maketext('Assignment Name'), - values => \@openSets, - menu_attr => { dir => 'ltr' } + return $c->tag( + 'p', + $c->maketext( + 'Extend the reduced scoring date to [_1] (an additional 48 hours).', + $c->formatDateTime($self->{new_date}, $c->ce->{studentDateDisplayFormat}) ) - )->join(''); + ); } -sub use_item ($self, $userName, $c) { - my $db = $c->db; - my $ce = $c->ce; - - # Validate data - - # Nothing to do if reduced scoring is not enabled. - return 'Reduce scoring disabled.' unless $c->{ce}->{pg}{ansEvalDefaults}{enableReducedScoring}; +sub use_item ($self, $set, $records, $c) { + return '' unless $c->{ce}->{pg}{ansEvalDefaults}{enableReducedScoring}; - my $globalUserAchievement = $db->getGlobalUserAchievement($userName); - return 'No achievement data?!?!?!' unless $globalUserAchievement->frozen_hash; + my $db = $c->db; + my $userSet = $db->getUserSet($set->user_id, $set->set_id); - my $globalData = thaw_base64($globalUserAchievement->frozen_hash); - return "You are $self->{id} trying to use an item you don't have" unless $globalData->{ $self->{id} }; - - my $setID = $c->param('super_ext_reduced_set_id'); - return 'You need to input a Set Name' unless defined $setID; - - my $set = $db->getMergedSet($userName, $setID); - my $userSet = $db->getUserSet($userName, $setID); - return q{Couldn't find that set!} unless $set && $userSet; - - # Add time to the reduced scoring date, keeping in mind this cannot extend past the due date. - my $new_date = $set->reduced_scoring_date() + TWO_DAYS; - $new_date = $set->due_date() if $set->due_date() < $new_date; - $userSet->reduced_scoring_date($new_date); + $set->reduced_scoring_date($self->{new_date}); + $userSet->reduced_scoring_date($set->reduced_scoring_date); $db->putUserSet($userSet); - $globalData->{ $self->{id} }--; - $globalUserAchievement->frozen_hash(nfreeze_base64($globalData)); - $db->putGlobalUserAchievement($globalUserAchievement); - - return; + return $c->maketext( + 'Reduced scoring date of this assignment exted by 48 hours to [_1].', + $c->formatDateTime($self->{new_date}, $c->ce->{studentDateDisplayFormat}) + ); } 1; diff --git a/lib/WeBWorK/AchievementItems/Surprise.pm b/lib/WeBWorK/AchievementItems/Surprise.pm index 299634df28..37dbd04669 100644 --- a/lib/WeBWorK/AchievementItems/Surprise.pm +++ b/lib/WeBWorK/AchievementItems/Surprise.pm @@ -28,10 +28,11 @@ sub new ($class) { }, $class; } -sub print_form ($self, $sets, $setProblemIds, $c) { - # The form opens the file "suprise_message.txt" in the achievements - # folder and prints the contents of the file. +sub can_use ($self, $set, $records) { return 1; } +sub print_form ($self, $set, $records, $c) { + # The form opens the file "surprise_message.txt" in the achievements + # folder and prints the contents of the file. open my $MESSAGE, '<', "$c->{ce}{courseDirs}{achievements}/surprise_message.txt" or return $c->tag('p', $c->maketext(q{I couldn't find the file [ACHIEVEMENT_DIR]/surprise_message.txt!})); local $/ = undef; @@ -41,7 +42,7 @@ sub print_form ($self, $sets, $setProblemIds, $c) { return $c->tag('div', $c->b($message)); } -sub use_item ($self, $userName, $c) { +sub use_item ($self, $set, $records, $c) { # This doesn't do anything. } diff --git a/lib/WeBWorK/ContentGenerator/Achievements.pm b/lib/WeBWorK/ContentGenerator/Achievements.pm index 9d08fc3d78..613f509054 100644 --- a/lib/WeBWorK/ContentGenerator/Achievements.pm +++ b/lib/WeBWorK/ContentGenerator/Achievements.pm @@ -37,25 +37,8 @@ sub initialize ($c) { $c->{globalData} = $db->getGlobalUserAchievement($c->{studentName}); # Check to see if user items are enabled and if the user has achievement data. - if ($ce->{achievementItemsEnabled} && defined $c->{globalData}) { - my $itemsWithCounts = WeBWorK::AchievementItems::UserItems($c->{studentName}, $db, $ce); - $c->{achievementItems} = $itemsWithCounts; - - my $usedItem = $c->param('useditem'); - - # If the useditem parameter is defined then the student wanted to use an item, so lets do that by calling the - # appropriate item's use method and printing results. - if (defined $usedItem) { - my $error = $itemsWithCounts->[$usedItem][0]->use_item($c->{studentName}, $c); - if ($error) { - $c->addbadmessage($error); - } else { - if ($itemsWithCounts->[$usedItem][1] != 1) { --$itemsWithCounts->[$usedItem][1]; } - else { splice(@$itemsWithCounts, $usedItem, 1); } - $c->addgoodmessage($c->maketext('Reward used successfully!')); - } - } - } + $c->{achievementItems} = WeBWorK::AchievementItems::UserItems($c, $c->{studentName}, undef, undef) + if $ce->{achievementItemsEnabled} && defined $c->{globalData}; return; } @@ -88,35 +71,6 @@ sub getAchievementLevelData ($c) { ); } -sub getAchievementItemsData ($c) { - my $db = $c->db; - - my $userID = $c->{studentName}; - - my (@items, %itemCounts, @sets, %setProblemIds); - - if ($c->ce->{achievementItemsEnabled} && $c->{achievementItems}) { - # Remove count data so @items is structured as originally designed. - for my $item (@{ $c->{achievementItems} }) { - push(@items, $item->[0]); - $itemCounts{ $item->[0]->id } = $item->[1]; - } - - for my $set ($db->getMergedSets(map { [ $userID, $_ ] } $db->listUserSets($userID))) { - push(@sets, $set); - $setProblemIds{ $set->set_id } = [ map { $_->[2] } - $db->listUserProblemsWhere({ user_id => $userID, set_id => $set->set_id }, 'problem_id') ]; - } - } - - return ( - items => \@items, - itemCounts => \%itemCounts, - sets => \@sets, - setProblemIds => \%setProblemIds - ); -} - sub getAchievementsData ($c) { my $db = $c->db; my $ce = $c->ce; diff --git a/lib/WeBWorK/ContentGenerator/Problem.pm b/lib/WeBWorK/ContentGenerator/Problem.pm index f3512a3a0f..e699e8b396 100644 --- a/lib/WeBWorK/ContentGenerator/Problem.pm +++ b/lib/WeBWorK/ContentGenerator/Problem.pm @@ -38,6 +38,7 @@ use WeBWorK::AchievementEvaluator qw(checkForAchievements); use WeBWorK::DB::Utils qw(global2user fake_set fake_problem); use WeBWorK::Localize; use WeBWorK::AchievementEvaluator; +use WeBWorK::AchievementItems; # GET/POST Parameters for this module # diff --git a/lib/WeBWorK/ContentGenerator/ProblemSet.pm b/lib/WeBWorK/ContentGenerator/ProblemSet.pm index d54b813417..3e8e24b9f0 100644 --- a/lib/WeBWorK/ContentGenerator/ProblemSet.pm +++ b/lib/WeBWorK/ContentGenerator/ProblemSet.pm @@ -31,6 +31,7 @@ use WeBWorK::Utils::Rendering qw(renderPG); use WeBWorK::Utils::Sets qw(is_restricted grade_set format_set_name_display); use WeBWorK::DB::Utils qw(grok_versionID_from_vsetID_sql); use WeBWorK::Localize; +use WeBWorK::AchievementItems; async sub initialize ($c) { my $db = $c->db; @@ -58,6 +59,22 @@ async sub initialize ($c) { $c->{displayMode} = $user->displayMode || $ce->{pg}{options}{displayMode}; + # Import problem records for assignments or test version records for tests now. Then initialize all + # achievement item data to have access to the updated records if an achievement item was used. + if ($c->{set}->assignment_type =~ /gateway/) { + $c->{setVersions} = [ + $db->getMergedSetVersionsWhere( + { user_id => $eUserID, set_id => { like => $c->{set}->set_id . ',v%' } }, + \grok_versionID_from_vsetID_sql($db->{set_version_merged}->sql->_quote('set_id')) + ) + ]; + $c->{achievementItems} = WeBWorK::AchievementItems::UserItems($c, $eUserID, $c->{set}, $c->{setVersions}); + } else { + $c->{setProblems} = + [ $db->getMergedProblemsWhere({ user_id => $eUserID, set_id => $c->{set}->set_id }, 'problem_id') ]; + $c->{achievementItems} = WeBWorK::AchievementItems::UserItems($c, $eUserID, $c->{set}, $c->{setProblems}); + } + # Display status messages. $c->addmessage($c->tag('p', $c->b($c->authen->flash('status_message')))) if $c->authen->flash('status_message'); @@ -168,16 +185,7 @@ sub info { # This is called by the ContentGenerator/ProblemSet/body template for a regular homework set. # It lists the problems in the set. sub problem_list ($c) { - my $authz = $c->authz; - my $db = $c->db; - - my $setID = $c->stash('setID'); - my $user = $c->param('user'); - - my @problems = - $db->getMergedProblemsWhere({ user_id => $c->param('effectiveUser'), set_id => $setID }, 'problem_id'); - - return $c->include('ContentGenerator/ProblemSet/problem_list', problems => \@problems); + return $c->include('ContentGenerator/ProblemSet/problem_list', problems => $c->{setProblems}); } # This is called by the ContentGenerator/ProblemSet/body template for a test. @@ -204,12 +212,7 @@ sub gateway_body ($c) { my $timeInterval = $set->time_interval || 0; my @versionData; - my @setVersions = $db->getMergedSetVersionsWhere( - { user_id => $effectiveUser, set_id => { like => $set->set_id . ',v%' } }, - \grok_versionID_from_vsetID_sql($db->{set_version_merged}->sql->_quote('set_id')) - ); - - for my $verSet (@setVersions) { + for my $verSet (@{ $c->{setVersions} }) { # Count number of versions in current timeInterval if (!$timeInterval || $verSet->version_creation_time > ($timeNow - $timeInterval)) { ++$currentVersions; @@ -323,7 +326,7 @@ sub gateway_body ($c) { timeInterval => $timeInterval, timeNow => $timeNow, lastTime => $lastTime, - setVersions => \@setVersions, + setVersions => $c->{setVersions}, versionData => \@versionData, currentVersions => $currentVersions ); diff --git a/templates/ContentGenerator/Achievements.html.ep b/templates/ContentGenerator/Achievements.html.ep index 8124e7def9..15eacb3867 100644 --- a/templates/ContentGenerator/Achievements.html.ep +++ b/templates/ContentGenerator/Achievements.html.ep @@ -4,10 +4,6 @@ <%= stylesheet getAssetURL($ce, 'js/Achievements/achievements.css') =%> % end % -% content_for js => begin - <%= javascript getAssetURL($ce, 'js/AchievementItems/achievementitems.js'), defer => undef =%> -% end -% % # Exit if there is no global achievement data for this user. % unless (defined $c->{globalData}) {

<%= maketext(q{You don't have any Achievement data associated to you!}) =%>

@@ -15,5 +11,5 @@ % } % <%= include 'ContentGenerator/Achievements/cheevobigbox', $c->getAchievementLevelData =%> -<%= include 'ContentGenerator/Achievements/achievement_items', $c->getAchievementItemsData =%> +<%= include 'ContentGenerator/Achievements/achievement_items' =%> <%= include 'ContentGenerator/Achievements/achievement_badges', $c->getAchievementsData =%> diff --git a/templates/ContentGenerator/Achievements/achievement_items.html.ep b/templates/ContentGenerator/Achievements/achievement_items.html.ep index eeada97d42..a490df2d3d 100644 --- a/templates/ContentGenerator/Achievements/achievement_items.html.ep +++ b/templates/ContentGenerator/Achievements/achievement_items.html.ep @@ -1,58 +1,21 @@ -% last unless $ce->{achievementItemsEnabled} && $c->{achievementItems}; +% last unless $ce->{achievementItemsEnabled}; % % # Show any items the user may have.

<%= maketext('Rewards') %>

-% if (@$items) { +% if ($c->{achievementItems} && @{ $c->{achievementItems} }) { +

+ <%= maketext( + 'Achievement rewards can be used to modify assignments. To use a reward, go to the assignment you ' + . 'wish to apply the reward to, then click the "Use Achievement Reward" button. You currently have ' + . 'access to the following rewards:' + ) %> +

- % my $itemNumber = 0; - % for my $item (@$items) { - % # Show each item's name, description, and reusability -
- <%= maketext($item->name) %> + % for my $item (@{ $c->{achievementItems} }) { +
+ <%= $item->remaining_title($c) %>
-
-

<%= maketext($item->description) %>

- % my $form = $item->print_form($sets, $setProblemIds, $c); - % # Print a modal popup for each item which contains the form necessary to get the data to use the item. - % my $button_text; - % if ($itemCounts->{ $item->id } > 1) { - % $button_text = maketext('[_1] ([_2] remaining)', maketext($item->name), $itemCounts->{ $item->id }); - % } elsif ($itemCounts->{ $item->id } < 0) { - % $button_text = maketext('[_1] (unlimited reusability)', maketext($item->name)); - % } else { - % $button_text = maketext($item->name); - % } - <%= link_to maketext('Use [_1]', $button_text) => '#modal_' . $item->id, - role => 'button', - class => 'btn btn-secondary' . ($form ? '' : ' disabled'), - id => 'popup_' . $item->id, - $form ? (data => { bs_toggle => 'modal' }) : () =%> - % if ($form) { - - % } -
- % $itemNumber++; +
<%= maketext($item->description) %>
% }
% } else { diff --git a/templates/ContentGenerator/ProblemSet.html.ep b/templates/ContentGenerator/ProblemSet.html.ep index 88aa48640d..61b4480ee2 100644 --- a/templates/ContentGenerator/ProblemSet.html.ep +++ b/templates/ContentGenerator/ProblemSet.html.ep @@ -25,6 +25,7 @@ % <%= $set->assignment_type =~ /gateway/ ? $c->gateway_body : $c->problem_list =%> % +<%= include 'ContentGenerator/ProblemSet/use_achievement_items' =%>
<%= $c->feedbackMacro( route => current_route, diff --git a/templates/ContentGenerator/ProblemSet/use_achievement_items.html.ep b/templates/ContentGenerator/ProblemSet/use_achievement_items.html.ep new file mode 100644 index 0000000000..4d5743344a --- /dev/null +++ b/templates/ContentGenerator/ProblemSet/use_achievement_items.html.ep @@ -0,0 +1,53 @@ +% last unless $c->ce->{achievementsEnabled}; +% +% my $achievementItems = $c->{achievementItems}; +% if ($achievementItems && @$achievementItems) { +
+ <%= link_to maketext('Use Achievement Reward') => '#modal_rewards', + role => 'button', + class => 'btn btn-primary', + id => 'popup_rewards', + data => { bs_toggle => 'modal' } + =%> + +
+% } elsif (param('user') ne param('effectiveUser')) { +
+ + + +
+% } else { +
+ + + +
+% } +