PluginProbe
GamiPress – Gamification plugin to reward points, badges & ranks in WordPress, now with AI / trunk
GamiPress – Gamification plugin to reward points, badges & ranks in WordPress, now with AI vtrunk
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 7.8.7 7.8.6 7.8.5 All 44 releases
gamipress / includes / rules-engine.php

rules-engine.php in GamiPress – Gamification plugin to reward points, badges & ranks in WordPress, now with AI trunk, at includes/rules-engine.php

1,969 lines 72.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Rules Engine: The brains behind this whole operation
4 *
5 * @package GamiPress\Rules_Engine
6 * @author GamiPress <contact@gamipress.com>, Ruben Garcia <rubengcdev@gmail.com>
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 );