PluginProbe
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce / 3.2.0
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce v3.2.0
3.3.0 3.2.1 3.2.0 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.1 trunk 1.0.4 1.1.0 1.1.0.1 1.1.1 1.1.10 1.1.11 1.1.12 1.1.13 1.1.14 1.1.15 1.1.16 1.1.17 1.1.18 1.1.19 1.1.2 All 162 releases
cartflows / modules / checkout / classes / class-cartflows-checkout-markup.php

class-cartflows-checkout-markup.php in CartFlows – Funnel Builder & Checkout Plugin for WooCommerce 3.2.0, at modules/checkout/classes/class-cartflows-checkout-markup.php

2,273 lines 73.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Checkout markup.
4 *
5 * @package CartFlows
6 */
7
8 // Exit if accessed directly.
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit;
11 }
12
13 /**
14 * Checkout Markup
15 *
16 * @since 1.0.0
17 */
18 class Cartflows_Checkout_Markup {
19
20
21 /**
22 * Member Variable
23 *
24 * @var object instance
25 */
26 private static $instance;
27
28 /**
29 * Initiator
30 */
31 public static function get_instance() {
32 if ( ! isset( self::$instance ) ) {
33 self::$instance = new self();
34 }
35 return self::$instance;
36 }
37
38 /**
39 * Constructor
40 */
41 public function __construct() {
42
43 // Register early so get_value() returns URL-param values inside
44 // WC_Checkout::initialize_checkout_fields(), which bakes the active
45 // country into the state field definition on first call — potentially
46 // before the wp action fires.
47 add_filter( 'woocommerce_checkout_get_value', array( $this, 'prefill_checkout_get_value' ), 10, 2 );
48
49 /* Set is checkout flag */
50 add_filter( 'woocommerce_is_checkout', array( $this, 'woo_checkout_flag' ), 9999 );
51
52 add_action( 'woocommerce_checkout_update_order_meta', array( $this, 'save_checkout_fields' ), 10, 2 );
53
54 /* Add order meta for payment gateways that use store api for checkout */
55 add_action( 'woocommerce_store_api_checkout_update_order_meta', array( $this, 'add_order_meta_for_store_api_checkout' ) );
56
57 /* Show notice if cart is empty */
58 add_action( 'cartflows_checkout_cart_empty', array( $this, 'display_woo_notices' ) );
59
60 /* Checkout Shortcode */
61 add_shortcode( 'cartflows_checkout', array( $this, 'checkout_shortcode_markup' ) );
62
63 /* Preconfigured cart data */
64 add_action( 'wp', array( $this, 'preconfigured_cart_data' ), 1 );
65
66 /* Embed Checkout */
67 add_action( 'wp', array( $this, 'shortcode_load_data' ), 999 );
68
69 add_action( 'cartflows_add_before_main_section', array( $this, 'enable_logo_in_header' ) );
70
71 add_action( 'cartflows_primary_container_bottom', array( $this, 'show_cartflows_copyright_message' ) );
72
73 add_filter( 'woocommerce_login_redirect', array( $this, 'after_login_redirect' ), 9999, 2 );
74
75 add_filter( 'global_cartflows_js_localize', array( $this, 'add_localize_vars' ) );
76
77 add_filter( 'woocommerce_paypal_args', array( $this, 'modify_paypal_args' ), 10, 2 );
78
79 add_filter( 'woocommerce_paypal_express_checkout_payment_button_data', array( $this, 'change_return_cancel_url' ), 10, 2 );
80
81 add_filter( 'woocommerce_cart_item_name', array( $this, 'modify_order_review_item_summary' ), 10, 3 );
82
83 add_action( 'woocommerce_before_calculate_totals', array( $this, 'custom_price_to_cart_item' ), 9999 );
84
85 add_action( 'init', array( $this, 'update_woo_actions_ajax' ), 10 );
86
87 // In case of multiple checkout open at same time we are restoring the cart of specific checkout.
88 add_action( 'woocommerce_checkout_update_order_review', array( $this, 'restore_cart_data' ) );
89
90 // Change the shipping error messages text and UI.
91 add_filter( 'woocommerce_shipping_may_be_available_html', array( $this, 'change_shipping_message_html' ) );
92 add_filter( 'woocommerce_no_shipping_available_html', array( $this, 'change_shipping_message_html' ) );
93
94 // Update the cart total price to display on button and on the mobile order view section.
95 add_filter( 'woocommerce_update_order_review_fragments', array( $this, 'add_updated_cart_price' ), 10, 1 );
96
97 $this->gutenberg_editor_compatibility();
98
99 if ( class_exists( '\Elementor\Plugin' ) ) {
100 // Load the widgets.
101 $this->elementor_editor_compatibility();
102 }
103
104 if ( class_exists( 'FLBuilder' ) ) {
105 $this->bb_editor_compatibility();
106 }
107
108 $page_builder = Cartflows_Helper::get_common_setting( 'default_page_builder' );
109 if ( Cartflows_Compatibility::is_bricks_enabled() && 'bricks-builder' === $page_builder ) {
110 $this->bricks_editor_compatibility();
111 }
112
113 // Load Google Auto fill address fields actions.
114 add_action( 'cartflows_checkout_scripts', array( $this, 'load_google_places_library' ) );
115 }
116
117 /**
118 * Enqueue Google Maps API js.
119 */
120 public function load_google_places_library() {
121
122 $auto_fields_settings = Cartflows_Helper::get_admin_settings_option( '_cartflows_google_auto_address', false, false );
123
124 if ( empty( $auto_fields_settings['google_map_api_key'] ) ) {
125 return;
126 }
127
128 global $post;
129
130 $checkout_id = $post->ID;
131
132 $is_autoaddress_enable = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-google-autoaddress' );
133
134 if ( 'yes' === $is_autoaddress_enable ) {
135 wp_enqueue_script(
136 'wcf-google-places-api',
137 'https://maps.googleapis.com/maps/api/js?key=' . $auto_fields_settings['google_map_api_key'] . '&libraries=places',
138 array( 'wcf-checkout-template' ),
139 CARTFLOWS_VER,
140 true
141 );
142
143 wp_enqueue_script(
144 'wcf-google-places',
145 wcf()->utils->get_js_url( 'google-auto-fields' ),
146 array( 'wcf-google-places-api' ),
147 CARTFLOWS_VER,
148 true
149 );
150 }
151 }
152
153 /**
154 * Restore the cart data on the checkout page.
155 */
156 public function restore_cart_data() {
157
158 global $post;
159
160 $active_checkout = isset( $_COOKIE[ CARTFLOWS_ACTIVE_CHECKOUT ] ) ? intval( $_COOKIE[ CARTFLOWS_ACTIVE_CHECKOUT ] ) : false; // phpcs:ignore WordPressVIPMinimum.Variables.RestrictedVariables.cache_constraints___COOKIE
161
162 if ( $post && $post->ID && $active_checkout ) {
163
164 $checkout_id = $post->ID;
165
166 if ( $checkout_id !== $active_checkout ) {
167
168 $user_key = WC()->session->get_customer_id();
169
170 $cart_data = get_transient( 'wcf_user_' . $user_key . '_checkout_' . $checkout_id );
171
172 if ( $cart_data ) {
173 WC()->cart->empty_cart();
174
175 foreach ( $cart_data as $key => $item ) {
176 WC()->cart->add_to_cart( $item['product_id'], $item['quantity'], $item['variation_id'], $item['variation'], $item );
177 }
178
179 $expiration_time = 30;
180 // Need to update the active checkout id.
181 setcookie( CARTFLOWS_ACTIVE_CHECKOUT, $checkout_id, time() + $expiration_time * MINUTE_IN_SECONDS, '/', COOKIE_DOMAIN, CARTFLOWS_HTTPS, true ); //phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.cookies_setcookie
182
183 // Prepare the cart data with cart item key. Need to update in product options.
184 add_filter( 'woocommerce_update_order_review_fragments', array( $this, 'prepare_required_cart_data' ), 10, 1 );
185
186 }
187 }
188 }
189 }
190
191 /**
192 * Prepare the cart data on the checkout page.
193 *
194 * @param array $fragments woo ajax fragments.
195 */
196 public function prepare_required_cart_data( $fragments ) {
197
198 $cart = WC()->cart->get_cart();
199
200 $cart_data = array();
201
202 foreach ( $cart as $key => $data ) {
203 $unique_id = isset( $data['wcf_product_data'] ) && isset( $data['wcf_product_data']['unique_id'] ) ? $data['wcf_product_data']['unique_id'] : '';
204 $cart_data[ $unique_id ] = $key;
205 }
206
207 $fragments['wcf_cart_data'] = $cart_data;
208
209 return $fragments;
210 }
211
212 /**
213 * Remove login and registration actions.
214 */
215 public function update_woo_actions_ajax() {
216 add_action( 'cartflows_woo_checkout_update_order_review', array( $this, 'after_the_order_review_ajax_call' ) );
217
218 if ( _is_wcf_doing_checkout_ajax() ) {
219 add_filter( 'woocommerce_order_button_text', array( $this, 'place_order_button_text' ), 99, 1 );
220 }
221 }
222
223 /**
224 * Call the actions after order review ajax call.
225 *
226 * @param string $post_data post data woo.
227 */
228 public function after_the_order_review_ajax_call( $post_data ) {
229 if ( isset( $post_data['_wcf_checkout_id'] ) ) {
230 add_filter( 'woocommerce_order_button_text', array( $this, 'place_order_button_text' ), 99, 1 );
231 }
232 }
233
234 /**
235 * Modify WooCommerce paypal arguments.
236 *
237 * @param array $args argumenets for payment.
238 * @param WC_Order $order order data.
239 * @return array
240 */
241 public function modify_paypal_args( $args, $order ) {
242 $checkout_id = wcf()->utils->get_checkout_id_from_post_data();
243
244 if ( ! $checkout_id ) {
245 return $args;
246 }
247
248 // Set cancel return URL.
249 $args['cancel_return'] = esc_url_raw( $order->get_cancel_order_url_raw( get_permalink( $checkout_id ) ) );
250
251 return $args;
252 }
253
254 /**
255 * Elementor editor compatibility.
256 */
257 public function elementor_editor_compatibility() {
258 /* Add data */
259
260 add_action(
261 'cartflows_elementor_editor_compatibility',
262 function ( $post_id, $elementor_ajax ) {
263
264 add_action( 'cartflows_elementor_before_checkout_shortcode', array( $this, 'before_checkout_shortcode_actions' ) );
265 },
266 10,
267 2
268 );
269 }
270
271 /**
272 * Gutenburg editor compatibility.
273 */
274 public function gutenberg_editor_compatibility() {
275 /* Add data */
276
277 add_action(
278 'cartflows_gutenberg_editor_compatibility',
279 function ( $post_id ) {
280
281 add_action( 'cartflows_gutenberg_before_checkout_shortcode', array( $this, 'before_checkout_shortcode_actions' ) );
282 },
283 10,
284 2
285 );
286 }
287
288 /**
289 * Function for bb editor compatibility.
290 */
291 public function bb_editor_compatibility() {
292 /* Add data. */
293 add_action(
294 'cartflows_bb_editor_compatibility',
295 function ( $post_id ) {
296 add_action( 'cartflows_bb_before_checkout_shortcode', array( $this, 'before_checkout_shortcode_actions' ) );
297 },
298 10,
299 1
300 );
301 }
302
303 /**
304 * Function for bricks editor compatibility.
305 *
306 * @return void
307 */
308 public function bricks_editor_compatibility() {
309 remove_action( 'woocommerce_before_checkout_form', 'woocommerce_checkout_coupon_form' );
310 add_action( 'woocommerce_checkout_order_review', array( $this, 'display_custom_coupon_field' ) );
311 }
312
313 /**
314 * Change PayPal Express cancel URL.
315 *
316 * @param array $data button data.
317 * @param string $page current page.
318 * @return array $data modified button data with new cancel url.
319 */
320 public function change_return_cancel_url( $data, $page ) {
321 global $post;
322
323 if ( _is_wcf_checkout_type() ) {
324
325 $checkout_id = $post->ID;
326
327 if ( $checkout_id ) {
328
329 // Change the default Cart URL with the CartFlows Checkout page.
330 $data['cancel_url'] = esc_url_raw( get_permalink( $checkout_id ) );
331 }
332 }
333
334 // Returing the modified data.
335 return $data;
336 }
337
338 /**
339 * Modify order review section.
340 *
341 * @param string $product_name product name.
342 * @param object $cart_item cart item.
343 * @param string $cart_item_key cart item key.
344 * @return string
345 */
346 public function modify_order_review_item_summary( $product_name, $cart_item, $cart_item_key ) {
347
348 if ( wp_doing_ajax() && isset( $_GET['wcf_checkout_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
349
350 $checkout_id = _get_wcf_checkout_id();
351
352 if ( ! $checkout_id ) {
353 $checkout_id = isset( $_GET['wcf_checkout_id'] ) && ! empty( $_GET['wcf_checkout_id'] ) ? intval( wp_unslash( $_GET['wcf_checkout_id'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
354 }
355
356 if ( ! empty( $checkout_id ) ) {
357
358 $is_remove_product_option = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-remove-product-field' );
359 $show_product_image = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-order-review-show-product-images' );
360
361 $remove_label = '';
362 $image = '';
363
364 if ( 'yes' === $is_remove_product_option ) {
365 $remove_label = apply_filters(
366 'woocommerce_cart_item_remove_link',
367 sprintf(
368 '<a href="#" rel="nofollow" class="wcf-remove-product cartflows-icon cartflows-circle-cross" data-id="%s" data-item-key="%s"></a>',
369 esc_attr( $cart_item['product_id'] ),
370 $cart_item_key
371 ),
372 $cart_item_key
373 );
374 }
375
376 if ( 'yes' === $show_product_image ) {
377
378 // Get product object.
379 $_product = apply_filters( 'woocommerce_cart_item_product', $cart_item['data'], $cart_item, $cart_item_key );
380
381 // Get product thumbnail.
382 $thumbnail = $_product->get_image();
383
384 // Add wrapper to image and add some css.
385 $image = '<div class="wcf-product-thumbnail">' . $thumbnail . $remove_label . ' </div>';
386 $image = apply_filters( 'cartflows_checkout_order_review_item_image', $image, $thumbnail, $remove_label, $cart_item, $checkout_id );
387 } else {
388 /**
389 * If no product image is enabled but remove_label is enabled
390 * then add the remove label outside image's div else blank will be added.
391 */
392 $image = $remove_label;
393 }
394
395 $product_name = '<div class="wcf-product-image"> ' . $image . ' <div class="wcf-product-name">' . $product_name . '</div></div>';
396 }
397 }
398
399 return apply_filters( 'cartflows_checkout_order_review_item_summary', $product_name, $cart_item );
400 }
401
402 /**
403 * Change order button text .
404 *
405 * @param string $button_text place order.
406 * @return string
407 */
408 public function place_order_button_text( $button_text ) {
409 $checkout_id = get_the_ID();
410 //phpcs:disable WordPress.Security.NonceVerification.Recommended
411 if ( ! $checkout_id && isset( Cartflows_Woo_Hooks::$ajax_data['_wcf_checkout_id'] ) ) {
412
413 $checkout_id = intval( Cartflows_Woo_Hooks::$ajax_data['_wcf_checkout_id'] );
414 }
415
416 if ( ! $checkout_id && isset( $_GET['wcf_checkout_id'] ) ) {
417
418 $checkout_id = intval( $_GET['wcf_checkout_id'] );
419 }
420
421 // Compatibility for Gutenebrg editor preview.
422 if ( isset( $_REQUEST['cartflows_gb'] ) && isset( $_REQUEST['id'] ) ) {
423 $checkout_id = intval( $_REQUEST['id'] );
424 }
425
426 if ( $checkout_id ) {
427
428 $wcf_order_button_text = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-checkout-place-order-button-text' );
429
430 if ( ! empty( $wcf_order_button_text ) ) {
431 $button_text = $wcf_order_button_text;
432 }
433
434 if ( 'yes' === wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-checkout-place-order-button-price-display' ) ) {
435 $button_text .= '&nbsp;&nbsp;' . wp_strip_all_tags( WC()->cart->get_total() );
436 }
437 }
438
439 return $button_text;
440 //phpcs:enable WordPress.Security.NonceVerification.Recommended
441 }
442
443 /**
444 * Display all WooCommerce notices.
445 *
446 * @since 1.1.5
447 */
448 public function display_woo_notices() {
449 if ( null != WC()->session && function_exists( 'woocommerce_output_all_notices' ) ) {
450 woocommerce_output_all_notices();
451 }
452 }
453
454 /**
455 * Check for checkout flag
456 *
457 * @param bool $is_checkout is checkout.
458 *
459 * @return bool
460 */
461 public function woo_checkout_flag( $is_checkout ) {
462 if ( ! is_admin() ) {
463
464 if ( _is_wcf_checkout_type() ) {
465
466 $is_checkout = true;
467 }
468 }
469
470 return $is_checkout;
471 }
472
473 /**
474 * Render checkout shortcode markup.
475 *
476 * @param array $atts attributes.
477 * @return string
478 */
479 public function checkout_shortcode_markup( $atts ) {
480 if ( ! function_exists( 'wc_print_notices' ) ) {
481 $notice_out = '<p class="woocommerce-notice">' . __( 'WooCommerce functions do not exist. If you are in an IFrame, please reload it.', 'cartflows' ) . '</p>';
482 $notice_out .= '<button onClick="location.reload()">' . __( 'Click Here to Reload', 'cartflows' ) . '</button>';
483
484 return $notice_out;
485 }
486
487 $atts = shortcode_atts(
488 array(
489 'id' => 0,
490 ),
491 $atts
492 );
493
494 $checkout_id = intval( $atts['id'] );
495
496 $show_checkout_demo = false;
497
498 if ( is_admin() ) {
499
500 $show_checkout_demo = apply_filters( 'cartflows_show_demo_checkout', false );
501
502 if ( $show_checkout_demo && 0 === $checkout_id && isset( $_POST['id'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Missing
503 $checkout_id = intval( $_POST['id'] ); //phpcs:ignore WordPress.Security.NonceVerification.Missing
504 }
505 }
506 if ( empty( $checkout_id ) ) {
507
508 if ( ! _is_wcf_checkout_type() && false === $show_checkout_demo ) {
509
510 $error_html = '<h4>' . __( 'Checkout ID not found', 'cartflows' ) . '</h4>';
511 $error_html .= '<p>' . sprintf(
512 /* translators: %1$1s, %2$2s Link to article */
513 __( 'It seems that this is not the CartFlows Checkout page where you have added this shortcode. Please refer to this %1$1sarticle%2$2s to know more.', 'cartflows' ),
514 '<a href="' . esc_url( \Cartflows_Helper::get_kb_doc_link( 'https://cartflows.com/docs/resolve-checkout-id-not-found-error/' ) ) . '" target="_blank">',
515 '</a>'
516 ) . '</p>';
517
518 return $error_html;
519 }
520
521 global $post;
522
523 $checkout_id = intval( $post->ID );
524 }
525
526 $output = '';
527
528 ob_start();
529
530 do_action( 'cartflows_checkout_form_before', $checkout_id );
531
532 $checkout_layout = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-checkout-layout' );
533
534 $template_default = CARTFLOWS_CHECKOUT_DIR . 'templates/embed/checkout-template-simple.php';
535
536 $template_layout = apply_filters( 'cartflows_checkout_layout_template', $checkout_layout );
537
538 // Security: Only include the filtered template when it resolves to a real file inside wp-content
539 // (themes/plugins/mu-plugins). Prevents path traversal via the cartflows_checkout_layout_template filter.
540 $real_path = is_string( $template_layout ) && file_exists( $template_layout ) ? realpath( $template_layout ) : false;
541
542 if ( $real_path && 0 === strpos( $real_path, WP_CONTENT_DIR . DIRECTORY_SEPARATOR ) ) {
543 include $template_layout;
544 } else {
545 include $template_default;
546 }
547
548 $output .= ob_get_clean();
549
550 return $output;
551 }
552
553 /**
554 * Configure Cart Data.
555 *
556 * @since 1.0.0
557 *
558 * @return void
559 */
560 public function preconfigured_cart_data() {
561 if ( is_admin() ) {
562 return;
563 }
564
565 // Skip cart manipulation for order-pay pages as the order already exists.
566 if ( is_checkout_pay_page() ) {
567 return;
568 }
569
570 global $post, $wcf_step;
571
572 if ( _is_wcf_checkout_type() ) {
573
574 if ( wp_doing_ajax() ) {
575 return;
576 } else {
577
578 $checkout_id = 0;
579 $skip_cart = false;
580
581 if ( _is_wcf_checkout_type() ) {
582 $checkout_id = $post->ID;
583 }
584
585 $store_checkout = \Cartflows_Helper::get_global_setting( '_cartflows_store_checkout' );
586
587 $flow_id = wcf()->utils->get_flow_id_from_step_id( $checkout_id );
588
589 if ( ! empty( $store_checkout ) && ( intval( $store_checkout ) === intval( $flow_id ) ) ) {
590
591 if ( WC()->cart->is_empty() && ! isset( $_GET['wcf-add-to-cart'] ) && apply_filters( 'cartflows_checkout_show_empty_cart_notice', true ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
592 wc_add_notice( __( 'Your cart is currently empty.', 'cartflows' ), 'error' );
593 }
594
595 $skip_cart = true;
596
597 /**
598 * This action should/can be used to configure the cart of store checkout.
599 *
600 * @param int $checkout_id
601 * @hooked to wp action.
602 * @since 1.11.16
603 */
604 do_action( 'cartflows_store_checkout_before_configure_cart', $checkout_id );
605 }
606
607 if ( ! apply_filters( 'cartflows_skip_configure_cart', $skip_cart, $checkout_id ) ) {
608
609 do_action( 'cartflows_checkout_before_configure_cart', $checkout_id );
610
611 $products = wcf()->utils->get_selected_checkout_products( $checkout_id );
612
613 if ( wcf()->flow->is_flow_testmode( $flow_id ) && ( ! is_array( $products ) || empty( $products[0]['product'] ) ) ) {
614 $products = $this->get_random_products();
615 }
616
617 if ( ! is_array( $products ) ) {
618 return;
619 }
620
621 /* Empty the current cart */
622 WC()->cart->empty_cart();
623
624 if ( is_array( $products ) && empty( $products[0]['product'] ) && apply_filters( 'cartflows_checkout_show_empty_cart_notice', true ) ) {
625
626 $a_start = '';
627 $a_close = '';
628
629 wc_add_notice(
630 sprintf(
631 /* translators: %1$1s, %2$2s Link to meta */
632 __( 'No product is selected. Please select products from the %1$1scheckout meta settings%2$2s to continue.', 'cartflows' ),
633 $a_start,
634 $a_close
635 ),
636 'error'
637 );
638 return;
639 }
640
641 /* Set customer session if not set */
642 if ( ! is_user_logged_in() && WC()->cart->is_empty() ) {
643 WC()->session->set_customer_session_cookie( true );
644 }
645
646 $cart_product_count = 0;
647 $cart_key = '';
648 $products_new = array();
649
650 foreach ( $products as $index => $data ) {
651
652 if ( ! isset( $data['product'] ) ) {
653 continue;
654 }
655
656 /* Since 1.6.5 */
657 if ( empty( $data['add_to_cart'] ) || 'no' === $data['add_to_cart'] ) {
658 continue;
659 }
660
661 if ( apply_filters( 'cartflows_skip_other_products', false, $cart_product_count ) ) {
662 break;
663 }
664
665 $product_id = $data['product'];
666 $_product = wc_get_product( $product_id );
667
668 if ( ! empty( $_product ) ) {
669
670 $quantity = 1;
671
672 if ( isset( $data['quantity'] ) && ! empty( $data['quantity'] ) ) {
673 $quantity = $data['quantity'];
674 }
675
676 $discount_type = isset( $data['discount_type'] ) ? $data['discount_type'] : '';
677 $discount_value = ! empty( $data['discount_value'] ) ? $data['discount_value'] : '';
678 $_product_price = $_product->get_price(); // Removed the parameter as it was not required and was creating issue with MultiCurrency plugin.
679
680 $custom_price = $this->calculate_discount( '', $discount_type, $discount_value, $_product_price );
681
682 $cart_item_data = array(
683 'wcf_product_data' => array(
684 'unique_id' => $data['unique_id'],
685 'wcf_checkout_id' => $checkout_id,
686 'wcf_flow_id' => $flow_id,
687 ),
688 );
689
690 // Set the Product's custom price even if it is zero. Discount may have applied.
691 if ( $custom_price >= 0 && '' !== $custom_price ) {
692
693 $cart_item_data['custom_price'] = $custom_price;
694 }
695
696 if ( ! $_product->is_type( 'grouped' ) && ! $_product->is_type( 'external' ) ) {
697
698 if ( $_product->is_type( 'variable' ) ) {
699
700 $default_attributes = $_product->get_default_attributes();
701
702 if ( ! empty( $default_attributes ) ) {
703
704 foreach ( $_product->get_children() as $variation_id ) {
705
706 $single_variation = new WC_Product_Variation( $variation_id );
707
708 if ( $default_attributes == $single_variation->get_attributes() ) {
709 $cart_key = WC()->cart->add_to_cart( $variation_id, $quantity, 0, array(), $cart_item_data );
710 $cart_product_count++;
711 }
712 }
713 } else {
714
715 $product_childrens = $_product->get_children();
716
717 $variation_product = false;
718 $variation_product_id = 0;
719
720 foreach ( $product_childrens as $key => $v_id ) {
721
722 $_var_product = wc_get_product( $v_id );
723
724 if ( $_var_product && $_var_product->is_in_stock() && 'publish' === $_var_product->get_status() ) {
725 $variation_product_id = $v_id;
726 $variation_product = $_var_product;
727 break;
728 }
729 }
730
731 if ( $variation_product ) {
732 $_product_price = $variation_product->get_price();
733
734 $custom_price = $this->calculate_discount( '', $discount_type, $discount_value, $_product_price );
735 if ( ! empty( $custom_price ) ) {
736 $cart_item_data['custom_price'] = $custom_price;
737 }
738
739 $cart_key = WC()->cart->add_to_cart( $variation_product_id, $quantity, 0, array(), $cart_item_data );
740 $cart_product_count++;
741 } else {
742 echo '<p>' . esc_html__( 'Variations Not set', 'cartflows' ) . '</p>';
743 }
744 }
745 } else {
746 $cart_key = WC()->cart->add_to_cart( $product_id, $quantity, 0, array(), $cart_item_data );
747
748 if ( false !== $cart_key ) {
749 $cart_product_count++;
750 }
751 }
752 } else {
753 $wrong_product_notice = __( 'This product can\'t be purchased', 'cartflows' );
754 wc_add_notice( $wrong_product_notice );
755 }
756 }
757
758 $products_new[ $index ] = array(
759 'cart_item_key' => $cart_key,
760 );
761 }
762
763 /* Set checkout products data */
764 wcf()->utils->set_selcted_checkout_products( $checkout_id, $products_new );
765
766 /* Since 1.2.2 */
767 do_action( 'cartflows_checkout_after_configure_cart', $checkout_id );
768 }
769
770 $this->set_active_checkout_cookie_data( $checkout_id );
771 }
772 }
773 }
774
775 /**
776 * Set the cookie to identify the active checkout.
777 *
778 * @param int $checkout_id Current page ID.
779 *
780 * @return void
781 */
782 public function set_active_checkout_cookie_data( $checkout_id ) {
783
784 $cart_data = WC()->cart->get_cart();
785 $expiration_time = 30;
786 setcookie( CARTFLOWS_ACTIVE_CHECKOUT, $checkout_id, time() + $expiration_time * MINUTE_IN_SECONDS, '/', COOKIE_DOMAIN, CARTFLOWS_HTTPS, true ); //phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.cookies_setcookie
787
788 $user_key = WC()->session->get_customer_id();
789
790 set_transient( 'wcf_user_' . $user_key . '_checkout_' . $checkout_id, $cart_data, $expiration_time * MINUTE_IN_SECONDS );
791 }
792
793 /**
794 * Load shortcode data.
795 *
796 * @return void
797 */
798 public function shortcode_load_data() {
799 if ( _is_wcf_checkout_type() ) {
800
801 add_action( 'wp_enqueue_scripts', array( $this, 'shortcode_scripts' ), 21 );
802
803 add_action( 'wp_enqueue_scripts', array( $this, 'compatibility_scripts' ), 101 );
804
805 $this->before_checkout_shortcode_actions();
806
807 global $post;
808
809 $checkout_id = $post->ID;
810
811 do_action( 'cartflows_checkout_before_shortcode', $checkout_id );
812 }
813 }
814
815 /**
816 * Render checkout ID hidden field.
817 *
818 * @return void
819 */
820 public function before_checkout_shortcode_actions() {
821 /* Show notices if cart has errors */
822 add_action( 'woocommerce_cart_has_errors', 'woocommerce_output_all_notices' );
823
824 // Outputting the hidden field in checkout page.
825 add_action( 'woocommerce_after_order_notes', array( $this, 'checkout_shortcode_post_id' ), 99 );
826 add_action( 'woocommerce_login_form_end', array( $this, 'checkout_shortcode_post_id' ), 99 );
827
828 // Astra removes this actions so need to add it again.
829 add_action( 'woocommerce_checkout_billing', array( WC()->checkout, 'checkout_form_billing' ) );
830 add_action( 'woocommerce_checkout_shipping', array( WC()->checkout, 'checkout_form_shipping' ) );
831
832 remove_action( 'woocommerce_before_checkout_form', 'woocommerce_checkout_coupon_form' );
833
834 add_action( 'woocommerce_checkout_order_review', array( $this, 'display_custom_coupon_field' ) );
835
836 add_filter( 'woocommerce_cart_totals_coupon_html', array( $this, 'remove_coupon_text' ) );
837
838 add_filter( 'woocommerce_order_button_text', array( $this, 'place_order_button_text' ), 99, 1 );
839
840 add_filter( 'woocommerce_checkout_fields', array( $this, 'checkout_fields_actions' ), 10, 1 );
841
842 // Auto-check "Ship to a different address?" when shipping URL params are present,
843 // so the pre-filled shipping fields are visible on page load.
844 add_filter( 'woocommerce_ship_to_different_address_checked', array( $this, 'maybe_check_ship_to_different_address' ) );
845
846 // Add file field type support.
847 add_filter( 'woocommerce_form_field_file', array( $this, 'render_file_field' ), 10, 4 );
848
849 $this->update_the_checkout_strings();
850 $this->add_customized_shipping_section();
851 }
852
853 /**
854 * Render file upload field for checkout.
855 *
856 * @param string $field Field HTML.
857 * @param string $key Field key.
858 * @param array $args Field arguments.
859 * @param string $value Field value.
860 * @return string
861 */
862 public function render_file_field( $field, $key, $args, $value ) {
863
864 $required = ! empty( $args['required'] ) ? ' <abbr class="required">*</abbr>' : '';
865 $label = isset( $args['label'] ) ? $args['label'] : '';
866 $label_class = ! empty( $args['label_class'] ) ? implode( ' ', $args['label_class'] ) : '';
867 $field_class = ! empty( $args['class'] ) ? implode( ' ', $args['class'] ) : '';
868 $validate = ! empty( $args['required'] ) ? 'validate-required' : '';
869
870 $attrs = isset( $args['custom_attributes'] ) && is_array( $args['custom_attributes'] )
871 ? $args['custom_attributes']
872 : array();
873
874 $max_size = ! empty( $attrs['file_size'] ) ? absint( $attrs['file_size'] ) : 5;
875 $raw_types = isset( $attrs['accepted_file_types'] ) ? $attrs['accepted_file_types'] : array();
876
877 /**
878 * Master frontend allowlist
879 */
880 $master_types = Cartflows_Helper::get_allowed_file_extensions();
881
882 /**
883 * Normalize admin-restricted file types.
884 */
885 $restricted = array();
886
887 if ( ! empty( $raw_types ) ) {
888 foreach ( (array) $raw_types as $item ) {
889 if ( is_array( $item ) && isset( $item['value'] ) ) {
890 $restricted[] = strtolower( trim( $item['value'] ) );
891 } elseif ( is_string( $item ) ) {
892 foreach ( array_map( 'trim', explode( ',', $item ) ) as $ext ) {
893 if ( '' !== $ext ) {
894 $restricted[] = strtolower( $ext );
895 }
896 }
897 }
898 }
899 }
900
901 $allowed_types = empty( $restricted )
902 ? $master_types
903 : array_intersect( $master_types, $restricted );
904
905 $accept_parts = array();
906
907 foreach ( $allowed_types as $ext ) {
908 $accept_parts[] = '.' . ltrim( $ext, '.' );
909 }
910
911 $accept_attr = implode( ',', $accept_parts );
912
913 $field = '<p class="form-row ' . esc_attr( $field_class . ' ' . $validate ) . '" id="' . esc_attr( $key ) . '_field">';
914 $field .= '<label for="' . esc_attr( $key ) . '" class="woocommerce-form__label-for-file ' . esc_attr( $label_class ) . '">';
915 $field .= wp_kses_post( $label ) . $required . '</label>';
916 $field .= '<span class="woocommerce-input-wrapper wcf-file-input-wrapper">';
917 $field .= '<input type="file"
918 id="' . esc_attr( $key ) . '_file"
919 class="wcf-checkout-file-input"
920 data-field-key="' . esc_attr( $key ) . '"
921 accept="' . esc_attr( $accept_attr ) . '"
922 data-max-size="' . esc_attr( (string) $max_size ) . '" />';
923 $field .= '<input type="hidden" id="' . esc_attr( $key ) . '" name="' . esc_attr( $key ) . '" class="wcf-checkout-file-url" value="' . esc_attr( $value ) . '" />';
924 $field .= '<span class="wcf-file-error" id="' . esc_attr( $key ) . '_error"></span>';
925 $field .= '</span></p>';
926
927 return $field;
928 }
929
930 /**
931 * Add actions to introduced new customized shipping section for all checkout styles.
932 *
933 * @since 2.1.0
934 * @return void
935 */
936 public function add_customized_shipping_section() {
937
938 add_action( 'woocommerce_checkout_after_customer_details', array( $this, 'add_custom_shipping_section' ), 10 );
939 }
940
941 /**
942 * Change checkout text.
943 */
944 public function update_the_checkout_strings() {
945
946 add_filter( 'cartflows_woo_billling_text', array( $this, 'update_billing_text' ), 10, 1 );
947 add_filter( 'cartflows_woo_shipping_text', array( $this, 'update_shipping_text' ), 10, 1 );
948 add_filter( 'cartflows_woo_customer_info_text', array( $this, 'update_customer_info_text' ), 10, 1 );
949 add_filter( 'cartflows_woo_your_order_text', array( $this, 'update_your_order_text' ), 10, 1 );
950 add_filter( 'cartflows_woo_payment_text', array( $this, 'update_payment_text' ), 10, 1 );
951 }
952
953 /**
954 * Change Payment text .
955 *
956 * @param string $text Payment.
957 * @return string
958 */
959 public function update_payment_text( $text ) {
960
961 $checkout_id = $this->get_checkout_id();
962
963 if ( $checkout_id ) {
964
965 $wcf_payment_text = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-checkout-payment-text' );
966 if ( ! empty( $wcf_payment_text ) ) {
967 $text = $wcf_payment_text;
968 }
969 }
970
971 return $text;
972 }
973
974 /**
975 * Change Your order text.
976 *
977 * @param string $text Your order.
978 * @return string
979 */
980 public function update_your_order_text( $text ) {
981
982 $checkout_id = $this->get_checkout_id();
983
984 if ( $checkout_id ) {
985
986 $wcf_your_order_text = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-checkout-your-order-text' );
987 if ( ! empty( $wcf_your_order_text ) ) {
988 $text = $wcf_your_order_text;
989 }
990 }
991
992 return $text;
993 }
994
995 /**
996 * Change ship to diff address text .
997 *
998 * @param string $text ship to diff address.
999 * @return string
1000 */
1001 public function update_shipping_text( $text ) {
1002
1003 $checkout_id = $this->get_checkout_id();
1004
1005 if ( $checkout_id ) {
1006
1007 $wcf_shipping_details_text = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-checkout-shipping-details-text' );
1008 if ( ! empty( $wcf_shipping_details_text ) ) {
1009 $text = $wcf_shipping_details_text;
1010 }
1011 }
1012
1013 return $text;
1014 }
1015
1016 /**
1017 * Change billing details text .
1018 *
1019 * @param string $text billing details.
1020 * @return string
1021 */
1022 public function update_billing_text( $text ) {
1023
1024 $checkout_id = $this->get_checkout_id();
1025
1026 if ( $checkout_id ) {
1027
1028 $wcf_billing_details_text = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-checkout-billing-details-text' );
1029
1030 if ( ! empty( $wcf_billing_details_text ) ) {
1031 $text = $wcf_billing_details_text;
1032 }
1033 }
1034
1035 return $text;
1036 }
1037
1038 /**
1039 * Change customer information text .
1040 *
1041 * @param string $text customer information.
1042 * @return string
1043 */
1044 public function update_customer_info_text( $text ) {
1045
1046 $checkout_id = $this->get_checkout_id();
1047
1048 if ( $checkout_id ) {
1049
1050 $wcf_customer_info_text = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-checkout-customer-info-text' );
1051
1052 if ( ! empty( $wcf_customer_info_text ) ) {
1053 $text = $wcf_customer_info_text;
1054 }
1055 }
1056
1057 return $text;
1058 }
1059
1060 /**
1061 * Get checkout id.
1062 */
1063 public function get_checkout_id() {
1064
1065 $checkout_id = get_the_ID();
1066
1067 if ( ! $checkout_id && isset( Cartflows_Woo_Hooks::$ajax_data['_wcf_checkout_id'] ) ) {
1068
1069 $checkout_id = intval( Cartflows_Woo_Hooks::$ajax_data['_wcf_checkout_id'] );
1070 }
1071
1072 if ( ! $checkout_id && isset( $_GET['wcf_checkout_id'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
1073
1074 $checkout_id = intval( $_GET['wcf_checkout_id'] ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended
1075 }
1076
1077 return $checkout_id;
1078 }
1079
1080 /**
1081 * Checkout fields actions.
1082 *
1083 * @param array $checkout_fields checkout fields.
1084 * @since 1.10.0
1085 */
1086 public function checkout_fields_actions( $checkout_fields ) {
1087
1088 $checkout_fields = Cartflows_Checkout_Fields::get_instance()->add_three_column_layout_fields( $checkout_fields );
1089
1090 $checkout_fields = $this->prefill_checkout_fields( $checkout_fields );
1091
1092 return $checkout_fields;
1093 }
1094
1095 /**
1096 * Prefill the checkout fields if available in url.
1097 *
1098 * @param array $checkout_fields checkout fields array.
1099 */
1100 public function prefill_checkout_fields( $checkout_fields ) {
1101 //phpcs:disable WordPress.Security.NonceVerification.Recommended
1102 if ( is_auto_prefill_checkout_fields_enabled() && ! empty( $_GET ) ) {
1103
1104 $billing_fields = isset( $checkout_fields['billing'] ) ? $checkout_fields['billing'] : array();
1105 $shipping_fields = isset( $checkout_fields['shipping'] ) ? $checkout_fields['shipping'] : array();
1106
1107 foreach ( $billing_fields as $key => $field ) {
1108 $field_value = isset( $_GET[ $key ] ) && ! empty( $_GET[ $key ] ) ? sanitize_text_field( wp_unslash( $_GET[ $key ] ) ) : '';
1109
1110 if ( ! empty( $field_value ) ) {
1111 $checkout_fields['billing'][ $key ]['default'] = $field_value;
1112 }
1113 }
1114
1115 foreach ( $shipping_fields as $key => $field ) {
1116 $field_value = isset( $_GET[ $key ] ) && ! empty( $_GET[ $key ] ) ? sanitize_text_field( wp_unslash( $_GET[ $key ] ) ) : '';
1117
1118 if ( ! empty( $field_value ) ) {
1119 $checkout_fields['shipping'][ $key ]['default'] = $field_value;
1120 }
1121 }
1122
1123 // WC bakes the country into the state field definition during
1124 // initialize_checkout_fields(). If that ran before our early-registered
1125 // woocommerce_checkout_get_value filter could return the URL param country,
1126 // the wrong country's states would be used. Override it here.
1127 $billing_country = isset( $_GET['billing_country'] ) && ! empty( $_GET['billing_country'] )
1128 ? sanitize_text_field( wp_unslash( $_GET['billing_country'] ) ) : '';
1129
1130 $shipping_country = isset( $_GET['shipping_country'] ) && ! empty( $_GET['shipping_country'] )
1131 ? sanitize_text_field( wp_unslash( $_GET['shipping_country'] ) ) : '';
1132
1133 if ( $billing_country && isset( $checkout_fields['billing']['billing_state'] ) ) {
1134 $checkout_fields['billing']['billing_state']['country'] = $billing_country;
1135 }
1136
1137 if ( $shipping_country && isset( $checkout_fields['shipping']['shipping_state'] ) ) {
1138 $checkout_fields['shipping']['shipping_state']['country'] = $shipping_country;
1139 }
1140 }
1141
1142 return $checkout_fields;
1143 //phpcs:enable WordPress.Security.NonceVerification.Recommended
1144 }
1145
1146 /**
1147 * Return URL param value for a checkout field so that it takes priority
1148 * over saved customer/session data.
1149 *
1150 * WC_Checkout::get_value() checks this filter before falling back to the
1151 * logged-in customer's saved address, so hooking here is the only reliable
1152 * way to pre-fill fields for logged-in users via checkout links.
1153 *
1154 * @since x.x.x
1155 * @param mixed $value The current value (null = not yet set).
1156 * @param string $input The field key (e.g. 'billing_first_name').
1157 * @return mixed URL param value when present, original $value otherwise.
1158 */
1159 public function prefill_checkout_get_value( $value, $input ) {
1160 //phpcs:disable WordPress.Security.NonceVerification.Recommended
1161 if ( is_auto_prefill_checkout_fields_enabled() && ! empty( $_GET[ $input ] ) ) {
1162 return sanitize_text_field( wp_unslash( $_GET[ $input ] ) );
1163 }
1164 //phpcs:enable WordPress.Security.NonceVerification.Recommended
1165 return $value;
1166 }
1167
1168 /**
1169 * Pre-check the "Ship to a different address?" checkbox when any shipping_*
1170 * URL parameter is present, so the pre-filled shipping fields are visible
1171 * on page load instead of hidden inside the collapsed section.
1172 *
1173 * @since x.x.x
1174 * @param int $checked 1 if the checkbox should be pre-checked, 0 otherwise.
1175 * @return int
1176 */
1177 public function maybe_check_ship_to_different_address( $checked ) {
1178 //phpcs:disable WordPress.Security.NonceVerification.Recommended
1179 if ( is_auto_prefill_checkout_fields_enabled() ) {
1180 foreach ( array_keys( $_GET ) as $key ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1181 if ( 0 === strpos( $key, 'shipping_' ) && ! empty( $_GET[ $key ] ) ) {
1182 return 1;
1183 }
1184 }
1185 }
1186 //phpcs:enable WordPress.Security.NonceVerification.Recommended
1187 return $checked;
1188 }
1189
1190 /**
1191 * Render checkout ID hidden field.
1192 *
1193 * @param array $checkout checkout session data.
1194 * @return void
1195 */
1196 public function checkout_shortcode_post_id( $checkout ) {
1197 global $post;
1198
1199 $checkout_id = 0;
1200
1201 if ( _is_wcf_checkout_type() ) {
1202 $checkout_id = $post->ID;
1203 }
1204
1205 $flow_id = get_post_meta( $checkout_id, 'wcf-flow-id', true );
1206
1207 echo '<input type="hidden" class="input-hidden _wcf_flow_id" name="_wcf_flow_id" value="' . intval( $flow_id ) . '">';
1208 echo '<input type="hidden" class="input-hidden _wcf_checkout_id" name="_wcf_checkout_id" value="' . intval( $checkout_id ) . '">';
1209 }
1210
1211 /**
1212 * Load shortcode scripts.
1213 *
1214 * @return void
1215 */
1216 public function shortcode_scripts() {
1217 wp_enqueue_style( 'wcf-checkout-template', wcf()->utils->get_css_url( 'checkout-template' ), '', CARTFLOWS_VER );
1218
1219 wp_enqueue_script(
1220 'wcf-checkout-template',
1221 wcf()->utils->get_js_url( 'checkout-template' ),
1222 array( 'jquery' ),
1223 CARTFLOWS_VER,
1224 true
1225 );
1226
1227 do_action( 'cartflows_checkout_scripts' );
1228
1229 $checkout_dynamic_css = apply_filters( 'cartflows_checkout_enable_dynamic_css', true );
1230
1231 if ( $checkout_dynamic_css ) {
1232
1233 global $post;
1234
1235 $checkout_id = $post->ID;
1236
1237 $style = get_post_meta( $checkout_id, 'wcf-dynamic-css', true );
1238
1239 $css_version = get_post_meta( $checkout_id, 'wcf-dynamic-css-version', true );
1240
1241 // Regenerate the dynamic css only when key is not exist or version does not match.
1242 if ( empty( $style ) || CARTFLOWS_ASSETS_VERSION !== $css_version ) {
1243 $style = $this->generate_style();
1244 update_post_meta( $checkout_id, 'wcf-dynamic-css', wp_slash( $style ) );
1245 update_post_meta( $checkout_id, 'wcf-dynamic-css-version', CARTFLOWS_ASSETS_VERSION );
1246 }
1247
1248 CartFlows_Font_Families::render_fonts( $checkout_id );
1249
1250 wp_add_inline_style( 'wcf-checkout-template', $style );
1251 }
1252 }
1253
1254 /**
1255 * Load compatibility scripts.
1256 *
1257 * @return void
1258 */
1259 public function compatibility_scripts() {
1260 global $post;
1261
1262 $checkout_id = 0;
1263
1264 if ( _is_wcf_checkout_type() ) {
1265 $checkout_id = $post->ID;
1266 }
1267
1268 // Add DIVI Compatibility css if DIVI theme is enabled.
1269 if (
1270 Cartflows_Compatibility::get_instance()->is_divi_enabled() ||
1271 Cartflows_Compatibility::get_instance()->is_divi_builder_enabled( $checkout_id )
1272 ) {
1273 wp_enqueue_style( 'wcf-checkout-template-divi', wcf()->utils->get_css_url( 'checkout-template-divi' ), '', CARTFLOWS_VER );
1274 }
1275
1276 // Add Flatsome Compatibility css if Flatsome theme is enabled.
1277 if ( Cartflows_Compatibility::get_instance()->is_flatsome_enabled() ) {
1278 wp_enqueue_style( 'wcf-checkout-template-flatsome', wcf()->utils->get_css_url( 'checkout-template-flatsome' ), '', CARTFLOWS_VER );
1279 }
1280
1281 // Add The7 Compatibility css if The7 theme is enabled.
1282 if ( Cartflows_Compatibility::get_instance()->is_the_seven_enabled() ) {
1283 wp_enqueue_style( 'wcf-checkout-template-the-seven', wcf()->utils->get_css_url( 'checkout-template-the-seven' ), '', CARTFLOWS_VER );
1284 }
1285 }
1286
1287 /**
1288 * Generate styles.
1289 *
1290 * @return string
1291 */
1292 public function generate_style() {
1293 global $post;
1294
1295 $checkout_id = 0;
1296
1297 if ( _is_wcf_checkout_type() ) {
1298 $checkout_id = $post->ID;
1299 }
1300
1301 /*Output css variable */
1302 $output = '';
1303
1304 $flow_id = wcf()->utils->is_step_post_type() ? wcf()->utils->get_flow_id() : 0;
1305 $enable_design_setting = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-enable-design-settings' );
1306 $enable_place_order_button_lock = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-checkout-place-order-button-lock' );
1307
1308 if ( 'yes' === $enable_design_setting ) {
1309
1310 $checkout_layout = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-checkout-layout' );
1311
1312 $primary_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-primary-color' );
1313
1314 $base_font_family = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-base-font-family' );
1315
1316 $header_logo_width = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-header-logo-width' );
1317
1318 $r = '';
1319 $g = '';
1320 $b = '';
1321
1322 $field_tb_padding = '';
1323 $field_lr_padding = '';
1324
1325 $field_heading_color = '';
1326 $field_color = '';
1327 $field_bg_color = '';
1328 $field_border_color = '';
1329 $field_label_color = '';
1330 $submit_tb_padding = '';
1331 $submit_lr_padding = '';
1332 $hl_bg_color = '';
1333 $field_input_size = '';
1334 $box_border_color = '';
1335 $section_bg_color = '';
1336 $submit_button_height = '';
1337 $submit_color = '';
1338 $submit_bg_color = $primary_color;
1339 $submit_border_color = $primary_color;
1340
1341 $submit_hover_color = '';
1342 $submit_bg_hover_color = $primary_color;
1343 $submit_border_hover_color = $primary_color;
1344
1345 $section_heading_color = '';
1346
1347 $is_advance_option = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-advance-options-fields' );
1348
1349 $button_font_family = '';
1350 $button_font_weight = '';
1351 $input_font_family = '';
1352 $input_font_weight = '';
1353 $heading_font_family = '';
1354 $heading_font_weight = '';
1355 $base_font_family = $base_font_family;
1356
1357 if ( 'yes' == $is_advance_option ) {
1358
1359 /**
1360 * Get Font Family and Font Weight weight values
1361 */
1362 $section_bg_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-section-bg-color' );
1363
1364 $heading_font_family = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-heading-font-family' );
1365 $heading_font_weight = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-heading-font-weight' );
1366 $section_heading_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-heading-color' );
1367 $button_font_family = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-button-font-family' );
1368 $button_font_weight = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-button-font-weight' );
1369 $input_font_family = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-input-font-family' );
1370 $input_font_weight = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-input-font-weight' );
1371 $field_tb_padding = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-field-tb-padding' );
1372 $field_lr_padding = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-field-lr-padding' );
1373 $field_input_size = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-input-field-size' );
1374
1375 $field_heading_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-field-heading-color' );
1376
1377 $field_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-field-color' );
1378
1379 $field_bg_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-field-bg-color' );
1380
1381 $field_border_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-field-border-color' );
1382
1383 $field_label_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-field-label-color' );
1384
1385 $submit_tb_padding = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-submit-tb-padding' );
1386
1387 $submit_lr_padding = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-submit-lr-padding' );
1388
1389 $submit_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-submit-color' );
1390
1391 $submit_bg_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-submit-bg-color', $primary_color );
1392
1393 $submit_border_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-submit-border-color', $primary_color );
1394
1395 $submit_border_hover_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-submit-border-hover-color', $primary_color );
1396
1397 $submit_hover_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-submit-hover-color' );
1398
1399 $submit_bg_hover_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-submit-bg-hover-color', $primary_color );
1400
1401 $hl_bg_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-hl-bg-color' );
1402
1403 $box_border_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-box-border-color' );
1404
1405 $submit_button_height = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-input-button-size' );
1406
1407 /**
1408 * Get font values
1409 */
1410
1411 if ( 'custom' == $submit_button_height ) {
1412 $submit_button_height = '38px';
1413 }
1414
1415 if ( 'custom' == $field_input_size ) {
1416 $field_input_size = '38px';
1417 }
1418 }
1419 if ( isset( $primary_color ) ) {
1420
1421 list($r, $g, $b) = sscanf( $primary_color, '#%02x%02x%02x' );
1422 }
1423
1424 /** Override the global colors and assign it to the default variables */
1425 $flow_global_style = ! empty( $flow_id ) ? wcf()->options->get_flow_meta_value( $flow_id, 'wcf-enable-gcp-styling', 'no' ) : false;
1426
1427 if ( 'yes' === $flow_global_style ) {
1428 $primary_color = ! empty( $flow_id ) ? get_post_meta( $flow_id, 'wcf-gcp-primary-color', true ) : '';
1429 $field_color = ! empty( $flow_id ) ? get_post_meta( $flow_id, 'wcf-gcp-text-color', true ) : '';
1430 $section_heading_color = ! empty( $flow_id ) ? get_post_meta( $flow_id, 'wcf-gcp-accent-color', true ) : '';
1431 }
1432
1433 /** Override the global colors and assign it to the default variables */
1434
1435 $submit_btn_bg_color = ( $submit_bg_color ) ? $submit_bg_color : $primary_color;
1436 $submit_btn_bg_hover_color = ( $submit_bg_hover_color ) ? $submit_bg_hover_color : $primary_color;
1437
1438 $output .= '.cartflows_step-template .select2-container--default .select2-results__option--highlighted[aria-selected], .cartflows_step-template .select2-container--default .select2-results__option--highlighted[data-selected], .wcf-embed-checkout-form { ';
1439 $output .= ! empty( $primary_color ) ? '--wcf-primary-color: ' . $primary_color . ';' : '';
1440 $output .= ! empty( $section_heading_color ) ? '--wcf-heading-color: ' . $section_heading_color . ';' : '';
1441 $output .= ! empty( $submit_btn_bg_color ) ? '--wcf-btn-bg-color: ' . $submit_btn_bg_color . ';' : '';
1442 $output .= ! empty( $submit_btn_bg_hover_color ) ? '--wcf-btn-bg-hover-color: ' . $submit_btn_bg_hover_color . ';' : '';
1443 $output .= ! empty( $submit_color ) ? '--wcf-btn-text-color: ' . $submit_color . ';' : '';
1444 $output .= ! empty( $submit_hover_color ) ? '--wcf-btn-hover-text-color: ' . $submit_hover_color . ';' : '';
1445 $output .= ! empty( $field_label_color ) ? '--wcf-field-label-color: ' . $field_label_color . ';' : '';
1446 $output .= ! empty( $field_bg_color ) ? '--wcf-field-bg-color: ' . $field_bg_color . ';' : '';
1447 $output .= ! empty( $field_border_color ) ? '--wcf-field-border-color:' . $field_border_color . ';' : '';
1448 $output .= ! empty( $field_color ) ? '--wcf-field-text-color: ' . $field_color . ';' : '';
1449 $output .= '}';
1450
1451 // Add instant layout styles.
1452 if ( Cartflows_Helper::is_instant_layout_enabled() ) {
1453
1454 $instant_checkout_left_column_bg_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-instant-checkout-left-side-bg-color' );
1455 $instant_checkout_right_column_bg_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-instant-checkout-right-side-bg-color' );
1456
1457 $submit_btn_bg_color = ( $submit_bg_color ) ? $submit_bg_color : $primary_color;
1458 $submit_btn_bg_hover_color = ( $submit_bg_hover_color ) ? $submit_bg_hover_color : $primary_color;
1459
1460 $output .= 'body .wcf-embed-checkout-form.wcf-embed-checkout-form-instant-checkout { ';
1461
1462 $output .= ! empty( $primary_color ) ? '--wcf-primary-color: ' . $primary_color . ';' : '';
1463 $output .= ! empty( $section_heading_color ) ? '--wcf-heading-color: ' . $section_heading_color . ';' : '';
1464 $output .= ! empty( $heading_font_weight ) ? '--wcf-heading-font-weight: ' . $heading_font_weight . ';' : '';
1465
1466 $output .= ! empty( $field_label_color ) ? '--wcf-field-label-color: ' . $field_label_color . ';' : '';
1467 $output .= ! empty( $field_input_size ) ? '--wcf-field-min-height: ' . $field_input_size . ';' : '';
1468 $output .= ! empty( $field_bg_color ) ? '--wcf-field-bg-color: ' . $field_bg_color . ';' : '';
1469 $output .= ! empty( $field_border_color ) ? '--wcf-field-border-color:' . $field_border_color . ';' : '';
1470 $output .= ! empty( $field_color ) ? '--wcf-field-text-color: ' . $field_color . ';' : '';
1471
1472 $output .= ! empty( $submit_btn_bg_color ) ? '--wcf-btn-bg-color: ' . $submit_btn_bg_color . ';' : '';
1473 $output .= ! empty( $submit_btn_bg_hover_color ) ? '--wcf-btn-bg-hover-color: ' . $submit_btn_bg_hover_color . ';' : '';
1474 $output .= ! empty( $submit_color ) ? '--wcf-btn-text-color: ' . $submit_color . ';' : '';
1475 $output .= ! empty( $submit_hover_color ) ? '--wcf-btn-hover-text-color: ' . $submit_hover_color . ';' : '';
1476
1477 $output .= ! empty( $hl_bg_color ) ? '--wcf-payment-section-desc-bg-color: ' . $hl_bg_color . ';' : '';
1478
1479 $output .= ! empty( $instant_checkout_left_column_bg_color ) ? '--wcf-ic-left-column-bg-color: ' . $instant_checkout_left_column_bg_color . ';' : '';
1480 $output .= ! empty( $instant_checkout_right_column_bg_color ) ? '--wcf-ic-right-column-bg-color: ' . $instant_checkout_right_column_bg_color . ';' : '';
1481
1482 $output .= '}';
1483 }
1484
1485 if (
1486 Cartflows_Compatibility::get_instance()->is_divi_enabled() ||
1487 Cartflows_Compatibility::get_instance()->is_divi_builder_enabled( $checkout_id )
1488 ) {
1489
1490 include CARTFLOWS_CHECKOUT_DIR . 'includes/checkout-dynamic-divi-css.php';
1491 } else {
1492 include CARTFLOWS_CHECKOUT_DIR . 'includes/checkout-dynamic-css.php';
1493 }
1494 }
1495
1496
1497 if ( 'yes' === $enable_place_order_button_lock ) {
1498 // If enabled then add the below css to show the lock icon on place order button.
1499 $output .= '
1500 .wcf-embed-checkout-form .woocommerce #payment #place_order:before{
1501 content: "\e902";
1502 font-family: "cartflows-icon" !important;
1503 margin-right: 10px;
1504 font-size: 16px;
1505 font-weight: 500;
1506 top: 0px;
1507 position: relative;
1508 opacity: 1;
1509 display: block;
1510 }';
1511 }
1512
1513 return $output;
1514 }
1515
1516 /**
1517 * Save checkout fields.
1518 *
1519 * @param int $order_id order id.
1520 * @param array $posted posted data.
1521 * @return void
1522 */
1523 public function save_checkout_fields( $order_id, $posted ) {
1524 //phpcs:disable WordPress.Security.NonceVerification
1525 $checkout_id = '';
1526 $flow_id = '';
1527 if ( isset( $_POST['_wcf_checkout_id'] ) ) {
1528 $checkout_id = intval( $_POST['_wcf_checkout_id'] );
1529 $flow_id = isset( $_POST['_wcf_flow_id'] ) ? intval( $_POST['_wcf_flow_id'] ) : 0;
1530
1531 } elseif ( isset( $_GET['wcf_checkout_id'] ) ) {
1532 $checkout_id = intval( $_GET['wcf_checkout_id'] );
1533 $flow_id = wcf()->utils->get_flow_id_from_step_id( $checkout_id );
1534 } elseif ( isset( $posted['_wcf_checkout_id'] ) ) {
1535 $checkout_id = intval( $posted['_wcf_checkout_id'] );
1536 $flow_id = isset( $posted['_wcf_flow_id'] ) ? intval( $posted['_wcf_flow_id'] ) : wcf()->utils->get_flow_id_from_step_id( $checkout_id );
1537 }
1538
1539 if ( empty( $checkout_id ) && empty( $flow_id ) ) {
1540 $meta_data = $this->get_cartflows_checkout_id_and_flow_id_from_cart();
1541
1542 if ( ! empty( $meta_data ) && is_array( $meta_data ) ) {
1543 $checkout_id = $meta_data['checkout_id'];
1544 $flow_id = $meta_data['flow_id'];
1545 }
1546 }
1547
1548 $order = wc_get_order( $order_id );
1549 // Ensure $order is a WC_Order instance.
1550 if ( ! $order instanceof WC_Order ) {
1551 return;
1552 }
1553 $this->store_flow_metadata_on_order( $checkout_id, $flow_id, $order );
1554
1555 // Save file upload custom field values to order meta.
1556 if ( $checkout_id ) {
1557 $this->save_file_field_meta( (int) $checkout_id, $order );
1558 }
1559 //phpcs:enable WordPress.Security.NonceVerification
1560 }
1561
1562 /**
1563 * Save file upload field values to order meta.
1564 *
1565 * @param int $checkout_id The checkout step ID.
1566 * @param WC_Order $order The order object.
1567 * @return void
1568 */
1569 private function save_file_field_meta( $checkout_id, $order ) {
1570
1571 $field_groups = array(
1572 'wcf_field_order_billing',
1573 'wcf_field_order_shipping',
1574 );
1575
1576 foreach ( $field_groups as $meta_key ) {
1577 $saved_fields = get_post_meta( $checkout_id, $meta_key, true );
1578
1579 if ( ! is_array( $saved_fields ) ) {
1580 continue;
1581 }
1582
1583 foreach ( $saved_fields as $field_key => $field_data ) {
1584 if ( ! isset( $field_data['type'] ) || 'file' !== $field_data['type'] ) {
1585 continue;
1586 }
1587
1588 // phpcs:ignore WordPress.Security.NonceVerification.Missing
1589 if ( ! empty( $_POST[ $field_key ] ) ) {
1590
1591 $file_url = esc_url_raw( wp_unslash( $_POST[ $field_key ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
1592
1593 if ( ! empty( $file_url ) ) {
1594 $order->update_meta_data( $field_key, $file_url );
1595 }
1596 }
1597 }
1598 }
1599
1600 $order->save();
1601 }
1602
1603 /**
1604 * Add the checkout ID and Flow ID to the order meta data.
1605 *
1606 * @param WC_Order $order The order object.
1607 * @return void
1608 */
1609 public function add_order_meta_for_store_api_checkout( $order ) {
1610 // Ensure $order is a WC_Order instance.
1611 if ( ! $order instanceof WC_Order ) {
1612 return;
1613 }
1614
1615 $checkout_id = '';
1616 $flow_id = '';
1617 $meta_data = $this->get_cartflows_checkout_id_and_flow_id_from_cart();
1618
1619 if ( empty( $meta_data ) ) {
1620 // Store Checkout products carry no cart item meta and express checkout sends no POST data.
1621 $meta_data = $this->get_store_checkout_id_and_flow_id();
1622 }
1623
1624 if ( ! empty( $meta_data ) && is_array( $meta_data ) ) {
1625 $checkout_id = $meta_data['checkout_id'];
1626 $flow_id = $meta_data['flow_id'];
1627 }
1628
1629 $this->store_flow_metadata_on_order( $checkout_id, $flow_id, $order );
1630 }
1631
1632 /**
1633 * Retrieve the checkout ID and flow ID of the configured Store Checkout.
1634 * Returns null when no Store Checkout is set or it has no checkout step.
1635 *
1636 * @return array{checkout_id: string, flow_id: string}|null
1637 */
1638 private function get_store_checkout_id_and_flow_id() {
1639 $store_checkout = Cartflows_Helper::get_global_setting( '_cartflows_store_checkout' );
1640 $flow_id = is_scalar( $store_checkout ) ? intval( $store_checkout ) : 0;
1641
1642 if ( empty( $flow_id ) ) {
1643 return null;
1644 }
1645
1646 $steps = get_post_meta( $flow_id, 'wcf-steps', true );
1647
1648 if ( ! is_array( $steps ) ) {
1649 return null;
1650 }
1651
1652 foreach ( $steps as $step ) {
1653 if ( isset( $step['id'], $step['type'] ) && 'checkout' === $step['type'] ) {
1654 return array(
1655 'checkout_id' => (string) $step['id'],
1656 'flow_id' => (string) $flow_id,
1657 );
1658 }
1659 }
1660
1661 return null;
1662 }
1663
1664 /**
1665 * Retrieve CartFlows checkout ID and flow ID stored in cart item meta.
1666 * If no matching cart item or cart is empty, returns null.
1667 *
1668 * @return array{checkout_id: string, flow_id: string}|null
1669 */
1670 private function get_cartflows_checkout_id_and_flow_id_from_cart() {
1671 if ( WC()->cart instanceof WC_Cart ) {
1672 $checkout_id = '';
1673 $flow_id = '';
1674 $cart_contents = WC()->cart->get_cart_contents();
1675 if ( empty( $cart_contents ) ) {
1676 return null;
1677 }
1678
1679 foreach ( $cart_contents as $cart_item ) {
1680 if ( ! isset( $cart_item['wcf_product_data'] ) ) {
1681 continue;
1682 }
1683
1684 $checkout_id = (string) $cart_item['wcf_product_data']['wcf_checkout_id'];
1685 $flow_id = (string) $cart_item['wcf_product_data']['wcf_flow_id'];
1686
1687 // if both found, stop scanning further items.
1688 if ( ! empty( $checkout_id ) && ! empty( $flow_id ) ) {
1689 break;
1690 }
1691 }
1692
1693 if ( ! empty( $flow_id ) && ! empty( $checkout_id ) ) {
1694 return array(
1695 'checkout_id' => $checkout_id,
1696 'flow_id' => $flow_id,
1697 );
1698 }
1699 }
1700
1701 return null;
1702 }
1703
1704 /**
1705 * Add checkout ID and flow ID as order meta on a given order.
1706 *
1707 * Accepts either a WC_Order object. Validates that
1708 * the provided post IDs match the expected post types (CARTFLOWS_FLOW_POST_TYPE
1709 * and CARTFLOWS_STEP_POST_TYPE) before saving meta.
1710 *
1711 * @param int|string $checkout_id Checkout (step) post ID (or empty).
1712 * @param int|string $flow_id Flow post ID (or empty).
1713 * @param WC_Order|bool $order WC_Order object, or null.
1714 * @return void
1715 */
1716 private function store_flow_metadata_on_order( $checkout_id, $flow_id, $order ) {
1717 if ( empty( $flow_id ) || empty( $checkout_id ) ) {
1718 // nothing to store.
1719 return;
1720 }
1721 // Ensure $order is a WC_Order instance.
1722 if ( ! $order instanceof WC_Order ) {
1723 return;
1724 }
1725
1726 // Convert to integers to get post type.
1727 $checkout_id = absint( $checkout_id );
1728 $flow_id = absint( $flow_id );
1729
1730 if ( CARTFLOWS_FLOW_POST_TYPE === get_post_type( $flow_id ) ) {
1731 $order->update_meta_data( '_wcf_flow_id', (string) $flow_id );
1732 }
1733
1734 if ( CARTFLOWS_STEP_POST_TYPE === get_post_type( $checkout_id ) ) {
1735 $order->update_meta_data( '_wcf_checkout_id', (string) $checkout_id );
1736 }
1737
1738 $order->save();
1739 }
1740
1741 /**
1742 * Enable Logo In Header Of Checkout Page
1743 *
1744 * @return void
1745 */
1746 public function enable_logo_in_header() {
1747 global $post;
1748
1749 $checkout_id = 0;
1750
1751 if ( _is_wcf_checkout_type() ) {
1752 $checkout_id = $post->ID;
1753 }
1754
1755 $header_logo_image = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-header-logo-image' );
1756 $add_image_markup = '';
1757
1758 if ( isset( $header_logo_image ) && ! empty( $header_logo_image ) ) {
1759 $add_image_markup = '<div class="wcf-checkout-header-image">';
1760 $add_image_markup .= '<img src="' . esc_url( $header_logo_image ) . '" />';
1761 $add_image_markup .= '</div>';
1762 }
1763
1764 echo wp_kses_post( $add_image_markup );
1765 }
1766
1767 /**
1768 * Add text to the bootom of the checkout page.
1769 *
1770 * @return void
1771 */
1772 public function show_cartflows_copyright_message() {
1773 $output_string = '';
1774
1775 $output_string .= '<div class="wcf-footer-primary">';
1776 $output_string .= '<div class="wcf-footer-content">';
1777 $output_string .= '<p class="wcf-footer-message">';
1778 $output_string .= 'Checkout powered by CartFlows';
1779 $output_string .= '</p>';
1780 $output_string .= '</div>';
1781 $output_string .= '</div>';
1782
1783 echo wp_kses_post( $output_string );
1784 }
1785
1786 /**
1787 * Redirect users to our checkout if hidden param
1788 *
1789 * @param string $redirect redirect url.
1790 * @param object $user user.
1791 * @return string
1792 */
1793 public function after_login_redirect( $redirect, $user ) {
1794 // We are calling this function on WooCommerce action where nonce is verified.
1795 //phpcs:disable WordPress.Security.NonceVerification.Missing
1796 if ( isset( $_POST['_wcf_checkout_id'] ) ) {
1797
1798 $checkout_id = intval( $_POST['_wcf_checkout_id'] );
1799
1800 $redirect = get_permalink( $checkout_id );
1801 }
1802 //phpcs:enable WordPress.Security.NonceVerification.Missing
1803
1804 return $redirect;
1805 }
1806
1807 /**
1808 * Display coupon code field after review order fields.
1809 */
1810 public function display_custom_coupon_field() {
1811
1812 if ( ! $this->is_custom_coupon_field_enabled() ) {
1813 return;
1814 }
1815
1816 $coupon_field = array(
1817 'field_text' => __( 'Coupon Code', 'cartflows' ),
1818 'button_text' => __( 'Apply', 'cartflows' ),
1819 'class' => '',
1820 );
1821
1822 $coupon_field = apply_filters( 'cartflows_coupon_field_options', $coupon_field );
1823
1824 ob_start();
1825 do_action( 'cartflows_before_custom_field_html', $coupon_field );
1826 ?>
1827 <div class="wcf-custom-coupon-field <?php echo esc_attr( $coupon_field['class'] ); ?>" id="wcf_custom_coupon_field">
1828 <div class="wcf-coupon-col-1">
1829 <span>
1830 <input type="text" name="coupon_code" class="input-text wcf-coupon-code-input" placeholder="<?php echo esc_attr( $coupon_field['field_text'] ); ?>" id="coupon_code" value="">
1831 </span>
1832 </div>
1833 <div class="wcf-coupon-col-2">
1834 <span>
1835 <button type="button" class="button wcf-submit-coupon wcf-btn-small" name="apply_coupon" value="Apply"><?php echo esc_html( $coupon_field['button_text'] ); ?></button>
1836 </span>
1837 </div>
1838 </div>
1839 <?php
1840 do_action( 'cartflows_after_custom_field_html', $coupon_field );
1841 // wp_kses_post will not work as it removing input tags hence ignoring below rule.
1842 echo ob_get_clean(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1843 }
1844
1845 /**
1846 * Apply filter to change class of remove coupon field.
1847 *
1848 * @param string $coupon coupon.
1849 * @return string
1850 */
1851 public function remove_coupon_text( $coupon ) {
1852 $coupon = str_replace( 'woocommerce-remove-coupon', 'wcf-remove-coupon', $coupon );
1853 return $coupon;
1854 }
1855 /**
1856 * Apply filter to change the placeholder text of coupon field.
1857 *
1858 * @return string
1859 */
1860 public function coupon_field_placeholder() {
1861 return apply_filters( 'cartflows_coupon_field_placeholder', __( 'Coupon Code', 'cartflows' ) );
1862 }
1863
1864 /**
1865 * Apply filter to change the button text of coupon field.
1866 *
1867 * @return string
1868 */
1869 public function coupon_button_text() {
1870 return apply_filters( 'cartflows_coupon_button_text', __( 'Apply', 'cartflows' ) );
1871 }
1872
1873 /**
1874 * Added ajax nonce to localize variable.
1875 *
1876 * @param array $vars localize variables.
1877 */
1878 public function add_localize_vars( $vars ) {
1879 $vars['wcf_validate_coupon_nonce'] = wp_create_nonce( 'wcf-apply-coupon' );
1880
1881 $vars['wcf_validate_remove_coupon_nonce'] = wp_create_nonce( 'wcf-remove-coupon' );
1882
1883 $vars['wcf_validate_remove_cart_product_nonce'] = wp_create_nonce( 'wcf-remove-cart-product' );
1884
1885 $vars['check_email_exist_nonce'] = wp_create_nonce( 'check-email-exist' );
1886
1887 $vars['woocommerce_login_nonce'] = wp_create_nonce( 'woocommerce-login' );
1888
1889 $vars['wcf_file_upload_nonce'] = wp_create_nonce( 'wcf-file-upload' );
1890
1891 $vars['allow_persistence'] = apply_filters( 'cartflows_allow_persistence', 'yes' );
1892
1893 $vars['is_logged_in'] = is_user_logged_in();
1894
1895 $vars['email_validation_msgs'] = array(
1896 'error_msg' => __( 'Entered email address is not a valid email.', 'cartflows' ),
1897 'success_msg' => __( 'This email is already registered. Please enter the password to continue.', 'cartflows' ),
1898 );
1899 $vars['field_validation_msgs'] = array(
1900 'number_field' => __( 'Value must be between ', 'cartflows' ),
1901 );
1902
1903 $vars['order_review_toggle_texts'] = array(
1904 'toggle_show_text' => $this->get_order_review_toggle_texts(),
1905 'toggle_hide_text' => $this->get_order_review_toggle_texts( 'hide_text' ),
1906 );
1907
1908 $checkout_id = _get_wcf_checkout_id();
1909
1910 $checkout_id = $checkout_id ? $checkout_id : 0;
1911
1912 $vars['field_validation'] = array(
1913 'is_enabled' => wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-enable-checkout-field-validation-text' ),
1914 'error_msg' => wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-checkout-field-validation-text' ),
1915 );
1916
1917 // Detect whether any file upload fields exist on checkout.
1918 $has_file_field = false;
1919 if ( $checkout_id ) {
1920 $billing_fields = get_post_meta( $checkout_id, 'wcf_field_order_billing', true );
1921 $shipping_fields = get_post_meta( $checkout_id, 'wcf_field_order_shipping', true );
1922 $all_fields = array_merge( is_array( $billing_fields ) ? $billing_fields : array(), is_array( $shipping_fields ) ? $shipping_fields : array() );
1923
1924 foreach ( $all_fields as $field_data ) {
1925 if ( isset( $field_data['type'] ) && 'file' === $field_data['type'] && ! empty( $field_data['enabled'] ) ) {
1926 $has_file_field = true;
1927 break;
1928 }
1929 }
1930 }
1931 $vars['has_file_field'] = $has_file_field;
1932
1933 return $vars;
1934 }
1935
1936 /**
1937 * Calculate discount for product.
1938 *
1939 * @param string $discount_coupon discount coupon.
1940 * @param string $discount_type discount type.
1941 * @param int $discount_value discount value.
1942 * @param int $_product_price product price.
1943 * @return int
1944 * @since 1.1.5
1945 */
1946 public function calculate_discount( $discount_coupon, $discount_type, $discount_value, $_product_price ) {
1947 $custom_price = '';
1948
1949 if ( ! empty( $discount_type ) ) {
1950 if ( 'discount_percent' === $discount_type ) {
1951
1952 if ( $discount_value > 0 ) {
1953 $custom_price = $_product_price - ( ( $_product_price * $discount_value ) / 100 );
1954 }
1955 } elseif ( 'discount_price' === $discount_type ) {
1956
1957 if ( $discount_value > 0 ) {
1958 $custom_price = $_product_price - $discount_value;
1959 }
1960 } elseif ( 'coupon' === $discount_type ) {
1961
1962 if ( ! empty( $discount_coupon ) ) {
1963 WC()->cart->add_discount( $discount_coupon );
1964 }
1965 }
1966 }
1967
1968 return $custom_price;
1969 }
1970
1971 /**
1972 * Preserve the custom item price added by Variations & Quantity feature
1973 *
1974 * @param Wc_cart $cart_object cart object.
1975 * @since 1.0.0
1976 */
1977 public function custom_price_to_cart_item( $cart_object ) {
1978 if ( apply_filters( 'cartflows_update_product_custom_price', wp_doing_ajax() ) && ! WC()->session->__isset( 'reload_checkout' ) ) {
1979
1980 foreach ( $cart_object->cart_contents as $key => $value ) {
1981
1982 if ( isset( $value['custom_price'] ) ) {
1983
1984 $custom_price = floatval( $value['custom_price'] );
1985 $value['data']->set_price( $custom_price );
1986 }
1987 }
1988 }
1989 }
1990
1991 /**
1992 * Get random product for test mode.
1993 */
1994 public function get_random_products() {
1995
1996 $products = array();
1997
1998 $args = array(
1999 'posts_per_page' => 1,
2000 'orderby' => 'rand',
2001 'post_type' => 'product',
2002 'meta_query' => array( //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
2003 // Exclude out of stock products.
2004 array(
2005 'key' => '_stock_status',
2006 'value' => 'outofstock',
2007 'compare' => 'NOT IN',
2008 ),
2009 ),
2010 'tax_query' => array( //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
2011 array(
2012 'taxonomy' => 'product_type',
2013 'field' => 'slug',
2014 'terms' => 'simple',
2015 ),
2016 ),
2017 );
2018
2019 $random_product = get_posts( $args );
2020
2021 if ( isset( $random_product[0]->ID ) ) {
2022 $products = array(
2023 array(
2024 'product' => $random_product[0]->ID,
2025 'unique_id' => wcf()->utils->get_unique_id(),
2026 'add_to_cart' => true,
2027 ),
2028 );
2029 }
2030
2031 return $products;
2032 }
2033
2034 /**
2035 * Change the Shipping error messages HTML
2036 *
2037 * @param string $message shipping message.
2038 *
2039 * @return string
2040 */
2041 public function change_shipping_message_html( $message ) {
2042
2043 $checkout_id = _get_wcf_checkout_id();
2044
2045 if ( ! $checkout_id ) {
2046
2047 $checkout_id = isset( $_GET['wcf_checkout_id'] ) && ! empty( $_GET['wcf_checkout_id'] ) ? intval( wp_unslash( $_GET['wcf_checkout_id'] ) ) : 0; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
2048 }
2049
2050 if ( empty( $checkout_id ) ) {
2051 return $message;
2052 }
2053
2054 $enabled_custom_shipping_message = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-custom-no-shipping-method-message-toggle' );
2055
2056 if ( 'yes' === $enabled_custom_shipping_message ) {
2057 $custom_shipping_message = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-no-shipping-method-message' );
2058
2059 $message = ! empty( $custom_shipping_message ) ? $custom_shipping_message : $message;
2060
2061 }
2062 return "<span class='wcf-shipping-error-msg'>" . $message . '</span>';
2063 }
2064
2065 /**
2066 * Update cart total on button and order review mobile sction.
2067 *
2068 * @param string $fragments shipping message.
2069 *
2070 * @return array $fragments updated Woo fragments.
2071 */
2072 public function add_updated_cart_price( $fragments ) {
2073
2074 $checkout_id = _get_wcf_checkout_id();
2075
2076 if ( ! $checkout_id ) {
2077
2078 $checkout_id = isset( $_GET['wcf_checkout_id'] ) && ! empty( $_GET['wcf_checkout_id'] ) ? intval( wp_unslash( $_GET['wcf_checkout_id'] ) ) : 0; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
2079 }
2080
2081 if ( empty( $checkout_id ) ) {
2082 return $fragments;
2083 }
2084 ob_start();
2085 echo "<div class='wcf-customer-shipping'>";
2086 $this->wcf_cart_totals_shipping_html();
2087 echo '</div>';
2088 $wcf_shipping_method_html = ob_get_clean();
2089
2090 ob_start();
2091 $this->wcf_order_review();
2092 $wcf_order_review = ob_get_clean();
2093
2094 ob_start();
2095 $this->show_selected_shipping_method();
2096 $selected_shipping_html = ob_get_clean();
2097
2098 $fragments['.woocommerce-checkout-review-order-table'] = $wcf_order_review;
2099 $fragments['.wcf-embed-checkout-form .wcf-shipping-methods-wrapper'] = $wcf_shipping_method_html;
2100 $fragments['.wcf-embed-checkout-form .woocommerce-checkout-review-order-table .cart-shipping'] = $selected_shipping_html;
2101 $fragments['.wcf-embed-checkout-form .wcf-customer-shipping'] = $wcf_shipping_method_html;
2102 $fragments['.wcf-embed-checkout-form .wcf-order-review-total'] = "<div class='wcf-order-review-total'>" . WC()->cart->get_total() . '</div>';
2103
2104
2105 return $fragments;
2106 }
2107
2108 /**
2109 * Array of order review toggler text.
2110 *
2111 * @param string $text array key to get specific value.
2112 *
2113 * @return string
2114 */
2115 public function get_order_review_toggle_texts( $text = 'show_text' ) {
2116
2117 $toggle_texts = apply_filters(
2118 'cartflows_order_review_toggle_texts',
2119 array(
2120 'show_text' => esc_html__( 'Show Order Summary', 'cartflows' ),
2121 'hide_text' => esc_html__( 'Hide Order Summary', 'cartflows' ),
2122 )
2123 );
2124
2125 return $toggle_texts[ $text ];
2126 }
2127
2128 /**
2129 * Get WC shipping methods HTML for modern Checkout.
2130 */
2131 public function wcf_cart_totals_shipping_html() {
2132
2133 // Return if WooCommerce is not active. Also check for wc is exists or not.
2134 if ( ! wcf()->is_woo_active || ! function_exists( 'WC' ) ) {
2135 return;
2136 }
2137
2138 $packages = WC()->shipping()->get_packages();
2139 if ( empty( $packages ) ) {
2140 WC()->cart->calculate_totals();
2141 $packages = WC()->shipping()->get_packages();
2142 }
2143 $first = true;
2144
2145 foreach ( $packages as $i => $package ) {
2146 $chosen_method = isset( WC()->session->chosen_shipping_methods[ $i ] ) ? WC()->session->chosen_shipping_methods[ $i ] : '';
2147 $product_names = array();
2148
2149 if ( count( $packages ) > 1 ) {
2150 foreach ( $package['contents'] as $item_id => $values ) {
2151 $product_names[ $item_id ] = $values['data']->get_name() . ' &times;' . $values['quantity'];
2152 }
2153 $product_names = apply_filters( 'woocommerce_shipping_package_details_array', $product_names, $package );
2154 }
2155
2156 include CARTFLOWS_CHECKOUT_DIR . 'templates/checkout/shipping-methods.php';
2157 do_action( 'woocommerce_review_order_after_shipping' );
2158
2159 $first = false;
2160 }
2161 }
2162
2163 /**
2164 * Get WC order review table HTML for modern Checkout.
2165 */
2166 public function wcf_order_review() {
2167
2168 // Return if Woo is not installed.
2169 if ( ! wcf()->is_woo_active ) {
2170 return;
2171 }
2172
2173 $checkout_id = _get_wcf_checkout_id();
2174
2175 if ( ! $checkout_id ) {
2176 $checkout_id = isset( $_GET['wcf_checkout_id'] ) && ! empty( $_GET['wcf_checkout_id'] ) ? intval( wp_unslash( $_GET['wcf_checkout_id'] ) ) : 0; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
2177 }
2178 $checkout_layout = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-checkout-layout' );
2179 $path_to_order_review_template = apply_filters( 'cartflows_get_order_review_template_path', CARTFLOWS_CHECKOUT_DIR . 'templates/checkout/order-review-table.php', $checkout_layout );
2180
2181
2182 include $path_to_order_review_template;
2183 }
2184
2185 /**
2186 * Check for the CartFlows coupon field is enabled or disabled.
2187 */
2188 public function is_custom_coupon_field_enabled() {
2189
2190 $enabled = false;
2191
2192 $coupon_enabled = apply_filters( 'woocommerce_coupons_enabled', true );
2193 $show_coupon = apply_filters( 'cartflows_show_coupon_field', true );
2194
2195 if ( $coupon_enabled && $show_coupon ) {
2196 $enabled = true;
2197 }
2198
2199 return $enabled;
2200 }
2201
2202 /**
2203 * Add custom shipping method section if not multistep checkout.
2204 *
2205 * @since 2.1.0
2206 * @return void
2207 */
2208 public function add_custom_shipping_section() {
2209 // Return if the current page is not a CartFlows Checkout page or if any plugin's styles are set to be skipped.
2210 if ( ! ( _is_wcf_checkout_type() || _is_wcf_doing_checkout_ajax() ) || ! apply_filters( 'cartflows_should_render_custom_shipping', true ) ) {
2211 return;
2212 }
2213 ob_start();
2214 echo "<div class='wcf-customer-shipping'>";
2215 $this->wcf_cart_totals_shipping_html();
2216 echo '</div>';
2217 ob_end_flush();
2218 }
2219
2220 /**
2221 * Show Selected Shipping Method in the order review table.
2222 *
2223 * @since 2.1.0
2224 * @return void
2225 */
2226 public static function show_selected_shipping_method() {
2227 if ( ( _is_wcf_checkout_type() || _is_wcf_doing_checkout_ajax() ) && WC()->cart->needs_shipping() && WC()->cart->show_shipping() ) :
2228 $shipping_method = WC()->session->get( 'chosen_shipping_methods' );
2229 $shipping_method = is_array( $shipping_method ) ? sanitize_text_field( $shipping_method[0] ) : '';
2230 $shipping_cost = '';
2231
2232 // Use WC_Shipping to get the shipping packages.
2233 $packages = WC()->shipping()->get_packages();
2234 if ( ! empty( $packages ) ) {
2235 foreach ( $packages as $package ) {
2236 $formatted_destination = WC()->countries->get_formatted_address( $package['destination'], ', ' );
2237 $has_calculated_shipping = WC()->customer->has_calculated_shipping();
2238 $available_methods = $package['rates'];
2239 if ( ! empty( $available_methods ) && is_array( $available_methods ) && $shipping_method && isset( $available_methods[ $shipping_method ] ) ) {
2240 $rate = $available_methods[ $shipping_method ];
2241 $shipping_cost = wc_cart_totals_shipping_method_label( $rate );
2242 } elseif ( ! $has_calculated_shipping || ! $formatted_destination ) {
2243 $shipping_cost = apply_filters( 'woocommerce_shipping_may_be_available_html', __( 'Enter your address to view shipping options.', 'cartflows' ) );
2244 }
2245 }
2246 } else {
2247 apply_filters( 'woocommerce_no_shipping_available_html', __( 'There are no shipping options available. Please ensure that your address has been entered correctly, or contact us if you need any help.', 'cartflows' ) );
2248 }
2249
2250 ?>
2251 <tr class="cart-shipping">
2252 <th><?php esc_html_e( 'Shipping', 'cartflows' ); ?></th>
2253 <td>
2254 <?php
2255 if ( empty( $shipping_cost ) ) {
2256 echo wp_kses_post( apply_filters( 'woocommerce_no_shipping_available_html', __( 'There are no shipping options available. Please ensure that your address has been entered correctly, or contact us if you need any help.', 'cartflows' ) ) );
2257 } else {
2258 echo wp_kses_post( $shipping_cost );
2259 }
2260 ?>
2261 </td>
2262 </tr>
2263 <?php
2264 endif;
2265 }
2266 }
2267
2268
2269 /**
2270 * Kicking this off by calling 'get_instance()' method
2271 */
2272 Cartflows_Checkout_Markup::get_instance();
2273