Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ $userAchievements:
$localData:
This is a hash which stores data for this user and achievement
(changes to this variable *will* be saved). This hash will persist
from evaluation to evaluation. You can store whatever you like in here
from evaluation to evaluation. You can store numbers, strings, and array
or hash references containing those in here (the data is saved as JSON,
so other values such as objects or code references will not be saved),
and it can be accessed next time this evaluator is run. Two things to
keep in mind. First, The data in this hash will *not* be accessible by
other achievements. Second, the first time a variable is accessed it
Expand All @@ -79,10 +81,11 @@ $localData:
$globalData:
This is a hash which stores data for all achievements (changes to this
variable *will* be saved). This hash will persist from evaluation to
evaluation and, like $localData, you can store whatever you like in here.
This data will be accessible from *every* achievement and is unique to
the user. There are three variables stored in this hash that are
maintained by the system and should not be modified.
evaluation and, like $localData, you can store numbers, strings, and array
or hash references containing those in here. This data will be accessible
from *every* achievement and is unique to the user. There are three
variables stored in this hash that are maintained by the system and should
not be modified.
- $globalData->{completeSets}:
This is the number of sets which the student has earned 100% on.
- $globalData->{completedSetIds}:
Expand Down
22 changes: 8 additions & 14 deletions lib/WeBWorK/AchievementEvaluator.pm
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use Mojo::Base 'Exporter', -signatures;

use DateTime;

