Headline
CVE-2023-2173: steps-ui.php in badgeos/trunk/includes – WordPress Plugin Repository
The BadgeOS plugin for WordPress is vulnerable to Insecure Direct Object Reference in versions up to, and including, 3.7.1.6. This is due to improper validation and authorization checks within the badgeos_delete_step_ajax_handler, badgeos_delete_award_step_ajax_handler, badgeos_delete_deduct_step_ajax_handler, and badgeos_delete_rank_req_step_ajax_handler functions. This makes it possible for authenticated attackers, with subscriber-level permissions and above, to delete arbitrary posts.
1<?php2/**3 * Custom Achievement Steps UI4 *5 * @package BadgeOS6 * @subpackage Achievements7 * @author LearningTimes, LLC8 * @license http://www.gnu.org/licenses/agpl.txt GNU AGPL v3.09 * @link https://credly.com10 */1112/**13 * Add our Steps JS to the Badge post editor14 *15 * @since 1.0.016 * @return void17 */18function badgeos_steps_ui_admin_scripts() {19 global $post_type;20 if ( in_array( $post_type, badgeos_get_achievement_types_slugs(), true ) ) {21 wp_enqueue_script( 'badgeos-steps-ui’, $GLOBALS[‘badgeos’]->directory_url . 'js/steps-ui.js’, array( ‘jquery-ui-sortable’ ), '2.0.0’, true );22 }23}24add_action( 'admin_print_scripts-post-new.php’, 'badgeos_steps_ui_admin_scripts’, 11 );25add_action( 'admin_print_scripts-post.php’, 'badgeos_steps_ui_admin_scripts’, 11 );2627/**28 * Adds our Steps metabox to the Badge post editor29 *30 * @since 1.0.031 * @return void32 */33function badgeos_add_steps_ui_meta_box() {34 $achievement_types_temp = badgeos_get_achievement_types_slugs();35 $achievement_types = array();36 $badgeos_settings = ! empty( badgeos_utilities::get_option( ‘badgeos_settings’ ) ) ? badgeos_utilities::get_option( ‘badgeos_settings’ ) : array();37 if ( $achievement_types_temp ) {38 foreach ( $achievement_types_temp as $key => $ach ) {39 if ( ! empty( $ach ) && trim( $badgeos_settings[‘achievement_step_post_type’] ) !== $ach ) {40 $achievement_types[] = $ach;41 }42 }43 }4445 if ( $achievement_types ) {46 foreach ( $achievement_types as $achievement_type ) {47 add_meta_box( 'badgeos_steps_ui’, apply_filters( 'badgeos_steps_ui_title’, esc_html__( 'Required Steps’, ‘badgeos’ ) ), 'badgeos_steps_ui_meta_box’, $achievement_type, 'advanced’, ‘high’ );48 }49 }50}51add_action( 'add_meta_boxes’, ‘badgeos_add_steps_ui_meta_box’ );5253/**54 * Renders the HTML for meta box, refreshes whenever a new step is added.55 *56 * @since 1.0.057 * @param object $post The current post object.58 * @return void59 */60function badgeos_steps_ui_meta_box( $post = null ) {6162 // Grab our Badge’s required steps.63 $badgeos_settings = ! empty( badgeos_utilities::get_option( ‘badgeos_settings’ ) ) ? badgeos_utilities::get_option( ‘badgeos_settings’ ) : array();64 $required_steps = get_posts(65 array(66 ‘post_type’ => trim( $badgeos_settings[‘achievement_step_post_type’] ),67 ‘posts_per_page’ => -1,68 ‘suppress_filters’ => false,69 ‘connected_direction’ => 'to’,70 ‘connected_type’ => trim( $badgeos_settings[‘achievement_step_post_type’] ) . '-to-' . $post->post_type,71 ‘connected_items’ => $post->ID,72 )73 );7475 // Loop through each step and set the sort order.76 foreach ( $required_steps as $required_step ) {77 $required_step->order = get_step_menu_order( $required_step->ID );78 }7980 // Sort the steps by their order.81 uasort( $required_steps, ‘badgeos_compare_step_order’ );8283 echo ‘<p>’ . esc_html__( 'Define the required “steps” for this achievement to be considered complete. Use the “Label” field to optionally customize the titles of each step.’, ‘badgeos’ ) . '</p>’;8485 // Concatenate our step output.86 echo '<ul id="steps_list">’;87 foreach ( $required_steps as $step ) {88 badgeos_steps_ui_html( $step->ID, $post->ID );89 }90 echo '</ul>’;9192 // Render our buttons.93 echo ‘<input style="margin-right: 1em" class="button" type="button" onclick="badgeos_add_new_step(' . esc_attr( $post->ID ) . ');" value="’ . esc_html( apply_filters( 'badgeos_steps_ui_add_new’, esc_html__( 'Add New Step’, ‘badgeos’ ) ) ) . '">’;94 echo ‘<input class="button-primary" type="button" onclick="badgeos_update_steps();" value="’ . esc_html( apply_filters( 'badgeos_steps_ui_save_all’, esc_html__( 'Save All Steps’, ‘badgeos’ ) ) ) . '">’;95 echo ‘<img class="save-steps-spinner" src="’ . esc_url( admin_url( ‘/images/wpspin_light.gif’ ) ) . '" style="margin-left: 10px; display: none;" />’;9697}9899/**100 * Helper function for generating the HTML output for configuring a given step.101 *102 * @since 1.0.0103 * @param integer $step_id The given step’s ID.104 * @param integer $post_id The given step’s parent $post ID.105 */106function badgeos_steps_ui_html( $step_id = 0, $post_id = 0 ) {107108 // Grab our step’s requirements and measurement.109 $requirements = badgeos_get_step_requirements( $step_id );110 $count = ! empty( $requirements[‘count’] ) ? $requirements[‘count’] : 1;111 $achievement_types = badgeos_get_achievement_types_slugs();112 $badgeos_settings = ! empty( badgeos_utilities::get_option( ‘badgeos_settings’ ) ) ? badgeos_utilities::get_option( ‘badgeos_settings’ ) : array();113 $dynamic_triggers = array();114 $badgeos_subtrigger_value = $requirements[‘badgeos_subtrigger_value’];115 $badgeos_subtrigger_id = $requirements[‘badgeos_subtrigger_id’];116 $badgeos_fields_data = $requirements[‘badgeos_fields_data’];117 ?>118119 <li class="step-row step-<?php echo esc_attr( $step_id ); ?>" data-step-id="<?php echo esc_attr( $step_id ); ?>">120 <div class="step-handle"></div>121 <a class="delete-step" href="javascript: badgeos_delete_step( <?php echo esc_attr( $step_id ); ?> );"><?php echo esc_html__( 'Delete’, ‘badgeos’ ); ?></a>122 <input type="hidden" name="post_id" value="<?php echo esc_attr( $post_id ); ?>" />123 <input type="hidden" name="order" value="<?php echo esc_attr( get_step_menu_order( $step_id ) ); ?>" />124125 <?php echo esc_html( apply_filters( 'badgeos_steps_ui_html_require_text’, esc_html__( 'Require’, ‘badgeos’ ), $step_id, $post_id ) ); ?>126127 <?php do_action( 'badgeos_steps_ui_html_after_require_text’, $step_id, $post_id ); ?>128129 <select class="select-trigger-type" data-step-id="<?php echo esc_attr( $step_id ); ?>">130 <?php131 foreach ( badgeos_get_activity_triggers() as $value => $label ) {132 if ( is_array( $label ) ) {133 echo ‘<option value="’ . esc_attr( $value ) . '" ' . selected( $requirements[‘trigger_type’], $value, false ) . ‘>’ . esc_html( $label[‘label’] ) . '</option>’;134 $dynamic_triggers[ $value ] = $label;135 } else {136 echo ‘<option value="’ . esc_attr( $value ) . '" ' . selected( $requirements[‘trigger_type’], $value, false ) . ‘>’ . esc_html( $label ) . '</option>’;137 }138 }139 ?>140 </select>141142 <?php do_action( 'badgeos_steps_ui_html_after_trigger_type’, $step_id, $post_id ); ?>143144 <?php145 if ( count( $dynamic_triggers ) > 0 ) {146 foreach ( $dynamic_triggers as $key => $data ) {147 $fields_group = array();148 ?>149 <div id="badgeos_achievements_step_dynamic_section_<?php echo esc_attr( $key ); ?>" style="display:inline-block">150 <select id="badgeos_achievements_step_ddl_dynamic_<?php echo esc_attr( $key ); ?>" data-trigger="<?php echo esc_attr( $key ); ?>" class="badgeos_achievements_step_fields badgeos_achievements_step_ddl_dynamic" name="badgeos_achievements_step_ddl_dynamic_<?php echo esc_attr( $key ); ?>">151 <?php152 foreach ( $data[‘sub_triggers’] as $key2 => $data2 ) {153 $sub_fields = array();154 echo ‘<option value="’ . esc_attr( $data2[‘trigger’] ) . ‘" ' . selected( $data2[‘trigger’], $badgeos_subtrigger_value, false ) . ' >’ . esc_attr( $data2[‘label’] ) . '</option>’;155156 foreach ( $data2[‘fields’] as $fieldkey => $field ) {157 $sub_fields[] = $field;158 }159160 $fields_group[ $data2[‘trigger’] ] = $sub_fields;161 }162163 echo '</select>’;164165 foreach ( $fields_group as $fields_groupkey => $fields ) {166 foreach ( $fields as $fieldkey => $field ) {167 $sel_val = '’;168 if ( isset( $badgeos_fields_data[ $field[‘id’] ] ) ) {169 $sel_val = $badgeos_fields_data[ $field[‘id’] ];170 }171172 switch ( trim( $field[‘type’] ) ) {173 case ‘select’:174 echo ‘<select id="’ . esc_attr( $field[‘id’] ) . ‘" class="badgeos_achievements_step_fields badgeos_achievements_step_subddl_dynamic badgeos_achievements_step_fields_’ . esc_attr( $fields_groupkey ) . ' badgeos_achievements_step_subddl_’ . esc_attr( $fields_groupkey ) . ‘" name="’ . esc_attr( $field[‘id’] ) . '">’;175 foreach ( $field[‘options’] as $ddlkey => $ddlval ) {176 echo ‘<option value="’ . esc_attr( $ddlkey ) . '" ' . ( $ddlkey === $sel_val ? ‘selected’ : ‘’ ) . ‘>’ . esc_attr( $ddlval ) . '</option>’;177 }178 echo '</select>’;179 break;180 case ‘text’:181 echo ‘<input value="’ . esc_attr( $sel_val ) . ‘" type="’ . esc_attr( $field[‘type’] ) . ‘" size="4" id="’ . esc_attr( $field[‘id’] ) . ‘" class="badgeos_achievements_step_fields badgeos_achievements_step_subtxt_dynamic badgeos_achievements_step_fields_’ . esc_attr( $fields_groupkey ) . ' badgeos_achievements_step_subtxt_’ . esc_attr( $fields_groupkey ) . ‘" name="’ . esc_attr( $field[‘id’] ) . '" />’;182 break;183 case ‘number’:184 echo ‘<input value="’ . esc_attr( $sel_val ) . ‘" type="’ . esc_attr( $field[‘type’] ) . ‘" size="4" step="1" min="0" id="’ . esc_attr( $field[‘id’] ) . ‘" class="badgeos_achievements_step_fields badgeos_achievements_step_subtxt_dynamic badgeos_achievements_step_fields_’ . esc_attr( $fields_groupkey ) . ' badgeos_achievements_step_subtxt_’ . esc_attr( $fields_groupkey ) . ‘" name="’ . esc_attr( $field[‘id’] ) . '" />’;185 break;186 }187 }188 }189 echo '</div>’;190 }191 }192 ?>193 <?php do_action( 'badgeos_steps_ui_html_after_dynamic_trigger_type’, $step_id, $post_id ); ?>194195 <select class="select-achievement-type select-achievement-type-<?php echo esc_attr( $step_id ); ?>">196 <?php197 foreach ( $achievement_types as $achievement_type ) {198 if ( trim( $badgeos_settings[‘achievement_step_post_type’] ) === $achievement_type ) {199 continue;200 }201 echo ‘<option value="’ . esc_attr( $achievement_type ) . '" ' . selected( $requirements[‘achievement_type’], $achievement_type, false ) . ‘>’ . esc_html( ucfirst( $achievement_type ) ) . '</option>’;202 }203 ?>204 </select>205 <?php do_action( 'badgeos_steps_ui_html_after_achievement_type’, $step_id, $post_id ); ?>206 <select class="badgeos-select-visit-post badgeos-select-visit-post-<?php echo esc_attr( $step_id ); ?>">207 <?php208 $defaults = array(209 ‘post_type’ => 'post’,210 ‘numberposts’ => -1,211 ‘orderby’ => 'menu_order’,212 );213 $posts = get_posts( $defaults );214 echo ‘<option value="" selected>’ . esc_html__( 'Any Post’, ‘badgeos’ ) . '</option>’;215 foreach ( $posts as $post ) {216 echo ‘<option value="’ . esc_attr( $post->ID ) . '" ' . selected( $post->ID, $requirements[‘visit_post’], false ) . ‘>’ . esc_html( ucfirst( $post->post_title ) ) . '</option>’;217 }218 ?>219 </select>220 <?php do_action( 'badgeos_steps_ui_html_after_visit_post’, $step_id, $post_id ); ?>221 <select class="badgeos-select-visit-page badgeos-select-visit-page-<?php echo esc_attr( $step_id ); ?>">222 <?php223 $pages = get_pages();224 echo ‘<option value="" selected>’ . esc_html__( 'Any Page’, ‘badgeos’ ) . '</option>’;225 foreach ( $pages as $page ) {226 echo ‘<option value="’ . esc_attr( $page->ID ) . '" ' . selected( $page->ID, trim( $requirements[‘visit_page’] ), false ) . ‘>’ . esc_html( ucfirst( $page->post_title ) ) . '</option>’;227 }228 ?>229 </select>230 <?php do_action( 'badgeos_steps_ui_html_after_visit_page’, $step_id, $post_id ); ?>231 <select class="select-achievement-post select-achievement-post-<?php echo esc_attr( $step_id ); ?>"></select>232233 <input type="text" size="5" placeholder="<?php echo esc_html__( 'Post ID’, ‘badgeos’ ); ?>" value="<?php echo esc_attr( $requirements[‘achievement_post’] ); ?>" class="select-achievement-post select-achievement-post-<?php echo esc_attr( $step_id ); ?>">234235 <?php do_action( 'badgeos_steps_ui_html_after_achievement_post’, $step_id, $post_id ); ?>236 237 <input type="number" size="5" min="1" placeholder="<?php echo esc_html__( 'Years’, ‘badgeos’ ); ?>" value="<?php echo esc_attr( intval( $requirements[‘num_of_years’] ) > 0 ? intval( $requirements[‘num_of_years’] ) : ‘1’ ); ?>" class="badgeos-num-of-years badgeos-num-of-years-<?php echo esc_attr( $step_id ); ?>">238 <?php do_action( 'badgeos_steps_ui_html_after_num_of_years’, $step_id, $post_id ); ?>239 240 <input type="number" size="5" min="1" placeholder="<?php echo esc_html__( 'X Users’, ‘badgeos’ ); ?>" value="<?php echo esc_attr( intval( $requirements[‘x_number_of_users’] ) > 0 ? intval( $requirements[‘x_number_of_users’] ) : ‘’ ); ?>" class="badgeos-x-number-of-users badgeos-x-number-of-users-<?php echo esc_attr( $step_id ); ?>">241 <?php do_action( 'badgeos_steps_ui_html_after_x_number_of_users’, $step_id, $post_id ); ?>242243 <input type="number" size="5" min="1" placeholder="<?php echo esc_html__( 'days’, ‘badgeos’ ); ?>" value="<?php echo esc_attr( intval( $requirements[‘num_of_days’] ) > 0 ? intval( $requirements[‘num_of_days’] ) : ‘1’ ); ?>" class="badgeos-num-of-days badgeos-num-of-days-<?php echo esc_attr( $step_id ); ?>">244 <?php do_action( 'badgeos_steps_ui_html_after_num_of_days’, $step_id, $post_id ); ?>245246 <input type="number" size="5" min="1" placeholder="<?php echo esc_html__( 'months’, ‘badgeos’ ); ?>" value="<?php echo esc_attr( intval( $requirements[‘num_of_months’] ) > 0 ? intval( $requirements[‘num_of_months’] ) : ‘1’ ); ?>" class="badgeos-num-of-months badgeos-num-of-months-<?php echo esc_attr( $step_id ); ?>">247 <?php do_action( 'badgeos_steps_ui_html_after_num_of_months’, $step_id, $post_id ); ?>248249 <input type="number" size="5" min="1" placeholder="<?php echo esc_html__( 'days’, ‘badgeos’ ); ?>" value="<?php echo esc_attr( intval( $requirements[‘num_of_days_login’] ) > 0 ? intval( $requirements[‘num_of_days_login’] ) : ‘1’ ); ?>" class="badgeos-num-of-days-login badgeos-num-of-days-login-<?php echo esc_attr( $step_id ); ?>">250 <?php do_action( 'badgeos_steps_ui_html_after_num_of_days_login’, $step_id, $post_id ); ?>251252 <input class="required-count" type="text" size="3" maxlength="3" value="<?php echo esc_attr( $count ); ?>" placeholder="1">253 <?php echo esc_html( apply_filters( 'badgeos_steps_ui_html_count_text’, esc_html__( 'time(s).’, ‘badgeos’ ), $step_id, $post_id ) ); ?>254255 <?php do_action( 'badgeos_steps_ui_html_after_count_text’, $step_id, $post_id ); ?>256257 <div class="step-title"><label for="step-<?php echo esc_attr( $step_id ); ?>-title"><?php echo esc_html__( 'Label’, ‘badgeos’ ); ?>:</label> <input type="text" name="step-title" id="step-<?php echo esc_attr( $step_id ); ?>-title" class="title" value="<?php echo esc_attr( get_the_title( $step_id ) ); ?>" /></div>258 <span class="spinner spinner-step-<?php echo esc_attr( $step_id ); ?>"></span>259 </li>260 <?php261}262263/**264 * Get all the requirements of a given step.265 *266 * @since 1.0.0267 * @param integer $step_id The given step’s post ID.268 * @return array|bool An array of all the step requirements if it has any, false if not.269 */270function badgeos_get_step_requirements( $step_id = 0 ) {271272 // Setup our default requirements array, assume we require nothing273 $badgeos_settings = ! empty( badgeos_utilities::get_option( ‘badgeos_settings’ ) ) ? badgeos_utilities::get_option( ‘badgeos_settings’ ) : array();274 $requirements = array(275 ‘count’ => absint( badgeos_utilities::get_post_meta( $step_id, '_badgeos_count’, true ) ),276 ‘trigger_type’ => badgeos_utilities::get_post_meta( $step_id, '_badgeos_trigger_type’, true ),277 ‘achievement_type’ => badgeos_utilities::get_post_meta( $step_id, '_badgeos_achievement_type’, true ),278 ‘num_of_days’ => badgeos_utilities::get_post_meta( $step_id, '_badgeos_num_of_days’, true ),279 ‘num_of_days_login’ => badgeos_utilities::get_post_meta( $step_id, '_badgeos_num_of_days_login’, true ),280 ‘num_of_years’ => badgeos_utilities::get_post_meta( $step_id, '_badgeos_num_of_years’, true ),281 ‘x_number_of_users’ => badgeos_utilities::get_post_meta( $step_id, '_badgeos_x_number_of_users’, true ),282 ‘num_of_months’ => badgeos_utilities::get_post_meta( $step_id, '_badgeos_num_of_months’, true ),283 ‘achievement_post’ => badgeos_utilities::get_post_meta( $step_id, '_badgeos_achievement_post’, true ),284 ‘badgeos_subtrigger_id’ => badgeos_utilities::get_post_meta( $step_id, '_badgeos_subtrigger_id’, true ),285 ‘badgeos_subtrigger_value’ => badgeos_utilities::get_post_meta( $step_id, '_badgeos_subtrigger_value’, true ),286 ‘badgeos_fields_data’ => badgeos_utilities::get_post_meta( $step_id, '_badgeos_fields_data’, true ),287 ‘visit_post’ => badgeos_utilities::get_post_meta( $step_id, '_badgeos_visit_post’, true ),288 ‘visit_page’ => badgeos_utilities::get_post_meta( $step_id, '_badgeos_visit_page’, true ),289 );290291 if ( ! empty( $requirements[‘badgeos_fields_data’] ) ) {292293 $requirements[‘badgeos_fields_data’] = badgeos_extract_array_from_query_params( $requirements[‘badgeos_fields_data’] );294 }295296 // If the step requires a specific achievement.297 if ( ! empty( $requirements[‘achievement_type’] ) ) {298 $connected_activities = get_posts(299 array(300 ‘post_type’ => $requirements[‘achievement_type’],301 ‘posts_per_page’ => 1,302 ‘suppress_filters’ => false,303 ‘connected_type’ => $requirements[‘achievement_type’] . '-to-' . trim( $badgeos_settings[‘achievement_step_post_type’] ),304 ‘connected_to’ => $step_id,305 )306 );307 if ( ! empty( $connected_activities ) ) {308 $requirements[‘achievement_post’] = $connected_activities[0]->ID;309 }310 } elseif ( ‘badgeos_specific_new_comment’ === $requirements[‘trigger_type’] ) {311 $achievement_post = absint( badgeos_utilities::get_post_meta( $step_id, '_badgeos_achievement_post’, true ) );312 if ( 0 < $achievement_post ) {313 $requirements[‘achievement_post’] = $achievement_post;314 }315 }316317 // Available filter for overriding elsewhere318 return apply_filters( 'badgeos_get_step_requirements’, $requirements, $step_id );319}320321/**322 * AJAX Handler for adding a new step323 *324 * @since 1.0.0325 * @return void326 */327function badgeos_add_step_ajax_handler() {328329 // Create a new Step post and grab it’s ID330 $badgeos_settings = ( $exists = badgeos_utilities::get_option( ‘badgeos_settings’ ) ) ? $exists : array();331 $step_id = wp_insert_post(332 array(333 ‘post_type’ => trim( $badgeos_settings[‘achievement_step_post_type’] ),334 ‘post_status’ => 'publish’,335 )336 );337338 $badgeos_achievement_id = isset( $_POST[‘achievement_id’] ) ? absint( $_POST[‘achievement_id’] ) : '’;339 // Output the edit step html to insert into the Steps metabox340 badgeos_steps_ui_html( $step_id, $badgeos_achievement_id );341342 // Grab the post object for our Badge343 $achievement = badgeos_utilities::badgeos_get_post( $badgeos_achievement_id );344345 // Create the P2P connection from the step to the badge346 $p2p_id = p2p_create_connection(347 trim( $badgeos_settings[‘achievement_step_post_type’] ) . '-to-' . $achievement->post_type,348 array(349 ‘from’ => $step_id,350 ‘to’ => $badgeos_achievement_id,351 ‘meta’ => array(352 ‘date’ => current_time( ‘mysql’ ),353 ),354 )355 );356357 // Add relevant meta to our P2P connection358 p2p_add_meta( $p2p_id, 'order’, ‘0’ );359360 // Die here, because it’s AJAX361 die;362}363add_action( 'wp_ajax_add_step’, ‘badgeos_add_step_ajax_handler’ );364365/**366 * AJAX Handler for deleting a step367 *368 * @since 1.0.0369 * @return void370 */371function badgeos_delete_step_ajax_handler() {372 if ( isset( $_POST[‘step_id’] ) ) {373 wp_delete_post( absint( $_POST[‘step_id’] ) );374 }375 die;376}377add_action( 'wp_ajax_delete_step’, ‘badgeos_delete_step_ajax_handler’ );378379380function sanitize_associative_array( array &$array, $filter = false ){381 array_walk_recursive( $array, function ( &$value ) use ( $filter ) {382 $value = trim( $value );383 if ( $filter ) {384 $value = filter_var( $value, FILTER_SANITIZE_STRING );385 }386 });387 return $array;388}389390/**391 * AJAX Handler for saving all steps.392 *393 * @since 1.0.0394 * @return void395 */396function badgeos_update_steps_ajax_handler() {397398 // Only continue if we have any steps.399 $badgeos_settings = ! empty( badgeos_utilities::get_option( ‘badgeos_settings’ ) ) ? badgeos_utilities::get_option( ‘badgeos_settings’ ) : array();400 if ( isset( $_POST[‘steps’] ) ) {401402 // Grab our $wpdb global.403 global $wpdb;404405 // Setup an array for storing all our step titles.406 // This lets us dynamically update the Label field when steps are saved.407 $new_titles = array();408409 $steps = isset( $_POST[‘steps’] ) ? sanitize_associative_array( $_POST[‘steps’] ) : array();410411 // Loop through each of the created steps.412 foreach ( $steps as $key => $step ) {413414 // Grab all of the relevant values of that step.415 $step_id = $step[‘step_id’];416 $required_count = ( ! empty( $step[‘required_count’] ) ) ? sanitize_text_field( $step[‘required_count’] ) : 1;417 $trigger_type = $step[‘trigger_type’];418 $achievement_type = $step[‘achievement_type’];419 $visit_post = $step[‘visit_post’];420 $visit_page = $step[‘visit_page’];421 $num_of_years = $step[‘num_of_years’];422 $x_number_of_users = $step[‘x_number_of_users’];423 $num_of_months = $step[‘num_of_months’];424 $num_of_days = $step[‘num_of_days’];425 $num_of_days_login = $step[‘num_of_days_login’];426 $badgeos_subtrigger_id = '’;427 $badgeos_subtrigger_value = '’;428 $badgeos_fields_data = '’;429 if ( isset( $step[‘badgeos_subtrigger_id’] ) ) {430 $badgeos_subtrigger_id = $step[‘badgeos_subtrigger_id’];431 }432 if ( isset( $step[‘badgeos_subtrigger_value’] ) ) {433 $badgeos_subtrigger_value = $step[‘badgeos_subtrigger_value’];434 }435 if ( isset( $step[‘badgeos_fields_data’] ) ) {436 $badgeos_fields_data = $step[‘badgeos_fields_data’];437 }438439 // Clear all relation data440 $wpdb->query( $wpdb->prepare( “DELETE FROM $wpdb->p2p WHERE p2p_to=%d", $step_id ) );441 badgeos_utilities::del_post_meta( $step_id, ‘_badgeos_achievement_post’ );442 badgeos_utilities::del_post_meta( $step_id, ‘_badgeos_num_of_days’ );443 badgeos_utilities::del_post_meta( $step_id, ‘_badgeos_num_of_days_login’ );444 badgeos_utilities::del_post_meta( $step_id, ‘_badgeos_num_of_years’ );445 badgeos_utilities::del_post_meta( $step_id, ‘_badgeos_x_number_of_users’ );446 badgeos_utilities::del_post_meta( $step_id, ‘_badgeos_num_of_months’ );447 // Flip between our requirement types and make an appropriate connection448 switch ( $trigger_type ) {449450 // Connect the step to ANY of the given achievement type451 case 'any-achievement’:452 $title = sprintf( __( 'any %s’, ‘badgeos’ ), $achievement_type );453 break;454 case 'all-achievements’:455 $title = sprintf( __( 'all %s’, ‘badgeos’ ), $achievement_type );456 break;457 case 'specific-achievement’:458 p2p_create_connection(459 $step[‘achievement_type’] . '-to-' . trim( $badgeos_settings[‘achievement_step_post_type’] ),460 array(461 ‘from’ => absint( $step[‘achievement_post’] ),462 ‘to’ => $step_id,463 ‘meta’ => array(464 ‘date’ => current_time( ‘mysql’ ),465 ),466 )467 );468469 badgeos_utilities::update_post_meta( $step_id, '_badgeos_achievement_post’, absint( $step[‘achievement_post’] ) );470 $title = ‘"’ . get_the_title( $step[‘achievement_post’] ) . '"’;471 break;472 case 'badgeos_specific_new_comment’:473 badgeos_utilities::update_post_meta( $step_id, '_badgeos_achievement_post’, absint( $step[‘achievement_post’] ) );474 $title = sprintf( esc_html__( 'comment on post %d’, ‘badgeos’ ), $step[‘achievement_post’] );475 break;476 case 'badgeos_wp_not_login’:477 badgeos_utilities::update_post_meta( $step_id, '_badgeos_num_of_days’, absint( $step[‘num_of_days’] ) );478 $title = sprintf( esc_html__( 'Not login for %d days’, ‘badgeos’ ), $step[‘num_of_days’] );479 break;480 case 'badgeos_wp_login_x_days’:481 badgeos_utilities::update_post_meta( $step_id, '_badgeos_num_of_days_login’, absint( $step[‘num_of_days_login’] ) );482 $title = sprintf( esc_html__( 'login for %d day(s)', ‘badgeos’ ), $step[‘num_of_days_login’] );483 break;484 case 'badgeos_on_completing_num_of_year’:485 badgeos_utilities::update_post_meta( $step_id, '_badgeos_num_of_years’, absint( $num_of_years ) );486 if ( ! empty( $num_of_years ) ) {487 $title = sprintf( esc_html__( 'on completing %d year(s)', ‘badgeos’ ), $num_of_years );488 } else {489 $title = esc_html__( 'on completing number of year(s)', ‘badgeos’ );490 }491 break;492 case 'badgeos_on_the_first_x_users’:493 badgeos_utilities::update_post_meta( $step_id, '_badgeos_x_number_of_users’, absint( $x_number_of_users ) );494495 $x_number_of_users_date = badgeos_utilities::get_post_meta( $step_id, '_badgeos_x_number_of_users_date’, true );496 if ( empty( $x_number_of_users_date ) ) {497 badgeos_utilities::update_post_meta( $step_id, '_badgeos_x_number_of_users_date’, date( ‘Y-m-d’ ) );498 }499500 if ( ! empty( $x_number_of_users ) ) {501 $title = sprintf( esc_html__( 'The first %d user(s)', 'badgeos’, ‘badgeos’ ), $x_number_of_users );502 } else {503 $title = esc_html__( 'The first X user(s)', ‘badgeos’ );504 }505 break;506 case 'badgeos_on_completing_num_of_month’:507 badgeos_utilities::update_post_meta( $step_id, '_badgeos_num_of_months’, absint( $num_of_months ) );508 if ( ! empty( $num_of_months ) ) {509 $title = sprintf( esc_html__( 'on completing %d month(s)', ‘badgeos’ ), $num_of_months );510 } else {511 $title = esc_html__( 'on completing number of month(s)', ‘badgeos’ );512 }513 break;514 case 'badgeos_on_completing_num_of_day’:515 badgeos_utilities::update_post_meta( $step_id, '_badgeos_num_of_days’, absint( $num_of_days ) );516 if ( ! empty( $num_of_days ) ) {517 $title = sprintf( esc_html__( 'on completing %d day(s)', ‘badgeos’ ), $num_of_days );518 } else {519 $title = esc_html__( 'on completing number of day(s)', ‘badgeos’ );520 }521 break;522 case 'badgeos_visit_a_post’:523 badgeos_utilities::update_post_meta( $step_id, '_badgeos_visit_post’, absint( $visit_post ) );524 if ( ! empty( $visit_post ) ) {525 $title = sprintf( esc_html__( 'Visit a Post#%d’, ‘badgeos’ ), $visit_post );526 } else {527 $title = esc_html__( 'Visit a Post’, ‘badgeos’ );528 }529 break;530 case 'badgeos_award_author_on_visit_post’:531 badgeos_utilities::update_post_meta( $step_id, '_badgeos_visit_post’, absint( $visit_post ) );532 if ( ! empty( $visit_post ) ) {533 $title = sprintf( esc_html__( 'Author on Visit a Post#%d’, ‘badgeos’ ), $visit_post );534 } else {535 $title = esc_html__( 'Author on Visit a Post’, ‘badgeos’ );536 }537 break;538 case 'badgeos_visit_a_page’:539 badgeos_utilities::update_post_meta( $step_id, '_badgeos_visit_page’, absint( $visit_page ) );540 if ( ! empty( $visit_page ) ) {541 $title = sprintf( esc_html__( 'Visit a Page#%d’, ‘badgeos’ ), $visit_page );542 } else {543 $title = esc_html__( 'Visit a Page’, ‘badgeos’ );544 }545 break;546 case 'badgeos_award_author_on_visit_page’:547 badgeos_utilities::update_post_meta( $step_id, '_badgeos_visit_page’, absint( $visit_page ) );548 if ( ! empty( $visit_page ) ) {549 $title = sprintf( esc_html__( 'Author on Visit a Page#%d’, ‘badgeos’ ), $visit_page );550 } else {551 $title = esc_html__( 'Author on Visit a Page’, ‘badgeos’ );552 }553 break;554 default:555 $triggers = badgeos_get_activity_triggers();556 $title = $triggers[ $trigger_type ];557 if ( is_array( $title ) ) {558 if ( ! empty( $badgeos_subtrigger_value ) ) {559 $title = $badgeos_subtrigger_value;560 } else {561 $title = $title[‘label’];562 }563 }564 break;565566 }567568 // Update the step order569 p2p_update_meta( badgeos_get_p2p_id_from_child_id( $step_id ), 'order’, $key );570571 // Update our relevant meta572 badgeos_utilities::update_post_meta( $step_id, '_badgeos_count’, $required_count );573 badgeos_utilities::update_post_meta( $step_id, '_badgeos_trigger_type’, $trigger_type );574 badgeos_utilities::update_post_meta( $step_id, '_badgeos_achievement_type’, $achievement_type );575 badgeos_utilities::update_post_meta( $step_id, '_badgeos_subtrigger_id’, $badgeos_subtrigger_id );576 badgeos_utilities::update_post_meta( $step_id, '_badgeos_subtrigger_value’, $badgeos_subtrigger_value );577 badgeos_utilities::update_post_meta( $step_id, '_badgeos_fields_data’, $badgeos_fields_data );578579 // Available hook for custom Activity Triggers580 $custom_title = sprintf( esc_html__( 'Earn %1$s %2$s.’, ‘badgeos’ ), $title, sprintf( _n( '%d time’, '%d times’, $required_count ), $required_count ) );581 $custom_title = apply_filters( 'badgeos_save_step’, $custom_title, $step_id, $step );582583 // Update our original post with the new title584 $post_title = ! empty( $step[‘title’] ) ? $step[‘title’] : $custom_title;585 wp_update_post(586 array(587 ‘ID’ => $step_id,588 ‘post_title’ => $post_title,589 )590 );591592 // Add the title to our AJAX return593 $new_titles[ $step_id ] = stripslashes( $post_title );594595 }596597 // Send back all our step titles598 echo wp_json_encode( $new_titles );599600 }601602 // Cave Johnson. We’re done here.603 die;604605}606add_action( 'wp_ajax_update_steps’, ‘badgeos_update_steps_ajax_handler’ );607608/**609 * AJAX helper for getting our posts and returning select options610 *611 * @since 1.0.0612 * @return void613 */614function badgeos_activity_trigger_post_select_ajax_handler() {615616 // Grab our achievement type from the AJAX request.617 $achievement_type = isset( $_REQUEST[‘achievement_type’] ) ? sanitize_text_field( wp_unslash( $_REQUEST[‘achievement_type’] ) ) : '’;618619 $exclude_posts = isset( $_REQUEST[‘excluded_posts’] ) ? array_map( 'sanitize_text_field’, (array) wp_unslash( $_REQUEST[‘excluded_posts’] ) ) : array();620621 $requirements = isset( $_REQUEST[‘step_id’] ) ? badgeos_get_step_requirements( sanitize_text_field( wp_unslash( $_REQUEST[‘step_id’] ) ) ) : '’;622623 // If we don’t have an achievement type, bail now.624 if ( empty( $achievement_type ) ) {625 die();626 }627628 // Grab all our posts for this achievement type.629 $achievements = get_posts(630 array(631 ‘post_type’ => $achievement_type,632 ‘post__not_in’ => $exclude_posts,633 ‘posts_per_page’ => -1,634 ‘orderby’ => 'title’,635 ‘order’ => 'ASC’,636 )637 );638639 // Setup our output.640 $output = '’;641 foreach ( $achievements as $achievement ) {642 $output .= ‘<option value="’ . $achievement->ID . '” ' . selected( $requirements[‘achievement_post’], $achievement->ID, false ) . ‘>’ . $achievement->post_title . '</option>’;643 }644645 // Send back our results and die like a man.646 echo $output;647 die();648}649add_action( 'wp_ajax_post_select_ajax’, ‘badgeos_activity_trigger_post_select_ajax_handler’ );650651/**652 * Get the the ID of a post connected to a given child post ID653 *654 * @since 1.0.0655 * @param integer $child_id The given child’s post ID656 * @return integer The resulting connected post ID657 */658function badgeos_get_p2p_id_from_child_id( $child_id = 0 ) {659 global $wpdb;660 $p2p_id = $wpdb->get_var( $wpdb->prepare( "SELECT p2p_id FROM $wpdb->p2p WHERE p2p_from = %d ", $child_id ) );661 return $p2p_id;662}663664/**665 * Get the sort order for a given step666 *667 * @since 1.0.0668 * @param integer $step_id The given step’s post ID669 * @return integer The step’s sort order670 */671function get_step_menu_order( $step_id = 0 ) {672 global $wpdb;673 $p2p_id = $wpdb->get_var( $wpdb->prepare( "SELECT p2p_id FROM $wpdb->p2p WHERE p2p_from = %d", $step_id ) );674 $menu_order = $wpdb->get_var( $wpdb->prepare( "SELECT meta_value FROM $wpdb->p2pmeta WHERE p2p_id=%d AND meta_key=’order’", $p2p_id ) );675 if ( ! $menu_order || ‘NaN’ === $menu_order ) {676 $menu_order = '0’;677 }678 return $menu_order;679}680681/**682 * Helper function for comparing our step sort order (used in uasort() in badgeos_create_steps_meta_box())683 *684 * @since 1.0.0685 * @param integer $step1 The order number of our given step686 * @param integer $step2 The order number of the step we’re comparing against687 * @return integer 0 if the order matches, -1 if it’s lower, 1 if it’s higher688 */689function badgeos_compare_step_order( $step1 = 0, $step2 = 0 ) {690 if ( $step1->order === $step2->order ) {691 return 0;692 }693 return ( $step1->order < $step2->order ) ? -1 : 1;694}