PluginProbe
SellKit – Funnel builder and checkout optimizer for WooCommerce to sell more, faster / trunk
SellKit – Funnel builder and checkout optimizer for WooCommerce to sell more, faster vtrunk
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 1.8.1 All 42 releases
sellkit / includes / elementor / modules / checkout / classes / local-hooks.php

local-hooks.php in SellKit – Funnel builder and checkout optimizer for WooCommerce to sell more, faster trunk, at includes/elementor/modules/checkout/classes/local-hooks.php

1,304 lines 38.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Sellkit\Elementor\Modules\Checkout\Classes;
4
5 defined( 'ABSPATH' ) || exit;
6
7 use Sellkit\Elementor\Modules\Checkout\Classes\{ Helper, Global_Hooks };
8 use Elementor\Plugin as Elementor;
9 use Sellkit_Funnel;
10 use Sellkit\Global_Checkout\Checkout;
11
12 /**
13 * Local hooks.
14 * Applies before widget.
15 *
16 * @SuppressWarnings(PHPMD.UnusedFormalParameter)
17 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
18 * @SuppressWarnings(PHPMD.TooManyMethods)
19 * @SuppressWarnings(PHPMD.NPathComplexity)
20 * @SuppressWarnings(PHPMD.CyclomaticComplexity)
21 * @since 1.1.0
22 */
23 class Local_Hooks {
24 /**
25 * Checkout widget settings.
26 *
27 * @since 1.8.6
28 * @var array
29 */
30 public $settings;
31
32 /**
33 * Checkout widget.
34 *
35 * @since 1.8.6
36 * @var object
37 */
38 public $widget;
39
40 /** Array of added products to cart.
41 *
42 * @since 1.7.9
43 * @var array
44 */
45 public static $in_cart = [];
46
47 /**
48 * Checks if bumps applied.
49 *
50 * @since 1.8.1
51 * @var bool
52 */
53 public static $is_bumps_applied = false;
54
55 /**
56 * Create instance of class without construct.
57 *
58 * @since 1.1.0
59 * @return object
60 */
61 public static function instance() {
62 $class = new \ReflectionClass( __CLASS__ );
63 return $class->newInstanceWithoutConstructor();
64 }
65
66 /**
67 * Class construct.
68 *
69 * @param array $settings widget settings.
70 * @param array $widget widget object.
71 * @since 1.1.0
72 */
73 public function __construct( $settings, $widget ) {
74 $this->settings = $settings;
75 $this->widget = $widget;
76 $this->actions();
77 }
78
79 /**
80 * Actions.
81 * required actions to apply changes to woocommerce checkout shortcode.
82 *
83 * @return void
84 * @since 1.1.0
85 */
86 private function actions() {
87 // sellkit wrapper for checkout widget.
88 add_action( 'woocommerce_before_checkout_form', [ $this, 'open_multistep_wrap' ] );
89 add_action( 'woocommerce_after_checkout_form', [ $this, 'close_multistep_wrap' ], 999 );
90
91 // Add express section to widget.
92 $this->add_express_checkout();
93
94 // Replace Woocommerce templates that require huge changes.
95 add_filter( 'woocommerce_locate_template', [ $this, 'template_replace' ], 1, 3 );
96
97 // Remove coupon from it's default location. will add custom coupon form based on design at desired location.
98 remove_action( 'woocommerce_before_checkout_form', 'woocommerce_checkout_coupon_form', 10, 1 );
99
100 // Filter all woocommerce field right before printing them in our widget.
101 add_filter( 'woocommerce_checkout_fields', [ $this, 'woocommerce_checkout_fields' ] );
102
103 // Hide shipping fields section title.
104 add_filter( 'sellkit-checkout-disable-shipping-fields-title', [ $this, 'check_to_disable_shipping_title' ] );
105
106 // Customize shipping fields based on user desire.
107 add_action( 'sellkit_checkout_shipping_fields', [ $this, 'sellkit_checkout_shipping_field' ], 10, 2 );
108
109 // Customize billing fields based on user desire.
110 add_action( 'sellkit_checkout_billing_fields', [ $this, 'sellkit_checkout_billing_field' ], 10, 2 );
111
112 // Rename Shipping text to Shipping Method.
113 add_filter( 'woocommerce_shipping_package_name', [ $this, 'rename_shipping_text' ] );
114
115 // Remove login form from default location.
116 remove_action( 'woocommerce_before_checkout_form', 'woocommerce_checkout_login_form', 10 );
117
118 // Append widget settings to checkout form 2 hidden fields form_id post_id.
119 add_action( 'woocommerce_checkout_before_customer_details', [ $this, 'widget_settings' ] );
120
121 // Apply custom messages.
122 $this->custom_messages();
123
124 // Enable or Disable Sections based on user selection.
125 add_action( 'woocommerce_before_checkout_form', function() {
126 self::enable_disable_sections( $this->settings, [] );
127 } );
128
129 // Inject required scripts for google address autocomplete.
130 add_action( 'sellkit-after-checkout-content', [ $this, 'inject_google_autocomplete' ] );
131
132 // Order bump html.
133 add_action( 'woocommerce_before_checkout_form', [ $this, 'order_bump' ] );
134
135 // Bundle products.
136 $this->bundled_products();
137
138 // Trigger for upsell steps popup.
139 add_action( 'sellkit_checkout_required_hidden_fields', [ $this, 'add_trigger_field_for_upsell_steps' ] );
140
141 // Add upsell templates at the end of page.
142 add_action( 'wp_footer', [ $this, 'sellkit_funnel_display_upsell_as_popup' ] );
143
144 // Add order notes to checkout form to desired location.
145 add_action( 'sellkit_checkout_after_term_and_condition', [ $this, 'order_notes' ] );
146 }
147
148 /**
149 * Opens a wrapper around widget checkout form.
150 *
151 * @return void
152 * @since 1.1.0
153 */
154 public function open_multistep_wrap() {
155 ?>
156 <div id="sellkit-checkout-widget-id" class="sellkit-multistep-checkout-wrap sellkit-mobile-design-checkout-widget">
157 <?php
158 }
159
160 /**
161 * Close opened wrapper around checkout form.
162 *
163 * @return void
164 * @since 1.1.0
165 */
166 public function close_multistep_wrap() {
167 echo '</div>';
168 }
169
170 /**
171 * Inject express checkout section into the checkout form based on layout.
172 *
173 * @return void
174 * @since 1.1.0
175 */
176 private function add_express_checkout() {
177 if ( ! sellkit()->has_pro ) {
178 return;
179 }
180
181 $show_express = true;
182
183 // Disable express checkout.
184 if ( 'yes' !== $this->settings['show_express_checkout'] ) {
185 $show_express = false;
186 }
187
188 $gateways = [
189 'Paypal_For_Woocommerce',
190 'Woo_Payment_Gateway',
191 'Stripe_For_Woocommerce',
192 'Klarma_Checkout_Woocommerce',
193 'Amazon_Pay_Woocommerce',
194 'Stripe_Woocommerce_Official',
195 ];
196
197 foreach ( $gateways as $gateway ) {
198 $class = 'Sellkit\Elementor\Modules\Checkout\Integrations\\' . $gateway;
199 $class = new $class();
200 $class->run();
201 }
202
203 if ( false === $show_express ) {
204 return;
205 }
206
207 if ( 'one-page' === $this->settings['layout-type'] ) {
208 add_action( 'sellkit_checkout_one_page_express_methods', [ $this, 'express_checkout_html' ] );
209 return;
210 }
211
212 add_action( 'sellkit-checkout-step-a-begins', [ $this, 'express_checkout_html' ], 20 );
213 }
214
215 /**
216 * Replace Woocommerce templates that needs to be changed, locally.
217 * this template_replace does not affect default checkout page.
218 *
219 * @param string $template template name.
220 * @param string $template_name name.
221 * @param string $template_path path of template.
222 * @return string
223 * @since 1.1.0
224 */
225 public function template_replace( $template, $template_name, $template_path ) {
226 $basename = basename( $template );
227 $our_path = sellkit()->plugin_dir() . 'includes/elementor/modules/checkout/';
228
229 switch ( $basename ) {
230 case 'form-checkout.php':
231 $template = $our_path . 'templates/form-checkout.php';
232 break;
233 case 'form-login.php':
234 $template = $our_path . 'templates/form-login.php';
235 break;
236 case 'form-shipping.php':
237 $template = $our_path . 'templates/form-shipping.php';
238 break;
239 case 'form-billing.php':
240 $template = $our_path . 'templates/form-billing.php';
241 break;
242 case 'payment.php':
243 $template = $our_path . 'templates/payment.php';
244 break;
245 case 'payment-method.php':
246 $template = $our_path . 'templates/payment-method.php';
247 break;
248 case 'review-order.php':
249 $template = $our_path . 'templates/review-order.php';
250 break;
251 case 'cart-shipping.php':
252 $template = $our_path . 'templates/cart-shipping.php';
253 break;
254 case 'cart-item-data.php':
255 $template = $our_path . 'templates/cart-item-data.php';
256 break;
257 case 'terms.php':
258 $template = $our_path . 'templates/terms.php';
259 break;
260 }
261
262 return $template;
263 }
264
265 /**
266 * Customize shipping fields parameters based on user needs through widget options.
267 *
268 * @param array $default_fields woocommerce fields.
269 * @return array
270 * @since 1.1.0
271 */
272 public function customize_shipping_field( $default_fields ) {
273 $widget_shipping_fields = $this->settings['shipping_list'];
274 $widget_field_slug = Helper::instance()->get_user_defined_fields_slug( $widget_shipping_fields, 'shipping_list_field' );
275
276 // Unset default fields.
277 foreach ( $default_fields as $key => $field ) {
278 if ( ! in_array( $key, $widget_field_slug, true ) ) {
279 unset( $default_fields[ $key ] );
280 }
281 }
282
283 return $default_fields;
284 }
285
286 /**
287 * Check to disable shipping fields section title.
288 *
289 * @since 1.2.1
290 */
291 public function check_to_disable_shipping_title() {
292 $widget_shipping_fields = $this->settings['shipping_list'];
293
294 if ( 0 === count( $widget_shipping_fields ) ) {
295 return false;
296 }
297
298 return true;
299 }
300
301 /**
302 * Print shipping field in frontend using our customized fields.
303 *
304 * @param array $fields : shipping fields.
305 * @param object $checkout : checkout object.
306 * @since 1.1.0
307 */
308 public function sellkit_checkout_shipping_field( $fields, $checkout ) {
309 $default_text = [
310 'shipping_first_name',
311 'shipping_last_name',
312 'shipping_company',
313 'shipping_address_1',
314 'shipping_address_2',
315 'shipping_postcode',
316 'shipping_city',
317 ];
318
319 $default_select = [
320 'shipping_country',
321 'shipping_state',
322 ];
323
324 foreach ( $fields as $key => $details ) {
325 if ( in_array( $key, $default_text, true ) ) {
326 $fields[ $key ]['type'] = 'text';
327 }
328
329 if ( in_array( $key, $default_select, true ) ) {
330 $fields[ $key ]['type'] = 'select';
331 }
332 }
333
334 $widget_shipping_fields = $this->settings['shipping_list'];
335 $default_fields = Helper::instance()->assign_settings_per_field( $fields, $widget_shipping_fields, 'shipping', $this->settings );
336
337 $priority = array_column( $default_fields, 'priority' );
338 array_multisort( $priority, SORT_ASC, $default_fields );
339
340 foreach ( $default_fields as $key => $details ) {
341 if ( ! array_key_exists( 'type', $details ) ) {
342 continue;
343 }
344
345 $class = $details['type'];
346 $class = '\Sellkit\Elementor\Modules\Checkout\Fields\\' . ucfirst( $class );
347 $class = new $class();
348 $field = '';
349
350 $class->final_html_structure( $field, $details, $key, $checkout->get_value( $key ) );
351 }
352 }
353
354 /**
355 * Customize billing fields parameters based on user needs through widget options.
356 *
357 * @param array $default_fields woocommerce fields.
358 * @return array
359 * @since 1.1.0
360 */
361 public function customize_billing_field( $default_fields ) {
362 $widget_billing_fields = $this->settings['billing_list'];
363 $widget_field_slug = Helper::instance()->get_user_defined_fields_slug( $widget_billing_fields, 'billing_list_field' );
364 $core_billing_fields = array_keys( Helper::instance()->billing_fields() );
365
366 // Unset default fields.
367 foreach ( $default_fields as $key => $field ) {
368 if ( in_array( $key, $core_billing_fields, true ) && ! in_array( $key, $widget_field_slug, true ) && 'billing_email' !== $key ) {
369 unset( $default_fields[ $key ] );
370 }
371 }
372
373 return $default_fields;
374 }
375
376 /**
377 * Print billing fields in frontend using our customized fields.
378 *
379 * @param array $fields : billing fields.
380 * @param object $checkout : checkout object.
381 * @since 1.1.0
382 */
383 public function sellkit_checkout_billing_field( $fields, $checkout ) {
384 $default_text = [
385 'billing_first_name',
386 'billing_last_name',
387 'billing_company',
388 'billing_address_1',
389 'billing_address_2',
390 'billing_postcode',
391 'billing_city',
392 ];
393
394 $default_select = [
395 'billing_country',
396 'billing_state',
397 ];
398
399 $default_tel = [
400 'billing_phone',
401 ];
402
403 foreach ( $fields as $key => $details ) {
404 if ( in_array( $key, $default_text, true ) ) {
405 $fields[ $key ]['type'] = 'text';
406 }
407
408 if ( in_array( $key, $default_select, true ) ) {
409 $fields[ $key ]['type'] = 'select';
410 }
411
412 if ( in_array( $key, $default_tel, true ) ) {
413 $fields[ $key ]['type'] = 'tel';
414 }
415 }
416
417 $widget_billing_fields = $this->settings['billing_list'];
418 $default_fields = Helper::instance()->assign_settings_per_field( $fields, $widget_billing_fields, 'billing', $this->settings );
419
420 $priority = array_column( $default_fields, 'priority' );
421 array_multisort( $priority, SORT_ASC, $default_fields );
422
423 foreach ( $default_fields as $key => $details ) {
424 // Never shot field that it's type is not defined.
425 if ( ! array_key_exists( 'type', $details ) ) {
426 continue;
427 }
428
429 // Billing email is already defined at top of page.
430 if ( 'billing_email' === $key ) {
431 continue;
432 }
433
434 $class = $details['type'];
435 $class = '\Sellkit\Elementor\Modules\Checkout\Fields\\' . ucfirst( $class );
436 $class = new $class();
437 $field = '';
438
439 $field = $class->final_html_structure( $field, $details, $key, $checkout->get_value( $key ) );
440 }
441 }
442
443 /**
444 * Hook to fields before shortcode.
445 *
446 * @param array $default_fields woocommerce fields.
447 * @return array
448 * @since 1.1.0
449 */
450 public function woocommerce_checkout_fields( $default_fields ) {
451 $widget_billing_fields = $this->settings['billing_list'];
452 $widget_shipping_fields = $this->settings['shipping_list'];
453
454 // Unset fields.
455 $default_shipping_fields = $default_fields['shipping'];
456 $default_fields['shipping'] = $this->customize_shipping_field( $default_shipping_fields );
457
458 // Unset fields.
459 $default_billing_fields = $default_fields['billing'];
460 $default_fields['billing'] = $this->customize_billing_field( $default_billing_fields );
461
462 foreach ( $widget_billing_fields as $data ) {
463 $slug = $data['billing_list_field'];
464
465 if ( 'yes' !== $data['billing_list_required'] && array_key_exists( $slug, $default_fields['billing'] ) ) {
466 $default_fields['billing'][ $slug ]['required'] = false;
467 unset( $default_fields['billing'][ $slug ]['required'] );
468 }
469 }
470
471 foreach ( $widget_shipping_fields as $data ) {
472 $slug = isset( $data['shipping_list_field'] ) ? $data['shipping_list_field'] : '';
473
474 if ( 'yes' !== $data['shipping_list_required'] && array_key_exists( $slug, $default_fields['shipping'] ) ) {
475 $default_fields['shipping'][ $slug ]['required'] = false;
476 unset( $default_fields['shipping'][ $slug ]['required'] );
477 }
478 }
479
480 return $default_fields;
481 }
482
483 /**
484 * Rename Shipping text
485 *
486 * @param string $name title of shipping methods.
487 * @return string
488 * @since 1.1.0
489 */
490 public function rename_shipping_text( $name ) {
491 return '<h4 id="shipping_header_title" class="shipping-method-header heading">' . __( 'Shipping Methods', 'sellkit' ) . '</h4>';
492 }
493
494 /**
495 * Adds widget settings as hidden input to checkout form, to be used in various places.
496 *
497 * @return void
498 * @since 1.1.0
499 */
500 public function widget_settings() {
501 ?>
502 <input type="hidden" name="post_id" value="<?php echo esc_attr( self::get_current_post_id() ); ?>" />
503 <input type="hidden" name="form_id" value="<?php echo esc_attr( $this->widget->get_id() ); ?>" />
504 <?php
505 }
506
507 /**
508 * Get post ID based on document.
509 *
510 * @since 1.1.0
511 */
512 public static function get_current_post_id() {
513 if ( isset( Elementor::$instance->documents ) && ! empty( Elementor::$instance->documents->get_current() ) ) {
514 return Elementor::$instance->documents->get_current()->get_main_id();
515 }
516
517 return get_the_ID();
518 }
519
520 /**
521 * Express checkout html.
522 *
523 * @return void
524 * @since 1.1.0
525 */
526 public function express_checkout_html() {
527 ob_start();
528 ?>
529 <section class="sellkit-checkout-widget-express-checkout sellkit-checkout-express-checkout-step-1">
530 <fieldset class="express-box sellkit-checkout-widget-divider">
531 <legend class="sellkit-express-checkout-legend heading">
532 <?php echo esc_html__( 'Express Checkout', 'sellkit' ); ?>
533 </legend>
534 <div class="express-methods">
535 <?php do_action( 'sellkit-checkout-widget-express-methods' ); ?>
536 </div>
537 </fieldset>
538 </section>
539 <section class="sellkit-checkout-widget-express-checkout sellkit-checkout-express-checkout-step-2">
540 <fieldset id="sellkit-checkout-or-divider" class="divider-box sellkit-checkout-widget-divider">
541 <legend class="heading"><?php echo esc_html__( 'OR', 'sellkit' ); ?></legend>
542 </fieldset>
543 </section>
544 <?php
545 $express = ob_get_clean();
546 $express = apply_filters( 'sellkit-checkout-widget-express-checkout', $express );
547 echo wp_kses_post( $express );
548 }
549
550 /**
551 * Apply custom Messages.
552 *
553 * @return void
554 * @since 1.1.0
555 */
556 private function custom_messages() {
557 add_filter( 'sellkit_core/widgets/checkout/custom_message/already_have_account_text', function() {
558 return $this->settings['already_have_account_text'];
559 } );
560
561 add_filter( 'sellkit_core/widgets/checkout/custom_message/login_toggle_text', function() {
562 return $this->settings['login_toggle_text'];
563 } );
564
565 add_action( 'sellkit_core/widgets/checkout/custom_message/create_website_account', function() {
566 $site = get_bloginfo( 'name' );
567 $text = $this->settings['create_website_account'];
568
569 echo wp_kses_post( str_replace( '{{website}}', $site, $text ) );
570 } );
571
572 add_filter( 'sellkit_core/widgets/checkout/custom_message/secure_transaction_text', function() {
573 return $this->settings['secure_transaction_text'];
574 } );
575
576 add_filter( 'sellkit_core/widgets/checkout/custom_message/select_address_text', function() {
577 return $this->settings['select_address_text'];
578 } );
579
580 add_filter( 'sellkit-checkout-place-order-btn-text', function( $default ) {
581 if ( ! empty( $this->settings['place_order_btn_txt'] ) ) {
582 return $this->settings['place_order_btn_txt'];
583 }
584
585 return $default;
586 }, 10 );
587 }
588
589 /**
590 * Enable or disable some of checkout sections.
591 *
592 * @param array $settings widget settings.
593 * @param array $posted_data checkout form data.
594 * @return void
595 * @since 1.1.0
596 */
597 public static function enable_disable_sections( $settings, $posted_data = [] ) {
598 if ( Elementor::$instance->editor->is_edit_mode() || array_key_exists( 'sellkit-elementor-editor-mode', $posted_data ) ) {
599
600 add_action( 'sellkit-checkout-after-coupon-form-ajax', [ Helper::instance(), 'editor_mode_extra_js' ] );
601
602 // Let coupon form to be viewable in editor mode.
603 add_action( 'sellkit-checkout-widget-custom-coupon-form', function() use ( $settings ) {
604 Global_Hooks::instance()->coupon_form( $settings );
605 } );
606
607 // Improve UI issue in the editor for the order summary input.
608 add_filter( 'woocommerce_cart_item_class', function( $default ) use ( $settings, $posted_data ) {
609 $default = str_replace( 'product-title-one-row', '', $default );
610
611 if (
612 ! array_key_exists( 'show_cart_edit', $settings ) ||
613 array_key_exists( 'bundle-products-force', $posted_data ) ||
614 empty( $settings['show_cart_edit'] )
615 ) {
616 $default .= ' product-title-one-row';
617 }
618
619 return $default;
620 } );
621
622 return;
623 }
624
625 if ( wp_doing_ajax() ) {
626 // Hide order item images.
627 add_filter( 'sellkit/includes/elementor/modules/checkout/product-image', function( $img_html ) use ( $settings ) {
628 if ( ! array_key_exists( 'order_summary_show_image', $settings ) ) {
629 return $img_html;
630 }
631
632 return '';
633 } );
634
635 add_filter( 'sellkit-checkout-cart-item', function( $cart_item ) use ( $settings ) {
636 if ( ! array_key_exists( 'show_cart_items', $settings ) ) {
637 return $cart_item;
638 }
639
640 return '';
641 } );
642
643 // Disable order item quantity input.
644 if ( array_key_exists( 'show_cart_edit', $settings ) || array_key_exists( 'bundle-products-force', $posted_data ) ) {
645 add_filter( 'sellkit-checkout-widget-disable-quantity', [ Helper::instance(), 'checkout_order_hidden_quantity' ], 10, 2 );
646
647 add_filter( 'woocommerce_cart_item_class', function( $default ) {
648 return $default . ' product-title-one-row';
649 } );
650 }
651
652 add_filter( 'woocommerce_shipping_package_name', function() {
653 return '<h4 id="shipping_header_title" class="shipping-method-header heading">' . esc_html__( 'Shipping Methods', 'sellkit' ) . '</h4>';
654 } );
655
656 add_filter( 'woocommerce_cart_ready_to_calc_shipping', function() use ( $settings ) {
657 if ( ! array_key_exists( 'show_shipping_method', $settings ) ) {
658 return true;
659 }
660
661 return false;
662 } );
663
664 add_action( 'sellkit-checkout-widget-custom-coupon-form', function() use ( $settings ) {
665 Global_Hooks::instance()->coupon_form( $settings );
666 } );
667
668 return;
669 }
670
671 // Normal mode when page is loading.
672 add_filter( 'woocommerce_cart_ready_to_calc_shipping', function() use ( $settings ) {
673 if ( 'yes' === $settings['show_shipping_method'] ) {
674 return true;
675 }
676
677 return false;
678 }, 10 );
679
680 add_filter( 'sellkit/includes/elementor/modules/checkout/product-image', function( $img_html ) use ( $settings ) {
681 if ( 'yes' === $settings['order_summary_show_image'] ) {
682 return $img_html;
683 }
684
685 return '';
686 } );
687
688 add_filter( 'sellkit-checkout-cart-item', function( $cart_item ) use ( $settings ) {
689 if ( 'yes' === $settings['show_cart_items'] ) {
690 return $cart_item;
691 }
692
693 return '';
694 }, 10 );
695
696 // Disable order item quantity input.
697 if ( array_key_exists( 'show_cart_edit', $settings ) ) {
698 add_filter( 'sellkit-checkout-widget-disable-quantity', [ Helper::instance(), 'checkout_order_hidden_quantity' ], 10, 2 );
699
700 add_filter( 'sellkit/includes/elementor/modules/checkout/quanity/class', function( $classes ) {
701 return $classes .= ' sellkit-checkout-widget-d-block';
702 } );
703 }
704
705 if ( array_key_exists( 'show_cart_edit', $settings ) || array_key_exists( 'bundle-products-force', $posted_data ) ) {
706 add_filter( 'woocommerce_cart_item_class', function( $default ) {
707 return $default . ' product-title-one-row';
708 } );
709 }
710
711 add_filter( 'sellkit-checkout-place-order-btn-text', function( $place_order_btn ) {
712 if ( ! empty( $settings['place_order_btn_txt'] ) ) {
713 return $settings['place_order_btn_txt'];
714 }
715
716 return $place_order_btn;
717 }, 10 );
718
719 add_action( 'sellkit-checkout-widget-custom-coupon-form', function() use ( $settings ) {
720 if ( 'yes' !== $settings['show_coupon_field'] ) {
721 return;
722 }
723
724 self::coupon_form( $settings );
725 } );
726 }
727
728 /**
729 * Inject google autocomplete address script if enabled for shipping/billing section.
730 *
731 * @return void
732 * @since 1.1.0
733 */
734 public function inject_google_autocomplete() {
735 if ( ! sellkit()->has_pro ) {
736 return;
737 }
738
739 \Sellkit_Elementor_Checkout_Pro_Module::checkout_google_autocomplete_address( $this->settings );
740 }
741
742 /**
743 * Place custom coupon form in checkout order-review based design.
744 * ! we have used same form in Global_Hooks. but this one for first load of page.
745 *
746 * @param array $settings widget settings.
747 * @see sellkit_Core\Raven\Modules\Checkout\Classes\Global_Hooks::coupon_form.
748 * @return void
749 * @since 1.1.0
750 */
751 public static function coupon_form( $settings ) {
752 echo '<tr class="coupon-form border-none"><td colspan="2">';
753 self::form( $settings );
754 echo '</td></tr>';
755 }
756
757 /**
758 * New custom coupon form HTML.
759 *
760 * @param array $settings widget settings.
761 * @return void
762 * @since 1.1.0
763 */
764 public static function form( $settings ) {
765 $class = 'sellkit-custom-coupon-form-d-none';
766 if ( ! array_key_exists( 'coupon_field_type', $settings ) || 'normal' === $settings['coupon_field_type'] ) {
767 $class = 'sellkit-normal-coupon-form';
768 }
769 ?>
770 <?php if ( array_key_exists( 'coupon_field_type', $settings ) && 'collapsible' === $settings['coupon_field_type'] ) : ?>
771 <span id="copoun_toggle" class="copoun_toggle sellkit-coupon-toggle sellkit-checkout-widget-links">
772 <?php echo esc_html__( 'Have a coupon? Click here to enter your code', 'sellkit' ); ?>
773 </span>
774 <?php $class .= ' sellkit-checkout-collapsible'; ?>
775 <?php Helper::instance()->editor_mode_extra_js(); ?>
776 <?php endif; ?>
777 <div class="sellkit-custom-coupon-form <?php echo esc_attr( $class ); ?>">
778 <p class="jx-form-row-first">
779 <input class="jx-coupon" type="text" placeholder="<?php esc_attr_e( 'Enter Promo code', 'sellkit' ); ?>" />
780 </p>
781
782 <p class="jx-form-row-last">
783 <span class="sellkit-checkout-widget-secondary-button jx-apply-coupon sellkit-apply-coupon" >
784 <?php esc_html_e( 'Apply', 'sellkit' ); ?>
785 </span>
786 </p>
787 </div>
788 <?php
789 }
790
791 /**
792 * Insert bundled product.
793 *
794 * @since 1.1.0
795 * @return void
796 */
797 public function bundled_products() {
798 global $wp_query;
799 $query = $wp_query->query_vars;
800 $option = 'default';
801
802 if ( ! array_key_exists( 'funnel_product_settings', $query ) ) {
803 return;
804 }
805
806 if ( ! empty( $query['funnel_product_settings'] ) ) {
807 $option = $query['funnel_product_settings'];
808 }
809
810 if ( 'default' === $option ) {
811 return;
812 }
813
814 add_action( 'sellkit_checkout_required_hidden_fields', function() use ( $option ) {
815 ?>
816 <input type="hidden" name="sellkit-bundle-products" value="<?php echo esc_attr( $option ); ?>" >
817 <?php
818 }, 10 );
819
820 self::bundle_product_action( $option );
821 }
822
823 /**
824 * Display bundle products.
825 *
826 * @param string $option bundle products type.
827 * @since 1.1.0
828 * return void
829 */
830 public static function bundle_product_action( $option ) {
831 add_filter( 'sellkit-checkout-widget-disable-quantity', [ Helper::instance(), 'checkout_order_hidden_quantity' ], 10, 2 );
832
833 if ( 'force-products' === $option || Elementor::$instance->editor->is_edit_mode() ) {
834 return;
835 }
836
837 $fields_type = 'radio';
838 $products = WC()->cart->get_cart();
839 $default = null;
840 $default_q = null;
841 $readonly = 'readonly';
842
843 if ( 'allow-buyers' === $option ) {
844 $fields_type = 'checkbox';
845 $readonly = '';
846 }
847
848 ob_start();
849 ?>
850 <section class="sellkit-checkout-bundled-products">
851 <div class="sellkit-checkout-bundled-inner-wrap">
852 <span class="sellkit-checkout-bundled-header heading"><?php echo esc_html__( 'Your Products', 'sellkit' ); ?></span>
853 <table class="sellkit-checkout-bundled-products-table">
854 <thead>
855 <tr class="sellkit-checkout-bundled-products-head-row">
856 <th class="sellkit-head-row-title"><?php echo esc_html__( 'Product', 'sellkit' ); ?></th>
857 <th class="sellkit-head-row-quantity"><?php echo esc_html__( 'Quantity', 'sellkit' ); ?></th>
858 <th class="sellkit-head-row-price"><?php echo esc_html__( 'Price', 'sellkit' ); ?></th>
859 </tr>
860 </thead>
861 <tbody>
862 <?php foreach ( $products as $cart_item_key => $cart_item ) : ?>
863 <?php
864 if ( in_array( $cart_item_key, self::$in_cart, true ) ) {
865 continue;
866 }
867
868 $_product = apply_filters( 'woocommerce_cart_item_product', $cart_item['data'], $cart_item, $cart_item_key );
869 $default = $_product->get_id();
870 $default_q = $cart_item['quantity'];
871 $unique_id = 'bundle-unique-id-' . $default;
872 $checked = '';
873
874 if ( 'checkbox' === $fields_type ) {
875 $checked = 'checked';
876 }
877
878 if ( 'radio' === $fields_type && array_key_last( $products ) === $cart_item_key ) {
879 $checked = 'checked';
880 }
881 ?>
882 <tr class="sellkit-checkout-bundled-products-item">
883 <td class="sellkit-checkout-bundled-title">
884 <input
885 type="<?php echo esc_attr( $fields_type ); ?>"
886 value="<?php echo esc_attr( $_product->get_id() ); ?>"
887 name="sellkit-checkout-bundle-item"
888 class="sellkit-checkout-bundle-item"
889 id="<?php echo esc_attr( $unique_id ); ?>"
890 <?php echo esc_html( $checked ); ?>
891 >
892 <label for="<?php echo esc_attr( $unique_id ); ?>">
893 <?php echo esc_html( $_product->get_name() ); ?>
894 </label>
895 </td>
896 <td class="sellkit-checkout-bundled-quantity">
897 <input type="number" <?php echo esc_attr( $readonly ); ?> min="1" value="<?php echo esc_attr( $cart_item['quantity'] ); ?>" data-id="<?php echo esc_attr( $cart_item_key ); ?>" class="sellkit-checkout-single-bundle-item-quantity" >
898 </td>
899 <td class="sellkit-checkout-bundled-price">
900 <?php echo wp_kses_post( apply_filters( 'woocommerce_cart_item_subtotal', WC()->cart->get_product_subtotal( $_product, $cart_item['quantity'] ), $cart_item, $cart_item_key ) ); ?>
901 </td>
902 <tr>
903 <?php if ( array_key_last( $products ) !== $cart_item_key ) : ?>
904 <tr>
905 <td colspan="3" class="sellkit-checkout-bundled-spacer-row">
906 <hr>
907 </td>
908 </tr>
909 <?php endif; ?>
910 <?php endforeach; ?>
911 </tbody>
912 </table>
913 </div>
914 </section>
915 <?php
916 $bundle_html = ob_get_clean();
917
918 add_action( 'sellkit-bundled-products-position', function() use ( $bundle_html ) {
919 $allowed_html = [
920 'section' => [
921 'class' => true,
922 ],
923 'div' => [
924 'class' => true,
925 ],
926 'span' => [
927 'class' => true,
928 ],
929 'table' => [
930 'class' => true,
931 ],
932 'thead' => [
933 'class' => true,
934 ],
935 'tr' => [
936 'class' => true,
937 ],
938 'th' => [
939 'class' => true,
940 ],
941 'tbody' => [
942 'class' => true,
943 ],
944 'td' => [
945 'class' => true,
946 'colspan' => true,
947 ],
948 'input' => [
949 'type' => true,
950 'value' => true,
951 'name' => true,
952 'class' => true,
953 'id' => true,
954 'checked' => true,
955 'readonly' => true,
956 'min' => true,
957 'data-id' => true,
958 ],
959 'label' => [
960 'for' => true,
961 ],
962 'hr' => [],
963 ];
964
965 echo wp_kses( $bundle_html, $allowed_html );
966 } );
967
968 if ( wp_doing_ajax() ) {
969 return;
970 }
971
972 if ( 'radio' === $fields_type && null !== $default ) {
973 WC()->cart->empty_cart();
974 WC()->cart->add_to_cart( $default, $default_q );
975 }
976
977 if ( ! WC()->cart->needs_shipping() ) {
978 wp_add_inline_script( 'sellkit-initialize-widgets', 'var sellkitCheckoutShipping = true', 'before' );
979 }
980
981 Global_Hooks::make_changes_after_cart_item_edit( get_the_id() );
982 }
983
984 /**
985 * Trigger to activate order bumb or not for this checkout.
986 *
987 * @param array $data checkout form data.
988 * @since 1.1.0
989 * @return void
990 */
991 public static function order_bump( $data = [] ) {
992 if ( 'woocommerce_before_checkout_form' === current_action() ) {
993 self::order_bump_frontend_manager();
994 return;
995 }
996 }
997
998 /**
999 * Order bump to manage frontend.
1000 *
1001 * @since 1.1.0
1002 * @return void
1003 */
1004 private static function order_bump_frontend_manager() {
1005 if ( self::$is_bumps_applied ) {
1006 return;
1007 }
1008
1009 global $wp_query;
1010 $query = $wp_query->query_vars;
1011
1012 if ( ! array_key_exists( 'bump_data', $query ) ) {
1013 return;
1014 }
1015
1016 $bumps = $query['bump_data'];
1017
1018 foreach ( $bumps as $bump ) {
1019 if ( ! array_key_exists( 'products', $bump['data'] ) ) {
1020 continue;
1021 }
1022
1023 $design = $bump['data']['design'];
1024 $products = $bump['data']['products'];
1025
1026 add_action( $design['sellkit_funnels_bump_position'], function() use ( $design, $products ) {
1027 self::bump_html( $design, $products );
1028 }, 5 );
1029 }
1030
1031 self::$is_bumps_applied = true;
1032 }
1033
1034 /**
1035 * Order bump html.
1036 *
1037 * @param array $design design properties.
1038 * @param array $products products details.
1039 * @since 1.1.0
1040 * @return void
1041 */
1042 public static function bump_html( $design, $products ) {
1043 $list = empty( $products['list'] ) ? [] : $products['list'];
1044 $ids = [];
1045
1046 foreach ( $list as $id => $details ) {
1047 $ids[] = $id;
1048 $qty = $details['quantity'];
1049 $discount = $details['discount'];
1050 $type = $details['discountType'];
1051 }
1052
1053 $ids_string = implode( '|', $ids );
1054 $product = wc_get_product( $ids_string );
1055 $qty = ( empty( $qty ) ) ? 1 : $qty;
1056
1057 if ( empty( $product ) || ! $product->get_id() || ! $product->is_in_stock() ) {
1058 return;
1059 }
1060
1061 $unique_id = 'bump-title-' . $ids_string;
1062
1063 $class = '';
1064 if (
1065 'sellkit-checkout-before-order-summary' === current_action() ||
1066 'sellkit-checkout-after-order-summary' === current_action()
1067 ) {
1068 $class = 'sellkit-bump-review-order';
1069 }
1070
1071 $checked = empty( WC()->cart->find_product_in_cart( WC()->cart->generate_cart_id( $product->get_id() ) ) ) ? false : true;
1072 ?>
1073 <div class="sellkit-checkout-step-bump-wrapper <?php echo esc_attr( $class ); ?>" >
1074 <div class="sellkit-checkout-bump-order-header">
1075 <div class="sellkit-bump-order-left-header">
1076 <img src="<?php echo esc_attr( sellkit()->plugin_assets_url() . 'img/right-arrow.svg' ); ?>" >
1077 <input
1078 type="checkbox"
1079 value="<?php echo esc_attr( $ids_string ); ?>"
1080 class="sellkit-checkout-bump-order-products"
1081 data-qty="<?php echo esc_attr( $qty ); ?>"
1082 name="sellkit_bump_data_<?php echo esc_attr( $ids_string ); ?>"
1083 id="<?php echo esc_html( $unique_id ); ?>"
1084 <?php echo ( true === $checked ) ? 'checked="true"' : ''; ?>
1085 >
1086 <label
1087 for="<?php echo esc_attr( $unique_id ); ?>"
1088 class="sellkit-checkout-order-bump-title"
1089 >
1090 <?php echo esc_html( $design['sellkit_funnels_bump_checkbox_label'] ); ?>
1091 </label>
1092 </div>
1093 <div class="sellkit-bump-order-right-header">
1094 <span class="sellkit-checkout-order-bump-price">
1095 <?php
1096 $discounted_price = Helper::calculate_discount( (int) $ids_string, $type, $discount );
1097 $sale_price = $product->get_sale_price();
1098 $regular_price = $product->get_regular_price();
1099 $main_price = ( strpos( $type, 'sale' ) !== false ) ? $sale_price : $regular_price;
1100
1101 if ( floatval( $main_price ) > floatval( $discounted_price ) ) {
1102 ?>
1103 <del aria-hidden="true">
1104 <span class="woocommerce-Price-amount amount">
1105 <bdi>
1106 <span class="woocommerce-Price-currencySymbol">
1107 <?php echo wp_kses_post( wc_price( $main_price ) ); ?>
1108 </span>
1109 </bdi>
1110 </span>
1111 </del>
1112 <bdi class="bump-price-bolded"><?php echo wp_kses_post( wc_price( $discounted_price ) ); ?></bdi>
1113 <?php
1114 } else {
1115 ?>
1116 <bdi><?php echo wp_kses_post( wc_price( $main_price ) ); ?></bdi>
1117 <?php
1118 }
1119 ?>
1120 </span>
1121 </div>
1122 </div>
1123 <div class="sellkit-checkout-bump-order-body" >
1124 <div class="sellkit-bump-order-left-body">
1125 <?php $image = wp_get_attachment_image_src( $product->get_image_id() ); ?>
1126 <?php if ( 'true' === $design['sellkit_funnels_bump_product_image'] && ! empty( $image[0] ) ) : ?>
1127 <img src="<?php echo esc_url( $image[0] ); ?>" >
1128 <?php endif; ?>
1129 </div>
1130 <div class="sellkit-bump-order-right-body">
1131 <div class="sellkit-bump-order-description">
1132 <?php echo wp_kses_post( $design['sellkit_content'] ); ?>
1133 </div>
1134 </div>
1135 </div>
1136 </div>
1137 <?php
1138 }
1139
1140 /**
1141 * Checks if funnel includes upsell step.
1142 *
1143 * @param string $goal customize return value.
1144 * @param int $ajax_id upsell id through ajax.
1145 * @since 1.6.2
1146 */
1147 public static function is_funnel_includes_upsell( $goal = 'is_upsell', $ajax_id = null ) {
1148 $id = get_queried_object_id();
1149
1150 if ( wp_doing_ajax() ) {
1151 $id = $ajax_id;
1152 }
1153
1154 $step_data = get_post_meta( $id, 'step_data', true );
1155 $include_upsell = false;
1156 $upsell_steps = [];
1157 $upsell_ids = [];
1158 $global_checkout_id = get_option( Checkout::SELLKIT_GLOBAL_CHECKOUT_OPTION, 0 );
1159
1160 // We are in default WooCommerce checkout page.
1161 if ( empty( $step_data ) && $global_checkout_id > 0 && 'publish' === get_post_status( $global_checkout_id ) ) {
1162 $steps = get_post_meta( $global_checkout_id, 'nodes', true );
1163 $checkout_id = 0;
1164
1165 foreach ( $steps as $step ) {
1166 $step['type'] = (array) $step['type'];
1167
1168 if ( 'checkout' === $step['type']['key'] ) {
1169 $checkout_id = $step['page_id'];
1170 }
1171 }
1172
1173 $step_data = get_post_meta( $checkout_id, 'step_data', true );
1174 }
1175
1176 if ( empty( $step_data ) ) {
1177 return $include_upsell;
1178 }
1179
1180 $funnel_id = intval( $step_data['funnel_id'] );
1181 $funnel_data = get_post_meta( $funnel_id, 'nodes', true );
1182
1183 if ( empty( $funnel_data ) ) {
1184 return $include_upsell;
1185 }
1186
1187 $popups = [ 'downsell', 'upsell' ];
1188
1189 foreach ( $funnel_data as $step ) {
1190 if ( isset( $step['data'] ) && isset( $step['data']['products'] ) && isset( $step['data']['products']['list'] ) ) {
1191 $product_id = array_keys( $step['data']['products']['list'] )[0] ?? 0;
1192
1193 if ( $product_id ) {
1194 $product = wc_get_product( $product_id );
1195
1196 if ( $product && ! $product->is_in_stock() ) {
1197 return $include_upsell;
1198 }
1199 }
1200 }
1201
1202 $step['type'] = (array) $step['type'];
1203
1204 if ( in_array( $step['type']['key'], $popups, true ) ) {
1205 $include_upsell = true;
1206 $upsell_steps[] = $step;
1207 $upsell_ids[] = $step['page_id'];
1208 }
1209 }
1210
1211 if ( 'get_ids' === $goal ) {
1212 return $upsell_ids;
1213 }
1214
1215 // Send data to the filter that keeps steps data to prepare popup.
1216 add_filter( 'sellkit_upsell_steps_popup_information', function( $default ) use ( $upsell_steps ) {
1217 if ( ! empty( $upsell_steps ) ) {
1218 return $upsell_steps;
1219 }
1220
1221 return $default;
1222 } );
1223
1224 return $include_upsell;
1225 }
1226
1227 /**
1228 * Add an extra hidden field to checkout form if funnel includes upsell step.
1229 *
1230 * @since 1.6.2
1231 */
1232 public function add_trigger_field_for_upsell_steps() {
1233 $include_upsell = self::is_funnel_includes_upsell();
1234
1235 if ( false === $include_upsell ) {
1236 return;
1237 }
1238
1239 echo '<input type="hidden" id="sellkit_funnel_has_upsell" value="upsell" >';
1240 echo '<input type="hidden" id="sellkit_funnel_popup_step_id" value="0" >';
1241 }
1242
1243 /**
1244 * Display sellkit upsell steps as popup.
1245 *
1246 * @since 1.6.2
1247 */
1248 public function sellkit_funnel_display_upsell_as_popup() {
1249 $upsell_data = apply_filters( 'sellkit_upsell_steps_popup_information', [] );
1250 $i = 1;
1251
1252 if ( empty( $upsell_data ) ) {
1253 return;
1254 }
1255
1256 foreach ( $upsell_data as $data ) {
1257 $id = 'sellkit_funnel_upsell_popup_' . $i;
1258 $class = 'sellkit_funnel_upsell_popup sellkit_funnel_upsell_popup_' . apply_filters( 'wpml_object_id', $data['page_id'], 'sellkit_step', true ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API.
1259 ?>
1260 <div class="<?php echo esc_attr( $class ); ?>" id="<?php echo esc_attr( $id ); ?>">
1261 <?php echo wp_kses_post( Elementor::instance()->frontend->get_builder_content_for_display( apply_filters( 'wpml_object_id', $data['page_id'], 'sellkit_step', true ) ) ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API. ?>
1262 <input type="hidden" class="identify" value="<?php echo esc_attr( apply_filters( 'wpml_object_id', $data['page_id'], 'sellkit_step', true ) ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API. ?>" >
1263 </div>
1264 <?php
1265 $i++;
1266 }
1267 }
1268
1269 /**
1270 * Display checkout order notes field.
1271 *
1272 * @since 1.8.9
1273 */
1274 public function order_notes() {
1275 // Get woocommerce order notes field.
1276 $notes = WC()->checkout()->get_checkout_fields( 'order' );
1277
1278 // print notes field.
1279 foreach ( $notes as $key => $field ) {
1280 ?>
1281 <div class="sellkit-checkout-order-fields sellkit-widget-checkout-fields">
1282 <?php
1283 if ( 'order_comments' === $key ) {
1284 ?>
1285 <div class="sellkit-checkout-order-notes">
1286 <div class="sellkit-order-note-field-wrapper">
1287 <input id="sellkit-add-notes-to-order-box" type="checkbox">
1288 <label for="sellkit-add-notes-to-order-box">
1289 <?php echo esc_html__( 'Add a note to your order', 'sellkit' ); ?>
1290 </label>
1291 </div>
1292 <?php
1293 woocommerce_form_field( $key, $field, WC()->checkout()->get_value( $key ) );
1294 ?>
1295 </div>
1296 <?php
1297 }
1298 ?>
1299 </div>
1300 <?php
1301 }
1302 }
1303 }
1304