PluginProbe
SellKit – Funnel builder and checkout optimizer for WooCommerce to sell more, faster / 2.8.0
SellKit – Funnel builder and checkout optimizer for WooCommerce to sell more, faster v2.8.0
2.8.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 All 44 releases
← All changes | includes/elementor/modules/checkout/classes/global-hooks.php +584 -35 1.3.2 → 2.8.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,20 @@
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 + /**
21 32 * Create instance of class without construct.
22 33 *
23 34 * @since 1.1.0
24 35 * @return object
@@ -57,8 +68,12 @@
57 68
58 69 // Save user defined fields.
59 70 add_action( 'woocommerce_checkout_update_order_meta', [ $this, 'save_user_defined_fields' ] );
60 71
72 + // Display user defined fields in order edit page.
73 + add_action( 'woocommerce_admin_order_data_after_billing_address', [ $this, 'add_item_to_order_details' ], 10, 1 );
74 + add_action( 'woocommerce_admin_order_data_after_shipping_address', [ $this, 'add_item_to_order_details' ], 10, 1 );
75 +
61 76 // Add custom shipping field to email.
62 77 add_filter( 'woocommerce_order_get_formatted_shipping_address', [ $this, 'attach_user_shipping_fields_to_order_email' ], 10, 3 );
63 78
64 79 // Add custom billing field to email.
@@ -76,9 +91,22 @@
76 91 add_action( 'woocommerce_checkout_update_order_review', [ $this, 'sellkit_checkout_during_ajax' ], 10, 1 );
77 92
78 93 add_filter( 'sellkit-shipping-methods-choosen-method', [ $this, 'sellkit_default_shipping_method' ], 10, 1 );
79 94
80 - add_action( 'woocommerce_before_calculate_totals', [ $this, 'before_cart_calculate' ], 9999 );
95 + // Modify discounted products.
96 + add_action( 'woocommerce_before_calculate_totals', [ $this, 'before_cart_calculate' ], 999 );
97 +
98 + // Allow viewing of Checkout page in the Editor with an empty cart.
99 + $action = isset( $_REQUEST['action'] ) ? filter_var( $_REQUEST['action'], FILTER_SANITIZE_FULL_SPECIAL_CHARS ) : ''; // phpcs:ignore
100 + $elementor_preview = isset( $_REQUEST['elementor-preview'] ) ? filter_var( $_REQUEST['elementor-preview'], FILTER_SANITIZE_FULL_SPECIAL_CHARS ) : ''; // phpcs:ignore
101 +
102 + if (
103 + ( 'elementor' === $action && is_admin() ) // Elementor Editor.
104 + || 'elementor_ajax' === $action // Elementor Editor Preview - Ajax Render Widget.
105 + || ! empty( $elementor_preview ) // Elementor Editor Preview.
106 + ) {
107 + add_filter( 'woocommerce_checkout_redirect_empty_cart', '__return_false', 5 );
108 + }
81 109 }
82 110
83 111 /**
84 112 * Handle ajax calls.
@@ -244,8 +272,16 @@
244 272 if ( empty( $optimization_data ) ) {
245 273 return;
246 274 }
247 275
276 + $auto_apply_coupon_cookie_value = null;
277 +
278 + if ( isset( $_COOKIE['sellkit_rejected_coupon'] ) ) {
279 + // Get the value of the cookie.
280 + $auto_apply_coupon_cookie_value = sanitize_text_field( wp_unslash( $_COOKIE['sellkit_rejected_coupon'] ) );
281 + $auto_apply_coupon_cookie_value = explode( ',', $auto_apply_coupon_cookie_value );
282 + }
283 +
248 284 // Help to apply funnel discount prices and coupons when changing product quantity.
249 285 self::apply_discounted_prices( wc()->cart, $funnel_id );
250 286 self::apply_discounted_prices( wc()->cart, $funnel_id, 'bumps' );
251 287
@@ -252,8 +288,11 @@
252 288 if ( Checkout::apply_coupon_validation( $optimization_data ) ) {
253 289 WC()->cart->remove_coupons();
254 290
255 291 foreach ( $optimization_data['auto_apply_coupons'] as $auto_apply_coupon ) {
292 + if ( ! empty( $auto_apply_coupon_cookie_value ) && in_array( $auto_apply_coupon['label'], $auto_apply_coupon_cookie_value, true ) ) {
293 + continue;
294 + }
256 295 wc()->cart->add_discount( get_the_title( $auto_apply_coupon['value'] ) );
257 296 }
258 297
259 298 wc_clear_notices();
@@ -268,9 +307,9 @@
268 307 */
269 308 public function add_shipping_total_to_review_order() {
270 309 ?>
271 310 <tr class="sellkit-shipping-total">
272 - <th><?php echo __( 'Shipping', 'sellkit' ); ?></th>
311 + <th><?php echo esc_html__( 'Shipping', 'sellkit' ); ?></th>
273 312 <td>
274 313 <?php
275 314 $price = WC()->cart->get_shipping_total();
276 315
@@ -277,9 +316,9 @@
277 316 if ( WC()->cart->display_prices_including_tax() ) {
278 317 $price = WC()->cart->get_shipping_total() + WC()->cart->shipping_tax_total;
279 318 }
280 319
281 - echo wc_price( $price );
320 + echo wp_kses_post( wc_price( $price ) );
282 321 ?>
283 322 </td>
284 323 </tr>
285 324 <?php
@@ -309,14 +348,19 @@
309 348 $img_html = apply_filters( 'sellkit/includes/elementor/modules/checkout/product-image', $img_html, $product, $cart_item_key );
310 349
311 350 ob_start();
312 351
313 - $extra_class = '';
352 + $extra_class = $product->get_type();
314 353 if ( $cart_items[ $cart_item_key ] === end( $cart_items ) ) {
315 - $extra_class = ' last-cart-item';
354 + $extra_class .= ' last-cart-item';
316 355 }
356 +
357 + if ( 'variation' === $product->get_type() ) {
358 + add_filter( 'woocommerce_cart_item_name', [ $this, 'modify_variation_title' ], 50, 2 );
359 + add_filter( 'woocommerce_get_item_data', [ $this, 'modify_variation_items' ], 10, 2 );
360 + }
317 361 ?>
318 - <tr class="<?php echo esc_attr( apply_filters( 'woocommerce_cart_item_class', 'cart_item', $cart_item, $cart_item_key ) ) . $extra_class; ?>">
362 + <tr class="<?php echo esc_attr( apply_filters( 'woocommerce_cart_item_class', 'cart_item', $cart_item, $cart_item_key ) ) . ' ' . esc_attr( $extra_class ); ?>">
319 363
320 364 <td class="product-name sellkit-one-page-checkout-product-name">
321 365 <?php
322 366 echo $img_html;
@@ -331,9 +375,9 @@
331 375 <div class="name-price" style="<?php echo esc_attr( $fix_style ); ?>">
332 376 <span style="display:inline-block">
333 377 <?php echo wp_kses_post( apply_filters( 'woocommerce_cart_item_name', $product->get_name(), $cart_item, $cart_item_key ) ) . '&nbsp;'; ?>
334 378 </span>
335 - <span class="sellkit-checkout-variations">
379 + <span class="sellkit-checkout-variations" id="sellkit-checkout-variations">
336 380 <?php echo wc_get_formatted_cart_item_data( $cart_item ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
337 381 </span>
338 382 <?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 383 <?php do_action( 'sellkit-checkout-editor-mode-quantity', $cart_item['quantity'] ); ?>
@@ -347,11 +391,58 @@
347 391 $cart_item = ob_get_clean();
348 392
349 393 echo apply_filters( 'sellkit-checkout-cart-item' , $cart_item );
350 394 //phpcs:enable
395 +
396 + if ( 'variation' === $product->get_type() ) {
397 + remove_filter( 'woocommerce_cart_item_name', [ $this, 'modify_variation_title' ], 50 );
398 + remove_filter( 'woocommerce_get_item_data', [ $this, 'modify_variation_items' ], 10 );
399 + }
351 400 }
352 401
353 402 /**
403 + * Remove variation attributes from variation product title.
404 + *
405 + * @param string $default default value.
406 + * @param array $cart_item cart item data.
407 + * @since 1.6.8
408 + */
409 + public function modify_variation_title( $default, $cart_item ) {
410 + $product = $cart_item['product_id'];
411 +
412 + return get_the_title( $product );
413 + }
414 +
415 + /**
416 + * Display variation attributes separated from title even if those are less than 3.
417 + *
418 + * @param array $item_data attributes array.
419 + * @param array $cart_item cart item data.
420 + * @since 1.6.8
421 + */
422 + public function modify_variation_items( $item_data, $cart_item ) {
423 + $product = new \WC_Product_Variation( $cart_item['variation_id'] );
424 + $attributes = $product->get_attributes();
425 +
426 + if ( count( $attributes ) > 2 ) {
427 + return $item_data;
428 + }
429 +
430 + foreach ( $attributes as $key => $value ) {
431 + $key = str_replace( 'pa_', '', $key );
432 + $key = str_replace( '-', '', $key );
433 + $key = str_replace( '_', '', $key );
434 +
435 + $item_data[] = [
436 + 'key' => $key,
437 + 'value' => $value,
438 + ];
439 + }
440 +
441 + return $item_data;
442 + }
443 +
444 + /**
354 445 * Modify fields after pressing place order button.
355 446 * we hooked here because some removed fields still get checked and have not removed by previous hooks.
356 447 *
357 448 * @SuppressWarnings(PHPMD.NPathComplexity)
@@ -752,8 +843,9 @@
752 843 * Modify cart contents using bundled products.
753 844 *
754 845 * @since 1.1.0
755 846 * @return void
847 + * @SuppressWarnings(PHPMD.CyclomaticComplexity)
756 848 */
757 849 public function sellkit_checkout_modify_cart_by_bundle_products() {
758 850 $qty = filter_input( INPUT_POST, 'qty', FILTER_SANITIZE_NUMBER_INT );
759 851 $id = filter_input( INPUT_POST, 'id', FILTER_SANITIZE_NUMBER_INT );
@@ -773,11 +865,31 @@
773 865 wp_send_json_success();
774 866 }
775 867
776 868 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 ];
869 + $step_data = get_post_meta( $checkout_id, 'step_data', true );
870 + $products = $step_data['data']['products']['list'];
871 + $reset_cart = isset( $step_data['data']['products']['reset_cart'] ) ? $step_data['data']['products']['reset_cart'] : 'true';
872 +
873 + if ( 'true' === $reset_cart ) {
874 + foreach ( WC()->cart->get_cart() as $item ) {
875 + if ( empty( $item['product_id'] ) ) {
876 + continue;
877 + }
878 +
879 + if ( array_key_exists( $item['product_id'], $products ) ) {
880 + continue;
881 + }
882 +
883 + $products[ $item['product_id'] ] = [
884 + 'quantity' => $item['quantity'],
885 + 'discount' => '',
886 + 'discountType' => 'fixed',
887 + ];
888 + }
889 + }
890 +
891 + $selected = ! empty( $products[ $id ] ) ? $products[ $id ] : [];
780 892 $real_quantity = ( empty( $selected['quantity'] ) ) ? 1 : $selected['quantity'];
781 893
782 894 if ( (int) $real_quantity !== (int) $qty ) {
783 895 wp_send_json_error( esc_html__( 'Oh do not be tricky.', 'sellkit' ) );
@@ -830,11 +942,13 @@
830 942 *
831 943 * @param object $cart cart object.
832 944 * @param int $checkout_id_ajax checkout id.
833 945 * @param string $source source of products, bump or default products to be checked.
946 + * @param array $upsell_data upsell product data coming from popup.
834 947 * @since 1.2.8
948 + * @SuppressWarnings(PHPMD.CyclomaticComplexity)
835 949 */
836 - public static function apply_discounted_prices( $cart = [], $checkout_id_ajax = null, $source = 'default' ) {
950 + public static function apply_discounted_prices( $cart = [], $checkout_id_ajax = null, $source = 'default', $upsell_data = [] ) {
837 951 $checkout_id = get_queried_object_id();
838 952
839 953 if ( wp_doing_ajax() ) {
840 954 $checkout_id = $checkout_id_ajax;
@@ -851,9 +965,13 @@
851 965 }
852 966
853 967 $products = [];
854 968
855 - if ( 'default' === $source ) {
969 + if (
970 + 'default' === $source &&
971 + array_key_exists( 'data', $step_data ) &&
972 + ( ! empty( $step_data['data']['products'] ) && array_key_exists( 'list', $step_data['data']['products'] ) )
973 + ) {
856 974 $products = $step_data['data']['products']['list'];
857 975 }
858 976
859 977 if ( 'bumps' === $source && ! empty( $step_data['bump'] ) ) {
@@ -863,13 +981,22 @@
863 981 if ( ! array_key_exists( 'products', $bump['data'] ) ) {
864 982 continue;
865 983 }
866 984
867 - $key = array_key_first( $bump['data']['products']['list'] );
985 + $key = ! empty( $bump['data']['products']['list'] ) ? array_key_first( $bump['data']['products']['list'] ) : null;
986 +
987 + if ( empty( $key ) ) {
988 + continue;
989 + }
990 +
868 991 $products[ $key ] = $bump['data']['products']['list'][ $key ];
869 992 }
870 993 }
871 994
995 + if ( 'upsell' === $source ) {
996 + $products = $upsell_data['data']['products']['list'];
997 + }
998 +
872 999 if ( empty( $products ) ) {
873 1000 return;
874 1001 }
875 1002
@@ -884,10 +1011,10 @@
884 1011 }
885 1012
886 1013 foreach ( $products as $product_id => $product_details ) {
887 1014 if ( $item_id === $product_id ) {
888 - $discount_type = $product_details['discountType'];
889 - $discount_value = $product_details['discount'];
1015 + $discount_type = isset( $product_details['discountType'] ) ? $product_details['discountType'] : '';
1016 + $discount_value = isset( $product_details['discount'] ) ? $product_details['discount'] : '';
890 1017
891 1018 $price = Helper::calculate_discount( $item_id, $discount_type, $discount_value );
892 1019 }
893 1020 }
@@ -895,9 +1022,9 @@
895 1022 if ( false !== $price ) {
896 1023 $details['data']->set_price( $price );
897 1024 }
898 1025
899 - $final_price += $details['data']->get_price( $price ) * $details['quantity'];
1026 + $final_price += $details['data']->get_price() * $details['quantity'];
900 1027 }
901 1028
902 1029 self::modify_products_price_before_woo_apply_discounts( $final_price );
903 1030 }
@@ -1030,8 +1157,9 @@
1030 1157 /**
1031 1158 * Recalculate checkout and cart prices.
1032 1159 *
1033 1160 * @since 1.2.8
1161 + * @SuppressWarnings(PHPMD.CyclomaticComplexity)
1034 1162 */
1035 1163 public function before_cart_calculate() {
1036 1164 // More than twice isn't necessary. this is called multiple times by WooCommerce.
1037 1165 if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 && wp_doing_ajax() ) {
@@ -1037,53 +1165,474 @@
1037 1165 if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 && wp_doing_ajax() ) {
1038 1166 return;
1039 1167 }
1040 1168
1041 - // Gather checkout id.
1169 + $checkout_id = self::get_valid_checkout_id();
1170 +
1171 + if ( 0 === $checkout_id ) {
1172 + return;
1173 + }
1174 +
1175 + // Apply funnel prices.
1176 + self::apply_discounted_prices( wc()->cart, $checkout_id );
1177 + self::apply_discounted_prices( wc()->cart, $checkout_id, 'bumps' );
1178 +
1179 + // Apply upsell discount.
1180 + $sellkit_upsell_ids = null;
1181 + if ( isset( $_POST['woocommerce-process-checkout-nonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['woocommerce-process-checkout-nonce'] ) ), 'woocommerce-process_checkout' ) ) {
1182 + $sellkit_upsell_ids = isset( $_POST['sellkit_upsell_ids'] ) ? sanitize_textarea_field( wp_unslash( $_POST['sellkit_upsell_ids'] ) ) : null;
1183 + }
1184 +
1185 + if ( ! empty( $sellkit_upsell_ids ) ) {
1186 + $upsell_ids = explode( ',', $sellkit_upsell_ids );
1187 + $list = self::upsell_downsell_ids_of_funnel( $checkout_id );
1188 +
1189 + if ( ! empty( $list ) ) {
1190 + foreach ( $upsell_ids as $id ) {
1191 +
1192 + if ( in_array( $id, $list, true ) ) {
1193 + self::applay_upsell_downsell_discount( $id );
1194 + }
1195 + }
1196 + }
1197 + }
1198 +
1199 + $auto_apply_coupon_cookie_value = null;
1200 +
1201 + if ( isset( $_COOKIE['sellkit_rejected_coupon'] ) ) {
1202 + // Get the value of the cookie.
1203 + $auto_apply_coupon_cookie_value = sanitize_text_field( wp_unslash( $_COOKIE['sellkit_rejected_coupon'] ) );
1204 + $auto_apply_coupon_cookie_value = explode( ',', $auto_apply_coupon_cookie_value );
1205 + }
1206 +
1207 + // We should re apply coupons after each price changes. to make sure everything is correct.
1208 + $optimization_data = ! empty( $funnel_data['data']['optimization'] ) ? $funnel_data['data']['optimization'] : '';
1209 +
1210 + if ( Checkout::apply_coupon_validation( $optimization_data ) ) {
1211 + WC()->cart->remove_coupons();
1212 +
1213 + foreach ( $optimization_data['auto_apply_coupons'] as $auto_apply_coupon ) {
1214 + if ( ! empty( $auto_apply_coupon_cookie_value ) && in_array( $auto_apply_coupon['label'], $auto_apply_coupon_cookie_value, true ) ) {
1215 + continue;
1216 + }
1217 + wc()->cart->add_discount( get_the_title( $auto_apply_coupon['value'] ) );
1218 + }
1219 +
1220 + wc_clear_notices();
1221 + }
1222 + }
1223 +
1224 + /**
1225 + * Extract upsell and downsell ides from funnel.
1226 + *
1227 + * @since 2.8.0
1228 + *
1229 + * @param int $checkout_id id of checkout.
1230 + *
1231 + * @return array
1232 + */
1233 + private static function upsell_downsell_ids_of_funnel( $checkout_id ) {
1234 + $step_data = get_post_meta( $checkout_id, 'step_data', true );
1235 + $funnel_id = $step_data['funnel_id'];
1236 + $nodes = get_post_meta( $funnel_id, 'nodes', true );
1237 + $ids = [];
1238 +
1239 + foreach ( $nodes as $node ) {
1240 + if ( isset( $node['type'] ) && in_array( $node['type']['key'], [ 'upsell', 'downsell' ], true ) ) {
1241 + $ids[] = $node['page_id'];
1242 + }
1243 + }
1244 +
1245 + return $ids;
1246 + }
1247 +
1248 + /**
1249 + * Apply discount to upsell or downsell.
1250 + *
1251 + * @param int $id id of checkout.
1252 + *
1253 + * @since 2.8.0
1254 + */
1255 + private static function applay_upsell_downsell_discount( $id ) {
1256 + $upsell_data = get_post_meta( $id, 'step_data', true );
1257 + $product = $upsell_data['data']['products']['list'];
1258 +
1259 + foreach ( $product as $key => $value ) {
1260 + foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
1261 + $product = $cart_item['data'];
1262 + $product_id = $product->get_id();
1263 +
1264 + if ( $key === $product_id ) {
1265 + $discount_type = isset( $value['discountType'] ) ? $value['discountType'] : '';
1266 + $discount_value = isset( $value['discount'] ) ? $value['discount'] : '';
1267 +
1268 + $price = Helper::calculate_discount( $product_id, $discount_type, $discount_value );
1269 + $product->set_price( $price );
1270 + }
1271 + }
1272 + }
1273 + }
1274 +
1275 + /**
1276 + * Get the checkout step associated with the current visitor.
1277 + *
1278 + * @since 2.8.0
1279 + *
1280 + * @return int
1281 + */
1282 + private static function get_valid_checkout_id() {
1042 1283 $checkout_id = get_queried_object_id();
1043 1284
1044 1285 if ( wp_doing_ajax() ) {
1045 - // First try to catch id using our ajax call.
1046 1286 $checkout_id = filter_input( INPUT_POST, 'related_checkout', FILTER_SANITIZE_NUMBER_INT );
1047 1287
1048 - // Second try to catch id using woocommerce ajax.
1049 1288 if ( empty( $checkout_id ) ) {
1050 1289 $checkout_id = filter_input( INPUT_POST, 'sellkit_current_page_id', FILTER_SANITIZE_NUMBER_INT );
1051 1290 }
1052 1291
1053 - // Catch id after pressing place order button. none of above methods worked.
1054 1292 if ( empty( $checkout_id ) ) {
1055 1293 $data = filter_input( INPUT_POST, 'post_data', FILTER_DEFAULT );
1056 1294
1057 - parse_str( $data, $data );
1295 + if ( ! empty( $data ) ) {
1296 + parse_str( $data, $data );
1297 + }
1058 1298
1059 - if ( array_key_exists( 'sellkit_current_page_id', $data ) ) {
1299 + if ( is_array( $data ) && array_key_exists( 'sellkit_current_page_id', $data ) ) {
1060 1300 $checkout_id = $data['sellkit_current_page_id'];
1061 1301 }
1062 1302 }
1063 1303 }
1064 1304
1065 - // Empty id ? no action.
1066 - if ( empty( $checkout_id ) ) {
1305 + $checkout_id = absint( $checkout_id );
1306 + $step_data = get_post_meta( $checkout_id, 'step_data', true );
1307 +
1308 + if (
1309 + 0 === $checkout_id ||
1310 + 'publish' !== get_post_status( $checkout_id ) ||
1311 + ! is_array( $step_data ) ||
1312 + ! isset( $step_data['type']['key'] ) ||
1313 + 'checkout' !== $step_data['type']['key']
1314 + ) {
1315 + return 0;
1316 + }
1317 +
1318 + if ( wp_doing_ajax() && ! function_exists( 'WC' ) ) {
1319 + return 0;
1320 + }
1321 +
1322 + return $checkout_id;
1323 + }
1324 +
1325 + /**
1326 + * Check sellkit step through ajax.
1327 + * And in order decide to show upsell or downsell steps through a popup.
1328 + *
1329 + * @since 1.6.2
1330 + */
1331 + public function call_funnel_popups() {
1332 + $step = filter_input( INPUT_POST, 'step', FILTER_SANITIZE_NUMBER_INT );
1333 + $funnel = new Sellkit_Funnel( $step );
1334 + $next_step = $funnel->next_step_data;
1335 + $popups = [ 'upsell', 'downsell' ];
1336 +
1337 + $funnel->next_step_data['type'] = (array) $funnel->next_step_data['type'];
1338 +
1339 + if ( 'decision' === $funnel->next_step_data['type']['key'] ) {
1340 + $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.
1341 +
1342 + $this->helper_id = $step;
1343 + $this->take_care_of_decision_step( $page_id, $funnel->funnel_id );
1067 1344 return;
1068 1345 }
1069 1346
1070 - $checkout_id = (int) $checkout_id;
1347 + if ( in_array( $funnel->next_step_data['type']['key'], $popups, true ) ) {
1071 1348
1072 - // Apply funnel prices.
1073 - self::apply_discounted_prices( wc()->cart, $checkout_id );
1074 - self::apply_discounted_prices( wc()->cart, $checkout_id, 'bumps' );
1349 + $next_step = $this->is_set_upsell_product( $next_step );
1075 1350
1076 - // We should re apply coupons after each price changes. to make sure everything is correct.
1077 - $optimization_data = ! empty( $funnel_data['data']['optimization'] ) ? $funnel_data['data']['optimization'] : '';
1351 + wp_send_json_success( [
1352 + 'next_id' => apply_filters( 'wpml_object_id', $next_step['page_id'], 'sellkit_step', true ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API.
1353 + 'next_type' => $next_step['type']['key'],
1354 + ] );
1355 + }
1356 + }
1078 1357
1079 - if ( Checkout::apply_coupon_validation( $optimization_data ) ) {
1080 - WC()->cart->remove_coupons();
1358 + /**
1359 + * Check the upsell or downsell have product or not, ignore upsell/downsell step if product doesn't set.
1360 + *
1361 + * @param array $next_step Next step data.
1362 + * @since 2.6.0
1363 + */
1364 + private function is_set_upsell_product( $next_step ) {
1365 + if ( isset( $next_step['type']['key'] ) && ! in_array( $next_step['type']['key'], [ 'upsell', 'downsell' ], true ) ) {
1366 + return $next_step;
1367 + }
1081 1368
1082 - foreach ( $optimization_data['auto_apply_coupons'] as $auto_apply_coupon ) {
1083 - wc()->cart->add_discount( get_the_title( $auto_apply_coupon['value'] ) );
1369 + $products = isset( $next_step['data'] ) ? $next_step['data']['products'] : [];
1370 + $list = isset( $products['list'] ) ? $products['list'] : [];
1371 +
1372 + if ( empty( $list ) ) {
1373 + $funnel = isset( $next_step['page_id'] ) ? new Sellkit_Funnel( $next_step['page_id'] ) : null;
1374 + $next_step = ! empty( $funnel ) ? $funnel->next_no_step_data : [];
1375 +
1376 + if ( empty( $next_step ) && ! empty( $funnel ) ) {
1377 + return $funnel->end_node_step_data;
1084 1378 }
1085 1379
1086 - wc_clear_notices();
1380 + if ( isset( $next_step['type']['key'] ) && in_array( $next_step['type']['key'], [ 'upsell', 'downsell' ], true ) ) {
1381 +
1382 + $next_step = $this->is_set_upsell_product( $next_step );
1383 + }
1384 + }
1385 +
1386 + return $next_step;
1387 + }
1388 +
1389 + /**
1390 + * Gets step id before the decision step and return result.
1391 + *
1392 + * @param int $step_id id of the step before decision step.
1393 + * @param int $funnel_id id of the funnel.
1394 + * @since 1.6.2
1395 + */
1396 + private function take_care_of_decision_step( $step_id, $funnel_id = null ) {
1397 + // Failed decision step due to no page id should be checked a little different.
1398 + if ( empty( $step_id ) && ! empty( $funnel_id ) ) {
1399 + $this->check_decision_step_with_no_page_id();
1400 + }
1401 +
1402 + $funnel = new Sellkit_Funnel( $step_id );
1403 + $conditions = ! empty( $funnel->current_step_data['data']['conditions'] ) ? $funnel->current_step_data['data']['conditions'] : [];
1404 + $is_valid = sellkit_conditions_validation( $conditions );
1405 + $next_step = $funnel->next_no_step_data;
1406 +
1407 + if ( $is_valid ) {
1408 + $next_step = $funnel->next_step_data;
1409 + }
1410 +
1411 + $next_step = $this->is_set_upsell_product( $next_step );
1412 + $next_step['type'] = (array) $next_step['type'];
1413 +
1414 + if ( 'decision' === $next_step['type']['key'] ) {
1415 + $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.
1416 +
1417 + return;
1418 + }
1419 +
1420 + wp_send_json_success( [
1421 + 'next_id' => apply_filters( 'wpml_object_id', $next_step['page_id'], 'sellkit_step', true ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API.
1422 + 'next_type' => $next_step['type']['key'],
1423 + ] );
1424 + }
1425 +
1426 + /**
1427 + * Decision next step directly using funnel data.
1428 + *
1429 + * @since 1.8.6
1430 + */
1431 + private function check_decision_step_with_no_page_id() {
1432 + $funnel = new Sellkit_Funnel( $this->helper_id );
1433 + $decicion_data = $funnel->next_step_data;
1434 + $conditions = $decicion_data['data']['conditions'];
1435 + $funnel_data = get_post_meta( $funnel->funnel_id, 'nodes', true );
1436 + $next_no = 'none' !== $decicion_data['targets'][1]['nodeId'] ? $funnel_data[ $decicion_data['targets'][1]['nodeId'] ] : $funnel->end_node_step_data;
1437 + $next_yes = 'none' !== $decicion_data['targets'][0]['nodeId'] ? $funnel_data[ $decicion_data['targets'][0]['nodeId'] ] : $funnel->end_node_step_data;
1438 + $is_valid = sellkit_conditions_validation( $conditions );
1439 + $next_step = $next_no;
1440 +
1441 + if ( $is_valid ) {
1442 + $next_step = $next_yes;
1443 + }
1444 +
1445 + $next_step = $this->is_set_upsell_product( $next_step );
1446 +
1447 + wp_send_json_success( [
1448 + 'next_id' => apply_filters( 'wpml_object_id', $next_step['page_id'], 'sellkit_step', true ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API.
1449 + 'next_type' => $next_step['type']['key'],
1450 + ] );
1451 + }
1452 +
1453 + /**
1454 + * Perform upsell popup accept button.
1455 + *
1456 + * @since 1.6.2
1457 + */
1458 + public function perform_upsell_accept_button() {
1459 + // Gather and validate information.
1460 + $upsell_id = filter_input( INPUT_POST, 'upsell_id', FILTER_SANITIZE_NUMBER_INT );
1461 + $checkout_id = filter_input( INPUT_POST, 'checkout_id', FILTER_SANITIZE_NUMBER_INT );
1462 + $response = [];
1463 +
1464 + if ( empty( $upsell_id ) ) {
1465 + wp_send_json_error( esc_html__( 'Empty Upsell ID.', 'sellkit' ) );
1466 + }
1467 +
1468 + $upsell_data = get_post_meta( $upsell_id, 'step_data', true );
1469 +
1470 + if ( empty( $upsell_data ) ) {
1471 + wp_send_json_error( esc_html__( 'Empty Step Data.', 'sellkit' ) );
1472 + }
1473 +
1474 + // Add product to cart.
1475 + $product_id = '';
1476 + $qty = '';
1477 +
1478 + if (
1479 + is_array( $upsell_data['data'] ) &&
1480 + array_key_exists( 'products', $upsell_data['data'] ) &&
1481 + ! empty( $upsell_data['data']['products']['list'] )
1482 + ) {
1483 + $product_id = array_key_first( $upsell_data['data']['products']['list'] );
1484 + $qty = ! empty( $upsell_data['data']['products']['list'][ $product_id ] ) ? $upsell_data['data']['products']['list'][ $product_id ]['quantity'] : 0;
1485 + }
1486 +
1487 + if ( empty( $qty ) ) {
1488 + $qty = 1;
1489 + }
1490 +
1491 + $added_product_hash = '';
1492 +
1493 + if ( ! in_array( $product_id, array_column( WC()->cart->get_cart(), 'product_id' ), true ) ) {
1494 + $added_product_hash = WC()->cart->add_to_cart( $product_id, $qty );
1495 +
1496 + // Take care of cart and applied discounts.
1497 + self::apply_discounted_prices( WC()->cart, $checkout_id, 'upsell', $upsell_data );
1498 + self::make_changes_after_cart_item_edit( $checkout_id );
1499 + }
1500 +
1501 + // Response.
1502 + $funnel = new Sellkit_Funnel( $upsell_id );
1503 + $next_step = $funnel->next_step_data;
1504 + $next_step = $this->is_set_upsell_product( $next_step );
1505 + $next_step['type'] = ! empty( $next_step['type'] ) ? (array) $next_step['type'] : null;
1506 +
1507 + if ( 'upsell' === $funnel->current_step_data['type']['key'] && isset( WC()->cart->cart_contents[ $added_product_hash ] ) ) {
1508 + $analytics_updater = new Data_Updater();
1509 + $total = empty( WC()->cart->cart_contents[ $added_product_hash ]['line_total'] ) ? 0 : WC()->cart->cart_contents[ $added_product_hash ]['line_total'];
1510 +
1511 + $analytics_updater->set_funnel_id( $funnel->funnel_id );
1512 + $analytics_updater->add_new_upsell_revenue_log( $total );
1513 + }
1514 +
1515 + $contact_data = [
1516 + 'key' => $funnel->current_step_data['type']['key'],
1517 + 'page_id' => $funnel->current_step_data['page_id'],
1518 + ];
1519 +
1520 + Base_Contacts::step_is_passed( $contact_data );
1521 +
1522 + // Step with empty data, will be redirected to thankyou page.
1523 + if ( empty( $next_step ) || empty( $next_step['type'] ) ) {
1524 + wp_send_json_success(
1525 + [
1526 + 'next_id' => $funnel->end_node_step_data['page_id'],
1527 + 'next_type' => $funnel->end_node_step_data['type']['key'],
1528 + 'upsell_ids' => $upsell_id,
1529 + ]
1530 + );
1531 + }
1532 +
1533 + if ( 'decision' === $next_step['type']['key'] ) {
1534 + $this->take_care_of_decision_step( $next_step['page_id'] );
1535 + return;
1536 + }
1537 +
1538 + $response = [
1539 + 'next_id' => apply_filters( 'wpml_object_id', $next_step['page_id'], 'sellkit_step', true ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API.
1540 + 'next_type' => $next_step['type']['key'],
1541 + 'upsell_ids' => $upsell_id,
1542 + ];
1543 +
1544 + wp_send_json_success( $response );
1545 + }
1546 +
1547 + /**
1548 + * Perform upsell popup reject button.
1549 + *
1550 + * @since 1.6.2
1551 + */
1552 + public function perform_upsell_reject_button() {
1553 + $upsell_id = filter_input( INPUT_POST, 'upsell_id', FILTER_SANITIZE_NUMBER_INT );
1554 +
1555 + if ( empty( $upsell_id ) ) {
1556 + wp_send_json_error( esc_html__( 'Empty Upsell ID.', 'sellkit' ) );
1557 + }
1558 +
1559 + $funnel = new Sellkit_Funnel( $upsell_id );
1560 + $next_step = $funnel->next_no_step_data;
1561 + $next_step = $this->is_set_upsell_product( $next_step );
1562 +
1563 + if ( $next_step ) {
1564 + $next_step['type'] = (array) $next_step['type'];
1565 + }
1566 +
1567 + // Step with empty data, will be redirected to thankyou page.
1568 + if ( empty( $next_step ) || empty( $next_step['type'] ) ) {
1569 + wp_send_json_success(
1570 + [
1571 + 'next_id' => $funnel->end_node_step_data['page_id'],
1572 + 'next_type' => $funnel->end_node_step_data['type']['key'],
1573 + ]
1574 + );
1575 + }
1576 +
1577 + if ( 'decision' === $next_step['type']['key'] ) {
1578 + $this->take_care_of_decision_step( $next_step['page_id'] );
1579 + return;
1580 + }
1581 +
1582 + $response = [
1583 + 'next_id' => apply_filters( 'wpml_object_id', $next_step['page_id'], 'sellkit_step', true ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API.
1584 + 'next_type' => $next_step['type']['key'],
1585 + ];
1586 +
1587 + wp_send_json_success( $response );
1588 + }
1589 +
1590 + /**
1591 + * Add new item to the WooCommerce order details on the edit page.
1592 + *
1593 + * @param object $order woocommerce order object.
1594 + * @since 1.8.9
1595 + */
1596 + public function add_item_to_order_details( $order ) {
1597 + $order_id = $order->get_id();
1598 + $custom_fields = get_post_meta( $order_id, 'sellkit_checkout_widget_custom_field_of_order', true );
1599 + $current_action = current_action();
1600 +
1601 + if ( empty( $custom_fields ) || ! is_array( $custom_fields ) ) {
1602 + return;
1603 + }
1604 +
1605 + foreach ( $custom_fields as $field ) {
1606 + $key_name = $field['key_name'];
1607 + $label = $field['label'];
1608 +
1609 + // For old users data.
1610 + if ( empty( $label ) ) {
1611 + $label = $key_name;
1612 + $label = str_replace( '_', ' ', $label );
1613 + $label = str_replace( '-', ' ', $label );
1614 + $label = ucwords( $label );
1615 + }
1616 +
1617 + if (
1618 + 'woocommerce_admin_order_data_after_shipping_address' === $current_action &&
1619 + 'shipping' === substr( $key_name, 0, 8 )
1620 + ) {
1621 + ?>
1622 + <h3><?php echo esc_html( $label ); ?></h3>
1623 + <div><?php echo esc_html( get_post_meta( $order_id, $key_name, true ) ); ?></div>
1624 + <?php
1625 + }
1626 +
1627 + if (
1628 + 'woocommerce_admin_order_data_after_billing_address' === $current_action &&
1629 + 'billing' === substr( $key_name, 0, 7 )
1630 + ) {
1631 + ?>
1632 + <h3><?php echo esc_html( $label ); ?></h3>
1633 + <div><?php echo esc_html( get_post_meta( $order_id, $key_name, true ) ); ?></div>
1634 + <?php
1635 + }
1087 1636 }
1088 1637 }
1089 1638 }