| @@ -43,8 +43,14 @@ | ||
| 43 | 43 | */ |
| 44 | 44 | public function __construct() { |
| 45 | 45 | add_action( 'wp_ajax_sellkit_get_applied_discount', [ $this, 'get_applied_discounts' ] ); |
| 46 | 46 | add_action( 'wp_ajax_sellkit_get_generated_coupons', [ $this, 'get_generated_coupons' ] ); |
| 47 | + add_action( 'wp_ajax_sellkit_funnel_get_contacts', [ $this, 'get_contacts' ] ); | |
| 48 | + add_action( 'wp_ajax_sellkit_funnel_get_contacts_history', [ $this, 'get_history' ] ); | |
| 49 | + add_action( 'wp_ajax_sellkit_funnel_delete_contact', [ $this, 'delete_contact' ] ); | |
| 50 | + add_action( 'wp_ajax_sellkit_funnel_get_contact_details', [ $this, 'get_contact_details' ] ); | |
| 51 | + add_action( 'wp_ajax_sellkit_funnel_get_funnel_orders', [ $this, 'get_funnel_orders' ] ); | |
| 52 | + add_action( 'wp_ajax_sellkit_funnel_save_contact_details', [ $this, 'save_contact_details' ] ); | |
| 47 | 53 | } |
| 48 | 54 | |
| 49 | 55 | /** |
| 50 | 56 | * Gets applied discounts. |
| @@ -53,11 +59,25 @@ | ||
| 53 | 59 | */ |
| 54 | 60 | public function get_applied_discounts() { |
| 55 | 61 | check_ajax_referer( 'sellkit', 'nonce' ); |
| 56 | 62 | |
| 57 | - $page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_STRING ); | |
| 58 | - $id = filter_input( INPUT_GET, 'id', FILTER_SANITIZE_STRING ); | |
| 63 | + // Prefer `paged` — `page` can collide with other WP query usage; accept both GET/POST for ajax. | |
| 64 | + $page = sellkit_htmlspecialchars( INPUT_GET, 'paged' ); | |
| 65 | + if ( empty( $page ) ) { | |
| 66 | + $page = sellkit_htmlspecialchars( INPUT_POST, 'paged' ); | |
| 67 | + } | |
| 68 | + if ( empty( $page ) ) { | |
| 69 | + $page = sellkit_htmlspecialchars( INPUT_GET, 'page' ); | |
| 70 | + } | |
| 71 | + if ( empty( $page ) ) { | |
| 72 | + $page = sellkit_htmlspecialchars( INPUT_POST, 'page' ); | |
| 73 | + } | |
| 59 | 74 | |
| 75 | + $id = sellkit_htmlspecialchars( INPUT_GET, 'id' ); | |
| 76 | + if ( empty( $id ) ) { | |
| 77 | + $id = sellkit_htmlspecialchars( INPUT_POST, 'id' ); | |
| 78 | + } | |
| 79 | + | |
| 60 | 80 | if ( empty( $page ) ) { |
| 61 | 81 | $page = 1; |
| 62 | 82 | } |
| 63 | 83 | |
| @@ -78,9 +98,9 @@ | ||
| 78 | 98 | $limit |
| 79 | 99 | ); |
| 80 | 100 | |
| 81 | 101 | $prepared_total_query = $wpdb->prepare( "SELECT count(*) as total_discounts FROM {$wpdb->prefix}{$sellkit_prefix}applied_discount where discount_id = %d;", |
| 82 | - $id, | |
| 102 | + $id | |
| 83 | 103 | ); |
| 84 | 104 | |
| 85 | 105 | $discounts = $wpdb->get_results( |
| 86 | 106 | $prepared_query, |
| @@ -89,9 +109,9 @@ | ||
| 89 | 109 | $total = $wpdb->get_results( |
| 90 | 110 | $prepared_total_query, |
| 91 | 111 | ARRAY_A ); |
| 92 | 112 | |
| 93 | - $total_discount_num = ! empty( $total[0]['total_discounts'] ) ? $total[0]['total_discounts'] : ''; | |
| 113 | + $total_discount_num = ! empty( $total[0]['total_discounts'] ) ? (int) $total[0]['total_discounts'] : 0; | |
| 94 | 114 | // phpcs:enable |
| 95 | 115 | |
| 96 | 116 | wp_send_json_success( [ |
| 97 | 117 | 'discounts' => $discounts, |
| @@ -107,10 +127,10 @@ | ||
| 107 | 127 | */ |
| 108 | 128 | public function get_generated_coupons() { |
| 109 | 129 | check_ajax_referer( 'sellkit', 'nonce' ); |
| 110 | 130 | |
| 111 | - $page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_STRING ); | |
| 112 | - $id = filter_input( INPUT_GET, 'id', FILTER_SANITIZE_STRING ); | |
| 131 | + $page = sellkit_htmlspecialchars( INPUT_GET, 'page' ); | |
| 132 | + $id = sellkit_htmlspecialchars( INPUT_GET, 'id' ); | |
| 113 | 133 | |
| 114 | 134 | $args = [ |
| 115 | 135 | 'post_type' => 'shop_coupon', |
| 116 | 136 | 'post_status' => 'publish', |
| @@ -130,9 +150,20 @@ | ||
| 130 | 150 | $expiry_date = get_post_meta( $post->ID, 'date_expires', true ); |
| 131 | 151 | $customer_email = get_post_meta( $post->ID, 'customer_email', true ); |
| 132 | 152 | $usage_limit = get_post_meta( $post->ID, 'usage_limit', true ); |
| 133 | 153 | $usage_count = get_post_meta( $post->ID, 'usage_count', true ); |
| 154 | + $used_by = get_post_meta( $post->ID, '_used_by', true ); | |
| 134 | 155 | |
| 156 | + if ( empty( $customer_email ) && ! empty( $used_by ) ) { | |
| 157 | + $used_by_customer = is_email( $used_by ) ? $used_by : ''; | |
| 158 | + | |
| 159 | + if ( empty( $used_by_customer ) ) { | |
| 160 | + $used_by_customer = get_userdata( intval( $used_by ) )->user_email; | |
| 161 | + } | |
| 162 | + | |
| 163 | + $customer_email = [ $used_by_customer ]; | |
| 164 | + } | |
| 165 | + | |
| 135 | 166 | $post->customer = ! empty( $customer_email ) ? $customer_email : ''; |
| 136 | 167 | $post->usage_limit = ! empty( $usage_limit ) ? $usage_limit : ''; |
| 137 | 168 | $post->usage_count = ! empty( $usage_count ) ? $usage_count : ''; |
| 138 | 169 | $post->created_at = date_i18n( 'Y/m/d h:i A', strtotime( $post->post_date ) ); |
| @@ -148,8 +179,321 @@ | ||
| 148 | 179 | 'coupons' => $coupons, |
| 149 | 180 | 'max_item_num' => self::CUSTOM_TABLE_PER_PAGE, |
| 150 | 181 | 'total' => $query->found_posts, |
| 151 | 182 | ] ); |
| 183 | + } | |
| 184 | + | |
| 185 | + /** | |
| 186 | + * Gets contacts. | |
| 187 | + * | |
| 188 | + * @since 1.5.0 | |
| 189 | + */ | |
| 190 | + public function get_contacts() { | |
| 191 | + check_ajax_referer( 'sellkit', 'nonce' ); | |
| 192 | + | |
| 193 | + $page = sellkit_htmlspecialchars( INPUT_GET, 'page' ); | |
| 194 | + $funnel_id = sellkit_htmlspecialchars( INPUT_GET, 'funnel_id' ); | |
| 195 | + | |
| 196 | + if ( empty( $page ) ) { | |
| 197 | + $page = 1; | |
| 198 | + } | |
| 199 | + | |
| 200 | + $page_index = ( $page - 1 ) * self::CUSTOM_TABLE_PER_PAGE; | |
| 201 | + $limit = self::CUSTOM_TABLE_PER_PAGE; | |
| 202 | + | |
| 203 | + global $wpdb; | |
| 204 | + | |
| 205 | + $sellkit_prefix = Database::DATABASE_PREFIX; | |
| 206 | + $contact_table = "{$wpdb->prefix}{$sellkit_prefix}funnel_contact"; | |
| 207 | + $users_table = "{$wpdb->prefix}users "; | |
| 208 | + | |
| 209 | + // phpcs:disable | |
| 210 | + $contacts = $wpdb->get_results( | |
| 211 | + $wpdb->prepare( "SELECT ct.user_id as user_id, ct.id as id, ct.created_at, ut.user_email, ut.display_name, SUM( ct.total_spent ) as sum_total_spent FROM {$contact_table} AS ct | |
| 212 | + LEFT JOIN {$users_table} ut ON ut.ID = ct.user_id | |
| 213 | + where ct.funnel_id = %d | |
| 214 | + group by ct.user_id | |
| 215 | + order by ct.id desc limit %d , %d;", $funnel_id, $page_index, $limit ), ARRAY_A ); | |
| 216 | + | |
| 217 | + // Must match the grouped list query (one row per user), not raw row count. | |
| 218 | + $total_contacts = $wpdb->get_results( | |
| 219 | + $wpdb->prepare( | |
| 220 | + "SELECT COUNT(*) AS total_contacts FROM ( SELECT user_id FROM {$contact_table} WHERE funnel_id = %d GROUP BY user_id ) AS grouped_contacts", | |
| 221 | + $funnel_id | |
| 222 | + ), | |
| 223 | + ARRAY_A | |
| 224 | + ); | |
| 225 | + $total_contacts_num = ! empty( $total_contacts[0]['total_contacts'] ) ? $total_contacts[0]['total_contacts'] : ''; | |
| 226 | + // phpcs:enable | |
| 227 | + | |
| 228 | + wp_send_json_success( [ | |
| 229 | + 'contacts' => $contacts, | |
| 230 | + 'total' => intval( $total_contacts_num ), | |
| 231 | + 'max_item_num' => self::CUSTOM_TABLE_PER_PAGE, | |
| 232 | + ] ); | |
| 233 | + } | |
| 234 | + | |
| 235 | + /** | |
| 236 | + * Gets contacts. | |
| 237 | + * | |
| 238 | + * @since 1.5.0 | |
| 239 | + */ | |
| 240 | + public function get_history() { | |
| 241 | + check_ajax_referer( 'sellkit', 'nonce' ); | |
| 242 | + | |
| 243 | + $page = sellkit_htmlspecialchars( INPUT_GET, 'page' ); | |
| 244 | + $funnel_id = sellkit_htmlspecialchars( INPUT_GET, 'funnel_id' ); | |
| 245 | + $contact_id = sellkit_htmlspecialchars( INPUT_GET, 'contact_id' ); | |
| 246 | + $user_id = $this->get_user_id_by_contact_table( $contact_id ); | |
| 247 | + | |
| 248 | + if ( empty( $user_id ) ) { | |
| 249 | + wp_send_json_error( esc_html__( 'Something went wrong.', 'sellkit' ) ); | |
| 250 | + } | |
| 251 | + | |
| 252 | + if ( empty( $page ) ) { | |
| 253 | + $page = 1; | |
| 254 | + } | |
| 255 | + | |
| 256 | + $page_index = ( $page - 1 ) * self::CUSTOM_TABLE_PER_PAGE; | |
| 257 | + $limit = self::CUSTOM_TABLE_PER_PAGE; | |
| 258 | + | |
| 259 | + global $wpdb; | |
| 260 | + | |
| 261 | + $sellkit_prefix = Database::DATABASE_PREFIX; | |
| 262 | + $contact_table = "{$wpdb->prefix}{$sellkit_prefix}funnel_contact"; | |
| 263 | + $users_table = "{$wpdb->prefix}users "; | |
| 264 | + | |
| 265 | + // phpcs:disable | |
| 266 | + $contacts = $wpdb->get_results( | |
| 267 | + $wpdb->prepare( "SELECT ct.*, ct.created_at, ut.user_email, ut.display_name, ct.total_spent as sum_total_spent FROM {$contact_table} AS ct | |
| 268 | + LEFT JOIN {$users_table} ut ON ut.ID = ct.user_id | |
| 269 | + where ct.funnel_id = %d and ct.user_id = %d | |
| 270 | + order by ct.id desc limit %d , %d;", $funnel_id, $user_id, $page_index, $limit ), ARRAY_A ); | |
| 271 | + | |
| 272 | + $total_contacts = $wpdb->get_results( | |
| 273 | + $wpdb->prepare( "SELECT count(*) as total_contacts FROM {$contact_table} WHERE funnel_id = %d AND user_id = %d;", $funnel_id, $user_id ), ARRAY_A ); | |
| 274 | + $total_contacts_num = ! empty( $total_contacts[0]['total_contacts'] ) ? $total_contacts[0]['total_contacts'] : ''; | |
| 275 | + // phpcs:enable | |
| 276 | + | |
| 277 | + wp_send_json_success( [ | |
| 278 | + 'contacts' => $contacts, | |
| 279 | + 'total' => intval( $total_contacts_num ), | |
| 280 | + 'max_item_num' => self::CUSTOM_TABLE_PER_PAGE, | |
| 281 | + ] ); | |
| 282 | + } | |
| 283 | + | |
| 284 | + /** | |
| 285 | + * Delete a single contact. | |
| 286 | + * | |
| 287 | + * @since 1.5.0 | |
| 288 | + */ | |
| 289 | + public function delete_contact() { | |
| 290 | + check_ajax_referer( 'sellkit', 'nonce' ); | |
| 291 | + | |
| 292 | + $entry_id = sellkit_htmlspecialchars( INPUT_GET, 'entry_id' ); | |
| 293 | + | |
| 294 | + global $wpdb; | |
| 295 | + | |
| 296 | + $sellkit_prefix = Database::DATABASE_PREFIX; | |
| 297 | + $contact_table = "{$wpdb->prefix}{$sellkit_prefix}funnel_contact"; | |
| 298 | + | |
| 299 | + // phpcs:disable | |
| 300 | + $delete_result = $wpdb->delete( $contact_table, [ 'id' => intval( $entry_id ) ] ); | |
| 301 | + // phpcs:enable | |
| 302 | + | |
| 303 | + if ( ! $delete_result ) { | |
| 304 | + wp_send_json_error(); | |
| 305 | + } | |
| 306 | + | |
| 307 | + wp_send_json_success(); | |
| 308 | + } | |
| 309 | + | |
| 310 | + /** | |
| 311 | + * Gets contact details. | |
| 312 | + * | |
| 313 | + * @since 1.5.0 | |
| 314 | + */ | |
| 315 | + public function get_contact_details() { | |
| 316 | + check_ajax_referer( 'sellkit', 'nonce' ); | |
| 317 | + | |
| 318 | + $contact_id = sellkit_htmlspecialchars( INPUT_GET, 'contact_id' ); | |
| 319 | + | |
| 320 | + global $wpdb; | |
| 321 | + | |
| 322 | + $sellkit_prefix = Database::DATABASE_PREFIX; | |
| 323 | + $contact_table = "{$wpdb->prefix}{$sellkit_prefix}funnel_contact"; | |
| 324 | + $contact_segmentation = "{$wpdb->prefix}{$sellkit_prefix}contact_segmentation"; | |
| 325 | + $users_table = "{$wpdb->prefix}users "; | |
| 326 | + | |
| 327 | + // phpcs:disable | |
| 328 | + $contact_details = $wpdb->get_results( | |
| 329 | + $wpdb->prepare( "SELECT ct.*, ut.user_email, ut.display_name, cs.* FROM {$contact_table} AS ct | |
| 330 | + LEFT JOIN {$users_table} ut ON ut.ID = ct.user_id | |
| 331 | + LEFT JOIN {$contact_segmentation} cs ON ut.user_email = cs.email | |
| 332 | + WHERE ct.id = %d limit 1;", $contact_id ), ARRAY_A ); | |
| 333 | + // phpcs:enable | |
| 334 | + | |
| 335 | + if ( empty( $contact_details[0] ) || ! is_array( $contact_details[0] ) ) { | |
| 336 | + wp_send_json_error( esc_html__( 'Something went wrong.', 'sellkit' ) ); | |
| 337 | + } | |
| 338 | + | |
| 339 | + $user_id = $contact_details[0]['user_id']; | |
| 340 | + | |
| 341 | + wp_send_json_success( [ | |
| 342 | + 'user_details' => $contact_details[0], | |
| 343 | + 'user_meta' => array_merge( get_user_meta( $user_id ), [ 'user_role' => get_userdata( $user_id )->roles ] ), | |
| 344 | + ] ); | |
| 345 | + } | |
| 346 | + | |
| 347 | + /** | |
| 348 | + * Gets funnel orders. | |
| 349 | + * | |
| 350 | + * @since 1.5.0 | |
| 351 | + */ | |
| 352 | + public function get_funnel_orders() { | |
| 353 | + check_ajax_referer( 'sellkit', 'nonce' ); | |
| 354 | + | |
| 355 | + $contact_id = sellkit_htmlspecialchars( INPUT_GET, 'contact_id' ); | |
| 356 | + $funnel_id = sellkit_htmlspecialchars( INPUT_GET, 'funnel_id' ); | |
| 357 | + $page_index = sellkit_htmlspecialchars( INPUT_GET, 'page' ); | |
| 358 | + $limit = self::CUSTOM_TABLE_PER_PAGE; | |
| 359 | + $user_id = $this->get_user_id_by_contact_table( $contact_id ); | |
| 360 | + | |
| 361 | + if ( empty( $user_id ) ) { | |
| 362 | + wp_send_json_error( esc_html__( 'Something went wrong.', 'sellkit' ) ); | |
| 363 | + } | |
| 364 | + | |
| 365 | + $contact_orders = $this->get_contact_order_details( $funnel_id, $user_id, $page_index, $limit ); | |
| 366 | + | |
| 367 | + wp_send_json_success( [ | |
| 368 | + 'user_orders' => $contact_orders['orders'], | |
| 369 | + 'total' => intval( $contact_orders['total'] ), | |
| 370 | + 'max_item_num' => self::CUSTOM_TABLE_PER_PAGE, | |
| 371 | + ] ); | |
| 372 | + } | |
| 373 | + | |
| 374 | + /** | |
| 375 | + * Gets funnel id by contact table. | |
| 376 | + * | |
| 377 | + * @since 1.5.0 | |
| 378 | + * @param string $contact_id Contact Id. | |
| 379 | + */ | |
| 380 | + public function get_user_id_by_contact_table( $contact_id ) { | |
| 381 | + global $wpdb; | |
| 382 | + | |
| 383 | + $sellkit_prefix = Database::DATABASE_PREFIX; | |
| 384 | + $contact_table = "{$wpdb->prefix}{$sellkit_prefix}funnel_contact"; | |
| 385 | + | |
| 386 | + // phpcs:disable | |
| 387 | + $contact_details = $wpdb->get_results( | |
| 388 | + $wpdb->prepare( "SELECT ct.user_id FROM {$contact_table} AS ct | |
| 389 | + WHERE ct.id = %d limit 1;", $contact_id ), ARRAY_A ); | |
| 390 | + // phpcs:enable | |
| 391 | + | |
| 392 | + if ( empty( $contact_details[0] ) || ! is_array( $contact_details[0] ) ) { | |
| 393 | + return false; | |
| 394 | + } | |
| 395 | + | |
| 396 | + return $contact_details[0]['user_id']; | |
| 397 | + } | |
| 398 | + | |
| 399 | + /** | |
| 400 | + * Saves contact details. | |
| 401 | + * | |
| 402 | + * @since 1.5.0 | |
| 403 | + */ | |
| 404 | + public function save_contact_details() { | |
| 405 | + check_ajax_referer( 'sellkit', 'nonce' ); | |
| 406 | + | |
| 407 | + $fields = sellkit_post( 'fields' ); | |
| 408 | + $first_name = ! empty( $fields['contacts_detail_first_name'] ) ? sanitize_text_field( $fields['contacts_detail_first_name'] ) : ''; | |
| 409 | + $last_name = ! empty( $fields['contacts_detail_last_name'] ) ? sanitize_text_field( $fields['contacts_detail_last_name'] ) : ''; | |
| 410 | + $user_id = filter_input( INPUT_POST, 'user_id', FILTER_SANITIZE_NUMBER_INT ); | |
| 411 | + | |
| 412 | + $result = wp_update_user( [ | |
| 413 | + 'ID' => $user_id, | |
| 414 | + 'first_name' => $first_name, | |
| 415 | + 'last_name' => $last_name, | |
| 416 | + ] ); | |
| 417 | + | |
| 418 | + if ( is_wp_error( $result ) ) { | |
| 419 | + wp_send_json_error( $result->get_error_message() ); | |
| 420 | + } | |
| 421 | + | |
| 422 | + wp_send_json_success( esc_html__( 'The details have been updated.', 'sellkit' ) ); | |
| 423 | + } | |
| 424 | + | |
| 425 | + /** | |
| 426 | + * Get order details of a contact ID related to a given funnel ID. | |
| 427 | + * | |
| 428 | + * @param int $funnel_id ID of the funnel. | |
| 429 | + * @param int $user_id Equals user ID of WordPress, customer ID of woocommerce and contact ID of sellkit. | |
| 430 | + * @param int $page Page number. | |
| 431 | + * @param int $limit Page limitation. | |
| 432 | + * @return array Order details with a custom structure which is parsed in frontend. | |
| 433 | + * | |
| 434 | + * @since 1.5.0 | |
| 435 | + * @access private | |
| 436 | + */ | |
| 437 | + private function get_contact_order_details( $funnel_id, $user_id, $page, $limit ) { | |
| 438 | + $funnel_orders = []; | |
| 439 | + | |
| 440 | + $total_orders = wc_get_orders( [ | |
| 441 | + 'customer_id' => $user_id, | |
| 442 | + 'meta_key' => 'sellkit_funnel_id', // phpcs:ignore | |
| 443 | + 'meta_value' => $funnel_id, // phpcs:ignore | |
| 444 | + 'meta_compare' => '=', | |
| 445 | + 'limit' => -1, | |
| 446 | + ] ); | |
| 447 | + | |
| 448 | + $orders = wc_get_orders( [ | |
| 449 | + 'customer_id' => $user_id, | |
| 450 | + 'meta_key' => 'sellkit_funnel_id', // phpcs:ignore | |
| 451 | + 'meta_value' => $funnel_id, // phpcs:ignore | |
| 452 | + 'meta_compare' => '=', | |
| 453 | + 'limit' => $limit, | |
| 454 | + 'paged' => $page, | |
| 455 | + 'return' => 'objects' | |
| 456 | + ] ); | |
| 457 | + | |
| 458 | + foreach ( $orders as $order ) { | |
| 459 | + $products = []; | |
| 460 | + | |
| 461 | + foreach ( $order->get_items() as $order_item ) { | |
| 462 | + $data = $order_item->get_data(); | |
| 463 | + | |
| 464 | + if ( ! array_key_exists( 'product_id', $data ) ) { | |
| 465 | + continue; | |
| 466 | + } | |
| 467 | + | |
| 468 | + $product = wc_get_product( $data['product_id'] ); | |
| 469 | + | |
| 470 | + if ( empty( $product ) ) { | |
| 471 | + continue; | |
| 472 | + } | |
| 473 | + | |
| 474 | + $products[] = [ | |
| 475 | + 'id' => $data['product_id'], | |
| 476 | + 'name' => $data['name'], | |
| 477 | + 'quantity' => $data['quantity'], | |
| 478 | + 'order_price' => floatval( $data['total'] / $data['quantity'] ), | |
| 479 | + 'regular_price' => floatval( $product->get_regular_price() ), | |
| 480 | + 'image_src' => wp_get_attachment_image_src( $product->get_image_id() ), | |
| 481 | + ]; | |
| 482 | + } | |
| 483 | + | |
| 484 | + $funnel_orders[] = [ | |
| 485 | + 'order_number' => $order->get_order_number(), | |
| 486 | + 'order_date' => $order->get_date_created()->date( 'Y/m/d g:i A' ), | |
| 487 | + 'order_revenue' => $order->get_total() - $order->get_total_tax(), | |
| 488 | + 'products' => $products, | |
| 489 | + ]; | |
| 490 | + } | |
| 491 | + | |
| 492 | + return [ | |
| 493 | + 'orders' => $funnel_orders, | |
| 494 | + 'total' => count( $total_orders ), | |
| 495 | + ]; | |
| 152 | 496 | } |
| 153 | 497 | } |
| 154 | 498 | |
| 155 | 499 | Sellkit_Custom_Tables::get_instance(); |