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 +509 -17 1.5.42.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.
@@ -78,8 +100,20 @@
78 100 add_filter( 'sellkit-shipping-methods-choosen-method', [ $this, 'sellkit_default_shipping_method' ], 10, 1 );
79 101
80 102 // Modify discounted products.
81 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 + }
82 116 }
83 117
84 118 /**
85 119 * Handle ajax calls.
@@ -245,8 +279,16 @@
245 279 if ( empty( $optimization_data ) ) {
246 280 return;
247 281 }
248 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 +
249 291 // Help to apply funnel discount prices and coupons when changing product quantity.
250 292 self::apply_discounted_prices( wc()->cart, $funnel_id );
251 293 self::apply_discounted_prices( wc()->cart, $funnel_id, 'bumps' );
252 294
@@ -253,8 +295,11 @@
253 295 if ( Checkout::apply_coupon_validation( $optimization_data ) ) {
254 296 WC()->cart->remove_coupons();
255 297
256 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 + }
257 302 wc()->cart->add_discount( get_the_title( $auto_apply_coupon['value'] ) );
258 303 }
259 304
260 305 wc_clear_notices();
@@ -269,9 +314,9 @@
269 314 */
270 315 public function add_shipping_total_to_review_order() {
271 316 ?>
272 317 <tr class="sellkit-shipping-total">
273 - <th><?php echo __( 'Shipping', 'sellkit' ); ?></th>
318 + <th><?php echo esc_html__( 'Shipping', 'sellkit' ); ?></th>
274 319 <td>
275 320 <?php
276 321 $price = WC()->cart->get_shipping_total();
277 322
@@ -278,9 +323,9 @@
278 323 if ( WC()->cart->display_prices_including_tax() ) {
279 324 $price = WC()->cart->get_shipping_total() + WC()->cart->shipping_tax_total;
280 325 }
281 326
282 - echo wc_price( $price );
327 + echo wp_kses_post( wc_price( $price ) );
283 328 ?>
284 329 </td>
285 330 </tr>
286 331 <?php
@@ -310,14 +355,19 @@
310 355 $img_html = apply_filters( 'sellkit/includes/elementor/modules/checkout/product-image', $img_html, $product, $cart_item_key );
311 356
312 357 ob_start();
313 358
314 - $extra_class = '';
359 + $extra_class = $product->get_type();
315 360 if ( $cart_items[ $cart_item_key ] === end( $cart_items ) ) {
316 - $extra_class = ' last-cart-item';
361 + $extra_class .= ' last-cart-item';
317 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 + }
318 368 ?>
319 - <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 ); ?>">
320 370
321 371 <td class="product-name sellkit-one-page-checkout-product-name">
322 372 <?php
323 373 echo $img_html;
@@ -332,9 +382,9 @@
332 382 <div class="name-price" style="<?php echo esc_attr( $fix_style ); ?>">
333 383 <span style="display:inline-block">
334 384 <?php echo wp_kses_post( apply_filters( 'woocommerce_cart_item_name', $product->get_name(), $cart_item, $cart_item_key ) ) . '&nbsp;'; ?>
335 385 </span>
336 - <span class="sellkit-checkout-variations">
386 + <span class="sellkit-checkout-variations" id="sellkit-checkout-variations">
337 387 <?php echo wc_get_formatted_cart_item_data( $cart_item ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
338 388 </span>
339 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'] ); ?>
340 390 <?php do_action( 'sellkit-checkout-editor-mode-quantity', $cart_item['quantity'] ); ?>
@@ -348,11 +398,58 @@
348 398 $cart_item = ob_get_clean();
349 399
350 400 echo apply_filters( 'sellkit-checkout-cart-item' , $cart_item );
351 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 + }
352 407 }
353 408
354 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 + /**
355 452 * Modify fields after pressing place order button.
356 453 * we hooked here because some removed fields still get checked and have not removed by previous hooks.
357 454 *
358 455 * @SuppressWarnings(PHPMD.NPathComplexity)
@@ -753,8 +850,9 @@
753 850 * Modify cart contents using bundled products.
754 851 *
755 852 * @since 1.1.0
756 853 * @return void
854 + * @SuppressWarnings(PHPMD.CyclomaticComplexity)
757 855 */
758 856 public function sellkit_checkout_modify_cart_by_bundle_products() {
759 857 $qty = filter_input( INPUT_POST, 'qty', FILTER_SANITIZE_NUMBER_INT );
760 858 $id = filter_input( INPUT_POST, 'id', FILTER_SANITIZE_NUMBER_INT );
@@ -774,11 +872,31 @@
774 872 wp_send_json_success();
775 873 }
776 874
777 875 if ( 'radio' === $type ) {
778 - $step_data = get_post_meta( $checkout_id, 'step_data', true );
779 - $products = $step_data['data']['products']['list'];
780 - $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 ] : [];
781 899 $real_quantity = ( empty( $selected['quantity'] ) ) ? 1 : $selected['quantity'];
782 900
783 901 if ( (int) $real_quantity !== (int) $qty ) {
784 902 wp_send_json_error( esc_html__( 'Oh do not be tricky.', 'sellkit' ) );
@@ -831,11 +949,13 @@
831 949 *
832 950 * @param object $cart cart object.
833 951 * @param int $checkout_id_ajax checkout id.
834 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.
835 954 * @since 1.2.8
955 + * @SuppressWarnings(PHPMD.CyclomaticComplexity)
836 956 */
837 - 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 = [] ) {
838 958 $checkout_id = get_queried_object_id();
839 959
840 960 if ( wp_doing_ajax() ) {
841 961 $checkout_id = $checkout_id_ajax;
@@ -852,9 +972,13 @@
852 972 }
853 973
854 974 $products = [];
855 975
856 - 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 + ) {
857 981 $products = $step_data['data']['products']['list'];
858 982 }
859 983
860 984 if ( 'bumps' === $source && ! empty( $step_data['bump'] ) ) {
@@ -864,13 +988,22 @@
864 988 if ( ! array_key_exists( 'products', $bump['data'] ) ) {
865 989 continue;
866 990 }
867 991
868 - $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 +
869 998 $products[ $key ] = $bump['data']['products']['list'][ $key ];
870 999 }
871 1000 }
872 1001
1002 + if ( 'upsell' === $source ) {
1003 + $products = $upsell_data['data']['products']['list'];
1004 + }
1005 +
873 1006 if ( empty( $products ) ) {
874 1007 return;
875 1008 }
876 1009
@@ -885,12 +1018,16 @@
885 1018 }
886 1019
887 1020 foreach ( $products as $product_id => $product_details ) {
888 1021 if ( $item_id === $product_id ) {
889 - $discount_type = $product_details['discountType'];
890 - $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'] : '';
891 1024
892 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 + }
893 1030 }
894 1031 }
895 1032
896 1033 if ( false !== $price ) {
@@ -896,9 +1033,9 @@
896 1033 if ( false !== $price ) {
897 1034 $details['data']->set_price( $price );
898 1035 }
899 1036
900 - $final_price += $details['data']->get_price( $price ) * $details['quantity'];
1037 + $final_price += $details['data']->get_price() * $details['quantity'];
901 1038 }
902 1039
903 1040 self::modify_products_price_before_woo_apply_discounts( $final_price );
904 1041 }
@@ -1031,8 +1168,9 @@
1031 1168 /**
1032 1169 * Recalculate checkout and cart prices.
1033 1170 *
1034 1171 * @since 1.2.8
1172 + * @SuppressWarnings(PHPMD.CyclomaticComplexity)
1035 1173 */
1036 1174 public function before_cart_calculate() {
1037 1175 // More than twice isn't necessary. this is called multiple times by WooCommerce.
1038 1176 if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 && wp_doing_ajax() ) {
@@ -1054,11 +1192,13 @@
1054 1192 // Catch id after pressing place order button. none of above methods worked.
1055 1193 if ( empty( $checkout_id ) ) {
1056 1194 $data = filter_input( INPUT_POST, 'post_data', FILTER_DEFAULT );
1057 1195
1058 - parse_str( $data, $data );
1196 + if ( ! empty( $data ) ) {
1197 + parse_str( $data, $data );
1198 + }
1059 1199
1060 - if ( array_key_exists( 'sellkit_current_page_id', $data ) ) {
1200 + if ( is_array( $data ) && array_key_exists( 'sellkit_current_page_id', $data ) ) {
1061 1201 $checkout_id = $data['sellkit_current_page_id'];
1062 1202 }
1063 1203 }
1064 1204 }
@@ -1073,8 +1213,43 @@
1073 1213 // Apply funnel prices.
1074 1214 self::apply_discounted_prices( wc()->cart, $checkout_id );
1075 1215 self::apply_discounted_prices( wc()->cart, $checkout_id, 'bumps' );
1076 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 +
1077 1252 // We should re apply coupons after each price changes. to make sure everything is correct.
1078 1253 $optimization_data = ! empty( $funnel_data['data']['optimization'] ) ? $funnel_data['data']['optimization'] : '';
1079 1254
1080 1255 if ( Checkout::apply_coupon_validation( $optimization_data ) ) {
@@ -1080,11 +1255,328 @@
1080 1255 if ( Checkout::apply_coupon_validation( $optimization_data ) ) {
1081 1256 WC()->cart->remove_coupons();
1082 1257
1083 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 + }
1084 1262 wc()->cart->add_discount( get_the_title( $auto_apply_coupon['value'] ) );
1085 1263 }
1086 1264
1087 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 + }
1088 1580 }
1089 1581 }
1090 1582 }