| @@ -40,11 +40,52 @@ | ||
| 40 | 40 | |
| 41 | 41 | add_action( 'woocommerce_checkout_create_order', [ $this, 'save_order_custom_meta_data' ], 10 ); |
| 42 | 42 | add_action( 'woocommerce_after_order_notes', [ $this, 'custom_checkout_hidden_fields' ], 10 ); |
| 43 | 43 | add_action( 'wc_ajax_update_order_review', [ $this, 'do_actions' ] ); |
| 44 | + | |
| 45 | + // Persist the current checkout step id in the WC session so express-checkout | |
| 46 | + // AJAX requests (which do not submit the sellkit_current_page_id hidden field) | |
| 47 | + // can still resolve the funnel context and save sellkit_funnel_next_step_data | |
| 48 | + // on the created order. | |
| 49 | + if ( ! wp_doing_ajax() && ! is_admin() ) { | |
| 50 | + add_action( 'wp', [ $this, 'store_checkout_step_in_session' ], 20 ); | |
| 51 | + } | |
| 44 | 52 | } |
| 45 | 53 | |
| 46 | 54 | /** |
| 55 | + * Store the current checkout step id in the WC session when rendering an actual | |
| 56 | + * SellKit funnel checkout step page. The value is later consumed by | |
| 57 | + * Sellkit_Funnel::get_page_id() during AJAX requests (e.g. Stripe express | |
| 58 | + * checkout) that lack the sellkit_current_page_id POST field. | |
| 59 | + * | |
| 60 | + * A timestamp is stored alongside the id so the fallback can be ignored if it | |
| 61 | + * is stale, avoiding cross-contamination between unrelated checkouts. | |
| 62 | + * | |
| 63 | + * @since 2.3.3 | |
| 64 | + * @return void | |
| 65 | + */ | |
| 66 | + public function store_checkout_step_in_session() { | |
| 67 | + if ( ! function_exists( 'WC' ) || ! is_object( WC()->session ) ) { | |
| 68 | + return; | |
| 69 | + } | |
| 70 | + | |
| 71 | + $post_id = get_the_ID(); | |
| 72 | + | |
| 73 | + if ( empty( $post_id ) || 'sellkit_step' !== get_post_type( $post_id ) ) { | |
| 74 | + return; | |
| 75 | + } | |
| 76 | + | |
| 77 | + $step_data = get_post_meta( $post_id, 'step_data', true ); | |
| 78 | + | |
| 79 | + if ( empty( $step_data['type']['key'] ) || 'checkout' !== $step_data['type']['key'] ) { | |
| 80 | + return; | |
| 81 | + } | |
| 82 | + | |
| 83 | + WC()->session->set( 'sellkit_current_page_id', (int) $post_id ); | |
| 84 | + WC()->session->set( 'sellkit_current_page_id_time', time() ); | |
| 85 | + } | |
| 86 | + | |
| 87 | + /** | |
| 47 | 88 | * Adding items to the checkout page. |
| 48 | 89 | * |
| 49 | 90 | * @since 1.1.0 |
| 50 | 91 | * @SuppressWarnings(PHPMD.NPathComplexity) |
| @@ -54,15 +95,29 @@ | ||
| 54 | 95 | // These variables should be used in checkout builder widget. |
| 55 | 96 | $funnel_data = $this->funnel->current_step_data; |
| 56 | 97 | $optimization_data = ! empty( $funnel_data['data']['optimization'] ) ? $funnel_data['data']['optimization'] : ''; |
| 57 | 98 | $redirect_url = ! empty( $optimization_data['closed_checkout_redirect_url'] ) ? $optimization_data['closed_checkout_redirect_url'] : site_url(); |
| 99 | + $reset_cart = isset( $funnel_data['data']['products']['reset_cart'] ) ? $funnel_data['data']['products']['reset_cart'] : 'true'; | |
| 58 | 100 | |
| 59 | - add_action( 'template_redirect', function () use ( $funnel_data ) { | |
| 101 | + $auto_apply_coupon_cookie_value = null; | |
| 102 | + | |
| 103 | + if ( function_exists( 'sellkit_pro' ) && ! sellkit_pro()->is_active_sellkit_pro ) { | |
| 104 | + $optimization_data = null; | |
| 105 | + } | |
| 106 | + | |
| 107 | + if ( isset( $_COOKIE['sellkit_rejected_coupon'] ) ) { | |
| 108 | + // Get the value of the cookie. | |
| 109 | + $auto_apply_coupon_cookie_value = sanitize_text_field( wp_unslash( $_COOKIE['sellkit_rejected_coupon'] ) ); | |
| 110 | + $auto_apply_coupon_cookie_value = explode( ',', $auto_apply_coupon_cookie_value ); | |
| 111 | + } | |
| 112 | + | |
| 113 | + add_action( 'template_redirect', function () use ( $funnel_data, $reset_cart ) { | |
| 60 | 114 | $product_settings = ! empty( $funnel_data['data']['products']['settings'] ) ? $funnel_data['data']['products']['settings'] : ''; |
| 61 | 115 | $bump_data = ! empty( $funnel_data['bump'] ) ? $funnel_data['bump'] : ''; |
| 62 | 116 | |
| 63 | 117 | if ( ! empty( $product_settings ) ) { |
| 64 | 118 | set_query_var( 'funnel_product_settings', $product_settings ); |
| 119 | + set_query_var( 'funnel_reset_cart', $reset_cart ); | |
| 65 | 120 | } |
| 66 | 121 | |
| 67 | 122 | if ( ! empty( $bump_data ) ) { |
| 68 | 123 | $bump_data = $this->get_valid_bumps( $bump_data ); |
| @@ -67,9 +122,13 @@ | ||
| 67 | 122 | if ( ! empty( $bump_data ) ) { |
| 68 | 123 | $bump_data = $this->get_valid_bumps( $bump_data ); |
| 69 | 124 | |
| 70 | 125 | set_query_var( 'bump_data', $bump_data ); |
| 126 | + } else { | |
| 127 | + set_query_var( 'bump_data', [] ); | |
| 71 | 128 | } |
| 129 | + | |
| 130 | + set_query_var( 'funnel_reset_cart', $reset_cart ); | |
| 72 | 131 | } ); |
| 73 | 132 | |
| 74 | 133 | if ( |
| 75 | 134 | ! empty( $optimization_data['expiration_date'] ) && |
| @@ -100,9 +159,11 @@ | ||
| 100 | 159 | |
| 101 | 160 | $action = sellkit_htmlspecialchars( INPUT_GET, 'wc-ajax' ); |
| 102 | 161 | |
| 103 | 162 | if ( 'update_order_review' !== $action && 'checkout' !== $action ) { |
| 104 | - wc()->cart->empty_cart(); | |
| 163 | + if ( 'true' === $reset_cart || true === $reset_cart ) { | |
| 164 | + wc()->cart->empty_cart(); | |
| 165 | + } | |
| 105 | 166 | |
| 106 | 167 | foreach ( $funnel_products as $product_id => $product ) { |
| 107 | 168 | if ( empty( $product ) ) { |
| 108 | 169 | continue; |
| @@ -107,11 +168,14 @@ | ||
| 107 | 168 | if ( empty( $product ) ) { |
| 108 | 169 | continue; |
| 109 | 170 | } |
| 110 | 171 | |
| 111 | - $quantity = ! empty( $product['quantity'] ) ? $product['quantity'] : 1; | |
| 172 | + $quantity = ! empty( $product['quantity'] ) ? $product['quantity'] : 1; | |
| 173 | + $product_cart_id = WC()->cart->generate_cart_id( $product_id ); | |
| 112 | 174 | |
| 113 | - wc()->cart->add_to_cart( $product_id, $quantity ); | |
| 175 | + if ( ! WC()->cart->find_product_in_cart( $product_cart_id ) ) { | |
| 176 | + WC()->cart->add_to_cart( $product_id, $quantity ); | |
| 177 | + } | |
| 114 | 178 | } |
| 115 | 179 | } |
| 116 | 180 | |
| 117 | 181 | if ( empty( $optimization_data ) ) { |
| @@ -119,8 +183,12 @@ | ||
| 119 | 183 | } |
| 120 | 184 | |
| 121 | 185 | if ( self::apply_coupon_validation( $optimization_data ) ) { |
| 122 | 186 | foreach ( $optimization_data['auto_apply_coupons'] as $auto_apply_coupon ) { |
| 187 | + | |
| 188 | + if ( ! empty( $auto_apply_coupon_cookie_value ) && in_array( $auto_apply_coupon['label'], $auto_apply_coupon_cookie_value, true ) ) { | |
| 189 | + continue; | |
| 190 | + } | |
| 123 | 191 | wc()->cart->add_discount( get_the_title( $auto_apply_coupon['value'] ) ); |
| 124 | 192 | } |
| 125 | 193 | |
| 126 | 194 | wc_clear_notices(); |
| @@ -174,8 +242,12 @@ | ||
| 174 | 242 | $id = get_the_ID(); |
| 175 | 243 | $global_checkout_id = get_option( Global_Checkout::SELLKIT_GLOBAL_CHECKOUT_OPTION, 0 ); |
| 176 | 244 | $default_checkout_id = wc_get_page_id( 'checkout' ); |
| 177 | 245 | |
| 246 | + if ( ! empty( $default_checkout_id ) ) { | |
| 247 | + $default_checkout_id = apply_filters( 'wpml_object_id', $default_checkout_id, 'page', true ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API. | |
| 248 | + } | |
| 249 | + | |
| 178 | 250 | if ( |
| 179 | 251 | (int) $global_checkout_id > 0 && |
| 180 | 252 | 'publish' === get_post_status( $global_checkout_id ) && |
| 181 | 253 | $id === $default_checkout_id |
| @@ -186,9 +258,9 @@ | ||
| 186 | 258 | foreach ( $steps as $step ) { |
| 187 | 259 | $step['type'] = (array) $step['type']; |
| 188 | 260 | |
| 189 | 261 | if ( 'checkout' === $step['type']['key'] ) { |
| 190 | - $checkout_id = $step['page_id']; | |
| 262 | + $checkout_id = apply_filters( 'wpml_object_id', $step['page_id'], 'sellkit_step', true ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API. | |
| 191 | 263 | } |
| 192 | 264 | } |
| 193 | 265 | |
| 194 | 266 | if ( (int) $checkout_id > 0 ) { |
| @@ -199,8 +271,17 @@ | ||
| 199 | 271 | woocommerce_form_field( 'sellkit_current_page_id', [ |
| 200 | 272 | 'type' => 'hidden', |
| 201 | 273 | 'class' => [ 'hidden form-row-wide' ], |
| 202 | 274 | ], $id ); |
| 275 | + | |
| 276 | + // This field added for passing upsell discount price to backend. | |
| 277 | + woocommerce_form_field( 'sellkit_product_prices', [ | |
| 278 | + 'type' => 'hidden', | |
| 279 | + 'class' => [ 'hidden form-row-wide' ], | |
| 280 | + 'autocomplete' => 'off', | |
| 281 | + 'autocorrect' => 'off', | |
| 282 | + 'autocapitalize' => 'off', | |
| 283 | + ], '0' ); | |
| 203 | 284 | } |
| 204 | 285 | |
| 205 | 286 | /** |
| 206 | 287 | * Check If a coupon should be applied. |
| @@ -302,8 +383,10 @@ | ||
| 302 | 383 | * @return void |
| 303 | 384 | * |
| 304 | 385 | * @since 1.5.0 |
| 305 | 386 | * @access private |
| 387 | + * | |
| 388 | + * @SuppressWarnings(PHPMD.NPathComplexity) | |
| 306 | 389 | */ |
| 307 | 390 | private function check_bump_acceptance( $order ) { |
| 308 | 391 | if ( |
| 309 | 392 | empty( $this->funnel->funnel_id ) || |
| @@ -317,9 +400,11 @@ | ||
| 317 | 400 | if ( empty( $bump['data']['products'] ) ) { |
| 318 | 401 | continue; |
| 319 | 402 | } |
| 320 | 403 | |
| 321 | - $bump_product_ids = array_merge( $bump_product_ids, array_keys( $bump['data']['products']['list'] ) ); | |
| 404 | + $product_list = ! empty( $bump['data']['products']['list'] ) ? $bump['data']['products']['list'] : []; | |
| 405 | + | |
| 406 | + $bump_product_ids = array_merge( $bump_product_ids, array_keys( $product_list ) ); | |
| 322 | 407 | } |
| 323 | 408 | |
| 324 | 409 | $bump_exists_in_order = false; |
| 325 | 410 | |
| @@ -339,10 +424,12 @@ | ||
| 339 | 424 | $database = new Database(); |
| 340 | 425 | $old_values = []; |
| 341 | 426 | |
| 342 | 427 | // Getting old data. |
| 343 | - $result = $database->get( 'funnel_contact', [ 'id' => $_SESSION['entered_funnel_id'] ] ); | |
| 428 | + $contact_id = isset( $_SESSION['entered_funnel_id'] ) ? absint( $_SESSION['entered_funnel_id'] ) : 0; | |
| 344 | 429 | |
| 430 | + $result = $database->get( 'funnel_contact', [ 'id' => $contact_id ] ); | |
| 431 | + | |
| 345 | 432 | if ( ! empty( $result[0]['order_bump'] ) ) { |
| 346 | 433 | $old_values = unserialize( $result[0]['order_bump'] ); // phpcs:ignore |
| 347 | 434 | } |
| 348 | 435 | |
| @@ -350,9 +437,9 @@ | ||
| 350 | 437 | |
| 351 | 438 | $database->update( |
| 352 | 439 | 'funnel_contact', |
| 353 | 440 | [ 'order_bump' => $new_values ], |
| 354 | - [ 'id' => $_SESSION['entered_funnel_id'] ] | |
| 441 | + [ 'id' => $contact_id ] | |
| 355 | 442 | ); |
| 356 | 443 | } |
| 357 | 444 | |
| 358 | 445 | /** |
| @@ -397,8 +484,12 @@ | ||
| 397 | 484 | |
| 398 | 485 | if ( $global_checkout_id > 0 && 'publish' === get_post_status( $global_checkout_id ) ) { |
| 399 | 486 | $steps = get_post_meta( $global_checkout_id, 'nodes', true ); |
| 400 | 487 | $checkout_id = 0; |
| 488 | + | |
| 489 | + if ( ! is_array( $steps ) ) { | |
| 490 | + return; | |
| 491 | + } | |
| 401 | 492 | |
| 402 | 493 | foreach ( $steps as $step ) { |
| 403 | 494 | $step['type'] = (array) $step['type']; |
| 404 | 495 | |