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 +511 -18 1.3.22.7.0 View file →
@@ -4,9 +4,12 @@
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;
11 +use Sellkit_Funnel;
9 12
10 13 /**
11 14 * Global hooks.
12 15 *
@@ -13,12 +16,27 @@
13 16 * @SuppressWarnings(PHPMD.UnusedFormalParameter)
14 17 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
15 18 * @SuppressWarnings(PHPMD.TooManyMethods)
16 19 * @SuppressWarnings(PHPMD.NPathComplexity)
20 + * @SuppressWarnings(PHPMD.ExcessiveClassLength)
17 21 * @since 1.1.0
18 22 */
19 23 class Global_Hooks {
20 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 + /**
21 39 * Create instance of class without construct.
22 40 *
23 41 * @since 1.1.0
24 42 * @return object
@@ -57,8 +75,12 @@
57 75
58 76 // Save user defined fields.
59 77 add_action( 'woocommerce_checkout_update_order_meta', [ $this, 'save_user_defined_fields' ] );
60 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 +
61 83 // Add custom shipping field to email.
62 84 add_filter( 'woocommerce_order_get_formatted_shipping_address', [ $this, 'attach_user_shipping_fields_to_order_email' ], 10, 3 );
63 85
64 86 // Add custom billing field to email.
@@ -76,9 +98,22 @@
76 98 add_action( 'woocommerce_checkout_update_order_review', [ $this, 'sellkit_checkout_during_ajax' ], 10, 1 );
77 99
78 100 add_filter( 'sellkit-shipping-methods-choosen-method', [ $this, 'sellkit_default_shipping_method' ], 10, 1 );
79 101
80 - add_action( 'woocommerce_before_calculate_totals', [ $this, 'before_cart_calculate' ], 9999 );
102 + // Modify discounted products.
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 + }
81 116 }
82 117
83 118 /**
84 119 * Handle ajax calls.
@@ -244,8 +279,16 @@
244 279 if ( empty( $optimization_data ) ) {
245 280 return;
246 281 }
247 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 +
248 291 // Help to apply funnel discount prices and coupons when changing product quantity.
249 292 self::apply_discounted_prices( wc()->cart, $funnel_id );
250 293 self::apply_discounted_prices( wc()->cart, $funnel_id, 'bumps' );
251 294
@@ -252,8 +295,11 @@
252 295 if ( Checkout::apply_coupon_validation( $optimization_data ) ) {
253 296 WC()->cart->remove_coupons();
254 297
255 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 + }
256 302 wc()->cart->add_discount( get_the_title( $auto_apply_coupon['value'] ) );
257 303 }
258 304
259 305 wc_clear_notices();
@@ -268,9 +314,9 @@
268 314 */
269 315 public function add_shipping_total_to_review_order() {
270 316 ?>
271 317 <tr class="sellkit-shipping-total">
272 - <th><?php echo __( 'Shipping', 'sellkit' ); ?></th>
318 + <th><?php echo esc_html__( 'Shipping', 'sellkit' ); ?></th>
273 319 <td>
274 320 <?php
275 321 $price = WC()->cart->get_shipping_total();
276 322
@@ -277,9 +323,9 @@
277 323 if ( WC()->cart->display_prices_including_tax() ) {
278 324 $price = WC()->cart->get_shipping_total() + WC()->cart->shipping_tax_total;
279 325 }
280 326
281 - echo wc_price( $price );
327 + echo wp_kses_post( wc_price( $price ) );
282 328 ?>
283 329 </td>
284 330 </tr>
285 331 <?php
@@ -309,14 +355,19 @@
309 355 $img_html = apply_filters( 'sellkit/includes/elementor/modules/checkout/product-image', $img_html, $product, $cart_item_key );
310 356
311 357 ob_start();
312 358
313 - $extra_class = '';
359 + $extra_class = $product->get_type();
314 360 if ( $cart_items[ $cart_item_key ] === end( $cart_items ) ) {
315 - $extra_class = ' last-cart-item';
361 + $extra_class .= ' last-cart-item';
316 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 + }
317 368 ?>
318 - <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 ); ?>">
319 370
320 371 <td class="product-name sellkit-one-page-checkout-product-name">
321 372 <?php
322 373 echo $img_html;
@@ -331,9 +382,9 @@
331 382 <div class="name-price" style="<?php echo esc_attr( $fix_style ); ?>">
332 383 <span style="display:inline-block">
333 384 <?php echo wp_kses_post( apply_filters( 'woocommerce_cart_item_name', $product->get_name(), $cart_item, $cart_item_key ) ) . '&nbsp;'; ?>
334 385 </span>
335 - <span class="sellkit-checkout-variations">
386 + <span class="sellkit-checkout-variations" id="sellkit-checkout-variations">
336 387 <?php echo wc_get_formatted_cart_item_data( $cart_item ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
337 388 </span>
338 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'] ); ?>
339 390 <?php do_action( 'sellkit-checkout-editor-mode-quantity', $cart_item['quantity'] ); ?>
@@ -347,11 +398,58 @@
347 398 $cart_item = ob_get_clean();
348 399
349 400 echo apply_filters( 'sellkit-checkout-cart-item' , $cart_item );
350 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 + }
351 407 }
352 408
353 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 + /**
354 452 * Modify fields after pressing place order button.
355 453 * we hooked here because some removed fields still get checked and have not removed by previous hooks.
356 454 *
357 455 * @SuppressWarnings(PHPMD.NPathComplexity)
@@ -752,8 +850,9 @@
752 850 * Modify cart contents using bundled products.
753 851 *
754 852 * @since 1.1.0
755 853 * @return void
854 + * @SuppressWarnings(PHPMD.CyclomaticComplexity)
756 855 */
757 856 public function sellkit_checkout_modify_cart_by_bundle_products() {
758 857 $qty = filter_input( INPUT_POST, 'qty', FILTER_SANITIZE_NUMBER_INT );
759 858 $id = filter_input( INPUT_POST, 'id', FILTER_SANITIZE_NUMBER_INT );
@@ -773,11 +872,31 @@
773 872 wp_send_json_success();
774 873 }
775 874
776 875 if ( 'radio' === $type ) {
777 - $step_data = get_post_meta( $checkout_id, 'step_data', true );
778 - $products = $step_data['data']['products']['list'];
779 - $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 ] : [];
780 899 $real_quantity = ( empty( $selected['quantity'] ) ) ? 1 : $selected['quantity'];
781 900
782 901 if ( (int) $real_quantity !== (int) $qty ) {
783 902 wp_send_json_error( esc_html__( 'Oh do not be tricky.', 'sellkit' ) );
@@ -830,11 +949,13 @@
830 949 *
831 950 * @param object $cart cart object.
832 951 * @param int $checkout_id_ajax checkout id.
833 952 * @param string $source source of products, bump or default products to be checked.
953 + * @param array $upsell_data upsell product data coming from popup.
834 954 * @since 1.2.8
955 + * @SuppressWarnings(PHPMD.CyclomaticComplexity)
835 956 */
836 - public static function apply_discounted_prices( $cart = [], $checkout_id_ajax = null, $source = 'default' ) {
957 + public static function apply_discounted_prices( $cart = [], $checkout_id_ajax = null, $source = 'default', $upsell_data = [] ) {
837 958 $checkout_id = get_queried_object_id();
838 959
839 960 if ( wp_doing_ajax() ) {
840 961 $checkout_id = $checkout_id_ajax;
@@ -851,9 +972,13 @@
851 972 }
852 973
853 974 $products = [];
854 975
855 - if ( 'default' === $source ) {
976 + if (
977 + 'default' === $source &&
978 + array_key_exists( 'data', $step_data ) &&
979 + ( ! empty( $step_data['data']['products'] ) && array_key_exists( 'list', $step_data['data']['products'] ) )
980 + ) {
856 981 $products = $step_data['data']['products']['list'];
857 982 }
858 983
859 984 if ( 'bumps' === $source && ! empty( $step_data['bump'] ) ) {
@@ -863,13 +988,22 @@
863 988 if ( ! array_key_exists( 'products', $bump['data'] ) ) {
864 989 continue;
865 990 }
866 991
867 - $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 +
868 998 $products[ $key ] = $bump['data']['products']['list'][ $key ];
869 999 }
870 1000 }
871 1001
1002 + if ( 'upsell' === $source ) {
1003 + $products = $upsell_data['data']['products']['list'];
1004 + }
1005 +
872 1006 if ( empty( $products ) ) {
873 1007 return;
874 1008 }
875 1009
@@ -884,12 +1018,16 @@
884 1018 }
885 1019
886 1020 foreach ( $products as $product_id => $product_details ) {
887 1021 if ( $item_id === $product_id ) {
888 - $discount_type = $product_details['discountType'];
889 - $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'] : '';
890 1024
891 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 + }
892 1030 }
893 1031 }
894 1032
895 1033 if ( false !== $price ) {
@@ -895,9 +1033,9 @@
895 1033 if ( false !== $price ) {
896 1034 $details['data']->set_price( $price );
897 1035 }
898 1036
899 - $final_price += $details['data']->get_price( $price ) * $details['quantity'];
1037 + $final_price += $details['data']->get_price() * $details['quantity'];
900 1038 }
901 1039
902 1040 self::modify_products_price_before_woo_apply_discounts( $final_price );
903 1041 }
@@ -1030,8 +1168,9 @@
1030 1168 /**
1031 1169 * Recalculate checkout and cart prices.
1032 1170 *
1033 1171 * @since 1.2.8
1172 + * @SuppressWarnings(PHPMD.CyclomaticComplexity)
1034 1173 */
1035 1174 public function before_cart_calculate() {
1036 1175 // More than twice isn't necessary. this is called multiple times by WooCommerce.
1037 1176 if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 && wp_doing_ajax() ) {
@@ -1053,11 +1192,13 @@
1053 1192 // Catch id after pressing place order button. none of above methods worked.
1054 1193 if ( empty( $checkout_id ) ) {
1055 1194 $data = filter_input( INPUT_POST, 'post_data', FILTER_DEFAULT );
1056 1195
1057 - parse_str( $data, $data );
1196 + if ( ! empty( $data ) ) {
1197 + parse_str( $data, $data );
1198 + }
1058 1199
1059 - if ( array_key_exists( 'sellkit_current_page_id', $data ) ) {
1200 + if ( is_array( $data ) && array_key_exists( 'sellkit_current_page_id', $data ) ) {
1060 1201 $checkout_id = $data['sellkit_current_page_id'];
1061 1202 }
1062 1203 }
1063 1204 }
@@ -1072,8 +1213,43 @@
1072 1213 // Apply funnel prices.
1073 1214 self::apply_discounted_prices( wc()->cart, $checkout_id );
1074 1215 self::apply_discounted_prices( wc()->cart, $checkout_id, 'bumps' );
1075 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 +
1076 1252 // We should re apply coupons after each price changes. to make sure everything is correct.
1077 1253 $optimization_data = ! empty( $funnel_data['data']['optimization'] ) ? $funnel_data['data']['optimization'] : '';
1078 1254
1079 1255 if ( Checkout::apply_coupon_validation( $optimization_data ) ) {
@@ -1079,11 +1255,328 @@
1079 1255 if ( Checkout::apply_coupon_validation( $optimization_data ) ) {
1080 1256 WC()->cart->remove_coupons();
1081 1257
1082 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 + }
1083 1262 wc()->cart->add_discount( get_the_title( $auto_apply_coupon['value'] ) );
1084 1263 }
1085 1264
1086 1265 wc_clear_notices();
1266 + }
1267 + }
1268 +
1269 + /**
1270 + * Check sellkit step through ajax.
1271 + * And in order decide to show upsell or downsell steps through a popup.
1272 + *
1273 + * @since 1.6.2
1274 + */
1275 + public function call_funnel_popups() {
1276 + $step = filter_input( INPUT_POST, 'step', FILTER_SANITIZE_NUMBER_INT );
1277 + $funnel = new Sellkit_Funnel( $step );
1278 + $next_step = $funnel->next_step_data;
1279 + $popups = [ 'upsell', 'downsell' ];
1280 +
1281 + $funnel->next_step_data['type'] = (array) $funnel->next_step_data['type'];
1282 +
1283 + if ( 'decision' === $funnel->next_step_data['type']['key'] ) {
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 );
1288 + return;
1289 + }
1290 +
1291 + if ( in_array( $funnel->next_step_data['type']['key'], $popups, true ) ) {
1292 +
1293 + $next_step = $this->is_set_upsell_product( $next_step );
1294 +
1295 + wp_send_json_success( [
1296 + 'next_id' => apply_filters( 'wpml_object_id', $next_step['page_id'], 'sellkit_step', true ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API.
1297 + 'next_type' => $next_step['type']['key'],
1298 + ] );
1299 + }
1300 + }
1301 +
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 + /**
1334 + * Gets step id before the decision step and return result.
1335 + *
1336 + * @param int $step_id id of the step before decision step.
1337 + * @param int $funnel_id id of the funnel.
1338 + * @since 1.6.2
1339 + */
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 +
1346 + $funnel = new Sellkit_Funnel( $step_id );
1347 + $conditions = ! empty( $funnel->current_step_data['data']['conditions'] ) ? $funnel->current_step_data['data']['conditions'] : [];
1348 + $is_valid = sellkit_conditions_validation( $conditions );
1349 + $next_step = $funnel->next_no_step_data;
1350 +
1351 + if ( $is_valid ) {
1352 + $next_step = $funnel->next_step_data;
1353 + }
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 +
1364 + wp_send_json_success( [
1365 + 'next_id' => apply_filters( 'wpml_object_id', $next_step['page_id'], 'sellkit_step', true ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API.
1366 + 'next_type' => $next_step['type']['key'],
1367 + ] );
1368 + }
1369 +
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 + /**
1398 + * Perform upsell popup accept button.
1399 + *
1400 + * @since 1.6.2
1401 + */
1402 + public function perform_upsell_accept_button() {
1403 + // Gather and validate information.
1404 + $upsell_id = filter_input( INPUT_POST, 'upsell_id', FILTER_SANITIZE_NUMBER_INT );
1405 + $checkout_id = filter_input( INPUT_POST, 'checkout_id', FILTER_SANITIZE_NUMBER_INT );
1406 + $response = [];
1407 +
1408 + if ( empty( $upsell_id ) ) {
1409 + wp_send_json_error( esc_html__( 'Empty Upsell ID.', 'sellkit' ) );
1410 + }
1411 +
1412 + $upsell_data = get_post_meta( $upsell_id, 'step_data', true );
1413 +
1414 + if ( empty( $upsell_data ) ) {
1415 + wp_send_json_error( esc_html__( 'Empty Step Data.', 'sellkit' ) );
1416 + }
1417 +
1418 + // Add product to cart.
1419 + $product_id = '';
1420 + $qty = '';
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 +
1431 + if ( empty( $qty ) ) {
1432 + $qty = 1;
1433 + }
1434 +
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 +
1445 + // Response.
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;
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 +
1477 + if ( 'decision' === $next_step['type']['key'] ) {
1478 + $this->take_care_of_decision_step( $next_step['page_id'] );
1479 + return;
1480 + }
1481 +
1482 + $response = [
1483 + 'next_id' => apply_filters( 'wpml_object_id', $next_step['page_id'], 'sellkit_step', true ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API.
1484 + 'next_type' => $next_step['type']['key'],
1485 + 'upsell_prices' => wp_json_encode( self::$sellkit_upsell_products ),
1486 + ];
1487 +
1488 + wp_send_json_success( $response );
1489 + }
1490 +
1491 + /**
1492 + * Perform upsell popup reject button.
1493 + *
1494 + * @since 1.6.2
1495 + */
1496 + public function perform_upsell_reject_button() {
1497 + $upsell_id = filter_input( INPUT_POST, 'upsell_id', FILTER_SANITIZE_NUMBER_INT );
1498 +
1499 + if ( empty( $upsell_id ) ) {
1500 + wp_send_json_error( esc_html__( 'Empty Upsell ID.', 'sellkit' ) );
1501 + }
1502 +
1503 + $funnel = new Sellkit_Funnel( $upsell_id );
1504 + $next_step = $funnel->next_no_step_data;
1505 + $next_step = $this->is_set_upsell_product( $next_step );
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 +
1521 + if ( 'decision' === $next_step['type']['key'] ) {
1522 + $this->take_care_of_decision_step( $next_step['page_id'] );
1523 + return;
1524 + }
1525 +
1526 + $response = [
1527 + 'next_id' => apply_filters( 'wpml_object_id', $next_step['page_id'], 'sellkit_step', true ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API.
1528 + 'next_type' => $next_step['type']['key'],
1529 + ];
1530 +
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 + }
1087 1580 }
1088 1581 }
1089 1582 }