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 +95 -32 1.7.92.7.0 View file →
@@ -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)
@@ -56,8 +97,20 @@
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();
58 99 $reset_cart = isset( $funnel_data['data']['products']['reset_cart'] ) ? $funnel_data['data']['products']['reset_cart'] : 'true';
59 100
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 +
60 113 add_action( 'template_redirect', function () use ( $funnel_data, $reset_cart ) {
61 114 $product_settings = ! empty( $funnel_data['data']['products']['settings'] ) ? $funnel_data['data']['products']['settings'] : '';
62 115 $bump_data = ! empty( $funnel_data['bump'] ) ? $funnel_data['bump'] : '';
63 116
@@ -69,9 +122,13 @@
69 122 if ( ! empty( $bump_data ) ) {
70 123 $bump_data = $this->get_valid_bumps( $bump_data );
71 124
72 125 set_query_var( 'bump_data', $bump_data );
126 + } else {
127 + set_query_var( 'bump_data', [] );
73 128 }
129 +
130 + set_query_var( 'funnel_reset_cart', $reset_cart );
74 131 } );
75 132
76 133 if (
77 134 ! empty( $optimization_data['expiration_date'] ) &&
@@ -102,44 +159,23 @@
102 159
103 160 $action = sellkit_htmlspecialchars( INPUT_GET, 'wc-ajax' );
104 161
105 162 if ( 'update_order_review' !== $action && 'checkout' !== $action ) {
106 - if ( 'true' !== $reset_cart ) {
107 - foreach ( WC()->cart->get_cart() as $item ) {
108 - if ( empty( $item['product_id'] ) ) {
109 - continue;
110 - }
111 -
112 - if ( array_key_exists( $item['product_id'], $funnel_products ) ) {
113 - $funnel_products[ $item['product_id'] ]['quantity'] = $item['quantity'];
114 -
115 - continue;
116 - }
117 -
118 - $key = $item['product_id'];
119 -
120 - if ( ! empty( $item['variation_id'] ) ) {
121 - $key = $item['variation_id'];
122 - }
123 -
124 - $funnel_products[ $key ] = [
125 - 'quantity' => $item['quantity'],
126 - 'discount' => '',
127 - 'discountType' => 'fixed',
128 - ];
129 - }
163 + if ( 'true' === $reset_cart || true === $reset_cart ) {
164 + wc()->cart->empty_cart();
130 165 }
131 166
132 - WC()->cart->empty_cart();
133 -
134 167 foreach ( $funnel_products as $product_id => $product ) {
135 168 if ( empty( $product ) ) {
136 169 continue;
137 170 }
138 171
139 - $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 );
140 174
141 - 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 + }
142 178 }
143 179 }
144 180
145 181 if ( empty( $optimization_data ) ) {
@@ -147,8 +183,12 @@
147 183 }
148 184
149 185 if ( self::apply_coupon_validation( $optimization_data ) ) {
150 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 + }
151 191 wc()->cart->add_discount( get_the_title( $auto_apply_coupon['value'] ) );
152 192 }
153 193
154 194 wc_clear_notices();
@@ -202,8 +242,12 @@
202 242 $id = get_the_ID();
203 243 $global_checkout_id = get_option( Global_Checkout::SELLKIT_GLOBAL_CHECKOUT_OPTION, 0 );
204 244 $default_checkout_id = wc_get_page_id( 'checkout' );
205 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 +
206 250 if (
207 251 (int) $global_checkout_id > 0 &&
208 252 'publish' === get_post_status( $global_checkout_id ) &&
209 253 $id === $default_checkout_id
@@ -214,9 +258,9 @@
214 258 foreach ( $steps as $step ) {
215 259 $step['type'] = (array) $step['type'];
216 260
217 261 if ( 'checkout' === $step['type']['key'] ) {
218 - $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.
219 263 }
220 264 }
221 265
222 266 if ( (int) $checkout_id > 0 ) {
@@ -227,8 +271,17 @@
227 271 woocommerce_form_field( 'sellkit_current_page_id', [
228 272 'type' => 'hidden',
229 273 'class' => [ 'hidden form-row-wide' ],
230 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' );
231 284 }
232 285
233 286 /**
234 287 * Check If a coupon should be applied.
@@ -330,8 +383,10 @@
330 383 * @return void
331 384 *
332 385 * @since 1.5.0
333 386 * @access private
387 + *
388 + * @SuppressWarnings(PHPMD.NPathComplexity)
334 389 */
335 390 private function check_bump_acceptance( $order ) {
336 391 if (
337 392 empty( $this->funnel->funnel_id ) ||
@@ -345,9 +400,11 @@
345 400 if ( empty( $bump['data']['products'] ) ) {
346 401 continue;
347 402 }
348 403
349 - $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 ) );
350 407 }
351 408
352 409 $bump_exists_in_order = false;
353 410
@@ -367,10 +424,12 @@
367 424 $database = new Database();
368 425 $old_values = [];
369 426
370 427 // Getting old data.
371 - $result = $database->get( 'funnel_contact', [ 'id' => $_SESSION['entered_funnel_id'] ] );
428 + $contact_id = isset( $_SESSION['entered_funnel_id'] ) ? absint( $_SESSION['entered_funnel_id'] ) : 0;
372 429
430 + $result = $database->get( 'funnel_contact', [ 'id' => $contact_id ] );
431 +
373 432 if ( ! empty( $result[0]['order_bump'] ) ) {
374 433 $old_values = unserialize( $result[0]['order_bump'] ); // phpcs:ignore
375 434 }
376 435
@@ -378,9 +437,9 @@
378 437
379 438 $database->update(
380 439 'funnel_contact',
381 440 [ 'order_bump' => $new_values ],
382 - [ 'id' => $_SESSION['entered_funnel_id'] ]
441 + [ 'id' => $contact_id ]
383 442 );
384 443 }
385 444
386 445 /**
@@ -425,8 +484,12 @@
425 484
426 485 if ( $global_checkout_id > 0 && 'publish' === get_post_status( $global_checkout_id ) ) {
427 486 $steps = get_post_meta( $global_checkout_id, 'nodes', true );
428 487 $checkout_id = 0;
488 +
489 + if ( ! is_array( $steps ) ) {
490 + return;
491 + }
429 492
430 493 foreach ( $steps as $step ) {
431 494 $step['type'] = (array) $step['type'];
432 495