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 +172 -8 1.5.42.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,14 +159,23 @@
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 - $quantity = ! empty( $product['quantity'] ) ? $product['quantity'] : 1;
168 + if ( empty( $product ) ) {
169 + continue;
170 + }
107 171
108 - wc()->cart->add_to_cart( $product_id, $quantity );
172 + $quantity = ! empty( $product['quantity'] ) ? $product['quantity'] : 1;
173 + $product_cart_id = WC()->cart->generate_cart_id( $product_id );
174 +
175 + if ( ! WC()->cart->find_product_in_cart( $product_cart_id ) ) {
176 + WC()->cart->add_to_cart( $product_id, $quantity );
177 + }
109 178 }
110 179 }
111 180
112 181 if ( empty( $optimization_data ) ) {
@@ -114,8 +183,12 @@
114 183 }
115 184
116 185 if ( self::apply_coupon_validation( $optimization_data ) ) {
117 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 + }
118 191 wc()->cart->add_discount( get_the_title( $auto_apply_coupon['value'] ) );
119 192 }
120 193
121 194 wc_clear_notices();
@@ -165,12 +238,50 @@
165 238 *
166 239 * @since 1.1.0
167 240 */
168 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 +
169 271 woocommerce_form_field( 'sellkit_current_page_id', [
170 272 'type' => 'hidden',
171 273 'class' => [ 'hidden form-row-wide' ],
172 - ], 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' );
173 284 }
174 285
175 286 /**
176 287 * Check If a coupon should be applied.
@@ -272,8 +383,10 @@
272 383 * @return void
273 384 *
274 385 * @since 1.5.0
275 386 * @access private
387 + *
388 + * @SuppressWarnings(PHPMD.NPathComplexity)
276 389 */
277 390 private function check_bump_acceptance( $order ) {
278 391 if (
279 392 empty( $this->funnel->funnel_id ) ||
@@ -287,9 +400,11 @@
287 400 if ( empty( $bump['data']['products'] ) ) {
288 401 continue;
289 402 }
290 403
291 - $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 ) );
292 407 }
293 408
294 409 $bump_exists_in_order = false;
295 410
@@ -309,10 +424,12 @@
309 424 $database = new Database();
310 425 $old_values = [];
311 426
312 427 // Getting old data.
313 - $result = $database->get( 'funnel_contact', [ 'id' => $_SESSION['entered_funnel_id'] ] );
428 + $contact_id = isset( $_SESSION['entered_funnel_id'] ) ? absint( $_SESSION['entered_funnel_id'] ) : 0;
314 429
430 + $result = $database->get( 'funnel_contact', [ 'id' => $contact_id ] );
431 +
315 432 if ( ! empty( $result[0]['order_bump'] ) ) {
316 433 $old_values = unserialize( $result[0]['order_bump'] ); // phpcs:ignore
317 434 }
318 435
@@ -320,9 +437,9 @@
320 437
321 438 $database->update(
322 439 'funnel_contact',
323 440 [ 'order_bump' => $new_values ],
324 - [ 'id' => $_SESSION['entered_funnel_id'] ]
441 + [ 'id' => $contact_id ]
325 442 );
326 443 }
327 444
328 445 /**
@@ -330,8 +447,12 @@
330 447 *
331 448 * @since 1.1.0
332 449 */
333 450 public function update_funnel_data() {
451 + if ( is_admin() ) {
452 + return;
453 + }
454 +
334 455 $action = sellkit_htmlspecialchars( INPUT_GET, 'wc-ajax' );
335 456
336 457 if ( 'update_order_review' === $action ) {
337 458 $post_data = sellkit_htmlspecialchars( INPUT_POST, 'post_data' );
@@ -337,10 +458,53 @@
337 458 $post_data = sellkit_htmlspecialchars( INPUT_POST, 'post_data' );
338 459
339 460 $this->funnel = new Sellkit_Funnel( $this->get_current_page_id_by_post_data( $post_data ) );
340 461
462 + if ( empty( $this->funnel->funnel_id ) ) {
463 + $this->global_checkout_funnel_integration();
464 + }
465 +
341 466 return;
342 467 }
343 468
344 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 + }
345 509 }
346 510 }