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/elementor/modules/checkout/classes/global-hooks.php +387 -30 1.6.22.7.0 View file →
@@ -4,8 +4,10 @@
4 4
5 5 defined( 'ABSPATH' ) || exit;
6 6
7 7 use Sellkit\Elementor\Modules\Checkout\Classes\{ Local_Hooks, Helper };
8 +use Sellkit\Funnel\Analytics\Data_Updater;
9 +use Sellkit\Funnel\Contacts\Base_Contacts;
8 10 use Sellkit\Funnel\Steps\Checkout;
9 11 use Sellkit_Funnel;
10 12
11 13 /**
@@ -14,12 +16,27 @@
14 16 * @SuppressWarnings(PHPMD.UnusedFormalParameter)
15 17 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
16 18 * @SuppressWarnings(PHPMD.TooManyMethods)
17 19 * @SuppressWarnings(PHPMD.NPathComplexity)
20 + * @SuppressWarnings(PHPMD.ExcessiveClassLength)
18 21 * @since 1.1.0
19 22 */
20 23 class Global_Hooks {
21 24 /**
25 + * Helper id during checkout process.
26 + *
27 + * @var int
28 + */
29 + private $helper_id = 0;
30 +
31 + /**
32 + * List of upsell product with price
33 + *
34 + * @var array
35 + */
36 + private static $sellkit_upsell_products;
37 +
38 + /**
22 39 * Create instance of class without construct.
23 40 *
24 41 * @since 1.1.0
25 42 * @return object
@@ -58,8 +75,12 @@
58 75
59 76 // Save user defined fields.
60 77 add_action( 'woocommerce_checkout_update_order_meta', [ $this, 'save_user_defined_fields' ] );
61 78
79 + // Display user defined fields in order edit page.
80 + add_action( 'woocommerce_admin_order_data_after_billing_address', [ $this, 'add_item_to_order_details' ], 10, 1 );
81 + add_action( 'woocommerce_admin_order_data_after_shipping_address', [ $this, 'add_item_to_order_details' ], 10, 1 );
82 +
62 83 // Add custom shipping field to email.
63 84 add_filter( 'woocommerce_order_get_formatted_shipping_address', [ $this, 'attach_user_shipping_fields_to_order_email' ], 10, 3 );
64 85
65 86 // Add custom billing field to email.
@@ -79,8 +100,20 @@
79 100 add_filter( 'sellkit-shipping-methods-choosen-method', [ $this, 'sellkit_default_shipping_method' ], 10, 1 );
80 101
81 102 // Modify discounted products.
82 103 add_action( 'woocommerce_before_calculate_totals', [ $this, 'before_cart_calculate' ], 999 );
104 +
105 + // Allow viewing of Checkout page in the Editor with an empty cart.
106 + $action = isset( $_REQUEST['action'] ) ? filter_var( $_REQUEST['action'], FILTER_SANITIZE_FULL_SPECIAL_CHARS ) : ''; // phpcs:ignore
107 + $elementor_preview = isset( $_REQUEST['elementor-preview'] ) ? filter_var( $_REQUEST['elementor-preview'], FILTER_SANITIZE_FULL_SPECIAL_CHARS ) : ''; // phpcs:ignore
108 +
109 + if (
110 + ( 'elementor' === $action && is_admin() ) // Elementor Editor.
111 + || 'elementor_ajax' === $action // Elementor Editor Preview - Ajax Render Widget.
112 + || ! empty( $elementor_preview ) // Elementor Editor Preview.
113 + ) {
114 + add_filter( 'woocommerce_checkout_redirect_empty_cart', '__return_false', 5 );
115 + }
83 116 }
84 117
85 118 /**
86 119 * Handle ajax calls.
@@ -246,8 +279,16 @@
246 279 if ( empty( $optimization_data ) ) {
247 280 return;
248 281 }
249 282
283 + $auto_apply_coupon_cookie_value = null;
284 +
285 + if ( isset( $_COOKIE['sellkit_rejected_coupon'] ) ) {
286 + // Get the value of the cookie.
287 + $auto_apply_coupon_cookie_value = sanitize_text_field( wp_unslash( $_COOKIE['sellkit_rejected_coupon'] ) );
288 + $auto_apply_coupon_cookie_value = explode( ',', $auto_apply_coupon_cookie_value );
289 + }
290 +
250 291 // Help to apply funnel discount prices and coupons when changing product quantity.
251 292 self::apply_discounted_prices( wc()->cart, $funnel_id );
252 293 self::apply_discounted_prices( wc()->cart, $funnel_id, 'bumps' );
253 294
@@ -254,8 +295,11 @@
254 295 if ( Checkout::apply_coupon_validation( $optimization_data ) ) {
255 296 WC()->cart->remove_coupons();
256 297
257 298 foreach ( $optimization_data['auto_apply_coupons'] as $auto_apply_coupon ) {
299 + if ( ! empty( $auto_apply_coupon_cookie_value ) && in_array( $auto_apply_coupon['label'], $auto_apply_coupon_cookie_value, true ) ) {
300 + continue;
301 + }
258 302 wc()->cart->add_discount( get_the_title( $auto_apply_coupon['value'] ) );
259 303 }
260 304
261 305 wc_clear_notices();
@@ -270,9 +314,9 @@
270 314 */
271 315 public function add_shipping_total_to_review_order() {
272 316 ?>
273 317 <tr class="sellkit-shipping-total">
274 - <th><?php echo __( 'Shipping', 'sellkit' ); ?></th>
318 + <th><?php echo esc_html__( 'Shipping', 'sellkit' ); ?></th>
275 319 <td>
276 320 <?php
277 321 $price = WC()->cart->get_shipping_total();
278 322
@@ -279,9 +323,9 @@
279 323 if ( WC()->cart->display_prices_including_tax() ) {
280 324 $price = WC()->cart->get_shipping_total() + WC()->cart->shipping_tax_total;
281 325 }
282 326
283 - echo wc_price( $price );
327 + echo wp_kses_post( wc_price( $price ) );
284 328 ?>
285 329 </td>
286 330 </tr>
287 331 <?php
@@ -311,14 +355,19 @@
311 355 $img_html = apply_filters( 'sellkit/includes/elementor/modules/checkout/product-image', $img_html, $product, $cart_item_key );
312 356
313 357 ob_start();
314 358
315 - $extra_class = '';
359 + $extra_class = $product->get_type();
316 360 if ( $cart_items[ $cart_item_key ] === end( $cart_items ) ) {
317 - $extra_class = ' last-cart-item';
361 + $extra_class .= ' last-cart-item';
318 362 }
363 +
364 + if ( 'variation' === $product->get_type() ) {
365 + add_filter( 'woocommerce_cart_item_name', [ $this, 'modify_variation_title' ], 50, 2 );
366 + add_filter( 'woocommerce_get_item_data', [ $this, 'modify_variation_items' ], 10, 2 );
367 + }
319 368 ?>
320 - <tr class="<?php echo esc_attr( apply_filters( 'woocommerce_cart_item_class', 'cart_item', $cart_item, $cart_item_key ) ) . $extra_class; ?>">
369 + <tr class="<?php echo esc_attr( apply_filters( 'woocommerce_cart_item_class', 'cart_item', $cart_item, $cart_item_key ) ) . ' ' . esc_attr( $extra_class ); ?>">
321 370
322 371 <td class="product-name sellkit-one-page-checkout-product-name">
323 372 <?php
324 373 echo $img_html;
@@ -333,9 +382,9 @@
333 382 <div class="name-price" style="<?php echo esc_attr( $fix_style ); ?>">
334 383 <span style="display:inline-block">
335 384 <?php echo wp_kses_post( apply_filters( 'woocommerce_cart_item_name', $product->get_name(), $cart_item, $cart_item_key ) ) . '&nbsp;'; ?>
336 385 </span>
337 - <span class="sellkit-checkout-variations">
386 + <span class="sellkit-checkout-variations" id="sellkit-checkout-variations">
338 387 <?php echo wc_get_formatted_cart_item_data( $cart_item ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
339 388 </span>
340 389 <?php echo apply_filters( 'sellkit-checkout-widget-disable-quantity', '<input type="number" data-id="' . esc_attr( $cart_item_key ) . '" value="' . esc_attr( $cart_item['quantity'] ) . '" class="sellkit-one-page-checkout-product-qty" >', $cart_item['quantity'] ); ?>
341 390 <?php do_action( 'sellkit-checkout-editor-mode-quantity', $cart_item['quantity'] ); ?>
@@ -349,11 +398,58 @@
349 398 $cart_item = ob_get_clean();
350 399
351 400 echo apply_filters( 'sellkit-checkout-cart-item' , $cart_item );
352 401 //phpcs:enable
402 +
403 + if ( 'variation' === $product->get_type() ) {
404 + remove_filter( 'woocommerce_cart_item_name', [ $this, 'modify_variation_title' ], 50 );
405 + remove_filter( 'woocommerce_get_item_data', [ $this, 'modify_variation_items' ], 10 );
406 + }
353 407 }
354 408
355 409 /**
410 + * Remove variation attributes from variation product title.
411 + *
412 + * @param string $default default value.
413 + * @param array $cart_item cart item data.
414 + * @since 1.6.8
415 + */
416 + public function modify_variation_title( $default, $cart_item ) {
417 + $product = $cart_item['product_id'];
418 +
419 + return get_the_title( $product );
420 + }
421 +
422 + /**
423 + * Display variation attributes separated from title even if those are less than 3.
424 + *
425 + * @param array $item_data attributes array.
426 + * @param array $cart_item cart item data.
427 + * @since 1.6.8
428 + */
429 + public function modify_variation_items( $item_data, $cart_item ) {
430 + $product = new \WC_Product_Variation( $cart_item['variation_id'] );
431 + $attributes = $product->get_attributes();
432 +
433 + if ( count( $attributes ) > 2 ) {
434 + return $item_data;
435 + }
436 +
437 + foreach ( $attributes as $key => $value ) {
438 + $key = str_replace( 'pa_', '', $key );
439 + $key = str_replace( '-', '', $key );
440 + $key = str_replace( '_', '', $key );
441 +
442 + $item_data[] = [
443 + 'key' => $key,
444 + 'value' => $value,
445 + ];
446 + }
447 +
448 + return $item_data;
449 + }
450 +
451 + /**
356 452 * Modify fields after pressing place order button.
357 453 * we hooked here because some removed fields still get checked and have not removed by previous hooks.
358 454 *
359 455 * @SuppressWarnings(PHPMD.NPathComplexity)
@@ -754,8 +850,9 @@
754 850 * Modify cart contents using bundled products.
755 851 *
756 852 * @since 1.1.0
757 853 * @return void
854 + * @SuppressWarnings(PHPMD.CyclomaticComplexity)
758 855 */
759 856 public function sellkit_checkout_modify_cart_by_bundle_products() {
760 857 $qty = filter_input( INPUT_POST, 'qty', FILTER_SANITIZE_NUMBER_INT );
761 858 $id = filter_input( INPUT_POST, 'id', FILTER_SANITIZE_NUMBER_INT );
@@ -775,11 +872,31 @@
775 872 wp_send_json_success();
776 873 }
777 874
778 875 if ( 'radio' === $type ) {
779 - $step_data = get_post_meta( $checkout_id, 'step_data', true );
780 - $products = $step_data['data']['products']['list'];
781 - $selected = $products[ $id ];
876 + $step_data = get_post_meta( $checkout_id, 'step_data', true );
877 + $products = $step_data['data']['products']['list'];
878 + $reset_cart = isset( $step_data['data']['products']['reset_cart'] ) ? $step_data['data']['products']['reset_cart'] : 'true';
879 +
880 + if ( 'true' === $reset_cart ) {
881 + foreach ( WC()->cart->get_cart() as $item ) {
882 + if ( empty( $item['product_id'] ) ) {
883 + continue;
884 + }
885 +
886 + if ( array_key_exists( $item['product_id'], $products ) ) {
887 + continue;
888 + }
889 +
890 + $products[ $item['product_id'] ] = [
891 + 'quantity' => $item['quantity'],
892 + 'discount' => '',
893 + 'discountType' => 'fixed',
894 + ];
895 + }
896 + }
897 +
898 + $selected = ! empty( $products[ $id ] ) ? $products[ $id ] : [];
782 899 $real_quantity = ( empty( $selected['quantity'] ) ) ? 1 : $selected['quantity'];
783 900
784 901 if ( (int) $real_quantity !== (int) $qty ) {
785 902 wp_send_json_error( esc_html__( 'Oh do not be tricky.', 'sellkit' ) );
@@ -834,8 +951,9 @@
834 951 * @param int $checkout_id_ajax checkout id.
835 952 * @param string $source source of products, bump or default products to be checked.
836 953 * @param array $upsell_data upsell product data coming from popup.
837 954 * @since 1.2.8
955 + * @SuppressWarnings(PHPMD.CyclomaticComplexity)
838 956 */
839 957 public static function apply_discounted_prices( $cart = [], $checkout_id_ajax = null, $source = 'default', $upsell_data = [] ) {
840 958 $checkout_id = get_queried_object_id();
841 959
@@ -857,9 +975,9 @@
857 975
858 976 if (
859 977 'default' === $source &&
860 978 array_key_exists( 'data', $step_data ) &&
861 - array_key_exists( 'list', $step_data['data']['products'] )
979 + ( ! empty( $step_data['data']['products'] ) && array_key_exists( 'list', $step_data['data']['products'] ) )
862 980 ) {
863 981 $products = $step_data['data']['products']['list'];
864 982 }
865 983
@@ -870,9 +988,14 @@
870 988 if ( ! array_key_exists( 'products', $bump['data'] ) ) {
871 989 continue;
872 990 }
873 991
874 - $key = array_key_first( $bump['data']['products']['list'] );
992 + $key = ! empty( $bump['data']['products']['list'] ) ? array_key_first( $bump['data']['products']['list'] ) : null;
993 +
994 + if ( empty( $key ) ) {
995 + continue;
996 + }
997 +
875 998 $products[ $key ] = $bump['data']['products']['list'][ $key ];
876 999 }
877 1000 }
878 1001
@@ -895,12 +1018,16 @@
895 1018 }
896 1019
897 1020 foreach ( $products as $product_id => $product_details ) {
898 1021 if ( $item_id === $product_id ) {
899 - $discount_type = $product_details['discountType'];
900 - $discount_value = $product_details['discount'];
1022 + $discount_type = isset( $product_details['discountType'] ) ? $product_details['discountType'] : '';
1023 + $discount_value = isset( $product_details['discount'] ) ? $product_details['discount'] : '';
901 1024
902 1025 $price = Helper::calculate_discount( $item_id, $discount_type, $discount_value );
1026 +
1027 + if ( 'upsell' === $source ) {
1028 + self::$sellkit_upsell_products[ $product_id ] = $price;
1029 + }
903 1030 }
904 1031 }
905 1032
906 1033 if ( false !== $price ) {
@@ -906,9 +1033,9 @@
906 1033 if ( false !== $price ) {
907 1034 $details['data']->set_price( $price );
908 1035 }
909 1036
910 - $final_price += $details['data']->get_price( $price ) * $details['quantity'];
1037 + $final_price += $details['data']->get_price() * $details['quantity'];
911 1038 }
912 1039
913 1040 self::modify_products_price_before_woo_apply_discounts( $final_price );
914 1041 }
@@ -1041,8 +1168,9 @@
1041 1168 /**
1042 1169 * Recalculate checkout and cart prices.
1043 1170 *
1044 1171 * @since 1.2.8
1172 + * @SuppressWarnings(PHPMD.CyclomaticComplexity)
1045 1173 */
1046 1174 public function before_cart_calculate() {
1047 1175 // More than twice isn't necessary. this is called multiple times by WooCommerce.
1048 1176 if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 && wp_doing_ajax() ) {
@@ -1064,11 +1192,13 @@
1064 1192 // Catch id after pressing place order button. none of above methods worked.
1065 1193 if ( empty( $checkout_id ) ) {
1066 1194 $data = filter_input( INPUT_POST, 'post_data', FILTER_DEFAULT );
1067 1195
1068 - parse_str( $data, $data );
1196 + if ( ! empty( $data ) ) {
1197 + parse_str( $data, $data );
1198 + }
1069 1199
1070 - if ( array_key_exists( 'sellkit_current_page_id', $data ) ) {
1200 + if ( is_array( $data ) && array_key_exists( 'sellkit_current_page_id', $data ) ) {
1071 1201 $checkout_id = $data['sellkit_current_page_id'];
1072 1202 }
1073 1203 }
1074 1204 }
@@ -1083,8 +1213,43 @@
1083 1213 // Apply funnel prices.
1084 1214 self::apply_discounted_prices( wc()->cart, $checkout_id );
1085 1215 self::apply_discounted_prices( wc()->cart, $checkout_id, 'bumps' );
1086 1216
1217 + // Apply upsell discount.
1218 + $sellkit_upsell_prices = null;
1219 + if ( isset( $_POST['woocommerce-process-checkout-nonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['woocommerce-process-checkout-nonce'] ) ), 'woocommerce-process_checkout' ) ) {
1220 + $sellkit_upsell_prices = isset( $_POST['sellkit_product_prices'] ) ? sanitize_textarea_field( wp_unslash( $_POST['sellkit_product_prices'] ) ) : null;
1221 + }
1222 + $upsell_product_ids;
1223 +
1224 + if ( isset( $sellkit_upsell_prices ) && ! empty( $sellkit_upsell_prices ) ) {
1225 + $sellkit_upsell_prices = json_decode( $sellkit_upsell_prices, true );
1226 + $upsell_product_ids = array_keys( $sellkit_upsell_prices );
1227 + }
1228 +
1229 + if ( isset( $upsell_product_ids ) ) {
1230 + foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
1231 + $product = $cart_item['data'];
1232 + $product_name = $product->get_name();
1233 + $product_id = $product->get_id();
1234 +
1235 + if ( in_array( $product_id, $upsell_product_ids, true ) ) {
1236 + $upsell_price = $sellkit_upsell_prices[ $product_id ];
1237 + $product->set_price( $upsell_price );
1238 + }
1239 +
1240 + $price = $product->get_price();
1241 + }
1242 + }
1243 +
1244 + $auto_apply_coupon_cookie_value = null;
1245 +
1246 + if ( isset( $_COOKIE['sellkit_rejected_coupon'] ) ) {
1247 + // Get the value of the cookie.
1248 + $auto_apply_coupon_cookie_value = sanitize_text_field( wp_unslash( $_COOKIE['sellkit_rejected_coupon'] ) );
1249 + $auto_apply_coupon_cookie_value = explode( ',', $auto_apply_coupon_cookie_value );
1250 + }
1251 +
1087 1252 // We should re apply coupons after each price changes. to make sure everything is correct.
1088 1253 $optimization_data = ! empty( $funnel_data['data']['optimization'] ) ? $funnel_data['data']['optimization'] : '';
1089 1254
1090 1255 if ( Checkout::apply_coupon_validation( $optimization_data ) ) {
@@ -1090,8 +1255,11 @@
1090 1255 if ( Checkout::apply_coupon_validation( $optimization_data ) ) {
1091 1256 WC()->cart->remove_coupons();
1092 1257
1093 1258 foreach ( $optimization_data['auto_apply_coupons'] as $auto_apply_coupon ) {
1259 + if ( ! empty( $auto_apply_coupon_cookie_value ) && in_array( $auto_apply_coupon['label'], $auto_apply_coupon_cookie_value, true ) ) {
1260 + continue;
1261 + }
1094 1262 wc()->cart->add_discount( get_the_title( $auto_apply_coupon['value'] ) );
1095 1263 }
1096 1264
1097 1265 wc_clear_notices();
@@ -1109,16 +1277,24 @@
1109 1277 $funnel = new Sellkit_Funnel( $step );
1110 1278 $next_step = $funnel->next_step_data;
1111 1279 $popups = [ 'upsell', 'downsell' ];
1112 1280
1281 + $funnel->next_step_data['type'] = (array) $funnel->next_step_data['type'];
1282 +
1113 1283 if ( 'decision' === $funnel->next_step_data['type']['key'] ) {
1114 - $this->take_care_of_decision_step( $funnel->next_step_data['page_id'] );
1284 + $page_id = isset( $funnel->next_step_data['page_id'] ) ? apply_filters( 'wpml_object_id', $funnel->next_step_data['page_id'], 'sellkit_step', true ) : 0; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API.
1285 +
1286 + $this->helper_id = $step;
1287 + $this->take_care_of_decision_step( $page_id, $funnel->funnel_id );
1115 1288 return;
1116 1289 }
1117 1290
1118 1291 if ( in_array( $funnel->next_step_data['type']['key'], $popups, true ) ) {
1292 +
1293 + $next_step = $this->is_set_upsell_product( $next_step );
1294 +
1119 1295 wp_send_json_success( [
1120 - 'next_id' => $next_step['page_id'],
1296 + 'next_id' => apply_filters( 'wpml_object_id', $next_step['page_id'], 'sellkit_step', true ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API.
1121 1297 'next_type' => $next_step['type']['key'],
1122 1298 ] );
1123 1299 }
1124 1300 }
@@ -1123,14 +1299,51 @@
1123 1299 }
1124 1300 }
1125 1301
1126 1302 /**
1303 + * Check the upsell or downsell have product or not, ignore upsell/downsell step if product doesn't set.
1304 + *
1305 + * @param array $next_step Next step data.
1306 + * @since 2.6.0
1307 + */
1308 + private function is_set_upsell_product( $next_step ) {
1309 + if ( isset( $next_step['type']['key'] ) && ! in_array( $next_step['type']['key'], [ 'upsell', 'downsell' ], true ) ) {
1310 + return $next_step;
1311 + }
1312 +
1313 + $products = isset( $next_step['data'] ) ? $next_step['data']['products'] : [];
1314 + $list = isset( $products['list'] ) ? $products['list'] : [];
1315 +
1316 + if ( empty( $list ) ) {
1317 + $funnel = isset( $next_step['page_id'] ) ? new Sellkit_Funnel( $next_step['page_id'] ) : null;
1318 + $next_step = ! empty( $funnel ) ? $funnel->next_no_step_data : [];
1319 +
1320 + if ( empty( $next_step ) && ! empty( $funnel ) ) {
1321 + return $funnel->end_node_step_data;
1322 + }
1323 +
1324 + if ( isset( $next_step['type']['key'] ) && in_array( $next_step['type']['key'], [ 'upsell', 'downsell' ], true ) ) {
1325 +
1326 + $next_step = $this->is_set_upsell_product( $next_step );
1327 + }
1328 + }
1329 +
1330 + return $next_step;
1331 + }
1332 +
1333 + /**
1127 1334 * Gets step id before the decision step and return result.
1128 1335 *
1129 1336 * @param int $step_id id of the step before decision step.
1337 + * @param int $funnel_id id of the funnel.
1130 1338 * @since 1.6.2
1131 1339 */
1132 - private function take_care_of_decision_step( $step_id ) {
1340 + private function take_care_of_decision_step( $step_id, $funnel_id = null ) {
1341 + // Failed decision step due to no page id should be checked a little different.
1342 + if ( empty( $step_id ) && ! empty( $funnel_id ) ) {
1343 + $this->check_decision_step_with_no_page_id();
1344 + }
1345 +
1133 1346 $funnel = new Sellkit_Funnel( $step_id );
1134 1347 $conditions = ! empty( $funnel->current_step_data['data']['conditions'] ) ? $funnel->current_step_data['data']['conditions'] : [];
1135 1348 $is_valid = sellkit_conditions_validation( $conditions );
1136 1349 $next_step = $funnel->next_no_step_data;
@@ -1138,15 +1351,51 @@
1138 1351 if ( $is_valid ) {
1139 1352 $next_step = $funnel->next_step_data;
1140 1353 }
1141 1354
1355 + $next_step = $this->is_set_upsell_product( $next_step );
1356 + $next_step['type'] = (array) $next_step['type'];
1357 +
1358 + if ( 'decision' === $next_step['type']['key'] ) {
1359 + $this->take_care_of_decision_step( apply_filters( 'wpml_object_id', $next_step['page_id'], 'sellkit_step', true ) ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API.
1360 +
1361 + return;
1362 + }
1363 +
1142 1364 wp_send_json_success( [
1143 - 'next_id' => $next_step['page_id'],
1365 + 'next_id' => apply_filters( 'wpml_object_id', $next_step['page_id'], 'sellkit_step', true ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API.
1144 1366 'next_type' => $next_step['type']['key'],
1145 1367 ] );
1146 1368 }
1147 1369
1148 1370 /**
1371 + * Decision next step directly using funnel data.
1372 + *
1373 + * @since 1.8.6
1374 + */
1375 + private function check_decision_step_with_no_page_id() {
1376 + $funnel = new Sellkit_Funnel( $this->helper_id );
1377 + $decicion_data = $funnel->next_step_data;
1378 + $conditions = $decicion_data['data']['conditions'];
1379 + $funnel_data = get_post_meta( $funnel->funnel_id, 'nodes', true );
1380 + $next_no = 'none' !== $decicion_data['targets'][1]['nodeId'] ? $funnel_data[ $decicion_data['targets'][1]['nodeId'] ] : $funnel->end_node_step_data;
1381 + $next_yes = 'none' !== $decicion_data['targets'][0]['nodeId'] ? $funnel_data[ $decicion_data['targets'][0]['nodeId'] ] : $funnel->end_node_step_data;
1382 + $is_valid = sellkit_conditions_validation( $conditions );
1383 + $next_step = $next_no;
1384 +
1385 + if ( $is_valid ) {
1386 + $next_step = $next_yes;
1387 + }
1388 +
1389 + $next_step = $this->is_set_upsell_product( $next_step );
1390 +
1391 + wp_send_json_success( [
1392 + 'next_id' => apply_filters( 'wpml_object_id', $next_step['page_id'], 'sellkit_step', true ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API.
1393 + 'next_type' => $next_step['type']['key'],
1394 + ] );
1395 + }
1396 +
1397 + /**
1149 1398 * Perform upsell popup accept button.
1150 1399 *
1151 1400 * @since 1.6.2
1152 1401 */
@@ -1166,23 +1415,66 @@
1166 1415 wp_send_json_error( esc_html__( 'Empty Step Data.', 'sellkit' ) );
1167 1416 }
1168 1417
1169 1418 // Add product to cart.
1170 - $product_id = array_key_first( $upsell_data['data']['products']['list'] );
1171 - $qty = $upsell_data['data']['products']['list'][ $product_id ]['quantity'];
1419 + $product_id = '';
1420 + $qty = '';
1172 1421
1422 + if (
1423 + is_array( $upsell_data['data'] ) &&
1424 + array_key_exists( 'products', $upsell_data['data'] ) &&
1425 + ! empty( $upsell_data['data']['products']['list'] )
1426 + ) {
1427 + $product_id = array_key_first( $upsell_data['data']['products']['list'] );
1428 + $qty = ! empty( $upsell_data['data']['products']['list'][ $product_id ] ) ? $upsell_data['data']['products']['list'][ $product_id ]['quantity'] : 0;
1429 + }
1430 +
1173 1431 if ( empty( $qty ) ) {
1174 1432 $qty = 1;
1175 1433 }
1176 1434
1177 - WC()->cart->add_to_cart( $product_id, $qty );
1178 - // Take care of cart and applied discounts.
1179 - self::apply_discounted_prices( WC()->cart, $checkout_id, 'upsell', $upsell_data );
1180 - self::make_changes_after_cart_item_edit( $checkout_id );
1435 + $added_product_hash = '';
1436 +
1437 + if ( ! in_array( $product_id, array_column( WC()->cart->get_cart(), 'product_id' ), true ) ) {
1438 + $added_product_hash = WC()->cart->add_to_cart( $product_id, $qty );
1439 +
1440 + // Take care of cart and applied discounts.
1441 + self::apply_discounted_prices( WC()->cart, $checkout_id, 'upsell', $upsell_data );
1442 + self::make_changes_after_cart_item_edit( $checkout_id );
1443 + }
1444 +
1181 1445 // Response.
1182 - $funnel = new Sellkit_Funnel( $upsell_id );
1183 - $next_step = $funnel->next_step_data;
1446 + $funnel = new Sellkit_Funnel( $upsell_id );
1447 + $next_step = $funnel->next_step_data;
1448 + $next_step = $this->is_set_upsell_product( $next_step );
1449 + $next_step['type'] = ! empty( $next_step['type'] ) ? (array) $next_step['type'] : null;
1184 1450
1451 + if ( 'upsell' === $funnel->current_step_data['type']['key'] && isset( WC()->cart->cart_contents[ $added_product_hash ] ) ) {
1452 + $analytics_updater = new Data_Updater();
1453 + $total = empty( WC()->cart->cart_contents[ $added_product_hash ]['line_total'] ) ? 0 : WC()->cart->cart_contents[ $added_product_hash ]['line_total'];
1454 +
1455 + $analytics_updater->set_funnel_id( $funnel->funnel_id );
1456 + $analytics_updater->add_new_upsell_revenue_log( $total );
1457 + }
1458 +
1459 + $contact_data = [
1460 + 'key' => $funnel->current_step_data['type']['key'],
1461 + 'page_id' => $funnel->current_step_data['page_id'],
1462 + ];
1463 +
1464 + Base_Contacts::step_is_passed( $contact_data );
1465 +
1466 + // Step with empty data, will be redirected to thankyou page.
1467 + if ( empty( $next_step ) || empty( $next_step['type'] ) ) {
1468 + wp_send_json_success(
1469 + [
1470 + 'next_id' => $funnel->end_node_step_data['page_id'],
1471 + 'next_type' => $funnel->end_node_step_data['type']['key'],
1472 + 'upsell_prices' => wp_json_encode( self::$sellkit_upsell_products ),
1473 + ]
1474 + );
1475 + }
1476 +
1185 1477 if ( 'decision' === $next_step['type']['key'] ) {
1186 1478 $this->take_care_of_decision_step( $next_step['page_id'] );
1187 1479 return;
1188 1480 }
@@ -1187,10 +1479,11 @@
1187 1479 return;
1188 1480 }
1189 1481
1190 1482 $response = [
1191 - 'next_id' => $next_step['page_id'],
1483 + 'next_id' => apply_filters( 'wpml_object_id', $next_step['page_id'], 'sellkit_step', true ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API.
1192 1484 'next_type' => $next_step['type']['key'],
1485 + 'upsell_prices' => wp_json_encode( self::$sellkit_upsell_products ),
1193 1486 ];
1194 1487
1195 1488 wp_send_json_success( $response );
1196 1489 }
@@ -1208,9 +1501,24 @@
1208 1501 }
1209 1502
1210 1503 $funnel = new Sellkit_Funnel( $upsell_id );
1211 1504 $next_step = $funnel->next_no_step_data;
1505 + $next_step = $this->is_set_upsell_product( $next_step );
1212 1506
1507 + if ( $next_step ) {
1508 + $next_step['type'] = (array) $next_step['type'];
1509 + }
1510 +
1511 + // Step with empty data, will be redirected to thankyou page.
1512 + if ( empty( $next_step ) || empty( $next_step['type'] ) ) {
1513 + wp_send_json_success(
1514 + [
1515 + 'next_id' => $funnel->end_node_step_data['page_id'],
1516 + 'next_type' => $funnel->end_node_step_data['type']['key'],
1517 + ]
1518 + );
1519 + }
1520 +
1213 1521 if ( 'decision' === $next_step['type']['key'] ) {
1214 1522 $this->take_care_of_decision_step( $next_step['page_id'] );
1215 1523 return;
1216 1524 }
@@ -1215,11 +1523,60 @@
1215 1523 return;
1216 1524 }
1217 1525
1218 1526 $response = [
1219 - 'next_id' => $next_step['page_id'],
1527 + 'next_id' => apply_filters( 'wpml_object_id', $next_step['page_id'], 'sellkit_step', true ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API.
1220 1528 'next_type' => $next_step['type']['key'],
1221 1529 ];
1222 1530
1223 1531 wp_send_json_success( $response );
1532 + }
1533 +
1534 + /**
1535 + * Add new item to the WooCommerce order details on the edit page.
1536 + *
1537 + * @param object $order woocommerce order object.
1538 + * @since 1.8.9
1539 + */
1540 + public function add_item_to_order_details( $order ) {
1541 + $order_id = $order->get_id();
1542 + $custom_fields = get_post_meta( $order_id, 'sellkit_checkout_widget_custom_field_of_order', true );
1543 + $current_action = current_action();
1544 +
1545 + if ( empty( $custom_fields ) || ! is_array( $custom_fields ) ) {
1546 + return;
1547 + }
1548 +
1549 + foreach ( $custom_fields as $field ) {
1550 + $key_name = $field['key_name'];
1551 + $label = $field['label'];
1552 +
1553 + // For old users data.
1554 + if ( empty( $label ) ) {
1555 + $label = $key_name;
1556 + $label = str_replace( '_', ' ', $label );
1557 + $label = str_replace( '-', ' ', $label );
1558 + $label = ucwords( $label );
1559 + }
1560 +
1561 + if (
1562 + 'woocommerce_admin_order_data_after_shipping_address' === $current_action &&
1563 + 'shipping' === substr( $key_name, 0, 8 )
1564 + ) {
1565 + ?>
1566 + <h3><?php echo esc_html( $label ); ?></h3>
1567 + <div><?php echo esc_html( get_post_meta( $order_id, $key_name, true ) ); ?></div>
1568 + <?php
1569 + }
1570 +
1571 + if (
1572 + 'woocommerce_admin_order_data_after_billing_address' === $current_action &&
1573 + 'billing' === substr( $key_name, 0, 7 )
1574 + ) {
1575 + ?>
1576 + <h3><?php echo esc_html( $label ); ?></h3>
1577 + <div><?php echo esc_html( get_post_meta( $order_id, $key_name, true ) ); ?></div>
1578 + <?php
1579 + }
1580 + }
1224 1581 }
1225 1582 }