PluginProbe
SellKit – Funnel builder and checkout optimizer for WooCommerce to sell more, faster / 2.7.0
SellKit – Funnel builder and checkout optimizer for WooCommerce to sell more, faster v2.7.0
2.7.0 2.6.0 trunk 1.1.0 1.1.4 1.2.1 1.2.2 1.2.3 1.2.5 1.2.9 1.3.1 1.3.2 1.5.0 1.5.1 1.5.4 1.5.7 1.5.8 1.5.9 1.6.2 1.6.5 1.6.8 1.7.2 1.7.4 1.7.5 1.7.9 All 43 releases
← All changes | includes/funnel/steps/checkout.php +168 -8 1.6.52.7.0 View file →
@@ -5,8 +5,9 @@
5 5 use Sellkit\Funnel\Analytics\Data_Updater;
6 6 use Sellkit\Funnel\Contacts\Base_Contacts;
7 7 use Sellkit\Database;
8 8 use Sellkit_Funnel;
9 +use Sellkit\Global_Checkout\Checkout as Global_Checkout;
9 10
10 11 defined( 'ABSPATH' ) || die();
11 12
12 13 /**
@@ -39,11 +40,52 @@
39 40
40 41 add_action( 'woocommerce_checkout_create_order', [ $this, 'save_order_custom_meta_data' ], 10 );
41 42 add_action( 'woocommerce_after_order_notes', [ $this, 'custom_checkout_hidden_fields' ], 10 );
42 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 + }
43 52 }
44 53
45 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 + /**
46 88 * Adding items to the checkout page.
47 89 *
48 90 * @since 1.1.0
49 91 * @SuppressWarnings(PHPMD.NPathComplexity)
@@ -53,15 +95,29 @@
53 95 // These variables should be used in checkout builder widget.
54 96 $funnel_data = $this->funnel->current_step_data;
55 97 $optimization_data = ! empty( $funnel_data['data']['optimization'] ) ? $funnel_data['data']['optimization'] : '';
56 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';
57 100
58 - 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 ) {
59 114 $product_settings = ! empty( $funnel_data['data']['products']['settings'] ) ? $funnel_data['data']['products']['settings'] : '';
60 115 $bump_data = ! empty( $funnel_data['bump'] ) ? $funnel_data['bump'] : '';
61 116
62 117 if ( ! empty( $product_settings ) ) {
63 118 set_query_var( 'funnel_product_settings', $product_settings );
119 + set_query_var( 'funnel_reset_cart', $reset_cart );
64 120 }
65 121
66 122 if ( ! empty( $bump_data ) ) {
67 123 $bump_data = $this->get_valid_bumps( $bump_data );
@@ -66,9 +122,13 @@
66 122 if ( ! empty( $bump_data ) ) {
67 123 $bump_data = $this->get_valid_bumps( $bump_data );
68 124
69 125 set_query_var( 'bump_data', $bump_data );
126 + } else {
127 + set_query_var( 'bump_data', [] );
70 128 }
129 +
130 + set_query_var( 'funnel_reset_cart', $reset_cart );
71 131 } );
72 132
73 133 if (
74 134 ! empty( $optimization_data['expiration_date'] ) &&
@@ -99,9 +159,11 @@
99 159
100 160 $action = sellkit_htmlspecialchars( INPUT_GET, 'wc-ajax' );
101 161
102 162 if ( 'update_order_review' !== $action && 'checkout' !== $action ) {
103 - wc()->cart->empty_cart();
163 + if ( 'true' === $reset_cart || true === $reset_cart ) {
164 + wc()->cart->empty_cart();
165 + }
104 166
105 167 foreach ( $funnel_products as $product_id => $product ) {
106 168 if ( empty( $product ) ) {
107 169 continue;
@@ -106,11 +168,14 @@
106 168 if ( empty( $product ) ) {
107 169 continue;
108 170 }
109 171
110 - $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 );
111 174
112 - 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 + }
113 178 }
114 179 }
115 180
116 181 if ( empty( $optimization_data ) ) {
@@ -118,8 +183,12 @@
118 183 }
119 184
120 185 if ( self::apply_coupon_validation( $optimization_data ) ) {
121 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 + }
122 191 wc()->cart->add_discount( get_the_title( $auto_apply_coupon['value'] ) );
123 192 }
124 193
125 194 wc_clear_notices();
@@ -169,12 +238,50 @@
169 238 *
170 239 * @since 1.1.0
171 240 */
172 241 public function custom_checkout_hidden_fields() {
242 + $id = get_the_ID();
243 + $global_checkout_id = get_option( Global_Checkout::SELLKIT_GLOBAL_CHECKOUT_OPTION, 0 );
244 + $default_checkout_id = wc_get_page_id( 'checkout' );
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 +
250 + if (
251 + (int) $global_checkout_id > 0 &&
252 + 'publish' === get_post_status( $global_checkout_id ) &&
253 + $id === $default_checkout_id
254 + ) {
255 + $steps = get_post_meta( $global_checkout_id, 'nodes', true );
256 + $checkout_id = 0;
257 +
258 + foreach ( $steps as $step ) {
259 + $step['type'] = (array) $step['type'];
260 +
261 + if ( 'checkout' === $step['type']['key'] ) {
262 + $checkout_id = apply_filters( 'wpml_object_id', $step['page_id'], 'sellkit_step', true ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API.
263 + }
264 + }
265 +
266 + if ( (int) $checkout_id > 0 ) {
267 + $id = $checkout_id;
268 + }
269 + }
270 +
173 271 woocommerce_form_field( 'sellkit_current_page_id', [
174 272 'type' => 'hidden',
175 273 'class' => [ 'hidden form-row-wide' ],
176 - ], get_the_ID() );
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' );
177 284 }
178 285
179 286 /**
180 287 * Check If a coupon should be applied.
@@ -276,8 +383,10 @@
276 383 * @return void
277 384 *
278 385 * @since 1.5.0
279 386 * @access private
387 + *
388 + * @SuppressWarnings(PHPMD.NPathComplexity)
280 389 */
281 390 private function check_bump_acceptance( $order ) {
282 391 if (
283 392 empty( $this->funnel->funnel_id ) ||
@@ -291,9 +400,11 @@
291 400 if ( empty( $bump['data']['products'] ) ) {
292 401 continue;
293 402 }
294 403
295 - $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 ) );
296 407 }
297 408
298 409 $bump_exists_in_order = false;
299 410
@@ -313,10 +424,12 @@
313 424 $database = new Database();
314 425 $old_values = [];
315 426
316 427 // Getting old data.
317 - $result = $database->get( 'funnel_contact', [ 'id' => $_SESSION['entered_funnel_id'] ] );
428 + $contact_id = isset( $_SESSION['entered_funnel_id'] ) ? absint( $_SESSION['entered_funnel_id'] ) : 0;
318 429
430 + $result = $database->get( 'funnel_contact', [ 'id' => $contact_id ] );
431 +
319 432 if ( ! empty( $result[0]['order_bump'] ) ) {
320 433 $old_values = unserialize( $result[0]['order_bump'] ); // phpcs:ignore
321 434 }
322 435
@@ -324,9 +437,9 @@
324 437
325 438 $database->update(
326 439 'funnel_contact',
327 440 [ 'order_bump' => $new_values ],
328 - [ 'id' => $_SESSION['entered_funnel_id'] ]
441 + [ 'id' => $contact_id ]
329 442 );
330 443 }
331 444
332 445 /**
@@ -334,8 +447,12 @@
334 447 *
335 448 * @since 1.1.0
336 449 */
337 450 public function update_funnel_data() {
451 + if ( is_admin() ) {
452 + return;
453 + }
454 +
338 455 $action = sellkit_htmlspecialchars( INPUT_GET, 'wc-ajax' );
339 456
340 457 if ( 'update_order_review' === $action ) {
341 458 $post_data = sellkit_htmlspecialchars( INPUT_POST, 'post_data' );
@@ -341,10 +458,53 @@
341 458 $post_data = sellkit_htmlspecialchars( INPUT_POST, 'post_data' );
342 459
343 460 $this->funnel = new Sellkit_Funnel( $this->get_current_page_id_by_post_data( $post_data ) );
344 461
462 + if ( empty( $this->funnel->funnel_id ) ) {
463 + $this->global_checkout_funnel_integration();
464 + }
465 +
345 466 return;
346 467 }
347 468
348 469 $this->funnel = Sellkit_Funnel::get_instance();
470 +
471 + if ( empty( $this->funnel->funnel_id ) ) {
472 + $this->global_checkout_funnel_integration();
473 + }
474 + }
475 +
476 + /**
477 + * Setup global checkout as a native sellkit funnel, so every functions works with no issue.
478 + * detect funnel id in the global checkout.
479 + *
480 + * @since 1.7.4
481 + */
482 + public function global_checkout_funnel_integration() {
483 + $global_checkout_id = get_option( Global_Checkout::SELLKIT_GLOBAL_CHECKOUT_OPTION, 0 );
484 +
485 + if ( $global_checkout_id > 0 && 'publish' === get_post_status( $global_checkout_id ) ) {
486 + $steps = get_post_meta( $global_checkout_id, 'nodes', true );
487 + $checkout_id = 0;
488 +
489 + if ( ! is_array( $steps ) ) {
490 + return;
491 + }
492 +
493 + foreach ( $steps as $step ) {
494 + $step['type'] = (array) $step['type'];
495 +
496 + if ( 'checkout' === $step['type']['key'] ) {
497 + $checkout_id = $step['page_id'];
498 + }
499 + }
500 +
501 + if ( (int) $checkout_id > 0 ) {
502 + $this->funnel = new Sellkit_Funnel( $checkout_id );
503 +
504 + add_filter( 'sellkit_global_checkout_activated', function() {
505 + return true;
506 + } );
507 + }
508 + }
349 509 }
350 510 }