|
16 | 16 | package WeBWorK::AchievementItems; |
17 | 17 | use Mojo::Base -signatures; |
18 | 18 |
|
19 | | -use WeBWorK::Utils qw(thaw_base64); |
| 19 | +use WeBWorK::Utils qw(nfreeze_base64 thaw_base64); |
20 | 20 |
|
21 | 21 | # List of available achievement items. Make sure to add any new items to this list. Furthermore, the elements in this |
22 | 22 | # list have to match the class name of the achievement item classes loaded below. |
@@ -44,65 +44,125 @@ use constant ITEMS => [ qw( |
44 | 44 |
|
45 | 45 | =head2 NAME |
46 | 46 |
|
47 | | -This is the base class for achievement times. This defines an interface for all of the achievement items. Each |
48 | | -achievement item will have a name, a description, a method for creating an html form to get its inputs called print_form |
49 | | -and a method for applying those inputs called use_item. |
| 47 | +This is the base class for achievement times. This defines an interface for all of the achievement items. |
| 48 | +Each achievement item will have an id, a name, a description, and the three methods can_use (checks if the |
| 49 | +item can be used on the given set), print_form (prints the form to use the item), and use_item. |
50 | 50 |
|
51 | 51 | Note: the ID has to match the name of the class. |
52 | 52 |
|
| 53 | +The global method UserItems returns an array of all achievement items available to the given user. If no |
| 54 | +set is included, a list of all earned achievement items is return. If provided a set and corresponding problem |
| 55 | +or test version records, a list of items usable on the current set and records paired with an input form to |
| 56 | +use the item is returned. This method will also process any posts to use the achievement item. |
| 57 | +
|
53 | 58 | =cut |
54 | 59 |
|
55 | | -sub id ($c) { return $c->{id}; } |
56 | | -sub name ($c) { return $c->{name}; } |
57 | | -sub description ($c) { return $c->{description}; } |
| 60 | +sub id ($self) { return $self->{id}; } |
| 61 | +sub name ($self) { return $self->{name}; } |
| 62 | +sub count ($self) { return $self->{count}; } |
| 63 | +sub description ($self) { return $self->{description}; } |
| 64 | + |
| 65 | +# Method to find all achievement items available to the given user. |
| 66 | +# If $set is undefined return an array reference of all earned items. |
| 67 | +# If $set is defined, return an array reference of the usable items |
| 68 | +# for the given $set and problem or test versions records. Each item |
| 69 | +# is paired with its input form to use the item. |
| 70 | +sub UserItems ($c, $userName, $set, $records) { |
| 71 | + my $db = $c->db; |
58 | 72 |
|
59 | | -# This is a global method that returns all of the provided users items. |
60 | | -sub UserItems ($userName, $db, $ce) { |
61 | | - # return unless the user has global achievement data |
62 | | - my $globalUserAchievement = $db->getGlobalUserAchievement($userName); |
| 73 | + # When acting as another user, achievement items can be listed but not used. |
| 74 | + return if $set && $userName ne $c->param('user'); |
63 | 75 |
|
64 | | - return unless ($globalUserAchievement->frozen_hash); |
| 76 | + # Return unless the user has global achievement data. |
| 77 | + my $globalUserAchievement = $c->{globalData} // $db->getGlobalUserAchievement($userName); |
| 78 | + return unless $globalUserAchievement && $globalUserAchievement->frozen_hash; |
65 | 79 |
|
66 | | - my $globalData = thaw_base64($globalUserAchievement->frozen_hash); |
| 80 | + my $globalData = thaw_base64($globalUserAchievement->frozen_hash); |
| 81 | + my $use_item_id = $c->param('use_achievement_item_id') // ''; |
67 | 82 | my @items; |
68 | 83 |
|
69 | | - # Get a new item object for each type of item. |
70 | 84 | for my $item (@{ +ITEMS }) { |
71 | | - push(@items, [ "WeBWorK::AchievementItems::$item"->new, $globalData->{$item} ]) |
72 | | - if ($globalData->{$item}); |
| 85 | + next unless $globalData->{$item}; |
| 86 | + my $achievementItem = "WeBWorK::AchievementItems::$item"->new; |
| 87 | + $achievementItem->{count} = $globalData->{$item}; |
| 88 | + |
| 89 | + # Return list of achievements items if $set is not defined. |
| 90 | + unless ($set) { |
| 91 | + push(@items, $achievementItem); |
| 92 | + next; |
| 93 | + } |
| 94 | + next unless $achievementItem->can_use($set, $records); |
| 95 | + |
| 96 | + # Use the achievement item. |
| 97 | + if ($use_item_id eq $item) { |
| 98 | + my $message = $achievementItem->use_item($set, $records, $c); |
| 99 | + if ($message) { |
| 100 | + $globalData->{$item}--; |
| 101 | + $achievementItem->{count}--; |
| 102 | + $globalUserAchievement->frozen_hash(nfreeze_base64($globalData)); |
| 103 | + $db->putGlobalUserAchievement($globalUserAchievement); |
| 104 | + $c->addgoodmessage($c->maketext('[_1] succesffuly used. [_2]', $achievementItem->name, $message)); |
| 105 | + } |
| 106 | + } |
| 107 | + |
| 108 | + push(@items, [ $achievementItem, $use_item_id ? '' : $achievementItem->print_form($set, $records, $c) ]); |
73 | 109 | } |
74 | 110 |
|
| 111 | + # If an achievement item has been used, double check if the achievement items can still be used |
| 112 | + # since the item count could now be zero or an achievement item has altered the set/records. |
| 113 | + # Input forms are also built here to account for any possible change. |
| 114 | + if ($set && $use_item_id) { |
| 115 | + my @new_items; |
| 116 | + for (@items) { |
| 117 | + my $item = $_->[0]; |
| 118 | + next unless $item->{count} && $item->can_use($set, $records); |
| 119 | + push(@new_items, [ $item, $item->print_form($set, $records, $c) ]); |
| 120 | + } |
| 121 | + return \@new_items; |
| 122 | + } |
75 | 123 | return \@items; |
76 | 124 | } |
77 | 125 |
|
| 126 | +# Method that returns a string with the achievement name and number of remaining items. |
| 127 | +sub remaining_title ($self, $c) { |
| 128 | + if ($self->count > 1) { |
| 129 | + return $c->maketext('[_1] ([_2] remaining)', $c->maketext($self->name), $self->count); |
| 130 | + } elsif ($self->count < 0) { |
| 131 | + return $c->maketext('[_1] (unlimited reusability)', $c->maketext($self->name)); |
| 132 | + } else { |
| 133 | + return $c->maketext('[_1] (1 remains)', $c->maketext($self->name)); |
| 134 | + } |
| 135 | +} |
| 136 | + |
78 | 137 | # Utility method for outputing a form row with a label and popup menu. |
79 | 138 | # The id, label_text, and values are required parameters. |
80 | 139 | sub form_popup_menu_row ($c, %options) { |
81 | 140 | my %params = ( |
82 | | - id => '', |
83 | | - label_text => '', |
84 | | - label_attr => {}, |
85 | | - values => [], |
86 | | - menu_attr => {}, |
87 | | - menu_container_attr => {}, |
88 | | - add_container => 1, |
| 141 | + id => '', |
| 142 | + first_item => '', |
| 143 | + label_text => '', |
| 144 | + label_attr => {}, |
| 145 | + values => [], |
| 146 | + menu_attr => {}, |
| 147 | + add_container => 1, |
89 | 148 | %options |
90 | 149 | ); |
91 | 150 |
|
92 | | - $params{label_attr}{class} //= 'col-4 col-form-label'; |
93 | | - $params{menu_attr}{class} //= 'form-select'; |
94 | | - $params{menu_container_attr}{class} //= 'col-8'; |
| 151 | + $params{label_attr}{class} //= 'col-form-label'; |
| 152 | + $params{menu_attr}{class} //= 'form-select'; |
95 | 153 |
|
96 | | - my $row_contents = $c->c( |
97 | | - $c->label_for($params{id} => $params{label_text}, %{ $params{label_attr} }), |
98 | | - $c->tag( |
99 | | - 'div', |
100 | | - %{ $params{menu_container_attr} }, |
101 | | - $c->select_field($params{id} => $params{values}, id => $params{id}, %{ $params{menu_attr} }) |
102 | | - ) |
103 | | - )->join(''); |
| 154 | + unshift(@{ $params{values} }, [ $params{first_item} => '' ]) if $params{first_item}; |
| 155 | + |
| 156 | + my $row_contents = $c->tag( |
| 157 | + 'div', |
| 158 | + class => 'form-floating', |
| 159 | + $c->c( |
| 160 | + $c->select_field($params{id} => $params{values}, %{ $params{menu_attr} }), |
| 161 | + $c->label_for($params{id} => $params{label_text}, %{ $params{label_attr} }) |
| 162 | + )->join('') |
| 163 | + ); |
104 | 164 |
|
105 | | - return $params{add_container} ? $c->tag('div', class => 'row mb-3', $row_contents) : $row_contents; |
| 165 | + return $params{add_container} ? $c->tag('div', class => 'my-3', $row_contents) : $row_contents; |
106 | 166 | } |
107 | 167 |
|
108 | 168 | END { |
|
0 commit comments