use WeBWorK::Utils qw(sortAchievements nfreeze_base64 thaw_base64);
use WeBWorK::Utils qw(sortAchievements encodeAchievementData decodeAchievementData);
use WeBWorK::Utils::ProblemProcessing qw(compute_unreduced_score);
use WeBWorK::Utils::Tags;
use WeBWorK::WWSafe;
Expand Down Expand Up @@ -84,10 +84,7 @@ sub checkForAchievements ($problem_in, $c, %options) {
# Methods allowed in the safe container.
$compartment->permit(qw(time localtime));

# Thaw_Base64 globalData hash.
if ($globalUserAchievement->frozen_hash) {
$globalData = thaw_base64($globalUserAchievement->frozen_hash);
}
$globalData = decodeAchievementData($globalUserAchievement->frozen_hash);

# Generate hash of user achievements:
foreach my $achievement (@achievements) {
Expand Down Expand Up @@ -186,10 +183,7 @@ sub checkForAchievements ($problem_in, $c, %options) {
my $setType = $set->assignment_type;
next unless $achievement->assignment_type =~ /$setType/;

# Thaw_base64 localData hash.
if ($userAchievement->frozen_hash) {
$localData = thaw_base64($userAchievement->frozen_hash);
}
$localData = decodeAchievementData($userAchievement->frozen_hash);

# Recover counter information (for progress bar achievements).
$counter = $userAchievement->counter;
Expand All @@ -203,7 +197,7 @@ sub checkForAchievements ($problem_in, $c, %options) {
$source = <$SOURCE>;
close($SOURCE);
} else {
warn('Couldnt find achievement evaluator $sourceFilePath');
warn "Could not find achievement evaluator $sourceFilePath";
next;
}

Expand Down Expand Up @@ -250,15 +244,15 @@ sub checkForAchievements ($problem_in, $c, %options) {
) if ($ce->{mail}{achievementEmailFrom} && $achievement->email_template);
}

# Update counter, nfreeze_base64 localData and store.
# Update counter, encode localData and store.
$userAchievement->counter($counter);
$userAchievement->frozen_hash(nfreeze_base64($localData));
$userAchievement->frozen_hash(encodeAchievementData($localData));
$db->putUserAchievement($userAchievement);

} # End for loop.

# nfreeze_base64 globalData and store.
$globalUserAchievement->frozen_hash(nfreeze_base64($globalData));
# Encode globalData and store.
$globalUserAchievement->frozen_hash(encodeAchievementData($globalData));
$db->putGlobalUserAchievement($globalUserAchievement);

if (@$cheevoMessage) {
Expand Down
6 changes: 3 additions & 3 deletions lib/WeBWorK/AchievementItems.pm
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package WeBWorK::AchievementItems;
use Mojo::Base -signatures;

use WeBWorK::Utils qw(nfreeze_base64 thaw_base64);
use WeBWorK::Utils qw(encodeAchievementData decodeAchievementData);

# 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.
Expand Down Expand Up @@ -63,7 +63,7 @@ sub UserItems ($c, $userName, $set, $records) {
my $globalUserAchievement = $c->{globalData} // $db->getGlobalUserAchievement($userName);
return unless $globalUserAchievement && $globalUserAchievement->frozen_hash;

my $globalData = thaw_base64($globalUserAchievement->frozen_hash);
my $globalData = decodeAchievementData($globalUserAchievement->frozen_hash);
my $use_item_id = $c->param('use_achievement_item_id') // '';
my @items;

Expand All @@ -88,7 +88,7 @@ sub UserItems ($c, $userName, $set, $records) {
if ($message) {
$globalData->{$item}--;
$achievementItem->{count}--;
$globalUserAchievement->frozen_hash(nfreeze_base64($globalData));
$globalUserAchievement->frozen_hash(encodeAchievementData($globalData));
$db->putGlobalUserAchievement($globalUserAchievement);
$c->addgoodmessage($c->maketext('[_1] successfully used. [_2]', $achievementItem->name, $message));
}
Expand Down
6 changes: 2 additions & 4 deletions lib/WeBWorK/ContentGenerator/Achievements.pm
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ This produces a list of earned achievements for each student.

=cut

use WeBWorK::Utils qw(sortAchievements thaw_base64);
use WeBWorK::Utils qw(sortAchievements decodeAchievementData);
use WeBWorK::AchievementItems;

sub initialize ($c) {
Expand Down Expand Up @@ -37,9 +37,7 @@ sub getAchievementLevelData ($c) {

if ($achievement) {
if ($c->{globalData}->next_level_points) {
# Get prev_level_points from the globalData frozen_hash in the database.
my $globalData = $c->{globalData}->frozen_hash ? thaw_base64($c->{globalData}->frozen_hash) : {};
my $prev_level = $globalData->{prev_level_points} || 0;
my $prev_level = decodeAchievementData($c->{globalData}->frozen_hash)->{prev_level_points} || 0;
$level_goal = $c->{globalData}->next_level_points - $prev_level;
$level_progress = $c->{globalData}->achievement_points - $prev_level;
$level_progress = 0 if $level_progress < 0;
Expand Down
46 changes: 27 additions & 19 deletions lib/WeBWorK/Utils.pm
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use Email::Address::XS;
use Email::Sender::Transport::SMTP;
use Mojo::JSON qw(from_json to_json);
use Mojo::Util qw(b64_encode b64_decode encode decode);
use Storable qw(nfreeze thaw);
use Storable qw(thaw);

our @EXPORT_OK = qw(
runtime_use
Expand All @@ -15,8 +15,8 @@ our @EXPORT_OK = qw(
decodeAnswers
encode_utf8_base64
decode_utf8_base64
nfreeze_base64
thaw_base64
encodeAchievementData
decodeAchievementData
min
max
wwRound
Expand Down Expand Up @@ -98,22 +98,23 @@ sub decode_utf8_base64 ($in) {
return decode('UTF-8', b64_decode($in));
}

sub nfreeze_base64 ($in) {
return b64_encode(nfreeze($in));
sub encodeAchievementData ($data) {
return encode_utf8_base64(to_json($data));
}

sub thaw_base64 ($string) {
my $result;
sub decodeAchievementData ($serialized) {
return {} unless $serialized;

eval { $result = thaw(b64_decode($string)); };
my $bytes = b64_decode($serialized);
# Data stored by older versions of WeBWorK was encoded with Storable::nfreeze.
my $data = eval { $bytes =~ /^\{/ ? from_json(decode('UTF-8', $bytes)) : thaw($bytes) };

if ($@) {
if ($@ || ref($data) ne 'HASH') {
warn('Deleting corrupted achievement data.');
return {};
} else {
return $result;
}

return $data;
}

sub min (@items) {
Expand Down Expand Up @@ -661,19 +662,26 @@ Usage: C<decode_utf8_base64($in)>

Base 64 decodes, and then UTF-8 decodes the input and returns the result.

=head2 nfreeze_base64
=head2 encodeAchievementData

Usage: C<encodeAchievementData($data)>

Usage: C<nfreeze_base64($in)>
JSON encodes the achievement data hash reference C<$data>, and then UTF-8 and
base 64 encodes the result. The base 64 encoding keeps the stored value ASCII,
which avoids database driver differences in the handling of UTF-8 data in the
binary C<frozen_hash> column.

This C<Storable::nfreeze> encodes and then base 64 encodes C<$in> and returns
the result.
=head2 decodeAchievementData

=head2 thaw_base64
Usage: C<decodeAchievementData($serialized)>

Usage: C<thaw_base64($in)>
Returns the achievement data hash reference decoded from C<$serialized>.

This base 64 decodes and then C<Storable::thaw> decodes C<$in> and returns
result.
This method detects if C<$serialized> is a JSON encoded hash (encoded with
C<encodeAchievementData>), or is a L<Storable::nfreeze> encoded hash (the old
method), and decodes using the appropriate method. An empty hash reference is
returned if C<$serialized> is empty, cannot be decoded, or does not decode to a
hash reference.

=head2 min

Expand Down
7 changes: 5 additions & 2 deletions templates/HelpFiles/InstructorAchievementEditor.html.ep
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,14 @@
<li>
<code>$localData</code>:
<%= maketext('A hash reference which saves data for this user and this achievement. This hash is persistent '
. 'between evaluations and changes to this variable will be saved in the database.') =%>
. 'between evaluations and changes to this variable will be saved in the database. The data is saved '
. 'as JSON, so only numbers, strings, and array or hash references containing those can be stored. '
. 'Other values such as objects or code references will not be saved.') =%>
</li>
<li>
<code>$globalData</code>:
<%= maketext('A hash reference which saves global data for this user. This hash is persistent between '
. 'evaluations and changes to this variable will be saved in the database.') =%>
. 'evaluations and changes to this variable will be saved in the database. Like $localData, only '
. 'numbers, strings, and array or hash references containing those can be stored.') =%>
</li>
</ul>