PluginProbe
GamiPress – Gamification plugin to reward points, badges & ranks in WordPress, now with AI / 8.0.5
GamiPress – Gamification plugin to reward points, badges & ranks in WordPress, now with AI v8.0.5
8.0.5 8.0.4 8.0.3 8.0.1 8.0.2 8.0.0 7.9.9.7 7.9.9.6 7.9.9.5 7.9.9.4 7.9.9.3 7.9.9.2 7.9.9.1 7.9.9 7.9.8 7.9.7 7.9.6 7.9.5 7.9.4 7.9.3 7.9.2 7.9.1 7.9.0 7.8.9 7.8.8 All 47 releases
← All changes | includes/rules-engine.php +1969 -0 7.9.9.3 → 8.0.5 View file →
@@ -1,0 +1,1969 @@
1 +<?php
2 +/**
3 + * Rules Engine: The brains behind this whole operation
4 + *
5 + * @package GamiPress\Rules_Engine
6 + * @author GamiPress <[email protected]>, Ruben Garcia <[email protected]>
7 + * @since 1.0.0
8 + */
9 +// Exit if accessed directly
10 +if( !defined( 'ABSPATH' ) ) exit;
11 +
12 +/**
13 + * Check if user should earn an achievement, and award it if so.
14 + *
15 + * @since 1.0.0
16 + * @since 1.8.2 Function now informs if achievement has been awarded or not
17 + *
18 + * @param int $achievement_id The given achievement ID to possibly award
19 + * @param int $user_id The given user's ID
20 + * @param string $trigger The trigger
21 + * @param int $site_id The triggered site id
22 + * @param array $args The triggered args
23 + *
24 + * @return bool|WP_Error True if achievement has been awarded, false or a WP_Error object otherwise
25 + */
26 +function gamipress_maybe_award_achievement_to_user( $achievement_id = 0, $user_id = 0, $trigger = '', $site_id = 0, $args = array() ) {
27 +
28 + // Set to current site id
29 + if ( ! $site_id ) {
30 + $site_id = get_current_blog_id();
31 + }
32 +
33 + // Grab current user ID if one isn't specified
34 + if ( ! $user_id ) {
35 + $user_id = get_current_user_id();
36 + }
37 +
38 + $result = true;
39 +
40 + // Checks if the user has access to this achievement
41 + if ( ! gamipress_user_has_access_to_achievement( $user_id, $achievement_id, $trigger, $site_id, $args ) ) {
42 + $result = new WP_Error( 'gamipress_not_access_achievement', __( 'User hasn\'t access to this achievement.', 'gamipress' ) );
43 + }
44 +
45 + // Checks if the user has completed the achievement
46 + if ( $result === true && ! gamipress_check_achievement_completion_for_user( $achievement_id, $user_id, $trigger, $site_id, $args ) ) {
47 + $result = new WP_Error( 'gamipress_not_completed_achievement', __( 'User hasn\'t completed this achievement yet.', 'gamipress' ) );
48 + }
49 +
50 + // Only award the achievement if no errors confirmed
51 + if ( $result === true ) {
52 + gamipress_award_achievement_to_user( $achievement_id, $user_id, false, $trigger, $site_id, $args );
53 + }
54 +
55 + /**
56 + * After check if user should earn an achievement, and award it if so.
57 + *
58 + * @since 1.8.2
59 + *
60 + * @param bool|WP_Error $result True if achievement has been awarded, false or a WP_Error object otherwise
61 + * @param int $user_id The given user's ID
62 + * @param int $achievement_id The given achievement ID to possibly award
63 + * @param string $trigger The trigger
64 + * @param int $site_id The triggered site id
65 + * @param array $args The triggered args
66 + *
67 + * @return bool|WP_Error True if achievement has been awarded, false or a WP_Error object otherwise
68 + */
69 + return apply_filters( 'gamipress_maybe_award_achievement_to_user', $result, $user_id, $achievement_id, $trigger, $site_id, $args );
70 +
71 +}
72 +
73 +/* --------------------------------------------------
74 + * Access Checks
75 + -------------------------------------------------- */
76 +
77 +/**
78 + * Check if user may access/earn achievement.
79 + *
80 + * @since 1.0.0
81 + *
82 + * @param int $achievement_id The given achievement ID to possibly award
83 + * @param int $user_id The given user's ID
84 + * @param string $trigger The trigger
85 + * @param int $site_id The triggered site id
86 + * @param array $args The triggered args
87 + *
88 + * @return bool True if user has access, false otherwise
89 + */
90 +function gamipress_user_has_access_to_achievement( $user_id = 0, $achievement_id = 0, $trigger = '', $site_id = 0, $args = array() ) {
91 +
92 + // Set to current site id
93 + if ( ! $site_id ) {
94 + $site_id = get_current_blog_id();
95 + }
96 +
97 + // Assume we have access
98 + $return = true;
99 +
100 + // If the achievement is not published, we do not have access
101 + if ( gamipress_get_post_status( $achievement_id ) !== 'publish' ) {
102 + $return = false;
103 + }
104 +
105 + // If we've exceeded the max earnings, we do not have access
106 + if ( $return && gamipress_achievement_user_exceeded_max_earnings( $user_id, $achievement_id ) ) {
107 + $return = false;
108 + }
109 +
110 + // Get the achievement post type to exclude some of them
111 + $post_type = gamipress_get_post_type( $achievement_id );
112 +
113 + if ( $return
114 + && ! in_array( $post_type, array( 'points-award', 'points-deduct' ) ) // If not is a points award or deduct
115 + && $parent_id = absint( gamipress_get_post_field( 'post_parent', $achievement_id ) ) // If achievement has a parent
116 + ) {
117 +
118 + // If we don't have access to the parent, we do not have access to this
119 + if ( ! gamipress_user_has_access_to_achievement( $user_id, $parent_id, $trigger, $site_id, $args ) ) {
120 + $return = false;
121 + }
122 +
123 + // If the parent requires sequential steps, confirm we've earned all previous steps
124 + if ( $return && gamipress_is_achievement_sequential( $parent_id ) ) {
125 +
126 + foreach ( gamipress_get_children_of_achievement( $parent_id ) as $sibling ) {
127 + // If this is the current step, we're good to go
128 + if ( $sibling->ID == $achievement_id ) {
129 + break;
130 + }
131 +
132 + // Skip optional requirements
133 + if( ( bool ) gamipress_get_post_meta( $sibling->ID, '_gamipress_optional' ) ) {
134 + continue;
135 + }
136 +
137 + // If we haven't earned any previous step, we can't earn this one
138 + if ( ! gamipress_has_user_earned_achievement( $sibling->ID, $user_id ) ) {
139 + $return = false;
140 + break;
141 + }
142 + }
143 +
144 + }
145 + }
146 +
147 + /**
148 + * Available filter for custom overrides
149 + *
150 + * @since 1.0.0
151 + *
152 + * @param bool $return True if user has access, false otherwise
153 + * @param int $user_id The given user's ID
154 + * @param int $achievement_id The given achievement ID to possibly award
155 + * @param string $trigger The trigger
156 + * @param int $site_id The triggered site id
157 + * @param array $args The triggered args
158 + *
159 + * @return bool True if user has access, false otherwise
160 + */
161 + return apply_filters( 'user_has_access_to_achievement', $return, $user_id, $achievement_id, $trigger, $site_id, $args );
162 +
163 +}
164 +
165 +/**
166 + * Checks if a user is allowed to work on a given requirement related to a specific ID
167 + *
168 + * @since 1.0.8
169 + *
170 + * @param bool $return The default return value
171 + * @param int $user_id The given user's ID
172 + * @param int $requirement_id The given requirement's post ID
173 + * @param string $trigger The trigger triggered
174 + * @param int $site_id The site id
175 + * @param array $args Arguments of this trigger
176 + *
177 + * @return bool True if user has access to the requirement, false otherwise
178 + */
179 +function gamipress_user_has_access_to_specific_requirement( $return = false, $user_id = 0, $requirement_id = 0, $trigger = '', $site_id = 0, $args = array() ) {
180 +
181 + // Bail if access is not already granted
182 + if( ! $return ) {
183 + return $return;
184 + }
185 +
186 + // If we're not working with a requirement, bail here
187 + if ( ! in_array( gamipress_get_post_type( $requirement_id ), gamipress_get_requirement_types_slugs() ) ) {
188 + return $return;
189 + }
190 +
191 + // If is specific trigger rules engine needs the attached id
192 + if( $return && in_array( $trigger, array_keys( gamipress_get_specific_activity_triggers() ) ) ) {
193 +
194 + $specific_id = gamipress_specific_trigger_get_id( $trigger, $args );
195 + $required_id = absint( gamipress_get_post_meta( $requirement_id, '_gamipress_achievement_post' ) );
196 +
197 + // True if there is a specific id, a attached id and both are equal
198 + $return = (bool) (
199 + $specific_id !== 0
200 + && $required_id !== 0
201 + && $specific_id === $required_id
202 + );
203 +
204 + }
205 +
206 + // Send back our eligibility
207 + return $return;
208 +}
209 +add_filter( 'user_has_access_to_achievement', 'gamipress_user_has_access_to_specific_requirement', 10, 6 );
210 +
211 +/**
212 + * Checks if a user is allowed to work on a given step
213 + *
214 + * @since 1.0.0
215 + *
216 + * @param bool $return The default return value
217 + * @param int $user_id The given user's ID
218 + * @param int $step_id The given step's post ID
219 + *
220 + * @return bool True if user has access to step, false otherwise
221 + */
222 +function gamipress_user_has_access_to_step( $return = false, $user_id = 0, $step_id = 0 ) {
223 +
224 + // Bail if access is not already granted
225 + if( ! $return ) {
226 + return $return;
227 + }
228 +
229 + // If we're not working with a step, bail here
230 + if ( 'step' !== gamipress_get_post_type( $step_id ) ) {
231 + return $return;
232 + }
233 +
234 + // Prevent user from earning steps with no parents
235 + $achievement = gamipress_get_step_achievement( $step_id );
236 +
237 + if ( ! $achievement ) {
238 + return false;
239 + }
240 +
241 + // Prevent user from earning steps if achievement is not setup to be earned through steps
242 + if( 'triggers' !== gamipress_get_post_meta( $achievement->ID, '_gamipress_earned_by' ) ) {
243 + return false;
244 + }
245 +
246 + if( $return ) {
247 +
248 + $earned_times = gamipress_get_earnings_count( array(
249 + 'user_id' => absint( $user_id ),
250 + 'post_id' => absint( $step_id ),
251 + 'since' => absint( gamipress_achievement_last_user_activity( $achievement->ID, $user_id ) )
252 + ) );
253 +
254 + // Prevent user from repeatedly earning the same step
255 + if ( $earned_times > 0 ) {
256 + $return = false;
257 + }
258 +
259 + }
260 +
261 + // Send back our eligibility
262 + return $return;
263 +}
264 +add_filter( 'user_has_access_to_achievement', 'gamipress_user_has_access_to_step', 10, 3 );
265 +
266 +/**
267 + * Checks if a user is allowed to work on a given points award
268 + *
269 + * @since 1.0.0
270 + *
271 + * @param bool $return The default return value
272 + * @param int $user_id The given user's ID
273 + * @param int $points_award_id The given points award's post ID
274 + *
275 + * @return bool True if user has access to step, false otherwise
276 + */
277 +function gamipress_user_has_access_to_points_award( $return = false, $user_id = 0, $points_award_id = 0 ) {
278 +
279 + // Bail if access is not already granted
280 + if( ! $return ) {
281 + return $return;
282 + }
283 +
284 + // If we're not working with a points award, bail here
285 + if ( 'points-award' !== gamipress_get_post_type( $points_award_id ) ) {
286 + return $return;
287 + }
288 +
289 + // Prevent user from earning points awards with no points type
290 + $points_type = gamipress_get_points_award_points_type( $points_award_id );
291 +
292 + if ( ! $points_type ) {
293 + return false;
294 + }
295 +
296 + $maximum_earnings = absint( gamipress_get_post_meta( $points_award_id, '_gamipress_maximum_earnings' ) );
297 +
298 + // No maximum earnings set
299 + if( $maximum_earnings === 0 ) {
300 + return $return;
301 + }
302 +
303 + if ( $return ) {
304 +
305 + $earned_times = gamipress_get_earnings_count( array(
306 + 'user_id' => absint( $user_id ),
307 + 'post_id' => absint( $points_award_id ),
308 + 'since' => absint( gamipress_achievement_last_user_activity( $points_award_id, $user_id ) )
309 + ) );
310 +
311 + // Prevent user to exceed maximum earnings the same points award
312 + if ( $earned_times >= $maximum_earnings ) {
313 + $return = false;
314 + }
315 +
316 + }
317 +
318 + // Send back our eligibility
319 + return $return;
320 +
321 +}
322 +add_filter( 'user_has_access_to_achievement', 'gamipress_user_has_access_to_points_award', 10, 3 );
323 +
324 +/**
325 + * Checks if a user is allowed to work on a given points deduct
326 + *
327 + * @since 1.3.7
328 + *
329 + * @param bool $return The default return value
330 + * @param int $user_id The given user's ID
331 + * @param int $points_deduct_id The given points deduct's post ID
332 + *
333 + * @return bool True if user has access to step, false otherwise
334 + */
335 +function gamipress_user_has_access_to_points_deduct( $return = false, $user_id = 0, $points_deduct_id = 0 ) {
336 +
337 + // Bail if access is not already granted
338 + if( ! $return ) {
339 + return $return;
340 + }
341 +
342 + // If we're not working with a step, bail here
343 + if ( 'points-deduct' !== gamipress_get_post_type( $points_deduct_id ) ) {
344 + return $return;
345 + }
346 +
347 + // Prevent user from earning points deducts with no points type
348 + $points_type = gamipress_get_points_deduct_points_type( $points_deduct_id );
349 +
350 + if ( ! $points_type ) {
351 + return false;
352 + }
353 +
354 + $maximum_earnings = absint( gamipress_get_post_meta( $points_deduct_id, '_gamipress_maximum_earnings' ) );
355 +
356 + // No maximum earnings set
357 + if( $maximum_earnings === 0 ) {
358 + return $return;
359 + }
360 +
361 + if ( $return ) {
362 +
363 + $earned_times = gamipress_get_earnings_count( array(
364 + 'user_id' => absint( $user_id ),
365 + 'post_id' => absint( $points_deduct_id ),
366 + 'since' => absint( gamipress_achievement_last_user_activity( $points_deduct_id, $user_id ) )
367 + ) );
368 +
369 + // Prevent user to exceed maximum earnings the same points deduct
370 + if ( $earned_times >= $maximum_earnings ) {
371 + $return = false;
372 + }
373 +
374 + }
375 +
376 + // Send back our eligibility
377 + return $return;
378 +
379 +}
380 +add_filter( 'user_has_access_to_achievement', 'gamipress_user_has_access_to_points_deduct', 10, 3 );
381 +
382 +/**
383 + * Checks if a user is allowed to work on a given rank requirement
384 + *
385 + * @since 1.0.0
386 + *
387 + * @param bool $return The default return value
388 + * @param int $user_id The given user's ID
389 + * @param int $rank_requirement_id The given rank requirement's post ID
390 + *
391 + * @return bool True if user has access to step, false otherwise
392 + */
393 +function gamipress_user_has_access_to_rank_requirement( $return = false, $user_id = 0, $rank_requirement_id = 0 ) {
394 +
395 + // Bail if access is not already granted
396 + if( ! $return ) {
397 + return $return;
398 + }
399 +
400 + // If we're not working with a rank requirement, bail here
401 + if ( 'rank-requirement' !== gamipress_get_post_type( $rank_requirement_id ) ) {
402 + return $return;
403 + }
404 +
405 + // If is a rank requirement, we need to check if rank requirement is for next rank and not other
406 + $rank = gamipress_get_rank_requirement_rank( $rank_requirement_id );
407 +
408 + // Bail if not rank assigned to this rank requirement
409 + if( ! $rank ) {
410 + return false;
411 + }
412 +
413 + $next_user_rank_id = gamipress_get_next_user_rank_id( $user_id, $rank->post_type );
414 +
415 + if( $return && $next_user_rank_id === 0 ) {
416 + $return = false;
417 + }
418 +
419 + if ( $return && absint( $rank->ID ) !== $next_user_rank_id ) {
420 + $return = false;
421 + }
422 +
423 + if ( $return ) {
424 +
425 + $earned_times = gamipress_get_earnings_count( array(
426 + 'user_id' => absint( $user_id ),
427 + 'post_id' => absint( $rank_requirement_id ),
428 + 'since' => absint( gamipress_achievement_last_user_activity( $rank->ID, $user_id ) )
429 + ) );
430 +
431 + // Prevent user from repeatedly earning the same requirement
432 + if ( $earned_times > 0 ) {
433 + $return = false;
434 + }
435 +
436 + }
437 +
438 + // Send back our eligibility
439 + return $return;
440 +
441 +}
442 +add_filter( 'user_has_access_to_achievement', 'gamipress_user_has_access_to_rank_requirement', 10, 3 );
443 +
444 +/**
445 + * Check if user meets the post type requirement for a given achievement
446 + *
447 + * @since 1.9.9
448 + *
449 + * @param bool $return True if user has access, false otherwise
450 + * @param int $user_id The given user's ID
451 + * @param int $achievement_id The given achievement ID to possibly award
452 + * @param string $trigger The trigger
453 + * @param int $site_id The triggered site id
454 + * @param array $args The triggered args
455 + *
456 + * @return bool True if user has access to step, false otherwise
457 + */
458 +function gamipress_user_meets_post_type_requirement( $return = false, $user_id = 0, $achievement_id = 0, $trigger = '', $site_id = 0, $args = array() ) {
459 +
460 + // Bail if access is not already granted
461 + if( ! $return ) {
462 + return $return;
463 + }
464 +
465 + $post_type = gamipress_get_post_type( $achievement_id );
466 +
467 + // Bail if not is a requirement
468 + if( ! in_array( $post_type, gamipress_get_requirement_types_slugs() ) ) {
469 + return $return;
470 + }
471 +
472 + if( empty( $trigger ) ) {
473 + $trigger = gamipress_get_post_meta( $achievement_id, '_gamipress_trigger_type' );
474 + }
475 +
476 + if( in_array( $trigger, array(
477 + 'gamipress_new_comment_post_type',
478 + 'gamipress_user_post_comment_post_type',
479 + 'gamipress_spam_comment_post_type',
480 + 'gamipress_publish_post_type',
481 + 'gamipress_delete_post_type',
482 + 'gamipress_post_type_visit',
483 + 'gamipress_user_post_type_visit',
484 + ) ) ) {
485 +
486 + $index = 2;
487 +
488 + if( in_array( $trigger, array(
489 + 'gamipress_new_comment_post_type',
490 + 'gamipress_user_post_comment_post_type',
491 + 'gamipress_spam_comment_post_type',
492 + ) ) ) {
493 + $index = 3;
494 + }
495 +
496 + $post_type = gamipress_get_event_arg( $args, 'post_type', $index );
497 + $post_type_required = gamipress_get_post_meta( $achievement_id, '_gamipress_post_type_required' );
498 +
499 + // Deserve if post type matches
500 + $return = (bool) ( $post_type === $post_type_required );
501 + }
502 +
503 + // Send back our eligibility
504 + return $return;
505 +
506 +}
507 +add_filter( 'user_has_access_to_achievement', 'gamipress_user_meets_post_type_requirement', 10, 6 );
508 +
509 +/**
510 + * Check if user meets the role requirement for a given achievement
511 + *
512 + * @since 1.8.9
513 + *
514 + * @param bool $return True if user has access, false otherwise
515 + * @param int $user_id The given user's ID
516 + * @param int $achievement_id The given achievement ID to possibly award
517 + * @param string $trigger The trigger
518 + * @param int $site_id The triggered site id
519 + * @param array $args The triggered args
520 + *
521 + * @return bool True if user has access to step, false otherwise
522 + */
523 +function gamipress_user_meets_role_requirement( $return = false, $user_id = 0, $achievement_id = 0, $trigger = '', $site_id = 0, $args = array() ) {
524 +
525 + // Bail if access is not already granted
526 + if( ! $return ) {
527 + return $return;
528 + }
529 +
530 + $post_type = gamipress_get_post_type( $achievement_id );
531 +
532 + // Bail if not is a requirement
533 + if( ! in_array( $post_type, gamipress_get_requirement_types_slugs() ) ) {
534 + return $return;
535 + }
536 +
537 + if( empty( $trigger ) ) {
538 + $trigger = gamipress_get_post_meta( $achievement_id, '_gamipress_trigger_type' );
539 + }
540 +
541 + if( in_array( $trigger, array(
542 + 'gamipress_add_specific_role',
543 + 'gamipress_set_specific_role',
544 + 'gamipress_remove_specific_role'
545 + ) ) ) {
546 + $role = gamipress_get_event_arg( $args, 'role', 1 );
547 + $role_required = gamipress_get_post_meta( $achievement_id, '_gamipress_user_role_required' );
548 +
549 + // Deserve if role matches
550 + $return = (bool) ( $role === $role_required );
551 + }
552 +
553 + // Send back our eligibility
554 + return $return;
555 +
556 +}
557 +add_filter( 'user_has_access_to_achievement', 'gamipress_user_meets_role_requirement', 10, 6 );
558 +
559 +/**
560 + * Check if user meets the meta requirement for a given achievement
561 + *
562 + * @since 1.0.0
563 + *
564 + * @param bool $return The default return value
565 + * @param int $user_id The given user's ID
566 + * @param int $requirement_id The given requirement's post ID
567 + * @param string $trigger The trigger triggered
568 + * @param int $site_id The site id
569 + * @param array $args Arguments of this trigger
570 + *
571 + * @return bool True if user has access to the requirement, false otherwise
572 + */
573 +function gamipress_user_meets_meta_requirement( $return = false, $user_id = 0, $requirement_id = 0, $trigger = '', $site_id = 0, $args = array() ) {
574 +
575 + // If we're not working with a requirement, bail here
576 + if ( ! in_array( get_post_type( $requirement_id ), gamipress_get_requirement_types_slugs() ) )
577 + return $return;
578 +
579 + // Check if user has access to the achievement ($return will be false if user has exceed the limit or achievement is not published yet)
580 + if( ! $return )
581 + return $return;
582 +
583 + // Rules engine needs to check if meta key matches required ones
584 + if( $return && ( $trigger === 'gamipress_update_post_meta_any_value'
585 + || $trigger === 'gamipress_update_user_meta_any_value' ) ) {
586 +
587 + $meta_key = $args[2];
588 + $required_meta_key = gamipress_get_post_meta( $requirement_id, '_gamipress_meta_key_required', true );
589 +
590 + $return = (bool) ( $meta_key === $required_meta_key );
591 +
592 + }
593 +
594 + // Rules engine needs to check if meta key and values matches required ones
595 + if( $return && ( $trigger === 'gamipress_update_post_meta_specific_value'
596 + || $trigger === 'gamipress_update_user_meta_specific_value' ) ) {
597 +
598 + $meta_key = $args[2];
599 + $meta_value = $args[3];
600 + $required_meta_key = gamipress_get_post_meta( $requirement_id, '_gamipress_meta_key_required', true );
601 + $required_meta_value = gamipress_get_post_meta( $requirement_id, '_gamipress_meta_value_required', true );
602 +
603 + if ( is_numeric( $meta_value ) ){
604 +
605 + $meta_value = strval( $meta_value );
606 + }
607 +
608 + $return = (bool) ( $meta_key === $required_meta_key );
609 +
610 + if ( $return ) {
611 + // Check if field value matches the required one (with support for arrays)
612 + if( is_array( $meta_value ) )
613 + $return = (bool) ( in_array( $required_meta_value, $meta_value ) );
614 + else
615 + $return = (bool) ( $meta_value === $required_meta_value );
616 + }
617 +
618 + }
619 +
620 + // Send back our eligibility
621 + return $return;
622 +
623 +}
624 +add_filter( 'user_has_access_to_achievement', 'gamipress_user_meets_meta_requirement', 10, 6 );
625 +
626 +/* --------------------------------------------------
627 + * Completion Checks
628 + -------------------------------------------------- */
629 +
630 +/**
631 + * Check if user has completed an achievement
632 + *
633 + * @since 1.0.0
634 + *
635 + * @param int $achievement_id The given achievement ID to verify
636 + * @param int $user_id The given user's ID
637 + * @param string $trigger_type The trigger
638 + * @param int $site_id The triggered site id
639 + * @param array $args The triggered args
640 + *
641 + * @return bool True if user has completed achievement, false otherwise
642 + */
643 +function gamipress_check_achievement_completion_for_user( $achievement_id = 0, $user_id = 0, $trigger_type = '', $site_id = 0, $args = array() ) {
644 +
645 + // Assume the user has completed the achievement
646 + $return = true;
647 +
648 + // Set to current site id
649 + if ( ! $site_id ) {
650 + $site_id = get_current_blog_id();
651 + }
652 +
653 + // Points types can't be earned
654 + if( gamipress_get_post_type( $achievement_id ) === 'points-type' ) {
655 + return false;
656 + }
657 +
658 + // If the user has not already earned the achievement...
659 + if ( gamipress_get_earnings_count( array(
660 + 'user_id' => absint( $user_id ),
661 + 'post_id' => absint( $achievement_id ),
662 + 'since' => gamipress_achievement_last_user_activity( $achievement_id, $user_id ) + 1
663 + ) ) === 0 ) {
664 +
665 + // Grab published required achievements for this achievement
666 + $required_achievements = gamipress_get_required_achievements_for_achievement( $achievement_id, 'publish' );
667 +
668 + // If we have requirements, loop through each and make sure they've been completed
669 + if ( is_array( $required_achievements ) && ! empty( $required_achievements ) ) {
670 +
671 + foreach ( $required_achievements as $requirement ) {
672 +
673 + // Skip optional requirements
674 + if( ( bool ) gamipress_get_post_meta( $requirement->ID, '_gamipress_optional' ) ) {
675 + continue;
676 + }
677 +
678 + $requirement_earned = gamipress_get_earnings_count( array(
679 + 'user_id' => $user_id,
680 + 'post_id' => $requirement->ID,
681 + 'since' => gamipress_achievement_last_user_activity( $achievement_id, $user_id ) - 1
682 + ) );
683 +
684 + // Has the user already earned the requirements?
685 + if ( $requirement_earned === 0 ) {
686 + $return = false;
687 + break;
688 + }
689 + }
690 +
691 + }
692 + }
693 +
694 + /**
695 + * Available filter to support custom earning rules
696 + *
697 + * @since 1.0.0
698 + *
699 + * @param bool $return True if user has completed achievement, false otherwise
700 + * @param int $user_id The given user's ID
701 + * @param int $achievement_id The given achievement ID to verify
702 + * @param string $trigger_type The trigger
703 + * @param int $site_id The triggered site id
704 + * @param array $args The triggered args
705 + *
706 + * @return bool True if user has completed achievement, false otherwise
707 + */
708 + return apply_filters( 'user_deserves_achievement', $return, $user_id, $achievement_id, $trigger_type, $site_id, $args );
709 +
710 +}
711 +
712 +/**
713 + * Check if user meets the points requirement for a given achievement
714 + *
715 + * @since 1.0.0
716 + * @updated 1.6.4 Added checks for 'points-balance' event
717 + *
718 + * @param bool $return The current status of whether or not the user deserves this achievement
719 + * @param int $user_id The given user's ID
720 + * @param int $achievement_id The given achievement's post ID
721 + * @param string $trigger_type The trigger
722 + * @param int $site_id The triggered site id
723 + * @param array $args The triggered args
724 + *
725 + * @return bool Our possibly updated earning status
726 + */
727 +function gamipress_user_meets_points_requirement( $return = false, $user_id = 0, $achievement_id = 0, $trigger_type = '', $site_id = 0, $args = array() ) {
728 +
729 + // Check if user has access to the achievement ($return will be false if user has exceed the limit or achievement is not published yet)
730 + if( ! $return ) {
731 + return $return;
732 + }
733 +
734 + if( empty( $trigger_type ) ) {
735 + $trigger_type = gamipress_get_post_meta( $achievement_id, '_gamipress_trigger_type' );
736 + }
737 +
738 + // First, see if the achievement requires a minimum amount of points
739 + if (
740 + 'points' === gamipress_get_post_meta( $achievement_id, '_gamipress_earned_by' ) // Check for achievements earned by points
741 + || 'earn-points' === $trigger_type // Check for requirements with earn points activity
742 + ) {
743 +
744 + // Grab our user's points and see if they at least as many as required
745 + $points_required = absint( gamipress_get_post_meta( $achievement_id, '_gamipress_points_required' ) );
746 + $points_type_required = gamipress_get_post_meta( $achievement_id, '_gamipress_points_type_required' );
747 + $last_activity = absint( gamipress_achievement_last_user_activity( $achievement_id, $user_id ) );
748 +
749 + // Get user points earned since last time has earning the achievement
750 + $awarded_points = gamipress_get_user_points_awarded_in_loop( $user_id, $points_type_required, $achievement_id );
751 +
752 + if( $awarded_points >= $points_required ) {
753 +
754 + // If the user just earned the achievement, though, don't let them earn it again
755 + // This prevents an infinite loop if the achievement has no maximum earnings limit
756 + $minimum_time = current_time( 'timestamp' ) - 1;
757 +
758 + if ( $last_activity > $minimum_time ) {
759 + $return = false;
760 + }
761 +
762 + // Increase the points awarded of this loop since the achievement will be awarded
763 + gamipress_update_user_points_awarded_in_loop( $points_required, $points_type_required, $achievement_id, $user_id );
764 +
765 + } else {
766 + $return = false;
767 + }
768 +
769 + } else if( 'points-balance' === $trigger_type ) {
770 +
771 + // Grab our user's points and see if they at least as many as required
772 + $points_condition = gamipress_get_post_meta( $achievement_id, '_gamipress_points_condition' );
773 + $points_required = absint( gamipress_get_post_meta( $achievement_id, '_gamipress_points_required' ) );
774 + $points_type_required = gamipress_get_post_meta( $achievement_id, '_gamipress_points_type_required' );
775 +
776 + // Get user points balance
777 + $points_balance = absint( gamipress_get_user_points( $user_id, $points_type_required ) );
778 +
779 + if( empty( $points_condition ) ) {
780 + $points_condition = 'greater_or_equal';
781 + }
782 +
783 + if( gamipress_number_condition_matches( $points_balance, $points_required, $points_condition ) ) {
784 +
785 + // If the user just earned the achievement, though, don't let them earn it again
786 + // This prevents an infinite loop if the achievement has no maximum earnings limit
787 + $minimum_time = current_time( 'timestamp' ) - 1;
788 + $last_activity = absint( gamipress_achievement_last_user_activity( $achievement_id, $user_id ) );
789 +
790 + if ( 'rank-requirement' === gamipress_get_post_type( $achievement_id ) ) {
791 + $return = true;
792 + } elseif ( $last_activity > $minimum_time ) {
793 + $return = false;
794 + }
795 +
796 + } else {
797 + $return = false;
798 + }
799 +
800 + } else if( 'gamipress_expend_points' === $trigger_type ) {
801 +
802 + // Grab our user's points expended and see if they at least as many as required
803 + $points_required = absint( gamipress_get_post_meta( $achievement_id, '_gamipress_points_required' ) );
804 + $points_type_required = gamipress_get_post_meta( $achievement_id, '_gamipress_points_type_required' );
805 +
806 + // Get user points expended since last time has earning the achievement
807 + $expended_points = gamipress_get_user_points_expended( $user_id, $points_type_required, gamipress_achievement_last_user_activity( $achievement_id, $user_id ) - 1 );
808 +
809 + if ( $expended_points < $points_required )
810 + $return = false;
811 +
812 + }
813 +
814 + // Return our eligibility status
815 + return $return;
816 +
817 +}
818 +add_filter( 'user_deserves_achievement', 'gamipress_user_meets_points_requirement', 10, 6 );
819 +
820 +/**
821 + * Check if user meets the rank requirement for a given achievement
822 + *
823 + * @since 1.3.1
824 + *
825 + * @param bool $return The current status of whether or not the user deserves this achievement
826 + * @param int $user_id The given user's ID
827 + * @param int $achievement_id The given achievement's post ID
828 + * @param string $trigger_type The trigger
829 + * @param int $site_id The triggered site id
830 + * @param array $args The triggered args
831 + *
832 + * @return bool Our possibly updated earning status
833 + */
834 +function gamipress_user_meets_rank_requirement( $return = false, $user_id = 0, $achievement_id = 0, $trigger_type = '', $site_id = 0, $args = array() ) {
835 +
836 + // Check if user has access to the achievement ($return will be false if user has exceed the limit or achievement is not published yet)
837 + if( ! $return ) {
838 + return $return;
839 + }
840 +
841 + if( empty( $trigger_type ) ) {
842 + $trigger_type = gamipress_get_post_meta( $achievement_id, '_gamipress_trigger_type' );
843 + }
844 +
845 + // First, see if the achievement requires a minimum rank
846 + if (
847 + 'rank' === gamipress_get_post_meta( $achievement_id, '_gamipress_earned_by' ) // Check for achievements earned by rank
848 + || 'earn-rank' === $trigger_type // Check for requirements with earn rank activity
849 + || 'revoke-rank' === $trigger_type // Check for requirements with revoke rank activity
850 + ) {
851 + // Grab our user's rank and compared it with the required one
852 + $rank_required = absint( gamipress_get_post_meta( $achievement_id, '_gamipress_rank_required' ) );
853 + $user_rank_id = gamipress_get_user_rank_id( $user_id, gamipress_get_post_type( $rank_required ) );
854 +
855 + if ( $user_rank_id === $rank_required )
856 + $return = true;
857 + else
858 + $return = false;
859 +
860 + if( $return ) {
861 +
862 + // If the user just earned the achievement, though, don't let them earn it again
863 + // This prevents an infinite loop if the achievement has no maximum earnings limit
864 + $last_activity = gamipress_achievement_last_user_activity( $achievement_id, $user_id );
865 + $minimum_time = current_time( 'timestamp' ) - 1;
866 +
867 + if ( $last_activity > $minimum_time )
868 + $return = false;
869 +
870 + }
871 + }
872 +
873 + // Return our eligibility status
874 + return $return;
875 +
876 +}
877 +add_filter( 'user_deserves_achievement', 'gamipress_user_meets_rank_requirement', 10, 6 );
878 +
879 +/**
880 + * Validate whether or not the user has completed all requirements for an achievement
881 + *
882 + * @since 1.0.0
883 + *
884 + * @param bool $return True if user deserves achievement, false otherwise
885 + * @param int $user_id The given user's ID
886 + * @param int $achievement_id The achievement post ID
887 + * @param string $trigger_type The trigger
888 + * @param int $site_id The triggered site id
889 + * @param array $args The triggered args
890 + *
891 + * @return bool True if user deserves limit requirements, false otherwise
892 + */
893 +function gamipress_user_deserves_limit_requirements( $return = false, $user_id = 0, $achievement_id = 0, $trigger_type = '', $site_id = 0, $args = array() ) {
894 +
895 + // Check if user has access to the achievement ($return will be false if user has exceed the limit or achievement is not published yet)
896 + if( ! $return ) {
897 + return $return;
898 + }
899 +
900 + // If we're not working with a requirement, bail here
901 + if ( ! in_array( gamipress_get_post_type( $achievement_id ), gamipress_get_requirement_types_slugs() ) ) {
902 + return $return;
903 + }
904 +
905 + if( empty( $trigger_type ) ) {
906 + $trigger_type = gamipress_get_post_meta( $achievement_id, '_gamipress_trigger_type' );
907 + }
908 +
909 + // Check if activity trigger is excluded from this check
910 + if( in_array( $trigger_type, gamipress_get_activity_triggers_excluded_from_activity_limit() ) ) {
911 + return $return;
912 + }
913 +
914 + // Check if is limited over time
915 + $since = gamipress_get_achievement_limit_timestamp( $achievement_id );
916 +
917 + if( $since > 0 ) {
918 +
919 + // Activity count limited to a timestamp
920 + $activity_count = absint( gamipress_get_achievement_activity_count( $user_id, $achievement_id, $since ) );
921 +
922 + if ( $activity_count !== 0 ) {
923 +
924 + // Activity count limit over time
925 + $limit = absint( gamipress_get_post_meta( $achievement_id, '_gamipress_limit' ) );
926 +
927 + // Force bail if user exceeds the limit over time
928 + if( $activity_count > $limit ) {
929 + return false;
930 + }
931 +
932 + }
933 +
934 + }
935 +
936 + // Get the required number of check-ins
937 + $count = absint( gamipress_get_post_meta( $achievement_id, '_gamipress_count' ) );
938 +
939 + if ( $count > 1 ) {
940 +
941 + // Grab the relevant activity count
942 + $activity_count = absint( gamipress_get_achievement_activity_count( $user_id, $achievement_id ) );
943 +
944 + // If exceed the required number of check-ins, then deserve the achievement
945 + if ( $activity_count < $count ) {
946 + return false;
947 + }
948 +
949 + }
950 +
951 + return $return;
952 +}
953 +add_filter( 'user_deserves_achievement', 'gamipress_user_deserves_limit_requirements', 10, 6 );
954 +
955 +/**
956 + * Count the user's relevant actions for a given achievement
957 + *
958 + * @since 1.0.0
959 + *
960 + * @param int $user_id The given user's ID
961 + * @param int $achievement_id The given achievement's ID
962 + * @param int $since Timestamp since retrieve the count
963 + *
964 + * @return int The total activity count
965 + */
966 +function gamipress_get_achievement_activity_count( $user_id = 0, $achievement_id = 0, $since = 0 ) {
967 +
968 + // Setup site id
969 + $site_id = get_current_blog_id();
970 +
971 + // Assume the user has no relevant activities
972 + $activity_count = 0;
973 +
974 + $post_type = gamipress_get_post_type( $achievement_id );
975 +
976 + if ( in_array( $post_type, gamipress_get_requirement_types_slugs() ) ) {
977 +
978 + // Grab the requirement object
979 + $requirement = gamipress_get_requirement_object( $achievement_id );
980 +
981 + // Determine which type of trigger we're using and return the corresponding activities
982 + switch( $requirement['trigger_type'] ) {
983 + case 'specific-achievement':
984 +
985 + if( $post_type === 'step' && $since === 0 ) {
986 +
987 + // Get our parent achievement
988 + $achievement = gamipress_get_step_achievement( $achievement_id );
989 +
990 + // If the user has any interaction with this achievement, only get activity since that date
991 + if ( $achievement && $date = gamipress_achievement_last_user_activity( $achievement->ID, $user_id ) ) {
992 + $since = $date;
993 + }
994 +
995 + }
996 +
997 + // Get our achievement activity
998 + $activity_count = gamipress_get_earnings_count( array(
999 + 'user_id' => absint( $user_id ),
1000 + 'post_id' => absint( $requirement['achievement_post'] ),
1001 + 'since' => $since
1002 + ) );
1003 + break;
1004 + case 'any-achievement':
1005 + $trigger = 'gamipress_unlock_' . $requirement['achievement_type'];
1006 + break;
1007 + case 'all-achievements':
1008 + $trigger = 'gamipress_unlock_all_' . $requirement['achievement_type'];
1009 + break;
1010 + case 'revoke-any-achievement':
1011 + $trigger = 'gamipress_revoke_' . $requirement['achievement_type'];
1012 + break;
1013 + default:
1014 + $trigger = $requirement['trigger_type'];
1015 + break;
1016 + }
1017 +
1018 + // Just continue if trigger is set
1019 + if( isset( $trigger ) ) {
1020 +
1021 + // If since is not defined check it from new ways
1022 + if( $since === 0 ) {
1023 +
1024 + if( gamipress_get_post_type( $achievement_id ) === 'rank-requirement' ) {
1025 + // If is a rank requirement, we need to get the last time user earned the latest rank of type
1026 + $rank = gamipress_get_rank_requirement_rank( $achievement_id );
1027 +
1028 + $since = gamipress_get_rank_earned_time( $user_id, $rank->post_type );
1029 + } else {
1030 +
1031 + // Get activity count from last time earned
1032 + $since = gamipress_achievement_last_user_activity( $achievement_id, $user_id );
1033 +
1034 + // If user hasn't earned this yet, then get activity count from publish date
1035 + if( $since === 0 ) {
1036 + $parent_id = absint( gamipress_get_post_field( 'post_parent', $achievement_id ) );
1037 +
1038 + // Try to get the date from the parent (achievement, rank or points type), if not possible, get it from the requirement
1039 + if( $parent_id !== 0 ) {
1040 + $since = strtotime( gamipress_get_post_date( $parent_id ) );
1041 + } else {
1042 + $since = strtotime( gamipress_get_post_date( $achievement_id ) );
1043 + }
1044 + }
1045 +
1046 + if( defined( 'GAMIPRESS_DOING_ACTIVITY_RECOUNT' ) && GAMIPRESS_DOING_ACTIVITY_RECOUNT ) {
1047 + // Reduce it in 1 if check comes from the recount activity tool
1048 + $since -= 1;
1049 + }
1050 + }
1051 +
1052 + }
1053 +
1054 + $activity_count = gamipress_get_user_trigger_count( $user_id, $trigger, $since, $site_id, $requirement );
1055 +
1056 + }
1057 +
1058 + }
1059 +
1060 + /**
1061 + * Available filter for overriding user activity count
1062 + *
1063 + * @since 1.0.0
1064 + *
1065 + * @param int $activity_count The achievement's activity count
1066 + * @param int $user_id The given user's ID
1067 + * @param int $achievement_id The given achievement's ID
1068 + * @param int $since Timestamp since retrieve the count
1069 + *
1070 + * @return int The total activity count
1071 + */
1072 + return absint( apply_filters( "gamipress_{$post_type}_activity_count", $activity_count, $user_id, $achievement_id, $since ) );
1073 +}
1074 +
1075 +/**
1076 + * Count the user's relevant actions for a given achievement applying the requirement limits
1077 + *
1078 + * @since 2.0.2
1079 + *
1080 + * @param int $user_id The given user's ID
1081 + * @param int $achievement_id The given achievement's ID
1082 + * @param int $since Timestamp since retrieve the count
1083 + *
1084 + * @return int The total activity count
1085 + */
1086 +function gamipress_get_achievement_activity_count_limited( $user_id = 0, $achievement_id = 0, $since = 0 ) {
1087 +
1088 + // Assume the user has no relevant activities
1089 + $activity_count = 0;
1090 +
1091 + $post_type = gamipress_get_post_type( $achievement_id );
1092 +
1093 + if ( in_array( $post_type, gamipress_get_requirement_types_slugs() ) ) {
1094 +
1095 + $limit_type = gamipress_get_post_meta( $achievement_id, '_gamipress_limit_type' );
1096 +
1097 + if( empty( $limit_type ) ) {
1098 + $limit_type = 'unlimited';
1099 + }
1100 +
1101 + if( $limit_type === 'unlimited' ) {
1102 + // Times activity has triggered
1103 + $activity_count = absint( gamipress_get_achievement_activity_count( $user_id, $achievement_id, $since ) );
1104 + } else {
1105 +
1106 + // Grab the requirement object
1107 + $requirement = gamipress_get_requirement_object( $achievement_id );
1108 +
1109 + $where = array(
1110 + 'type' => 'event_trigger',
1111 + );
1112 +
1113 + // Determine which type of trigger we're using and return the corresponding activities
1114 + switch( $requirement['trigger_type'] ) {
1115 + case 'specific-achievement':
1116 + $trigger = 'gamipress_unlock_' . $requirement['achievement_type'];
1117 + $where['post_id'] = absint( $requirement['achievement_post'] );
1118 + break;
1119 + case 'any-achievement':
1120 + $trigger = 'gamipress_unlock_' . $requirement['achievement_type'];
1121 + break;
1122 + case 'all-achievements':
1123 + $trigger = 'gamipress_unlock_all_' . $requirement['achievement_type'];
1124 + break;
1125 + case 'revoke-specific-achievement':
1126 + $trigger = 'gamipress_revoke_' . $requirement['achievement_type'];
1127 + $where['post_id'] = absint( $requirement['achievement_post'] );
1128 + break;
1129 + case 'revoke-any-achievement':
1130 + $trigger = 'gamipress_revoke_' . $requirement['achievement_type'];
1131 + break;
1132 + default:
1133 + $trigger = $requirement['trigger_type'];
1134 + break;
1135 + }
1136 +
1137 + $where['trigger_type'] = $trigger;
1138 + $group_by = '';
1139 +
1140 + // Grab the requirement object
1141 + $requirement = gamipress_get_requirement_object( $achievement_id );
1142 + $site_id = get_current_blog_id();
1143 +
1144 + /**
1145 + * Filter required to get the same where conditions as in the gamipress_get_user_trigger_count() function
1146 + *
1147 + * @see gamipress_get_user_trigger_count()
1148 + */
1149 + $where = apply_filters( 'gamipress_get_user_trigger_count_log_meta', $where, $user_id, $trigger, $since, $site_id, $requirement );
1150 +
1151 + /**
1152 + * Filter to override the where data to filter the logs count applying the requirement limits
1153 + *
1154 + * @since 2.0.4
1155 + *
1156 + * @param array $log_meta The meta data to filter the logs count
1157 + * @param int $user_id The given user's ID
1158 + * @param string $trigger The given trigger we're checking
1159 + * @param int $since The since timestamp where retrieve the logs
1160 + * @param int $site_id The desired Site ID to check
1161 + * @param array $args The triggered args or requirement object
1162 + *
1163 + * @return array The where data to filter the logs count
1164 + */
1165 + $where = apply_filters( 'gamipress_get_achievement_activity_count_limited_where', $where, $user_id, $trigger, $since, $site_id, $requirement );
1166 +
1167 + // If since is not defined check it from new ways
1168 + if( $since === 0 ) {
1169 +
1170 + if( $post_type === 'rank-requirement' ) {
1171 + // If is a rank requirement, we need to get the last time user earned the latest rank of type
1172 + $rank = gamipress_get_rank_requirement_rank( $achievement_id );
1173 +
1174 + $since = gamipress_get_rank_earned_time( $user_id, $rank->post_type );
1175 + } else {
1176 +
1177 + // Get activity count from last time earned
1178 + $since = gamipress_achievement_last_user_activity( $achievement_id, $user_id );
1179 +
1180 + // If user hasn't earned this yet, then get activity count from publish date
1181 + if( $since === 0 ) {
1182 + $since = strtotime( gamipress_get_post_date( $achievement_id ) ) - 1;
1183 + }
1184 +
1185 + }
1186 +
1187 + }
1188 +
1189 + // Setup the group by clause based on the requirement limit type
1190 + switch( $limit_type ) {
1191 + case 'minutely':
1192 + $group_by = 'YEAR(l.date), MONTH(l.date), DAY(l.date), HOUR(l.date), MINUTE(l.date)';
1193 + break;
1194 + case 'hourly':
1195 + $group_by = 'YEAR(l.date), MONTH(l.date), DAY(l.date), HOUR(l.date)';
1196 + break;
1197 + case 'daily':
1198 + $group_by = 'YEAR(l.date), MONTH(l.date), DAY(l.date)';
1199 + break;
1200 + case 'weekly':
1201 + $group_by = "YEAR(l.date), DATE_FORMAT( l.date, '%u' )";
1202 + break;
1203 + case 'monthly':
1204 + $group_by = 'YEAR(l.date), MONTH(l.date)';
1205 + break;
1206 + case 'yearly':
1207 + $group_by = 'YEAR(l.date)';
1208 + break;
1209 + }
1210 +
1211 + // Get the user activity counts grouped by date
1212 + $results = gamipress_query_logs( array(
1213 + 'select' => 'LEAST(COUNT(*), ' . $requirement['limit'] . ') AS count', // Least() returns the lowest value (like PHP min() function)
1214 + 'user_id' => $user_id,
1215 + 'where' => $where,
1216 + 'since' => $since,
1217 + 'group_by' => $group_by,
1218 + ) );
1219 +
1220 + if( is_array( $results ) ) {
1221 + foreach( $results as $result ) {
1222 + $activity_count += absint( $result->count );
1223 + }
1224 + }
1225 +
1226 + }
1227 +
1228 + }
1229 +
1230 + /**
1231 + * Available filter for overriding user activity count applying the requirement limits
1232 + *
1233 + * @since 1.0.0
1234 + *
1235 + * @param int $activity_count The achievement's activity count
1236 + * @param int $user_id The given user's ID
1237 + * @param int $achievement_id The given achievement's ID
1238 + * @param int $since Timestamp since retrieve the count
1239 + *
1240 + * @return int The total activity count
1241 + */
1242 + return absint( apply_filters( "gamipress_{$post_type}_activity_count_limited", $activity_count, $user_id, $achievement_id, $since ) );
1243 +
1244 +}
1245 +
1246 +/**
1247 + * Get the start date where an achievement is limiting
1248 + *
1249 + * @param int $achievement_id The achievement ID
1250 + *
1251 + * @return int Timestamp from the limit starts
1252 + */
1253 +function gamipress_get_achievement_limit_timestamp( $achievement_id = 0 ) {
1254 +
1255 + $limit_type = gamipress_get_post_meta( $achievement_id, '_gamipress_limit_type' );
1256 +
1257 + // No limit
1258 + if( ! $limit_type || $limit_type === 'unlimited' ) {
1259 + return 0;
1260 + }
1261 +
1262 + $now = current_time( 'timestamp' );
1263 + $from = current_time( 'timestamp' );
1264 +
1265 + $hour = date( 'H', $now );
1266 + $minute = date( 'i', $now );
1267 + $second = date( 's', $now );
1268 + $day = date( 'n', $now );
1269 + $month = date( 'j', $now );
1270 + $year = date( 'Y', $now );
1271 +
1272 + switch( $limit_type ) {
1273 + case 'minutely':
1274 + $from = mktime( $hour, $minute - 1, $second, $day, $month, $year );
1275 + break;
1276 + case 'hourly':
1277 + $from = mktime( $hour - 1, $minute, $second, $day, $month, $year );
1278 + break;
1279 + case 'daily':
1280 + $from = mktime( 0, 0, 0, $day, $month, $year );
1281 + break;
1282 + case 'weekly':
1283 + $from = mktime( 0, 0, 0, $day, $month - date( 'N', $now ) + 1 );
1284 + break;
1285 + case 'monthly':
1286 + $from = mktime( 0, 0, 0, $day, 1, $year );
1287 + break;
1288 + case 'yearly':
1289 + $from = mktime( 0, 0, 0, 1, 1, $year );
1290 + break;
1291 + }
1292 +
1293 + /**
1294 + * Available filter to override the start date where an achievement is limiting
1295 + *
1296 + * @since 1.0.0
1297 + *
1298 + * @param int $from The start date where an achievement is limiting
1299 + * @param int $achievement_id The achievement ID
1300 + * @param string $limit_type The achievement limit type
1301 + *
1302 + * @return int Timestamp from the limit starts
1303 + */
1304 + return apply_filters( 'gamipress_get_achievement_limit_timestamp', $from, $achievement_id, $limit_type );
1305 +
1306 +}
1307 +
1308 +/* --------------------------------------------------
1309 + * Award Checks
1310 + -------------------------------------------------- */
1311 +
1312 +/**
1313 + * Award an achievement to the user
1314 + *
1315 + * @since 1.0.0
1316 + *
1317 + * @param int $achievement_id The given achievement ID to award
1318 + * @param int $user_id The given user's ID
1319 + * @param int $admin_id The given admin's ID
1320 + * @param string $trigger The trigger
1321 + * @param int $site_id The triggered site id
1322 + * @param array $args The triggered args
1323 + *
1324 + * @return mixed False on not achievement, void otherwise
1325 + */
1326 +function gamipress_award_achievement_to_user( $achievement_id = 0, $user_id = 0, $admin_id = 0, $trigger = '', $site_id = 0, $args = array() ) {
1327 +
1328 + global $wp_filter;
1329 +
1330 + // Sanity Check: ensure we're working with an achievement or requirement post
1331 + if ( ! ( gamipress_is_achievement( $achievement_id ) || gamipress_is_requirement( $achievement_id ) ) ) {
1332 + return false;
1333 + }
1334 +
1335 + // Use the current user ID if none specified
1336 + if ( $user_id == 0 ) {
1337 + $user_id = wp_get_current_user()->ID;
1338 + }
1339 +
1340 + // Get the current site ID none specified
1341 + if ( ! $site_id ) {
1342 + $site_id = get_current_blog_id();
1343 + }
1344 +
1345 + // Setup our achievement
1346 + $achievement = gamipress_build_achievement_object( $achievement_id );
1347 +
1348 + // Update user's earned achievements
1349 + gamipress_update_user_achievements( array( 'user_id' => $user_id, 'new_achievements' => array( $achievement ) ) );
1350 +
1351 + // Log the earning of the award
1352 + gamipress_log_user_achievement_award( $user_id, $achievement_id, $admin_id, $trigger );
1353 +
1354 + // Available hook for unlocking any achievement of this achievement type
1355 + do_action( 'gamipress_unlock_' . $achievement->post_type, $user_id, $achievement_id, $trigger, $site_id, $args );
1356 +
1357 + // Patch for WordPress to support recursive actions, specifically for gamipress_award_achievement
1358 + // Because global iteration is fun, assuming we can get this fixed for WordPress 3.9
1359 + $is_recursive_filter = ( current_filter() === 'gamipress_award_achievement');
1360 + $current_key = null;
1361 +
1362 + $gamipress_award_achievement_filter = $wp_filter[ 'gamipress_award_achievement' ];
1363 +
1364 + // Ensure that filter is an array
1365 + if ( ! is_array( $gamipress_award_achievement_filter ) ) {
1366 + $gamipress_award_achievement_filter = (array) $gamipress_award_achievement_filter;
1367 + }
1368 +
1369 + // Get current position
1370 + if ( $is_recursive_filter ) {
1371 + $current_key = key( $gamipress_award_achievement_filter );
1372 + }
1373 +
1374 + /**
1375 + * Available hook to do other things with each awarded achievement
1376 + *
1377 + * @since 1.0.0
1378 + *
1379 + * @param int $user_id The given user's ID
1380 + * @param int $achievement_id The given achievement ID to award
1381 + * @param string $trigger The trigger
1382 + * @param int $site_id The triggered site id
1383 + * @param array $args The triggered args
1384 + */
1385 + do_action( 'gamipress_award_achievement', $user_id, $achievement_id, $trigger, $site_id, $args );
1386 +
1387 + if ( $is_recursive_filter ) {
1388 +
1389 + reset( $gamipress_award_achievement_filter );
1390 +
1391 + while ( key( $gamipress_award_achievement_filter ) !== $current_key ) {
1392 + next( $gamipress_award_achievement_filter );
1393 + }
1394 +
1395 + }
1396 +
1397 +}
1398 +
1399 +/**
1400 + * Award additional achievements to user
1401 + *
1402 + * @since 1.0.0
1403 + * @update 1.6.9 Correctly add the trigger type to the maybe award function call
1404 + *
1405 + * @param int $user_id The given user's ID
1406 + * @param int $achievement_id The given achievement's post ID
1407 + *
1408 + * @return void
1409 + */
1410 +function gamipress_maybe_award_additional_achievements_to_user( $user_id = 0, $achievement_id = 0 ) {
1411 +
1412 + // Get the achievement post type
1413 + $post_type = gamipress_get_post_type( $achievement_id );
1414 + $achievement_types = gamipress_get_achievement_types_slugs();
1415 + $rank_types = gamipress_get_achievement_types_slugs();
1416 +
1417 + // Get achievements that can be earned from completing this achievement
1418 + $dependent_achievements = gamipress_get_dependent_achievements( $achievement_id );
1419 +
1420 + // See if the user has unlocked all achievements of a given type
1421 + gamipress_maybe_trigger_unlock_all( $user_id, $achievement_id );
1422 +
1423 + // Loop through each dependent achievement and see if it can be awarded
1424 + foreach ( $dependent_achievements as $achievement ) {
1425 + $trigger = '';
1426 +
1427 + $trigger_type = gamipress_get_post_meta( $achievement->ID, '_gamipress_trigger_type' );
1428 +
1429 + // Skip achievements for revoking
1430 + if( in_array( $trigger_type, array( 'revoke-specific-achievement', 'revoke-rank' ) ) ) {
1431 + continue;
1432 + }
1433 +
1434 + if( in_array( $post_type, $achievement_types ) ) {
1435 + $trigger = 'specific-achievement';
1436 + } else if( in_array( $post_type, $rank_types ) ) {
1437 + $trigger = 'earn-rank';
1438 + }
1439 +
1440 + gamipress_maybe_award_achievement_to_user( $achievement->ID, $user_id, $trigger );
1441 + }
1442 +
1443 +}
1444 +add_action( 'gamipress_award_achievement', 'gamipress_maybe_award_additional_achievements_to_user', 10, 2 );
1445 +
1446 +/**
1447 + * Check if the user has unlocked all achievements of a given type
1448 + *
1449 + * Triggers hook gamipress_unlock_all_{$post_type}
1450 + *
1451 + * @since 1.0.0
1452 + *
1453 + * @param int $user_id The given user's ID
1454 + * @param int $achievement_id The given achievement's post ID
1455 + *
1456 + * @return void
1457 + */
1458 +function gamipress_maybe_trigger_unlock_all( $user_id = 0, $achievement_id = 0 ) {
1459 +
1460 + // Get the post type of the earned achievement
1461 + $post_type = gamipress_get_post_type( $achievement_id );
1462 +
1463 + // Grab our user's (presumably updated) earned achievements
1464 + $earned_achievements = gamipress_get_user_achievements( array( 'user_id' => $user_id, 'achievement_type' => $post_type ) );
1465 +
1466 + // Hook for unlocking all achievements of this achievement type
1467 + if ( $all_achievements_of_type = gamipress_get_achievements( array( 'post_type' => $post_type ) ) ) {
1468 +
1469 + // Assume we can award the user for unlocking all achievements of this type
1470 + $all_per_type = true;
1471 +
1472 + // Loop through each of our achievements of this type
1473 + foreach ( $all_achievements_of_type as $achievement ) {
1474 +
1475 + // Assume the user hasn't earned this achievement
1476 + $found_achievement = false;
1477 +
1478 + // Loop through each earned achievement and see if we've earned it
1479 + foreach ( $earned_achievements as $earned_achievement ) {
1480 + if ( $earned_achievement->ID == $achievement->ID ) {
1481 + $found_achievement = true;
1482 + break;
1483 + }
1484 + }
1485 +
1486 + // If we haven't earned this single achievement, we haven't earned them all
1487 + if ( ! $found_achievement ) {
1488 + $all_per_type = false;
1489 + break;
1490 + }
1491 + }
1492 +
1493 + // If we've earned all achievements of this type, trigger our hook
1494 + if ( $all_per_type ) {
1495 + do_action( 'gamipress_unlock_all_' . $post_type, $user_id, $achievement_id );
1496 + }
1497 + }
1498 +}
1499 +
1500 +/**
1501 + * Award new points to the user based on logged activites and earned achievements
1502 + *
1503 + * @since 1.0.0
1504 + *
1505 + * @param int $user_id The given user's ID
1506 + * @param int $achievement_id The given achievement's post ID
1507 + */
1508 +function gamipress_maybe_award_points( $user_id = 0, $achievement_id = 0 ) {
1509 + // Grab our points from the provided post
1510 + $points = absint( gamipress_get_post_meta( $achievement_id, '_gamipress_points' ) );
1511 + $points_type = gamipress_get_post_meta( $achievement_id, '_gamipress_points_type' );
1512 + $points_types = gamipress_get_points_types();
1513 +
1514 + if ( isset( $points_types[$points_type] ) ) {
1515 +
1516 + /**
1517 + * Filter available to override the amount of points to award or deduct
1518 + *
1519 + * @since 6.9.8
1520 + *
1521 + * @param int $points The points amount
1522 + * @param string $points_type The points type
1523 + * @param int $user_id The given user's ID
1524 + * @param int $achievement_id The given achievement's post ID
1525 + *
1526 + * @return int
1527 + */
1528 + $points = apply_filters( 'gamipress_maybe_award_points_amount', $points, $points_type, $user_id, $achievement_id );
1529 +
1530 + if( ! empty( $points ) ) {
1531 + $post_type = gamipress_get_post_type( $achievement_id );
1532 +
1533 + if( $post_type === 'points-deduct' ) {
1534 + gamipress_deduct_points_to_user( $user_id, $points, $points_type, array( 'achievement_id' => $achievement_id ) );
1535 + } else {
1536 + gamipress_award_points_to_user( $user_id, $points, $points_type, array( 'achievement_id' => $achievement_id ) );
1537 + }
1538 + }
1539 + }
1540 +
1541 +}
1542 +add_action( 'gamipress_award_achievement', 'gamipress_maybe_award_points', 10, 2 );
1543 +
1544 +/**
1545 + * Award same achievement to the user based on how many points has earn
1546 + *
1547 + * @since 1.0.0
1548 + *
1549 + * @param int $user_id The given user's ID
1550 + * @param int $achievement_id The given achievement's post ID
1551 + */
1552 +function gamipress_maybe_award_multiple_points( $user_id = 0, $achievement_id = 0 ) {
1553 +
1554 + $post_type = gamipress_get_post_type( $achievement_id );
1555 +
1556 + // First, see if the requirement requires a minimum amount of points (just requirements has this meta)
1557 + // Note: Rank requirements are excluded from this
1558 + if ( 'earn-points' === gamipress_get_post_meta( $achievement_id, '_gamipress_trigger_type' )
1559 + && ! in_array( $post_type, array( 'rank-requirement' ) ) ) {
1560 +
1561 + // Grab our user's points and see if they at least as many as required
1562 + $points_required = absint( gamipress_get_post_meta( $achievement_id, '_gamipress_points_required' ) );
1563 + $points_type_required = gamipress_get_post_meta( $achievement_id, '_gamipress_points_type_required' );
1564 + $multiple_award_key = "gamipress_doing_multiple_{$points_type_required}_award";
1565 +
1566 + // Check if we are in a loop of multiple points to award
1567 + if( ! ( isset( $GLOBALS[$multiple_award_key] ) && $GLOBALS[$multiple_award_key] === true ) ) {
1568 +
1569 + $last_achievement_activity = absint( gamipress_achievement_last_user_activity( $achievement_id, $user_id ) );
1570 +
1571 + // Get user points earned since last time has earning the achievement
1572 + $user_last_points = gamipress_get_user_points_awarded_in_loop( $user_id, $points_type_required, $achievement_id );
1573 +
1574 + if( $user_last_points >= $points_required && $points_required > 0 ) {
1575 +
1576 + // Starting multiple points award
1577 + $GLOBALS[$multiple_award_key] = true;
1578 +
1579 + // Set times to award
1580 + $times_to_award = intval( $user_last_points / $points_required );
1581 +
1582 + // Check the maximum times this requirement could be earned
1583 + $maximum_earnings = absint( gamipress_get_post_meta( $achievement_id, '_gamipress_maximum_earnings' ) );
1584 +
1585 + // If maximum earnings is different to 0, we need to set how many times to award it
1586 + if( $maximum_earnings !== 0 ) {
1587 +
1588 + $earned_times = gamipress_get_earnings_count( array(
1589 + 'user_id' => absint( $user_id ),
1590 + 'post_id' => absint( $achievement_id ),
1591 + 'since' => $last_achievement_activity
1592 + ) );
1593 +
1594 + // If times to award and earned times exceed the maximum earnings
1595 + if( ( $times_to_award + $earned_times ) >= $maximum_earnings )
1596 + $times_to_award = ( $maximum_earnings - $earned_times );
1597 +
1598 + }
1599 +
1600 + // Award same achievement many times (rules engine will check limited times to earn it)
1601 + for( $i=0; $i < $times_to_award; $i++ ) {
1602 + gamipress_award_achievement_to_user( $achievement_id, $user_id );
1603 +
1604 + // On every loop increase the points awarded
1605 + gamipress_update_user_points_awarded_in_loop( $points_required, $points_type_required, $achievement_id, $user_id );
1606 + }
1607 +
1608 + // Ending multiple points award
1609 + $GLOBALS[$multiple_award_key] = false;
1610 +
1611 + }
1612 +
1613 + }
1614 +
1615 + }
1616 +
1617 +}
1618 +add_action( 'gamipress_award_achievement', 'gamipress_maybe_award_multiple_points', 15, 2 );
1619 +
1620 +/**
1621 + * Award new rank to the user based on logged activites and earned achievements
1622 + *
1623 + * @since 1.3.1
1624 + *
1625 + * @param int $user_id The given user's ID
1626 + * @param int $achievement_id The given achievement's post ID
1627 + */
1628 +function gamipress_maybe_award_rank( $user_id = 0, $achievement_id = 0 ) {
1629 +
1630 + if( gamipress_get_post_type( $achievement_id ) !== 'rank-requirement' ) {
1631 + return;
1632 + }
1633 +
1634 + // Get the requirement rank
1635 + $rank = gamipress_get_rank_requirement_rank( $achievement_id );
1636 +
1637 + if( ! $rank )
1638 + return;
1639 +
1640 + $old_rank = gamipress_get_user_rank( $user_id, $rank->post_type );
1641 +
1642 + // Return if current rank is this one
1643 + if( $old_rank && $old_rank->ID === $rank->ID )
1644 + return;
1645 +
1646 + // Get all requirements of this rank
1647 + $requirements = gamipress_get_rank_requirements( $rank->ID );
1648 +
1649 + $completed = true;
1650 +
1651 + foreach( $requirements as $requirement ) {
1652 +
1653 + // Skip optional requirements
1654 + if( ( bool ) gamipress_get_post_meta( $requirement->ID, '_gamipress_optional' ) ) {
1655 + continue;
1656 + }
1657 +
1658 + // Check if rank requirement has been earned
1659 + if( gamipress_get_earnings_count( array(
1660 + 'user_id' => $user_id,
1661 + 'post_id' => $requirement->ID,
1662 + 'since' => strtotime( $rank->post_date )
1663 + ) ) === 0 ) {
1664 + $completed = false;
1665 + break;
1666 + }
1667 +
1668 + }
1669 +
1670 + // If all rank requirements has been completed, award the rank
1671 + if ( $completed ) {
1672 + gamipress_update_user_rank( $user_id, $rank->ID, false, $achievement_id );
1673 + }
1674 +
1675 +}
1676 +add_action( 'gamipress_award_achievement', 'gamipress_maybe_award_rank', 10, 2 );
1677 +
1678 +/* --------------------------------------------------
1679 + * Revoke Checks
1680 + -------------------------------------------------- */
1681 +
1682 +/**
1683 + * Revoke an achievement to the user
1684 + *
1685 + * @since 1.4.3
1686 + *
1687 + * @param int $achievement_id The given achievement's post ID
1688 + * @param int $user_id The given user's ID
1689 + * @param int $earning_id The user's earning ID
1690 + *
1691 + * @return void
1692 + */
1693 +function gamipress_revoke_achievement_to_user( $achievement_id = 0, $user_id = 0, $earning_id = 0 ) {
1694 +
1695 + // Use the current user's ID if none specified
1696 + if ( ! $user_id ) {
1697 + $user_id = get_current_user_id();
1698 + }
1699 +
1700 + // Setup CT object
1701 + $ct_table = ct_setup_table( 'gamipress_user_earnings' );
1702 +
1703 + // If earning ID not provide, find it
1704 + if( $earning_id === 0 ) {
1705 +
1706 + $query = new CT_Query( array(
1707 + 'user_id' => $user_id,
1708 + 'post_id' => $achievement_id,
1709 + 'items_per_page' => 1
1710 + ) );
1711 +
1712 + $results = $query->get_results();
1713 +
1714 + if( count( $results ) > 0 ) {
1715 + $earning_id = $results[0]->user_earning_id;
1716 + }
1717 +
1718 + }
1719 +
1720 + $earning = false;
1721 +
1722 + if( $earning_id ) {
1723 + $earning = ct_get_object( $earning_id );
1724 + }
1725 +
1726 + // If achievement ID provided, check if requirement should get revoked too
1727 + if( $achievement_id !== 0 ) {
1728 +
1729 + // Setup vars
1730 + $post_type = gamipress_get_post_type( $achievement_id );
1731 + $points_types = gamipress_get_points_types();
1732 + $is_achievement = in_array( $post_type, gamipress_get_achievement_types_slugs() );
1733 + $is_rank = in_array( $post_type, gamipress_get_rank_types_slugs() );
1734 +
1735 + // If is achievement or rank, revoke also all its requirements
1736 + if ( $is_achievement || $is_rank ) {
1737 +
1738 + /**
1739 + * Available filter to determine if should revoke requirements when parent element is revoked too
1740 + *
1741 + * @since 1.8.7
1742 + *
1743 + * @param bool $revoke Revoke confirmation, by default true
1744 + * @param int $user_id The given user ID
1745 + * @param int $achievement_id The post ID to revoke
1746 + * @param int $earning_id The user earning ID
1747 + */
1748 + $revoke_requirements = apply_filters( 'gamipress_revoke_requirements_on_revoke_parent', true, $achievement_id, $user_id, $earning_id );
1749 +
1750 + if( $revoke_requirements ) {
1751 +
1752 + $requirements = array();
1753 +
1754 + if ( $is_achievement ) {
1755 + // Get the achievement steps
1756 + $requirements = gamipress_get_achievement_steps( $achievement_id );
1757 + } else if ( $is_rank ) {
1758 + // Get the rank requirements
1759 + $requirements = gamipress_get_rank_requirements( $achievement_id );
1760 + }
1761 +
1762 + foreach( $requirements as $requirement ) {
1763 + gamipress_revoke_achievement_to_user( $requirement->ID, $user_id );
1764 + $last_interaction = current_time( 'timestamp' );
1765 + update_user_meta( $user_id, '_gamipress_' . $requirement->ID . '_last_interaction_timestamp', $last_interaction );
1766 + }
1767 +
1768 + }
1769 +
1770 + }
1771 +
1772 + // If is revoking the current user rank, update user rank with the previous one
1773 + if( $is_rank ) {
1774 +
1775 + $user_rank_id = gamipress_get_user_rank_id( $user_id, $post_type );
1776 +
1777 + if( $achievement_id === $user_rank_id ) {
1778 +
1779 + $prev_rank_id = gamipress_get_prev_rank_id( $user_rank_id );
1780 +
1781 + $prev_rank_query = new CT_Query( array(
1782 + 'user_id' => $user_id,
1783 + 'post_id' => $prev_rank_id,
1784 + 'items_per_page' => 1
1785 + ) );
1786 +
1787 + $prev_rank_earnings = $prev_rank_query->get_results();
1788 +
1789 + // If previous rank is registered in user earnings, remove the action to register it in user earnings
1790 + if( count( $prev_rank_earnings ) > 0 ) {
1791 + remove_action( 'gamipress_update_user_rank', 'gamipress_register_user_rank_earning', 10 );
1792 + }
1793 +
1794 + // Update the user rank with the previous one
1795 + gamipress_update_user_rank( $user_id, $prev_rank_id );
1796 +
1797 + // Restore the removed action
1798 + if( count( $prev_rank_earnings ) > 0 ) {
1799 + add_action( 'gamipress_update_user_rank', 'gamipress_register_user_rank_earning', 10, 5 );
1800 + }
1801 +
1802 + }
1803 +
1804 + }
1805 +
1806 + if( $earning ) {
1807 + // Get the points and points type from the user earning entry
1808 + // Check if negative points
1809 + $is_negative = $earning->points < 0;
1810 + $points = absint( $earning->points );
1811 +
1812 + if( $is_negative )
1813 + $points *= -1;
1814 +
1815 + $points_type = $earning->points_type;
1816 + } else {
1817 + // Get the points and points type from the post provided
1818 + $points = absint( gamipress_get_post_meta( $achievement_id, '_gamipress_points' ) );
1819 + $points_type = gamipress_get_post_meta( $achievement_id, '_gamipress_points_type' );
1820 + }
1821 +
1822 + if( $points !== 0 ) {
1823 +
1824 + // Turn the "points-type" post type to award or deduct type depending on the amount of points assigned
1825 + if( $post_type === 'points-type' ) {
1826 + if( $points > 0 ) {
1827 + $post_type = 'points-award';
1828 + } else {
1829 + $post_type = 'points-deduct';
1830 + }
1831 + }
1832 +
1833 + if ( in_array( $post_type, gamipress_get_achievement_types_slugs() ) || $post_type === 'points-award' ) {
1834 +
1835 + /**
1836 + * Available filter to determine if should deduct points when an element is revoked
1837 + *
1838 + * @since 1.8.9
1839 + *
1840 + * @param bool $revoke Deduct confirmation, by default true
1841 + * @param int $user_id The given user ID
1842 + * @param int $achievement_id The post ID to revoke (commonly an achievement ID or points award ID)
1843 + * @param int $earning_id The user earning ID
1844 + */
1845 + $deduct_points = apply_filters( 'gamipress_deduct_points_on_revoke', true, $achievement_id, $user_id, $earning_id );
1846 +
1847 + if( $deduct_points ) {
1848 +
1849 + if ( ! empty( $points ) && isset( $points_types[$points_type] ) ) {
1850 + // Revoke points awarded from this achievement or points award
1851 + gamipress_deduct_points_to_user( $user_id, $points, $points_type, array( 'achievement_id' => $achievement_id ) );
1852 + }
1853 +
1854 + }
1855 +
1856 + }
1857 +
1858 + if ( $post_type === 'points-deduct' ) {
1859 +
1860 + /**
1861 + * Available filter to determine if should award points when an element is revoked
1862 + *
1863 + * @since 1.8.9
1864 + *
1865 + * @param bool $revoke Award confirmation, by default true
1866 + * @param int $user_id The given user ID
1867 + * @param int $achievement_id The post ID to revoke (commonly a points deduct ID)
1868 + * @param int $earning_id The user earning ID
1869 + */
1870 + $award_points = apply_filters( 'gamipress_award_points_on_revoke', true, $achievement_id, $user_id, $earning_id );
1871 +
1872 + if( $award_points ) {
1873 +
1874 + if ( ! empty( $points ) && isset( $points_types[$points_type] ) ) {
1875 + // Restore points deducted from these points deduct
1876 + gamipress_award_points_to_user( $user_id, $points, $points_type, array( 'achievement_id' => $achievement_id ) );
1877 + }
1878 +
1879 + }
1880 +
1881 + }
1882 +
1883 + }
1884 +
1885 + /**
1886 + * Available action per type for triggering other processes
1887 + *
1888 + * @since 2.3.1
1889 + *
1890 + * @param int $user_id The given user's ID
1891 + * @param int $achievement_id The given achievement's post ID
1892 + * @param int $earning_id The user's earning ID
1893 + */
1894 + do_action( 'gamipress_revoke_' . $post_type, $user_id, $achievement_id, $earning_id );
1895 +
1896 + }
1897 +
1898 + /**
1899 + * Available action for triggering other processes
1900 + *
1901 + * @since 1.4.3
1902 + *
1903 + * @param int $user_id The given user's ID
1904 + * @param int $achievement_id The given achievement's post ID
1905 + * @param int $earning_id The user's earning ID
1906 + */
1907 + do_action( 'gamipress_revoke_achievement_to_user', $user_id, $achievement_id, $earning_id );
1908 +
1909 + if( $earning_id ) {
1910 + $ct_table->db->delete( $earning_id );
1911 + }
1912 +
1913 + /**
1914 + * Available action for triggering after removing achievement
1915 + *
1916 + * @since 2.5.9
1917 + *
1918 + * @param int $user_id The given user's ID
1919 + * @param int $achievement_id The given achievement's post ID
1920 + * @param int $earning_id The user's earning ID
1921 + */
1922 + do_action( 'gamipress_revoke_achievement_to_user_after', $user_id, $achievement_id, $earning_id );
1923 +
1924 + ct_reset_setup_table();
1925 +
1926 +}
1927 +
1928 +/**
1929 + * Award additional achievements to user
1930 + *
1931 + * @since 2.3.1
1932 + *
1933 + * @param int $user_id The given user's ID
1934 + * @param int $achievement_id The given achievement's post ID
1935 + *
1936 + * @return void
1937 + */
1938 +function gamipress_maybe_award_additional_achievements_to_user_on_revoke( $user_id = 0, $achievement_id = 0 ) {
1939 +
1940 + // Get the achievement post type
1941 + $post_type = gamipress_get_post_type( $achievement_id );
1942 + $achievement_types = gamipress_get_achievement_types_slugs();
1943 + $rank_types = gamipress_get_achievement_types_slugs();
1944 +
1945 + // Get achievements that can be earned from completing this achievement
1946 + $dependent_achievements = gamipress_get_dependent_achievements( $achievement_id );
1947 +
1948 + // Loop through each dependent achievement and see if it can be awarded
1949 + foreach ( $dependent_achievements as $achievement ) {
1950 + $trigger = '';
1951 +
1952 + $trigger_type = gamipress_get_post_meta( $achievement->ID, '_gamipress_trigger_type' );
1953 +
1954 + // Skip achievements for awarding
1955 + if( in_array( $trigger_type, array( 'specific-achievement', 'earn-rank' ) ) ) {
1956 + continue;
1957 + }
1958 +
1959 + if( in_array( $post_type, $achievement_types ) ) {
1960 + $trigger = 'revoke-specific-achievement';
1961 + } else if( in_array( $post_type, $rank_types ) ) {
1962 + $trigger = 'revoke-rank';
1963 + }
1964 +
1965 + gamipress_maybe_award_achievement_to_user( $achievement->ID, $user_id, $trigger );
1966 + }
1967 +
1968 +}
1969 +add_action( 'gamipress_revoke_achievement_to_user', 'gamipress_maybe_award_additional_achievements_to_user_on_revoke', 10, 2 );