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 +592 -32 1.2.92.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.
@@ -183,13 +218,13 @@
183 218 $id = filter_input( INPUT_POST, 'id', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
184 219 $action = filter_input( INPUT_POST, 'mode', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
185 220 $funnel_id = filter_input( INPUT_POST, 'related_checkout', FILTER_SANITIZE_NUMBER_INT );
186 221
187 - $this->make_changes_after_cart_item_edit( $funnel_id );
222 + self::make_changes_after_cart_item_edit( $funnel_id );
188 223
189 224 if ( 'add' === $action ) {
190 225 WC()->cart->add_to_cart( $id, $qty );
191 - $this->make_changes_after_cart_item_edit( $funnel_id );
226 + self::make_changes_after_cart_item_edit( $funnel_id );
192 227 wp_send_json_success();
193 228 }
194 229
195 230 if ( 'remove' === $action ) {
@@ -202,9 +237,9 @@
202 237 break; // stop the loop.
203 238 }
204 239 }
205 240
206 - $this->make_changes_after_cart_item_edit( $funnel_id );
241 + self::make_changes_after_cart_item_edit( $funnel_id );
207 242
208 243 wp_send_json_success();
209 244 }
210 245
@@ -214,9 +249,9 @@
214 249 if ( $cart_item_key ) {
215 250 WC()->cart->remove_cart_item( $cart_item_key );
216 251 }
217 252
218 - $this->make_changes_after_cart_item_edit( $funnel_id );
253 + self::make_changes_after_cart_item_edit( $funnel_id );
219 254
220 255 wp_send_json_success();
221 256 }
222 257
@@ -221,9 +256,9 @@
221 256 }
222 257
223 258 ( $qty > 0 ) ? WC()->cart->set_quantity( $id, $qty ) : WC()->cart->remove_cart_item( $id );
224 259
225 - $this->make_changes_after_cart_item_edit( $funnel_id );
260 + self::make_changes_after_cart_item_edit( $funnel_id );
226 261
227 262 wp_send_json_success();
228 263 }
229 264
@@ -232,9 +267,9 @@
232 267 *
233 268 * @since 1.2.5
234 269 * @param int $funnel_id funnel step id.
235 270 */
236 - private function make_changes_after_cart_item_edit( $funnel_id ) {
271 + public static function make_changes_after_cart_item_edit( $funnel_id ) {
237 272 if ( empty( $funnel_id ) ) {
238 273 return;
239 274 }
240 275
@@ -244,16 +279,27 @@
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 - $this->apply_discounted_prices( wc()->cart, $funnel_id );
250 - $this->apply_discounted_prices( wc()->cart, $funnel_id, 'bumps' );
292 + self::apply_discounted_prices( wc()->cart, $funnel_id );
293 + self::apply_discounted_prices( wc()->cart, $funnel_id, 'bumps' );
251 294
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,10 +314,20 @@
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>
273 - <td><?php echo wc_price( WC()->cart->get_shipping_total() ); ?></td>
318 + <th><?php echo esc_html__( 'Shipping', 'sellkit' ); ?></th>
319 + <td>
320 + <?php
321 + $price = WC()->cart->get_shipping_total();
322 +
323 + if ( WC()->cart->display_prices_including_tax() ) {
324 + $price = WC()->cart->get_shipping_total() + WC()->cart->shipping_tax_total;
325 + }
326 +
327 + echo wp_kses_post( wc_price( $price ) );
328 + ?>
329 + </td>
274 330 </tr>
275 331 <?php
276 332 }
277 333
@@ -299,14 +355,19 @@
299 355 $img_html = apply_filters( 'sellkit/includes/elementor/modules/checkout/product-image', $img_html, $product, $cart_item_key );
300 356
301 357 ob_start();
302 358
303 - $extra_class = '';
359 + $extra_class = $product->get_type();
304 360 if ( $cart_items[ $cart_item_key ] === end( $cart_items ) ) {
305 - $extra_class = ' last-cart-item';
361 + $extra_class .= ' last-cart-item';
306 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 + }
307 368 ?>
308 - <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 ); ?>">
309 370
310 371 <td class="product-name sellkit-one-page-checkout-product-name">
311 372 <?php
312 373 echo $img_html;
@@ -321,9 +382,9 @@
321 382 <div class="name-price" style="<?php echo esc_attr( $fix_style ); ?>">
322 383 <span style="display:inline-block">
323 384 <?php echo wp_kses_post( apply_filters( 'woocommerce_cart_item_name', $product->get_name(), $cart_item, $cart_item_key ) ) . '&nbsp;'; ?>
324 385 </span>
325 - <span class="sellkit-checkout-variations">
386 + <span class="sellkit-checkout-variations" id="sellkit-checkout-variations">
326 387 <?php echo wc_get_formatted_cart_item_data( $cart_item ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
327 388 </span>
328 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'] ); ?>
329 390 <?php do_action( 'sellkit-checkout-editor-mode-quantity', $cart_item['quantity'] ); ?>
@@ -337,11 +398,58 @@
337 398 $cart_item = ob_get_clean();
338 399
339 400 echo apply_filters( 'sellkit-checkout-cart-item' , $cart_item );
340 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 + }
341 407 }
342 408
343 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 + /**
344 452 * Modify fields after pressing place order button.
345 453 * we hooked here because some removed fields still get checked and have not removed by previous hooks.
346 454 *
347 455 * @SuppressWarnings(PHPMD.NPathComplexity)
@@ -742,20 +850,97 @@
742 850 * Modify cart contents using bundled products.
743 851 *
744 852 * @since 1.1.0
745 853 * @return void
854 + * @SuppressWarnings(PHPMD.CyclomaticComplexity)
746 855 */
747 856 public function sellkit_checkout_modify_cart_by_bundle_products() {
748 - $qty = filter_input( INPUT_POST, 'qty', FILTER_SANITIZE_NUMBER_INT );
749 - $id = filter_input( INPUT_POST, 'id', FILTER_SANITIZE_NUMBER_INT );
750 - $type = filter_input( INPUT_POST, 'type', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
857 + $qty = filter_input( INPUT_POST, 'qty', FILTER_SANITIZE_NUMBER_INT );
858 + $id = filter_input( INPUT_POST, 'id', FILTER_SANITIZE_NUMBER_INT );
859 + $type = filter_input( INPUT_POST, 'type', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
860 + $checkout_id = filter_input( INPUT_POST, 'checkout_id', FILTER_SANITIZE_NUMBER_INT );
861 + $modify = filter_input( INPUT_POST, 'modify', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
862 + $key = filter_input( INPUT_POST, 'key', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
751 863
752 - WC()->cart->empty_cart();
864 + if ( ! empty( $key ) ) {
865 + foreach ( WC()->cart->get_cart() as $item_key => $item ) {
866 + if ( $item_key === $key ) {
867 + WC()->cart->set_quantity( $item_key, $qty, true );
868 + break; // stop the loop.
869 + }
870 + }
753 871
872 + wp_send_json_success();
873 + }
874 +
754 875 if ( 'radio' === $type ) {
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 ] : [];
899 + $real_quantity = ( empty( $selected['quantity'] ) ) ? 1 : $selected['quantity'];
900 +
901 + if ( (int) $real_quantity !== (int) $qty ) {
902 + wp_send_json_error( esc_html__( 'Oh do not be tricky.', 'sellkit' ) );
903 + }
904 + }
905 +
906 + if ( 'radio' === $type ) {
907 + WC()->cart->empty_cart();
755 908 WC()->cart->add_to_cart( $id, $qty );
756 909 }
757 910
911 + if ( 'checkbox' === $type && 'add' === $modify ) {
912 + WC()->cart->add_to_cart( $id, $qty );
913 + }
914 +
915 + if ( 'checkbox' === $type && 'remove' === $modify ) {
916 + $post_type = get_post_type( $id );
917 +
918 + // Maybe product is a variation.
919 + if ( 'product_variation' === $post_type ) {
920 + foreach ( WC()->cart->get_cart() as $item_key => $item ) {
921 + if ( $item['variation_id'] === (int) $id ) {
922 + WC()->cart->remove_cart_item( $item_key ); // we remove it.
923 + break; // stop the loop.
924 + }
925 + }
926 +
927 + self::make_changes_after_cart_item_edit( $checkout_id );
928 +
929 + wp_send_json_success();
930 + }
931 +
932 + // Default product.
933 + $product_cart_id = WC()->cart->generate_cart_id( $id );
934 + $cart_item_key = WC()->cart->find_product_in_cart( $product_cart_id );
935 +
936 + if ( $cart_item_key ) {
937 + WC()->cart->remove_cart_item( $cart_item_key );
938 + }
939 + }
940 +
941 + self::make_changes_after_cart_item_edit( $checkout_id );
942 +
758 943 wp_send_json_success();
759 944 }
760 945
761 946 /**
@@ -764,11 +949,13 @@
764 949 *
765 950 * @param object $cart cart object.
766 951 * @param int $checkout_id_ajax checkout id.
767 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.
768 954 * @since 1.2.8
955 + * @SuppressWarnings(PHPMD.CyclomaticComplexity)
769 956 */
770 - public 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 = [] ) {
771 958 $checkout_id = get_queried_object_id();
772 959
773 960 if ( wp_doing_ajax() ) {
774 961 $checkout_id = $checkout_id_ajax;
@@ -785,9 +972,13 @@
785 972 }
786 973
787 974 $products = [];
788 975
789 - 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 + ) {
790 981 $products = $step_data['data']['products']['list'];
791 982 }
792 983
793 984 if ( 'bumps' === $source && ! empty( $step_data['bump'] ) ) {
@@ -797,13 +988,22 @@
797 988 if ( ! array_key_exists( 'products', $bump['data'] ) ) {
798 989 continue;
799 990 }
800 991
801 - $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 +
802 998 $products[ $key ] = $bump['data']['products']['list'][ $key ];
803 999 }
804 1000 }
805 1001
1002 + if ( 'upsell' === $source ) {
1003 + $products = $upsell_data['data']['products']['list'];
1004 + }
1005 +
806 1006 if ( empty( $products ) ) {
807 1007 return;
808 1008 }
809 1009
@@ -818,12 +1018,16 @@
818 1018 }
819 1019
820 1020 foreach ( $products as $product_id => $product_details ) {
821 1021 if ( $item_id === $product_id ) {
822 - $discount_type = $product_details['discountType'];
823 - $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'] : '';
824 1024
825 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 + }
826 1030 }
827 1031 }
828 1032
829 1033 if ( false !== $price ) {
@@ -829,9 +1033,9 @@
829 1033 if ( false !== $price ) {
830 1034 $details['data']->set_price( $price );
831 1035 }
832 1036
833 - $final_price += $details['data']->get_price( $price ) * $details['quantity'];
1037 + $final_price += $details['data']->get_price() * $details['quantity'];
834 1038 }
835 1039
836 1040 self::modify_products_price_before_woo_apply_discounts( $final_price );
837 1041 }
@@ -888,10 +1092,10 @@
888 1092 if ( ! array_key_exists( 'sellkit_current_page_id', $clear ) ) {
889 1093 return;
890 1094 }
891 1095
892 - $this->apply_discounted_prices( wc()->cart, $clear['sellkit_current_page_id'] );
893 - $this->apply_discounted_prices( wc()->cart, $clear['sellkit_current_page_id'], 'bumps' );
1096 + self::apply_discounted_prices( wc()->cart, $clear['sellkit_current_page_id'] );
1097 + self::apply_discounted_prices( wc()->cart, $clear['sellkit_current_page_id'], 'bumps' );
894 1098 }
895 1099
896 1100 /**
897 1101 * Template replacement during ajax call.
@@ -912,8 +1116,9 @@
912 1116 'payment.php',
913 1117 'form-checkout.php',
914 1118 'cart-item-data.php',
915 1119 'cart-shipping.php',
1120 + 'terms.php',
916 1121 ];
917 1122 $template = str_replace( 'checkout/', '', $template_name );
918 1123 $template = str_replace( 'cart/', '', $template );
919 1124
@@ -963,12 +1168,13 @@
963 1168 /**
964 1169 * Recalculate checkout and cart prices.
965 1170 *
966 1171 * @since 1.2.8
1172 + * @SuppressWarnings(PHPMD.CyclomaticComplexity)
967 1173 */
968 1174 public function before_cart_calculate() {
969 1175 // More than twice isn't necessary. this is called multiple times by WooCommerce.
970 - if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 ) {
1176 + if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 && wp_doing_ajax() ) {
971 1177 return;
972 1178 }
973 1179
974 1180 // Gather checkout id.
@@ -986,11 +1192,13 @@
986 1192 // Catch id after pressing place order button. none of above methods worked.
987 1193 if ( empty( $checkout_id ) ) {
988 1194 $data = filter_input( INPUT_POST, 'post_data', FILTER_DEFAULT );
989 1195
990 - parse_str( $data, $data );
1196 + if ( ! empty( $data ) ) {
1197 + parse_str( $data, $data );
1198 + }
991 1199
992 - if ( array_key_exists( 'sellkit_current_page_id', $data ) ) {
1200 + if ( is_array( $data ) && array_key_exists( 'sellkit_current_page_id', $data ) ) {
993 1201 $checkout_id = $data['sellkit_current_page_id'];
994 1202 }
995 1203 }
996 1204 }
@@ -1002,11 +1210,46 @@
1002 1210
1003 1211 $checkout_id = (int) $checkout_id;
1004 1212
1005 1213 // Apply funnel prices.
1006 - $this->apply_discounted_prices( wc()->cart, $checkout_id );
1007 - $this->apply_discounted_prices( wc()->cart, $checkout_id, 'bumps' );
1214 + self::apply_discounted_prices( wc()->cart, $checkout_id );
1215 + self::apply_discounted_prices( wc()->cart, $checkout_id, 'bumps' );
1008 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 +
1009 1252 // We should re apply coupons after each price changes. to make sure everything is correct.
1010 1253 $optimization_data = ! empty( $funnel_data['data']['optimization'] ) ? $funnel_data['data']['optimization'] : '';
1011 1254
1012 1255 if ( Checkout::apply_coupon_validation( $optimization_data ) ) {
@@ -1012,11 +1255,328 @@
1012 1255 if ( Checkout::apply_coupon_validation( $optimization_data ) ) {
1013 1256 WC()->cart->remove_coupons();
1014 1257
1015 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 + }
1016 1262 wc()->cart->add_discount( get_the_title( $auto_apply_coupon['value'] ) );
1017 1263 }
1018 1264
1019 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 + }
1020 1580 }
1021 1581 }
1022 1582 }