PluginProbe
GamiPress – Gamification plugin to reward points, badges & ranks in WordPress, now with AI / 8.0.6
GamiPress – Gamification plugin to reward points, badges & ranks in WordPress, now with AI v8.0.6
8.0.6 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 All 48 releases
← All changes | includes/ajax-functions.php +1146 -0 7.8.9 → 8.0.6 View file →
@@ -1,0 +1,1146 @@
1 +<?php
2 +/**
3 + * Ajax Functions
4 + *
5 + * @package GamiPress\Ajax_Functions
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 + * Ajax Helper for returning achievements
14 + *
15 + * @since 1.0.0
16 + * @updated 1.7.9 Added nonce usage
17 + *
18 + * @return void
19 + */
20 +function gamipress_ajax_get_achievements() {
21 + // Security check, forces to die if not security passed
22 + check_ajax_referer( 'gamipress', 'nonce' );
23 +
24 + // Send back our successful response
25 + wp_send_json_success( gamipress_achievements_shortcode_query( $_REQUEST ) );
26 +
27 +}
28 +add_action( 'wp_ajax_gamipress_get_achievements', 'gamipress_ajax_get_achievements' );
29 +add_action( 'wp_ajax_nopriv_gamipress_get_achievements', 'gamipress_ajax_get_achievements' );
30 +
31 +/**
32 + * Ajax Helper for returning logs
33 + *
34 + * @since 1.4.9
35 + * @updated 1.7.9 Added nonce usage
36 + *
37 + * @return void
38 + */
39 +function gamipress_ajax_get_logs() {
40 + // Security check, forces to die if not security passed
41 + check_ajax_referer( 'gamipress', 'nonce' );
42 +
43 + // Set current page var
44 + if( isset( $_REQUEST['page'] ) && absint( $_REQUEST['page'] ) > 1 ) {
45 + set_query_var( 'paged', absint( $_REQUEST['page'] ) );
46 + }
47 +
48 + $atts = $_REQUEST;
49 +
50 + // Change the attribute to display only public logs if current user is different
51 + if( $atts['access'] !== 'public' ) {
52 + $current_user_id = get_current_user_id();
53 +
54 + // If user is not logged in, force to public
55 + if ( $current_user_id === 0 ) $atts['access'] = 'public';
56 + // If user ID is different from logged in, force to public
57 + if ( $current_user_id !== absint( $atts['user_id'] ) ) $atts['access'] = 'public';
58 + }
59 +
60 + // Unset non required shortcode atts
61 + unset( $atts['action'] );
62 + unset( $atts['page'] );
63 +
64 + // Sanitize
65 + foreach( $atts as $attr => $value ) {
66 + $value = sanitize_text_field( $value );
67 + $atts[$attr] = str_replace( array( '[', ']' ), '', $value);
68 + }
69 +
70 + $atts = shortcode_atts( gamipress_logs_shortcode_defaults(), $atts, 'gamipress_logs' );
71 +
72 + // Send back our successful response
73 + wp_send_json_success( gamipress_do_shortcode( 'gamipress_logs', $atts ) );
74 +
75 +}
76 +add_action( 'wp_ajax_gamipress_get_logs', 'gamipress_ajax_get_logs' );
77 +add_action( 'wp_ajax_nopriv_gamipress_get_logs', 'gamipress_ajax_get_logs' );
78 +
79 +/**
80 + * Ajax Helper for returning user earnings
81 + *
82 + * @since 1.4.9
83 + * @updated 1.7.9 Added nonce usage
84 + *
85 + * @return void
86 + */
87 +function gamipress_ajax_get_user_earnings() {
88 + // Security check, forces to die if not security passed
89 + check_ajax_referer( 'gamipress', 'nonce' );
90 +
91 + // Set current page var
92 + if( isset( $_REQUEST['page'] ) && absint( $_REQUEST['page'] ) > 1 ) {
93 + set_query_var( 'paged', absint( $_REQUEST['page'] ) );
94 + }
95 +
96 + $atts = $_REQUEST;
97 +
98 + // Unset non required shortcode atts
99 + unset( $atts['action'] );
100 + unset( $atts['page'] );
101 +
102 + // Sanitize
103 + foreach( $atts as $attr => $value ) {
104 + $atts[$attr] = sanitize_text_field( $value );
105 + $atts[$attr] = str_replace( array( '[', ']' ), '', $value);
106 + }
107 +
108 + $atts = shortcode_atts( gamipress_earnings_shortcode_defaults(), $atts, 'gamipress_earnings' );
109 +
110 + // Send back our successful response
111 + wp_send_json_success( gamipress_do_shortcode( 'gamipress_earnings', $atts ) );
112 +
113 +}
114 +add_action( 'wp_ajax_gamipress_get_user_earnings', 'gamipress_ajax_get_user_earnings' );
115 +add_action( 'wp_ajax_nopriv_gamipress_get_user_earnings', 'gamipress_ajax_get_user_earnings' );
116 +
117 +/**
118 + * Ajax Helper for save email settings
119 + *
120 + * @since 2.2.1
121 + *
122 + * @return void
123 + */
124 +function gamipress_ajax_save_email_settings() {
125 + // Security check, forces to die if not security passed
126 + check_ajax_referer( 'gamipress', 'nonce' );
127 +
128 + if( ! isset( $_REQUEST['setting'] ) ) {
129 + wp_send_json_error( __( 'Invalid setting key.', 'gamipress' ) );
130 + }
131 +
132 + if( ! isset( $_REQUEST['value'] ) ) {
133 + wp_send_json_error( __( 'Invalid setting value.', 'gamipress' ) );
134 + }
135 +
136 + // Sanitize the setting key
137 + $setting = sanitize_text_field( $_REQUEST['setting'] );
138 + $setting = sanitize_key( $setting );
139 +
140 + // Sanitize the setting value
141 + $value = sanitize_text_field( $_REQUEST['value'] );
142 +
143 + // The unique accepted values are "yes" or "no"
144 + if( $value !== 'yes' && $value !== 'no' ) {
145 + $value = 'yes';
146 + }
147 +
148 + $user_id = get_current_user_id();
149 +
150 + // Bail if user is not logged in
151 + if( $user_id === 0 ) {
152 + wp_send_json_error( __( 'Please, log in to update your email preferences.', 'gamipress' ) );
153 + }
154 +
155 + $user_settings = gamipress_get_user_email_settings( $user_id );
156 +
157 + $user_settings[$setting] = $value;
158 +
159 + switch ( $setting ) {
160 + case 'all':
161 + // Update all user settings to the same value
162 + foreach( $user_settings as $user_setting => $setting_value ) {
163 + $user_settings[$user_setting] = $value;
164 + }
165 + break;
166 + case 'points_types':
167 + case 'achievement_types':
168 + case 'rank_types':
169 + // Update the group settings to the same value
170 + foreach( $user_settings as $user_setting => $setting_value ) {
171 + if( gamipress_starts_with( $user_setting, $setting . '_' ) ) {
172 + $user_settings[$user_setting] = $value;
173 + }
174 + }
175 + break;
176 + }
177 +
178 + update_user_meta( $user_id, 'gamipress_email_settings', $user_settings );
179 +
180 + // Send back our successful response
181 + wp_send_json_success( __( 'Email preferences saved successfully.', 'gamipress' ) );
182 +
183 +}
184 +add_action( 'wp_ajax_gamipress_save_email_settings', 'gamipress_ajax_save_email_settings' );
185 +
186 +/**
187 + * AJAX Helper for selecting users in Shortcode Embedder
188 + *
189 + * @since 1.0.0
190 + */
191 +function gamipress_ajax_get_users() {
192 + // Security check, forces to die if not security passed
193 + check_ajax_referer( 'gamipress_admin', 'nonce' );
194 +
195 + if( ! current_user_can( gamipress_get_manager_capability() ) ) {
196 + return;
197 + }
198 +
199 + // If no word query sent, initialize it
200 + if ( ! isset( $_REQUEST['q'] ) ) {
201 + $_REQUEST['q'] = '';
202 + }
203 +
204 + global $wpdb;
205 +
206 + // Pull back the search string
207 + $search = esc_sql( $wpdb->esc_like( $_REQUEST['q'] ) );
208 + $where = '';
209 + $from = "FROM {$wpdb->users} as u ";
210 +
211 + if ( ! empty( $search ) ) {
212 + $where = "WHERE ( u.user_login LIKE '%{$search}%' ";
213 + $where .= "OR u.user_email LIKE '%{$search}%' ";
214 + $where .= "OR u.display_name LIKE '%{$search}%' ";
215 + $where .= "OR u.ID LIKE '%{$search}%' ) ";
216 + }
217 +
218 + // Get users from the current site
219 + if( is_multisite() ) {
220 + $from .= "LEFT JOIN {$wpdb->usermeta} AS umcap ON ( umcap.user_id = u.ID ) ";
221 +
222 + // Check if where have been initialized or not
223 + if( empty( $where ) ) {
224 + $where = "WHERE ";
225 + } else {
226 + $where .= "AND ";
227 + }
228 +
229 + $where .= "umcap.meta_key = '" . $wpdb->get_blog_prefix() . "capabilities' ";
230 + }
231 +
232 + // Pagination args
233 + $page = isset( $_REQUEST['page'] ) ? absint( $_REQUEST['page'] ) : 1;
234 + $limit = 20;
235 + $offset = $limit * ( $page - 1 );
236 +
237 + // Fetch our results (store as associative array)
238 + $results = $wpdb->get_results(
239 + "SELECT ID, user_login, user_email, display_name
240 + {$from}
241 + {$where}
242 + LIMIT {$offset}, {$limit}",
243 + 'ARRAY_A'
244 + );
245 +
246 + $count = absint( $wpdb->get_var( "SELECT COUNT(*) {$from} {$where}" ) );
247 +
248 + /**
249 + * Ajax users results (used on almost every post selector)
250 + * Note: Use $_REQUEST for all given parameters
251 + *
252 + * @since 1.0.0
253 + *
254 + * @param array $results
255 + *
256 + * @return array
257 + */
258 + $results = apply_filters( 'gamipress_ajax_get_user_results', $results );
259 +
260 + $response = array(
261 + 'results' => $results,
262 + 'more_results' => $count > $offset,
263 + );
264 +
265 + // Return our results
266 + wp_send_json_success( $response );
267 +}
268 +add_action( 'wp_ajax_gamipress_get_users', 'gamipress_ajax_get_users' );
269 +
270 +/**
271 + * AJAX Helper for selecting posts
272 + *
273 + * @since 1.0.0
274 + * @updated 1.4.8 Added multisite support
275 + */
276 +function gamipress_ajax_get_posts() {
277 + // Security check, forces to die if not security passed
278 + check_ajax_referer( 'gamipress_admin', 'nonce' );
279 +
280 + if( ! current_user_can( gamipress_get_manager_capability() ) ) {
281 + return;
282 + }
283 +
284 + global $wpdb;
285 +
286 + // Pull back the search string
287 + $search = isset( $_REQUEST['q'] ) ? esc_sql( $wpdb->esc_like( $_REQUEST['q'] ) ) : '';
288 +
289 + // Setup where conditions (initialized with 1=1)
290 + $where = '1=1';
291 +
292 + // Post type conditional
293 + $post_type = ( isset( $_REQUEST['post_type'] ) && ! empty( $_REQUEST['post_type'] ) ? $_REQUEST['post_type'] : array( 'post', 'page' ) );
294 +
295 + if ( is_array( $post_type ) ) {
296 +
297 + // Sanitize all post types given
298 + foreach( $post_type as $i => $value ) {
299 + $post_type[$i] = sanitize_text_field( $value );
300 + }
301 +
302 + $post_type = sprintf( ' AND p.post_type IN(\'%s\')', implode( "','", $post_type ) );
303 + } else {
304 +
305 + // Sanitize the post type
306 + $post_type = sanitize_text_field( $post_type );
307 +
308 + $post_type = sprintf( ' AND p.post_type = \'%s\'', $post_type );
309 + }
310 +
311 + $where .= $post_type;
312 +
313 + // Post title and ID conditional
314 + $where .= " AND ( p.post_title LIKE '%{$search}%' OR p.ID LIKE '%{$search}%' )";
315 +
316 + // Post status conditional
317 + $where .= " AND p.post_status IN( 'publish', 'private', 'inherit' )";
318 +
319 + // Check for trigger type extra conditionals
320 + if( isset( $_REQUEST['trigger_type'] ) ) {
321 +
322 + $query_args = array();
323 + $trigger_type = sanitize_text_field( $_REQUEST['trigger_type'] );
324 +
325 + // Get trigger type query args (This function is filtered!)
326 + $query_args = gamipress_get_specific_activity_triggers_query_args( $query_args, $trigger_type );
327 +
328 + if( ! empty( $query_args ) ) {
329 +
330 + if( is_array( $query_args ) ) {
331 + // If is an array of conditionals, then build the new conditionals
332 + foreach( $query_args as $field => $value ) {
333 + $where .= " AND p.{$field} = '$value'";
334 + }
335 + } else {
336 + // Leave an extra space if query args doesn't have one
337 + $where .= ' ' . $query_args;
338 + }
339 +
340 + }
341 + }
342 +
343 + /**
344 + * Ajax posts query args (used on almost every post selector)
345 + *
346 + * Note: Use $_REQUEST for all given parameters
347 + *
348 + * @since 1.6.6
349 + *
350 + * @param string $query_args
351 + *
352 + * @return array|string
353 + */
354 + $extra_query_args = apply_filters( 'gamipress_ajax_get_posts_query_args', '' );
355 +
356 + // Check for extra conditionals
357 + if( ! empty( $extra_query_args ) ) {
358 +
359 + if( is_array( $extra_query_args ) ) {
360 + // If is an array of conditionals, then build the new conditionals
361 + foreach( $extra_query_args as $field => $value ) {
362 + $where .= " AND p.{$field} = '$value'";
363 + }
364 + } else {
365 + // Leave an extra space if extra query args doesn't have one
366 + $where .= ' ' . $extra_query_args;
367 + }
368 +
369 + }
370 +
371 + // Setup from (from is filtered to allow joins)
372 + $from = "{$wpdb->posts} AS p";
373 +
374 + /**
375 + * Ajax posts from (used on almost every post selector)
376 + * Note: Use $_REQUEST for all given parameters
377 + *
378 + * @since 1.6.6
379 + *
380 + * @param string $from By default '{$wpdb->posts} AS p'
381 + *
382 + * @return string
383 + */
384 + $from = apply_filters( 'gamipress_ajax_get_posts_from', $from );
385 +
386 + /**
387 + * Ajax posts where (used on almost every post selector)
388 + * Note: Use $_REQUEST for all given parameters
389 + *
390 + * @since 1.6.6
391 + *
392 + * @param string $where Contains all wheres
393 + *
394 + * @return string
395 + */
396 + $where = apply_filters( 'gamipress_ajax_get_posts_where', $where );
397 +
398 + // Setup order by
399 + $order_by = "p.post_type ASC, p.menu_order DESC";
400 +
401 + /**
402 + * Ajax posts order by (used on almost every post selector)
403 + * Note: Use $_REQUEST for all given parameters
404 + *
405 + * @since 1.6.6
406 + *
407 + * @param string $order_by By default 'p.post_type ASC, p.menu_order DESC'
408 + *
409 + * @return string
410 + */
411 + $order_by = apply_filters( 'gamipress_ajax_get_posts_order_by', $order_by );
412 +
413 + // Pagination args
414 + $page = isset( $_REQUEST['page'] ) ? absint( $_REQUEST['page'] ) : 1;
415 + $limit = 20;
416 + $offset = $limit * ( $page - 1 );
417 +
418 + if( gamipress_is_network_wide_active() ) {
419 +
420 + $results = array();
421 + $count = 0;
422 +
423 + foreach( gamipress_get_network_site_ids() as $site_id ) {
424 +
425 + // Switch to site
426 + switch_to_blog( $site_id );
427 +
428 + // Get the current site name to append it to results
429 + $site_name = get_bloginfo( 'name' );
430 +
431 + // Setup from after switch site to get the site's posts table correctly
432 + $from = "{$wpdb->posts} AS p";
433 +
434 + /**
435 + * Ajax posts from (for current switched site)
436 + *
437 + * @since 1.7.4
438 + *
439 + * @param string $from By default '{$wpdb->posts} AS p'
440 + * @param int $site_id Site ID
441 + *
442 + * @return string
443 + */
444 + $from = apply_filters( 'gamipress_ajax_get_posts_site_from', $from, $site_id );
445 +
446 + // On this query, keep $wpdb->posts to get sub site posts
447 + $site_results = $wpdb->get_results(
448 + "SELECT p.ID, p.post_title, p.post_type
449 + FROM {$from}
450 + WHERE {$where}
451 + ORDER BY {$order_by}
452 + LIMIT {$offset}, {$limit}"
453 + );
454 +
455 + // Loop all site results to add the site ID and name
456 + foreach( $site_results as $index => $site_result ) {
457 +
458 + $site_result->site_id = absint( $site_id );
459 + $site_result->site_name = $site_name;
460 +
461 + $site_results[$index] = $site_result;
462 + }
463 +
464 + // Merge it to all results
465 + $results = array_merge( $results, $site_results );
466 +
467 + $count += absint( $wpdb->get_var( "SELECT COUNT(*) FROM {$from} WHERE {$where}" ) );
468 +
469 + // Restore current site
470 + restore_current_blog();
471 +
472 + }
473 +
474 + } else {
475 +
476 + // On this query, keep $wpdb->posts to get current site posts
477 + $results = $wpdb->get_results(
478 + "SELECT p.ID, p.post_title, p.post_type
479 + FROM {$from}
480 + WHERE {$where}
481 + ORDER BY {$order_by}
482 + LIMIT {$offset}, {$limit}"
483 + );
484 +
485 + $count = absint( $wpdb->get_var( "SELECT COUNT(*) FROM {$from} WHERE {$where}" ) );
486 +
487 + }
488 +
489 + $response = array(
490 + 'results' => $results,
491 + 'more_results' => $count > $limit && $count > $offset,
492 + );
493 +
494 + // Return our results
495 + wp_send_json_success( $response );
496 +
497 +}
498 +add_action( 'wp_ajax_gamipress_get_posts', 'gamipress_ajax_get_posts' );
499 +
500 +/**
501 + * AJAX Helper for selecting requirements
502 + *
503 + * @since 1.0.0
504 + * @updated 1.4.8 Added multisite support
505 + */
506 +function gamipress_ajax_get_requirements() {
507 + // Security check, forces to die if not security passed
508 + check_ajax_referer( 'gamipress_admin', 'nonce' );
509 +
510 + if( ! current_user_can( gamipress_get_manager_capability() ) ) {
511 + return;
512 + }
513 +
514 + global $wpdb;
515 +
516 + // Pull back the search string
517 + $search = isset( $_REQUEST['q'] ) ? esc_sql( $wpdb->esc_like( $_REQUEST['q'] ) ) : '';
518 +
519 + // Setup where conditions (initialized with 1=1)
520 + $where = '1=1';
521 +
522 + // Post type conditional
523 + $post_type = ( isset( $_REQUEST['post_type'] ) && ! empty( $_REQUEST['post_type'] ) ? $_REQUEST['post_type'] : gamipress_get_requirement_types_slugs() );
524 +
525 + if ( is_array( $post_type ) ) {
526 +
527 + // Sanitize all post types given
528 + foreach( $post_type as $i => $value ) {
529 + $post_type[$i] = sanitize_text_field( $value );
530 + }
531 +
532 + $post_type = sprintf( ' AND p.post_type IN(\'%s\')', implode( "','", $post_type ) );
533 + } else {
534 +
535 + // Sanitize the post type
536 + $post_type = sanitize_text_field( $post_type );
537 +
538 + $post_type = sprintf( ' AND p.post_type = \'%s\'', $post_type );
539 + }
540 +
541 + $where .= $post_type;
542 +
543 + // Post title and ID conditional
544 + $where .= " AND ( p.post_title LIKE '%{$search}%' OR p.ID LIKE '%{$search}%' )";
545 +
546 + // Post status conditional
547 + $where .= " AND p.post_status = 'publish'";
548 +
549 + // Setup from (from is filtered to allow joins)
550 + $posts = GamiPress()->db->posts;
551 + $from = "{$posts} AS p";
552 +
553 + /**
554 + * Ajax requirements FROM (used on almost every post selector)
555 + * Note: Use $_REQUEST for all given parameters
556 + *
557 + * @since 1.6.6
558 + *
559 + * @param string $from By default '{GamiPress()->db->posts} AS p'
560 + *
561 + * @return string
562 + */
563 + $from = apply_filters( 'gamipress_ajax_get_requirements_from', $from );
564 +
565 + /**
566 + * Ajax requirements WHERE (used on almost every post selector)
567 + * Note: Use $_REQUEST for all given parameters
568 + *
569 + * @since 1.6.6
570 + *
571 + * @param string $where Contains all wheres
572 + *
573 + * @return string
574 + */
575 + $where = apply_filters( 'gamipress_ajax_get_requirements_where', $where );
576 +
577 + // Setup order by
578 + $order_by = "p.post_type ASC, p.post_parent ASC, p.menu_order DESC";
579 +
580 + /**
581 + * Ajax requirements ORDER BY (used on almost every post selector)
582 + * Note: Use $_REQUEST for all given parameters
583 + *
584 + * @since 1.6.6
585 + *
586 + * @param string $order_by By default 'p.post_type ASC, p.post_parent ASC, p.menu_order DESC'
587 + *
588 + * @return string
589 + */
590 + $order_by = apply_filters( 'gamipress_ajax_get_requirements_order_by', $order_by );
591 +
592 + // Pagination args
593 + $page = isset( $_REQUEST['page'] ) ? absint( $_REQUEST['page'] ) : 1;
594 + $limit = 20;
595 + $offset = $limit * ( $page - 1 );
596 +
597 + // On this query, keep $wpdb->posts to get current site posts
598 + $results = $wpdb->get_results(
599 + "SELECT p.ID, p.post_title, p.post_type, p.post_parent
600 + FROM {$from}
601 + WHERE {$where}
602 + ORDER BY {$order_by}
603 + LIMIT {$offset}, {$limit}", ARRAY_A
604 + );
605 +
606 + $count = absint( $wpdb->get_var( "SELECT COUNT(*) FROM {$from} WHERE {$where}" ) );
607 +
608 + $parents = array();
609 +
610 + foreach( $results as $i => $result ) {
611 + $parent_id = absint( $result['post_parent'] );
612 +
613 + // Check if parent have been found previously
614 + if( ! isset( $parents[$parent_id] ) ) {
615 + $parent = $wpdb->get_row( $wpdb->prepare(
616 + "SELECT p.post_title, p.post_type
617 + FROM {$posts} AS p
618 + WHERE p.ID = %d
619 + LIMIT 1",
620 + $parent_id
621 + ), ARRAY_A );
622 +
623 + if( $parent ) {
624 + $parents[$parent_id] = $parent;
625 + }
626 + }
627 +
628 + // Add the parent post type & title
629 + $parent = ( isset( $parents[$parent_id] ) ? $parents[$parent_id] : null );
630 +
631 + if( $parent !== null ) {
632 + $results[$i]['post_parent_title'] = $parent['post_title'];
633 + $results[$i]['post_parent_type'] = $parent['post_type'];
634 + }
635 + }
636 +
637 + $response = array(
638 + 'results' => $results,
639 + 'more_results' => $count > $limit && $count > $offset,
640 + );
641 +
642 + // Return our results
643 + wp_send_json_success( $response );
644 +
645 +}
646 +add_action( 'wp_ajax_gamipress_get_requirements', 'gamipress_ajax_get_requirements' );
647 +
648 +/**
649 + * AJAX Helper for selecting posts in Shortcode Embedder
650 + *
651 + * @since 1.0.0
652 + */
653 +function gamipress_ajax_get_achievements_options() {
654 +
655 + global $wpdb;
656 +
657 + // Pull back the search string
658 + $search = isset( $_REQUEST['q'] ) ? $wpdb->esc_like( $_REQUEST['q'] ) : '';
659 + $achievement_types = isset( $_REQUEST['post_type'] ) && 'all' !== $_REQUEST['post_type']
660 + ? array( esc_sql( $_REQUEST['post_type'] ) )
661 + : gamipress_get_achievement_types_slugs();
662 + $post_type = sprintf( 'AND p.post_type IN(\'%s\')', implode( "','", $achievement_types ) );
663 +
664 + // For single type, is not needed to add the post type, but for multiples types is a better option to distinguish them easily
665 + $select = 'p.ID, p.post_title';
666 +
667 + if( count( $achievement_types ) > 1 ) {
668 + $select = 'p.ID, p.post_title, p.post_type';
669 + }
670 +
671 + $posts = GamiPress()->db->posts;
672 + $postmeta = GamiPress()->db->postmeta;
673 +
674 + $results = $wpdb->get_results( $wpdb->prepare(
675 + "SELECT {$select}
676 + FROM {$posts} AS p
677 + JOIN {$postmeta} AS pm
678 + ON p.ID = pm.post_id
679 + WHERE p.post_title LIKE %s
680 + {$post_type}
681 + AND p.post_status IN( 'publish', 'private', 'inherit' )
682 + AND pm.meta_key = %s
683 + AND pm.meta_value = %s",
684 + "%%{$search}%%",
685 + "_gamipress_hidden",
686 + "show"
687 + ) );
688 +
689 + // Return our results
690 + wp_send_json_success( $results );
691 +
692 +}
693 +add_action( 'wp_ajax_gamipress_get_achievements_options', 'gamipress_ajax_get_achievements_options' );
694 +
695 +/**
696 + * AJAX helper for getting our posts and returning select options
697 + *
698 + * @since 1.0.0
699 + * @updated 1.0.5
700 + * @updated 1.3.0
701 + * @updated 1.3.5 Make function accessible through gamipress_get_achievements_options_html action
702 + */
703 +function gamipress_achievement_post_ajax_handler() {
704 + // Security check, forces to die if not security passed
705 + check_ajax_referer( 'gamipress_admin', 'nonce' );
706 +
707 + $selected = '';
708 +
709 + // If requirement_id requested, then retrieve the selected option from this requirement
710 + if( isset( $_REQUEST['requirement_id'] ) && ! empty( $_REQUEST['requirement_id'] ) ) {
711 +
712 + $requirements = gamipress_get_requirement_object( absint( $_REQUEST['requirement_id'] ) );
713 +
714 + $selected = isset( $requirements['achievement_post'] ) ? $requirements['achievement_post'] : '';
715 + } else if( isset( $_REQUEST['selected'] ) && ! empty( $_REQUEST['selected'] ) ) {
716 + $selected = $_REQUEST['selected'];
717 + }
718 +
719 + // Sanitize
720 + $selected = absint( $selected );
721 +
722 + $achievement_type = sanitize_text_field( $_REQUEST['achievement_type'] );
723 + $exclude_posts = isset( $_REQUEST['excluded_posts'] ) ? (array) $_REQUEST['excluded_posts'] : array();
724 +
725 + // If we don't have an achievement type, bail now
726 + if ( empty( $achievement_type ) ) {
727 + die();
728 + }
729 +
730 + $achievement_types = gamipress_get_achievement_types();
731 +
732 + if( ! isset( $achievement_types[$achievement_type] ) ) {
733 + return;
734 + }
735 +
736 + // Sanitize excluded posts
737 + foreach( $exclude_posts as $i => $exclude_post ) {
738 + $exclude_posts[$i] = absint( $exclude_post );
739 + }
740 +
741 + $singular_name = ! empty( $achievement_types[$achievement_type]['singular_name'] ) ? $achievement_types[$achievement_type]['singular_name'] : __( 'Achievement', 'gamipress' );
742 +
743 + // Grab all our posts for this achievement type
744 + $achievements = get_posts( array(
745 + 'post_type' => $achievement_type,
746 + 'post__not_in' => $exclude_posts,
747 + 'posts_per_page' => -1,
748 + 'orderby' => 'title',
749 + 'order' => 'ASC',
750 + 'suppress_filters' => false,
751 + ));
752 +
753 + // Setup our output
754 + $output = '<option value="">' . sprintf( __( 'Choose the %s', 'gamipress' ), $singular_name ) . '</option>';
755 + foreach ( $achievements as $achievement ) {
756 + $achievement_id = absint( $achievement->ID );
757 + $output .= '<option value="' . $achievement_id . '" ' . selected( $selected, $achievement_id, false ) . '>' . $achievement->post_title . '</option>';
758 + }
759 +
760 + // Send back our results and die like a man
761 + echo $output;
762 + die();
763 +
764 +}
765 +add_action( 'wp_ajax_gamipress_requirement_achievement_post', 'gamipress_achievement_post_ajax_handler' );
766 +add_action( 'wp_ajax_gamipress_get_achievements_options_html', 'gamipress_achievement_post_ajax_handler' );
767 +
768 +/**
769 + * AJAX Helper for selecting ranks in achievement earned by
770 + *
771 + * @since 1.3.1
772 + */
773 +function gamipress_ajax_get_ranks_options_html() {
774 + // Security check, forces to die if not security passed
775 + check_ajax_referer( 'gamipress_admin', 'nonce' );
776 +
777 + global $wpdb;
778 +
779 + // Post type conditional
780 + $post_type = ( isset( $_REQUEST['post_type'] ) && ! empty( $_REQUEST['post_type'] ) ? $_REQUEST['post_type'] : gamipress_get_rank_types_slugs() );
781 +
782 + if ( is_array( $post_type ) ) {
783 +
784 + // Sanitize all post types given
785 + foreach( $post_type as $i => $value ) {
786 + $post_type[$i] = sanitize_text_field( $value );
787 + }
788 +
789 + $post_type = sprintf( 'AND p.post_type IN(\'%s\')', implode( "','", $post_type ) );
790 + $singular_name = __( 'Rank', 'gamipress' );
791 + } else {
792 +
793 + // Sanitize the post type
794 + $post_type = sanitize_text_field( $post_type );
795 +
796 + $singular_name = gamipress_get_rank_type_singular( $post_type, true );
797 + $post_type = sprintf( 'AND p.post_type = \'%s\'', $post_type );
798 + }
799 +
800 + $selected = '';
801 +
802 + // If requirement_id requested, then retrieve the selected option from this requirement
803 + if( isset( $_REQUEST['requirement_id'] ) && ! empty( $_REQUEST['requirement_id'] ) ) {
804 +
805 + $requirements = gamipress_get_requirement_object( absint( $_REQUEST['requirement_id'] ) );
806 +
807 + $selected = isset( $requirements['rank_required'] ) ? $requirements['rank_required'] : '';
808 + } else if( isset( $_REQUEST['selected'] ) && ! empty( $_REQUEST['selected'] ) ) {
809 + $selected = $_REQUEST['selected'];
810 + }
811 +
812 + // Sanitize
813 + $selected = absint( $selected );
814 +
815 + $posts = GamiPress()->db->posts;
816 +
817 + $ranks = $wpdb->get_results(
818 + "SELECT p.ID, p.post_title
819 + FROM {$posts} AS p
820 + WHERE p.post_status IN( 'publish', 'private', 'inherit' )
821 + {$post_type}
822 + ORDER BY p.post_type ASC, p.menu_order DESC"
823 + );
824 +
825 + // Setup our output
826 + $output = '<option value="">' . sprintf( __( 'Choose the %s', 'gamipress' ), $singular_name ) . '</option>';
827 + foreach ( $ranks as $rank ) {
828 + $rank_id = absint( $rank->ID );
829 + $output .= '<option value="' . $rank_id . '" ' . selected( $selected, $rank_id, false ) . '>' . $rank->post_title . '</option>';
830 + }
831 +
832 + // Send back our results and die like a man
833 + echo $output;
834 + die();
835 +
836 +}
837 +add_action( 'wp_ajax_gamipress_get_ranks_options_html', 'gamipress_ajax_get_ranks_options_html' );
838 +
839 +/**
840 + * AJAX Helper for selecting ranks in Shortcode Embedder
841 + *
842 + * @since 1.3.1
843 + */
844 +function gamipress_ajax_get_ranks_options() {
845 + global $wpdb;
846 +
847 + // Pull back the search string
848 + $search = isset( $_REQUEST['q'] ) ? $wpdb->esc_like( $_REQUEST['q'] ) : '';
849 +
850 + // For single type, is not needed to add the post type, but for multiples types is a better option to distinguish them easily
851 + $select = 'p.ID, p.post_title';
852 +
853 + // Post type conditional
854 + $post_type = ( isset( $_REQUEST['post_type'] ) && ! empty( $_REQUEST['post_type'] ) ? $_REQUEST['post_type'] : gamipress_get_rank_types_slugs() );
855 +
856 + if ( is_array( $post_type ) ) {
857 +
858 + // Sanitize all post types given
859 + foreach( $post_type as $i => $value ) {
860 + $post_type[$i] = sanitize_text_field( $value );
861 + }
862 +
863 + $post_type = sprintf( 'AND p.post_type IN(\'%s\')', implode( "','", $post_type ) );
864 +
865 + $select = 'p.ID, p.post_title, p.post_type';
866 + } else {
867 +
868 + // Sanitize the post type
869 + $post_type = sanitize_text_field( $post_type );
870 +
871 + $post_type = sprintf( 'AND p.post_type = \'%s\'', $post_type );
872 + }
873 +
874 + $posts = GamiPress()->db->posts;
875 +
876 + $ranks = $wpdb->get_results( $wpdb->prepare(
877 + "SELECT {$select}
878 + FROM {$posts} AS p
879 + WHERE p.post_status IN( 'publish', 'private', 'inherit' )
880 + {$post_type}
881 + AND p.post_title LIKE %s
882 + ORDER BY p.post_type ASC, p.menu_order DESC",
883 + "%%{$search}%%"
884 + ) );
885 +
886 + // Return our results
887 + wp_send_json_success( $ranks );
888 +}
889 +add_action( 'wp_ajax_gamipress_get_ranks_options', 'gamipress_ajax_get_ranks_options' );
890 +
891 +/**
892 + * Ajax function to check and unlock an achievement by expend an amount of points
893 + *
894 + * @since 1.3.7
895 + * @updated 1.7.9 Added nonce usage
896 + *
897 + * @return void
898 + */
899 +function gamipress_ajax_unlock_achievement_with_points() {
900 + // Security check, forces to die if not security passed
901 + check_ajax_referer( 'gamipress', 'nonce' );
902 +
903 + $achievement_id = isset( $_POST['achievement_id'] ) ? absint( $_POST['achievement_id'] ) : 0;
904 +
905 + $achievement = gamipress_get_post( $achievement_id );
906 +
907 + // Return if achievement not exists
908 + if( ! $achievement )
909 + wp_send_json_error( __( 'Achievement not found.', 'gamipress' ) );
910 +
911 + $achievement_types = gamipress_get_achievement_types();
912 +
913 + // Return if not a valid achievement
914 + if( ! isset( $achievement_types[$achievement->post_type] ) )
915 + wp_send_json_error( __( 'Invalid achievement.', 'gamipress' ) );
916 +
917 + $achievement_type = $achievement_types[$achievement->post_type];
918 +
919 + $user_id = get_current_user_id();
920 +
921 + // Guest not supported yet (basically because they has not points)
922 + if( $user_id === 0 )
923 + wp_send_json_error( __( 'You are not allowed to perform this action.', 'gamipress' ) );
924 +
925 + // Return if this option not was enabled
926 + if( ! (bool) gamipress_get_post_meta( $achievement_id, '_gamipress_unlock_with_points' ) )
927 + wp_send_json_error( sprintf( __( 'You are not allowed to unlock this %s.', 'gamipress' ), $achievement_type['singular_name'] ) );
928 +
929 + $points = absint( gamipress_get_post_meta( $achievement_id, '_gamipress_points_to_unlock' ) );
930 +
931 + // Return if no points configured
932 + if( $points === 0 )
933 + wp_send_json_error( sprintf( __( 'You are not allowed to unlock this %s.', 'gamipress' ), $achievement_type['singular_name'] ) );
934 +
935 + $earned = gamipress_achievement_user_exceeded_max_earnings( $user_id, $achievement_id );
936 +
937 + // Return if user has completely earned this achievement
938 + if( $earned )
939 + wp_send_json_error( sprintf( __( 'You already unlocked this %s.', 'gamipress' ), $achievement_type['singular_name'] ) );
940 +
941 + // Setup points type
942 + $points_types = gamipress_get_points_types();
943 + $points_type = gamipress_get_post_meta( $achievement_id, '_gamipress_points_type_to_unlock' );
944 +
945 + // Default points label
946 + $points_label = __( 'Points', 'gamipress' );
947 +
948 + // Points type label
949 + if( isset( $points_types[$points_type] ) )
950 + $points_label = $points_types[$points_type]['plural_name'];
951 +
952 + // Setup user points
953 + $user_points = gamipress_get_user_points( $user_id, $points_type );
954 +
955 + // Return if insufficient points
956 + if( $user_points < $points ) {
957 +
958 + $message = sprintf( __( 'Insufficient %s.', 'gamipress' ), $points_label );
959 +
960 + /**
961 + * Available filter to override the insufficient points text when unlocking a rank using points
962 + *
963 + * @since 1.0.5
964 + *
965 + * @param string $message The insufficient points message
966 + * @param int $achievement_id The achievement ID
967 + * @param int $user_id The current logged in user ID
968 + * @param int $points The required amount of points
969 + * @param string $points_type The required amount points type
970 + */
971 + $message = apply_filters( 'gamipress_unlock_achievement_with_points_insufficient_points_message', $message, $achievement_id, $user_id, $points, $points_type );
972 +
973 + wp_send_json_error( $message );
974 + }
975 +
976 + // Deduct points to user
977 + gamipress_deduct_points_to_user( $user_id, $points, $points_type, array(
978 + 'log_type' => 'points_expend',
979 + 'reason' => apply_filters( 'gamipress_points_expended_log_pattern', __( '{user} expended {points} {points_type} for a new total of {total_points} {points_type}', 'gamipress' ) )
980 + ) );
981 +
982 + // Award the achievement to the user
983 + gamipress_award_achievement_to_user( $achievement_id, $user_id );
984 +
985 + $congratulations = gamipress_get_post_meta( $achievement_id, '_gamipress_congratulations_text' );
986 +
987 + if( empty( $congratulations ) ) {
988 + $congratulations = sprintf( __( 'Congratulations! You unlocked the %s %s.', 'gamipress' ), $achievement_type['singular_name'], $achievement->post_title );
989 + }
990 +
991 + $congratulations = wpautop( $congratulations );
992 + $congratulations = do_shortcode( $congratulations );
993 +
994 + // Filter to change congratulations message
995 + $congratulations = apply_filters( 'gamipress_achievement_unlocked_with_points_congratulations', $congratulations, $achievement_id, $user_id, $points, $points_type );
996 +
997 + /**
998 + * Achievement unlocked with points action
999 + *
1000 + * @since 1.3.7
1001 + *
1002 + * @param integer $achievement_id The achievement unlocked ID
1003 + * @param integer $user_id The user ID
1004 + * @param integer $points The amount of points expended
1005 + * @param string $points_type The points type of the amount of points expended
1006 + */
1007 + do_action( 'gamipress_achievement_unlocked_with_points', $achievement_id, $user_id, $points, $points_type );
1008 +
1009 + wp_send_json_success( $congratulations );
1010 +
1011 +}
1012 +add_action( 'wp_ajax_gamipress_unlock_achievement_with_points', 'gamipress_ajax_unlock_achievement_with_points' );
1013 +
1014 +/**
1015 + * Ajax function to check and unlock a rank by expend an amount of points
1016 + *
1017 + * @since 1.3.7
1018 + * @updated 1.7.9 Added nonce usage
1019 + *
1020 + * @return void
1021 + */
1022 +function gamipress_ajax_unlock_rank_with_points() {
1023 + // Security check, forces to die if not security passed
1024 + check_ajax_referer( 'gamipress', 'nonce' );
1025 +
1026 + $rank_id = isset( $_POST['rank_id'] ) ? absint( $_POST['rank_id'] ) : 0;
1027 +
1028 + $rank = gamipress_get_post( $rank_id );
1029 +
1030 + // Return if rank not exists
1031 + if( ! $rank ) {
1032 + wp_send_json_error( __( 'Rank not found.', 'gamipress' ) );
1033 + }
1034 +
1035 + $rank_types = gamipress_get_rank_types();
1036 +
1037 + // Return if not a valid rank
1038 + if( ! isset( $rank_types[$rank->post_type] ) ) {
1039 + wp_send_json_error( __( 'Invalid rank.', 'gamipress' ) );
1040 + }
1041 +
1042 + $rank_type = $rank_types[$rank->post_type];
1043 +
1044 + $user_id = get_current_user_id();
1045 +
1046 + // Guest not supported yet (basically because they has not points)
1047 + if( $user_id === 0 ) {
1048 + wp_send_json_error( __( 'You are not allowed to perform this action.', 'gamipress' ) );
1049 + }
1050 +
1051 + // Return if this option not was enabled
1052 + if( ! (bool) gamipress_get_post_meta( $rank_id, '_gamipress_unlock_with_points' ) ) {
1053 + wp_send_json_error( sprintf( __( 'You are not allowed to unlock this %s.', 'gamipress' ), $rank_type['singular_name'] ) );
1054 + }
1055 +
1056 + $points = absint( gamipress_get_post_meta( $rank_id, '_gamipress_points_to_unlock' ) );
1057 +
1058 + // Return if no points configured
1059 + if( $points === 0 ) {
1060 + wp_send_json_error( sprintf( __( 'You are not allowed to unlock this %s.', 'gamipress' ), $rank_type['singular_name'] ) );
1061 + }
1062 +
1063 + // Bail if not is the next rank to unlock
1064 + if( gamipress_get_next_user_rank_id( $user_id, $rank->post_type ) !== $rank_id ) {
1065 + wp_send_json_error( sprintf( __( 'You are not allowed to unlock this %s.', 'gamipress' ), $rank_type['singular_name'] ) );
1066 + }
1067 +
1068 + $user_rank = gamipress_get_user_rank( $user_id, $rank_type );
1069 +
1070 + // Return if user is in a higher rank
1071 + if( gamipress_get_rank_priority( $rank_id ) <= gamipress_get_rank_priority( $user_rank ) ) {
1072 + wp_send_json_error( sprintf( __( 'You are already in a higher %s.', 'gamipress' ), $rank_type['singular_name'] ) );
1073 + }
1074 +
1075 + // Setup points type
1076 + $points_types = gamipress_get_points_types();
1077 + $points_type = gamipress_get_post_meta( $rank_id, '_gamipress_points_type_to_unlock' );
1078 +
1079 + // Default points label
1080 + $points_label = __( 'Points', 'gamipress' );
1081 +
1082 + // Points type label
1083 + if( isset( $points_types[$points_type] ) )
1084 + $points_label = $points_types[$points_type]['plural_name'];
1085 +
1086 + // Setup user points
1087 + $user_points = gamipress_get_user_points( $user_id, $points_type );
1088 +
1089 + // Return if insufficient points
1090 + if( $user_points < $points ) {
1091 +
1092 + $message = sprintf( __( 'Insufficient %s.', 'gamipress' ), $points_label );
1093 +
1094 + /**
1095 + * Available filter to override the insufficient points text when unlocking a rank using points
1096 + *
1097 + * @since 1.0.5
1098 + *
1099 + * @param string $message The insufficient points message
1100 + * @param int $rank_id The rank ID
1101 + * @param int $user_id The current logged in user ID
1102 + * @param int $points The required amount of points
1103 + * @param string $points_type The required amount points type
1104 + */
1105 + $message = apply_filters( 'gamipress_unlock_rank_with_points_insufficient_points_message', $message, $rank_id, $user_id, $points, $points_type );
1106 +
1107 + wp_send_json_error( $message );
1108 + }
1109 +
1110 + // Deduct points to user
1111 + gamipress_deduct_points_to_user( $user_id, $points, $points_type, array(
1112 + 'log_type' => 'points_expend',
1113 + 'reason' => apply_filters( 'gamipress_points_expended_log_pattern', __( '{user} expended {points} {points_type} for a new total of {total_points} {points_type}', 'gamipress' ) )
1114 + ) );
1115 +
1116 + // Award the rank to the user
1117 + gamipress_update_user_rank( $user_id, $rank_id );
1118 +
1119 + $congratulations = gamipress_get_post_meta( $rank_id, '_gamipress_congratulations_text' );
1120 +
1121 + if( empty( $congratulations ) ) {
1122 + $congratulations = sprintf( __( 'Congratulations! You reached to the %s %s.', 'gamipress' ), $rank_type['singular_name'], $rank->post_title );
1123 + }
1124 +
1125 + $congratulations = wpautop( $congratulations );
1126 + $congratulations = do_shortcode( $congratulations );
1127 +
1128 + // Filter to change congratulations message
1129 + $congratulations = apply_filters( 'gamipress_rank_unlocked_with_points_congratulations', $congratulations, $rank_id, $user_id, $points, $points_type );
1130 +
1131 + /**
1132 + * Achievement unlocked with points action
1133 + *
1134 + * @since 1.3.7
1135 + *
1136 + * @param integer $rank_id The rank unlocked ID
1137 + * @param integer $user_id The user ID
1138 + * @param integer $points The amount of points expended
1139 + * @param string $points_type The points type of the amount of points expended
1140 + */
1141 + do_action( 'gamipress_rank_unlocked_with_points', $rank_id, $user_id, $points, $points_type );
1142 +
1143 + wp_send_json_success( $congratulations );
1144 +
1145 +}
1146 +add_action( 'wp_ajax_gamipress_unlock_rank_with_points', 'gamipress_ajax_unlock_rank_with_points' );