@@ -23,17 +23,17 @@ sub checkForAchievements ($problem_in, $c, %options) {
2323 # Make a copy of the problem so that local modifications do not persist.
2424 our $problem = $db -> newUserProblem($problem_in );
2525
26- # Date and time for course timezone (may differ from the server timezone)
27- # Saved into separate array
26+ # Date and time for course timezone (may differ from the server timezone).
27+ # Saved into separate array:
2828 # https://metacpan.org/pod/DateTime
2929 my $dtCourseTime = DateTime-> from_epoch(epoch => time (), time_zone => $ce -> {siteDefaults }{timezone } || ' local' );
3030
31- # Set up variables and get achievements
31+ # Set up variables and get achievements.
3232 my $cheevoMessage = $c -> c;
3333 my $user_id = $problem -> user_id;
3434 my $set_id = $problem -> set_id;
3535
36- # exit early if the set is to be ignored by achievements
36+ # Exit early if the set is to be ignored by achievements.
3737 foreach my $excludedSet (@{ $ce -> {achievementExcludeSet } }) {
3838 return ' ' if $set_id eq $excludedSet ;
3939 }
@@ -45,20 +45,20 @@ sub checkForAchievements ($problem_in, $c, %options) {
4545 my $isGatewaySet = ($set -> assignment_type =~ / gateway/ ) ? 1 : 0;
4646 my $isJitarSet = ($set -> assignment_type eq ' jitar' ) ? 1 : 0;
4747
48- # If its a gateway set get the current version
48+ # If its a gateway set get the current version.
4949 if ($isGatewaySet ) {
5050 $set = $db -> getSetVersion($user_id , $set_id , $options {setVersion });
5151 }
5252
53- # If no global data then initialize
53+ # If no global data then initialize.
5454 if (not $globalUserAchievement ) {
5555 $globalUserAchievement = $db -> newGlobalUserAchievement();
5656 $globalUserAchievement -> user_id($user_id );
5757 $globalUserAchievement -> achievement_points(0);
5858 $db -> addGlobalUserAchievement($globalUserAchievement );
5959 }
6060
61- # These need to be "our" so that they can share to the safe container
61+ # These need to be "our" so that they can share to the safe container.
6262 our $counter ;
6363 our $maxCounter ;
6464 our $achievementPoints = $globalUserAchievement -> achievement_points;
@@ -75,21 +75,21 @@ sub checkForAchievements ($problem_in, $c, %options) {
7575
7676 my $compartment = WeBWorK::WWSafe-> new;
7777
78- # initialize things that are ""
78+ # Initialize things that are "".
7979 if (not $achievementPoints ) {
8080 $achievementPoints = 0;
8181 $globalUserAchievement -> achievement_points(0);
8282 }
8383
84- # Methods alowed in the safe container
84+ # Methods allowed in the safe container.
8585 $compartment -> permit(qw( time localtime) );
8686
87- # Thaw_Base64 globalData hash
87+ # Thaw_Base64 globalData hash.
8888 if ($globalUserAchievement -> frozen_hash) {
8989 $globalData = thaw_base64($globalUserAchievement -> frozen_hash);
9090 }
9191
92- # Generate hash of user achievements:
92+ # Generate hash of user achievements:
9393 foreach my $achievement (@achievements ) {
9494 next unless $achievement -> enabled;
9595 my $userAchievement = $db -> getUserAchievement($user_id , $achievement -> achievement_id);
@@ -132,19 +132,24 @@ sub checkForAchievements ($problem_in, $c, %options) {
132132 }
133133 }
134134
135- $globalData -> {completeSets }++ if ($allcorrect );
135+ # Store the setID of all completed sets so they can be used in achievements and not double counted.
136+ $globalData -> {completedSetIds } //= {};
137+ if ($allcorrect ) {
138+ ++$globalData -> {completeSets } unless $globalData -> {completedSetIds }{$set_id };
139+ $globalData -> {completedSetIds }{$set_id } = 1;
140+ }
136141
137- # get the problem tags if its not a gatway
138- # if it is a gateway get rid of $problem since it doensn 't make sense
142+ # Get the problem tags if its not a gateway.
143+ # If it is a gateway get rid of $problem since it doesn 't make sense.
139144 if ($isGatewaySet ) {
140145 $problem = undef ;
141146 } else {
142147 my $templateDir = $ce -> {courseDirs }{templates };
143148 $tags = WeBWorK::Utils::Tags-> new($templateDir . ' /' . $problem -> source_file());
144149 }
145150
146- # These variables are shared with the safe compartment. The achievement evaulators
147- # have access too
151+ # These variables are shared with the safe compartment.
152+ # The achievement evaluators have access too:
148153 # $problem - the problem data;
149154 # @setProblems - the problem data for everything from this set;
150155 # $localData - the hash that is used only for this achievement
@@ -161,7 +166,7 @@ sub checkForAchievements ($problem_in, $c, %options) {
161166 $compartment -> share(qw( $problem @setProblems $localData $maxCounter $userAchievements
162167 $globalData $counter $nextLevelPoints $set $achievementPoints $tags @courseDateTime) );
163168
164- # load any preamble code
169+ # Load any preamble code.
165170 my $preamble = ' ' ;
166171 my $source ;
167172 if (-e " $ce ->{courseDirs}{achievements}/$ce ->{achievementPreambleFile}" ) {
@@ -170,27 +175,27 @@ sub checkForAchievements ($problem_in, $c, %options) {
170175 $preamble = <$PREAMB >;
171176 close ($PREAMB );
172177 }
173- # loop through the various achievements, see if they have been obtained,
178+ # Loop through the various achievements, see if they have been obtained.
174179 foreach my $achievement (@achievements ) {
175- # skip achievements not assigned, not enabled, and that are already earned, or if it doesn't match the set type
180+ # Skip achievements not assigned, not enabled, and that are already earned, or if it doesn't match the set type.
176181 next unless $achievement -> enabled;
177182 my $achievement_id = $achievement -> achievement_id;
178- next unless ( $db -> existsUserAchievement($user_id , $achievement_id ) );
183+ next unless $db -> existsUserAchievement($user_id , $achievement_id );
179184 my $userAchievement = $db -> getUserAchievement($user_id , $achievement_id );
180- next if ( $userAchievement -> earned) ;
185+ next if $userAchievement -> earned;
181186 my $setType = $set -> assignment_type;
182187 next unless $achievement -> assignment_type =~ / $setType / ;
183188
184- # thaw_base64 localData hash
189+ # Thaw_base64 localData hash.
185190 if ($userAchievement -> frozen_hash) {
186191 $localData = thaw_base64($userAchievement -> frozen_hash);
187192 }
188193
189- # recover counter information (for progress bar achievements)
194+ # Recover counter information (for progress bar achievements).
190195 $counter = $userAchievement -> counter;
191196 $maxCounter = $achievement -> max_counter;
192197
193- # check the achievement using Safe
198+ # Check the achievement using Safe.
194199 my $sourceFilePath = $ce -> {courseDirs }{achievements } . ' /' . $achievement -> test;
195200 if (-e $sourceFilePath ) {
196201 local $/ = undef ;
@@ -205,11 +210,11 @@ sub checkForAchievements ($problem_in, $c, %options) {
205210 my $earned = $compartment -> reval($preamble . " \n " . $source );
206211 warn " There were errors in achievement $achievement_id \n " . $@ if $@ ;
207212
208- # if we have a new achievement then update achievement points
213+ # If we have a new achievement then update achievement points.
209214 if ($earned ) {
210215 $userAchievement -> earned(1);
211216
212- # update userAchievements hash with earned status.
217+ # Update userAchievements hash with earned status.
213218 $userAchievements -> {$achievement_id } = $earned ;
214219
215220 if ($achievement -> category eq ' level' ) {
@@ -223,14 +228,14 @@ sub checkForAchievements ($problem_in, $c, %options) {
223228 push (@$cheevoMessage , $c -> include(' AchievementEvaluator/cheevoMessage' , achievement => $achievement ));
224229
225230 my $points = $achievement -> points;
226- # just in case points is an uninitialized variable
231+ # Just in case points is an uninitialized variable.
227232 $points = 0 unless $points ;
228233
229234 $globalUserAchievement -> achievement_points($globalUserAchievement -> achievement_points + $points );
230- # this variable is shared and should be considered iffy
235+ # This variable is shared and should be considered iffy.
231236 $achievementPoints += $points ;
232237
233- # if email_template is defined, send an email to the user
238+ # If email_template is defined, send an email to the user.
234239 $c -> minion-> enqueue(
235240 send_achievement_email => [ {
236241 recipient => $user_id ,
@@ -245,14 +250,14 @@ sub checkForAchievements ($problem_in, $c, %options) {
245250 ) if ($ce -> {mail }{achievementEmailFrom } && $achievement -> email_template);
246251 }
247252
248- # update counter, nfreeze_base64 localData and store
253+ # Update counter, nfreeze_base64 localData and store.
249254 $userAchievement -> counter($counter );
250255 $userAchievement -> frozen_hash(nfreeze_base64($localData ));
251256 $db -> putUserAchievement($userAchievement );
252257
253- } # end for loop
258+ } # End for loop.
254259
255- # nfreeze_base64 globalData and store
260+ # nfreeze_base64 globalData and store.
256261 $globalUserAchievement -> frozen_hash(nfreeze_base64($globalData ));
257262 $db -> putGlobalUserAchievement($globalUserAchievement );
258263
0 commit comments