PluginProbe
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce / 3.3.0
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce v3.3.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.3.0, at modules/checkout/classes/class-cartflows-checkout-markup.php

2,479 lines 80.6 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 * Whether a post ID is a CartFlows checkout step (not merely a step post type).
475 *
476 * @since 3.2.1
477 *
478 * @param int $post_id Post ID to test.
479 * @return bool
480 */
481 private function is_checkout_step( $post_id ) {
482 return CARTFLOWS_STEP_POST_TYPE === get_post_type( $post_id ) && 'checkout' === get_post_meta( $post_id, 'wcf-step-type', true );
483 }
484
485 /**
486 * Resolve a checkout template candidate to a safe, includable path.
487 *
488 * Fail-closed: returns the real path only when the candidate is an existing
489 * `.php` file inside an allowed root (plugin, mu-plugin, or active/parent
490 * theme). The writable uploads dir is excluded by construction.
491 *
492 * @since 3.2.1
493 *
494 * @param mixed $candidate Filtered template value.
495 * @return string|false Real path to include, or false when it is not allowed.
496 */
497 private function resolve_allowed_template( $candidate ) {
498
499 if ( ! is_string( $candidate ) || '' === $candidate || ! file_exists( $candidate ) ) {
500 return false;
501 }
502
503 $real_path = realpath( $candidate );
504
505 if ( ! $real_path || 'php' !== strtolower( pathinfo( $real_path, PATHINFO_EXTENSION ) ) ) {
506 return false;
507 }
508
509 $allowed_roots = array_filter(
510 array_map(
511 'realpath',
512 array(
513 WP_PLUGIN_DIR,
514 defined( 'WPMU_PLUGIN_DIR' ) ? WPMU_PLUGIN_DIR : '',
515 get_stylesheet_directory(),
516 get_template_directory(),
517 )
518 )
519 );
520
521 foreach ( $allowed_roots as $allowed_root ) {
522 if ( 0 === strpos( $real_path, $allowed_root . DIRECTORY_SEPARATOR ) ) {
523 return $real_path;
524 }
525 }
526
527 return false;
528 }
529
530 /**
531 * Resolve the checkout template to include for a step.
532 *
533 * The `wcf-checkout-layout` meta is a layout slug, never a path. It is still
534 * passed to `cartflows_checkout_layout_template` as argument 1 so existing
535 * callbacks keep working, but when no callback replaces it the hardcoded
536 * default is used. A callback's return is honoured only when it resolves to a
537 * `.php` file inside an allowed root, so nothing writable can be included.
538 *
539 * @since 3.2.1
540 *
541 * @param int $checkout_id Checkout step post ID.
542 * @return array Template path to include, and whether an override was refused.
543 */
544 private function resolve_checkout_template( $checkout_id ) {
545
546 $template_default = CARTFLOWS_CHECKOUT_DIR . 'templates/embed/checkout-template-simple.php';
547
548 $checkout_layout = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-checkout-layout' );
549
550 // Back-compat: arg 1 stays the layout slug, arg 2 adds the checkout ID.
551 $template_layout = apply_filters( 'cartflows_checkout_layout_template', $checkout_layout, $checkout_id );
552
553 // Security: no callback replaced the slug, so there is no override - the meta is never used as a path.
554 if ( $template_layout === $checkout_layout ) {
555 $template_layout = $template_default;
556 }
557
558 $real_path = $this->resolve_allowed_template( $template_layout );
559
560 if ( false !== $real_path ) {
561 return array(
562 'template' => $real_path,
563 'rejected' => false,
564 );
565 }
566
567 return array(
568 'template' => $template_default,
569 // A filter that returned a real file we refused to include is a misconfiguration worth surfacing.
570 'rejected' => is_string( $template_layout ) && $template_layout !== $template_default && is_file( $template_layout ),
571 );
572 }
573
574 /**
575 * Render checkout shortcode markup.
576 *
577 * @param array $atts attributes.
578 * @return string
579 */
580 public function checkout_shortcode_markup( $atts ) {
581 if ( ! function_exists( 'wc_print_notices' ) ) {
582 $notice_out = '<p class="woocommerce-notice">' . __( 'WooCommerce functions do not exist. If you are in an IFrame, please reload it.', 'cartflows' ) . '</p>';
583 $notice_out .= '<button onClick="location.reload()">' . __( 'Click Here to Reload', 'cartflows' ) . '</button>';
584
585 return $notice_out;
586 }
587
588 $atts = shortcode_atts(
589 array(
590 'id' => 0,
591 ),
592 $atts
593 );
594
595 $checkout_id = intval( $atts['id'] );
596
597 // Security: an explicit `id` must resolve to a real checkout step, else it drives template inclusion for any post.
598 if ( $checkout_id > 0 && ! $this->is_checkout_step( $checkout_id ) ) {
599 $checkout_id = 0;
600 }
601
602 $show_checkout_demo = false;
603
604 if ( is_admin() ) {
605
606 $show_checkout_demo = apply_filters( 'cartflows_show_demo_checkout', false );
607
608 if ( $show_checkout_demo && 0 === $checkout_id && isset( $_POST['id'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Missing
609 $demo_id = intval( $_POST['id'] ); //phpcs:ignore WordPress.Security.NonceVerification.Missing
610 $checkout_id = $this->is_checkout_step( $demo_id ) ? $demo_id : 0;
611 }
612 }
613 if ( empty( $checkout_id ) ) {
614
615 if ( ! _is_wcf_checkout_type() && false === $show_checkout_demo ) {
616
617 $error_html = '<h4>' . __( 'Checkout ID not found', 'cartflows' ) . '</h4>';
618 $error_html .= '<p>' . sprintf(
619 /* translators: %1$1s, %2$2s Link to article */
620 __( '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' ),
621 '<a href="' . esc_url( \Cartflows_Helper::get_kb_doc_link( 'https://cartflows.com/docs/resolve-checkout-id-not-found-error/' ) ) . '" target="_blank">',
622 '</a>'
623 ) . '</p>';
624
625 return $error_html;
626 }
627
628 global $post;
629
630 $checkout_id = ! empty( $post ) ? intval( $post->ID ) : 0;
631 }
632
633 $output = '';
634
635 ob_start();
636
637 do_action( 'cartflows_checkout_form_before', $checkout_id );
638
639 // Kept in scope because included templates have historically read it.
640 $checkout_layout = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-checkout-layout' );
641
642 $resolved_template = $this->resolve_checkout_template( $checkout_id );
643
644 include $resolved_template['template'];
645
646 $output .= ob_get_clean();
647
648 if ( $resolved_template['rejected'] ) {
649 _doing_it_wrong( __METHOD__, esc_html__( 'The cartflows_checkout_layout_template filter must return a .php file inside a plugin, mu-plugin, or theme directory.', 'cartflows' ), '3.2.1' );
650 }
651
652 return $output;
653 }
654
655 /**
656 * Configure Cart Data.
657 *
658 * @since 1.0.0
659 *
660 * @return void
661 */
662 public function preconfigured_cart_data() {
663 if ( is_admin() ) {
664 return;
665 }
666
667 // Skip cart manipulation for order-pay pages as the order already exists.
668 if ( is_checkout_pay_page() ) {
669 return;
670 }
671
672 global $post, $wcf_step;
673
674 if ( _is_wcf_checkout_type() ) {
675
676 if ( wp_doing_ajax() ) {
677 return;
678 } else {
679
680 $checkout_id = 0;
681 $skip_cart = false;
682
683 if ( _is_wcf_checkout_type() ) {
684 $checkout_id = $post->ID;
685 }
686
687 $store_checkout = \Cartflows_Helper::get_global_setting( '_cartflows_store_checkout' );
688
689 $flow_id = wcf()->utils->get_flow_id_from_step_id( $checkout_id );
690
691 if ( ! empty( $store_checkout ) && ( intval( $store_checkout ) === intval( $flow_id ) ) ) {
692
693 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
694 wc_add_notice( __( 'Your cart is currently empty.', 'cartflows' ), 'error' );
695 }
696
697 $skip_cart = true;
698
699 /**
700 * This action should/can be used to configure the cart of store checkout.
701 *
702 * @param int $checkout_id
703 * @hooked to wp action.
704 * @since 1.11.16
705 */
706 do_action( 'cartflows_store_checkout_before_configure_cart', $checkout_id );
707 }
708
709 if ( ! apply_filters( 'cartflows_skip_configure_cart', $skip_cart, $checkout_id ) ) {
710
711 do_action( 'cartflows_checkout_before_configure_cart', $checkout_id );
712
713 $products = wcf()->utils->get_selected_checkout_products( $checkout_id );
714
715 if ( wcf()->flow->is_flow_testmode( $flow_id ) && ( ! is_array( $products ) || empty( $products[0]['product'] ) ) ) {
716 $products = $this->get_random_products();
717 }
718
719 if ( ! is_array( $products ) ) {
720 return;
721 }
722
723 /* Empty the current cart */
724 WC()->cart->empty_cart();
725
726 if ( is_array( $products ) && empty( $products[0]['product'] ) && apply_filters( 'cartflows_checkout_show_empty_cart_notice', true ) ) {
727
728 $a_start = '';
729 $a_close = '';
730
731 wc_add_notice(
732 sprintf(
733 /* translators: %1$1s, %2$2s Link to meta */
734 __( 'No product is selected. Please select products from the %1$1scheckout meta settings%2$2s to continue.', 'cartflows' ),
735 $a_start,
736 $a_close
737 ),
738 'error'
739 );
740 return;
741 }
742
743 /* Set customer session if not set */
744 if ( ! is_user_logged_in() && WC()->cart->is_empty() ) {
745 WC()->session->set_customer_session_cookie( true );
746 }
747
748 $cart_product_count = 0;
749 $cart_key = '';
750 $products_new = array();
751
752 foreach ( $products as $index => $data ) {
753
754 if ( ! isset( $data['product'] ) ) {
755 continue;
756 }
757
758 /* Since 1.6.5 */
759 if ( empty( $data['add_to_cart'] ) || 'no' === $data['add_to_cart'] ) {
760 continue;
761 }
762
763 if ( apply_filters( 'cartflows_skip_other_products', false, $cart_product_count ) ) {
764 break;
765 }
766
767 $product_id = $data['product'];
768 $_product = wc_get_product( $product_id );
769
770 if ( ! empty( $_product ) ) {
771
772 $quantity = 1;
773
774 if ( isset( $data['quantity'] ) && ! empty( $data['quantity'] ) ) {
775 $quantity = $data['quantity'];
776 }
777
778 $discount_type = isset( $data['discount_type'] ) ? $data['discount_type'] : '';
779 $discount_value = ! empty( $data['discount_value'] ) ? $data['discount_value'] : '';
780 $_product_price = $_product->get_price(); // Removed the parameter as it was not required and was creating issue with MultiCurrency plugin.
781
782 $custom_price = $this->calculate_discount( '', $discount_type, $discount_value, $_product_price );
783
784 $cart_item_data = array(
785 'wcf_product_data' => array(
786 'unique_id' => $data['unique_id'],
787 'wcf_checkout_id' => $checkout_id,
788 'wcf_flow_id' => $flow_id,
789 ),
790 );
791
792 // Set the Product's custom price even if it is zero. Discount may have applied.
793 if ( $custom_price >= 0 && '' !== $custom_price ) {
794
795 $cart_item_data['custom_price'] = $custom_price;
796 }
797
798 if ( ! $_product->is_type( 'grouped' ) && ! $_product->is_type( 'external' ) ) {
799
800 if ( $_product->is_type( 'variable' ) ) {
801
802 $default_attributes = $_product->get_default_attributes();
803
804 if ( ! empty( $default_attributes ) ) {
805
806 foreach ( $_product->get_children() as $variation_id ) {
807
808 $single_variation = new WC_Product_Variation( $variation_id );
809
810 if ( $default_attributes == $single_variation->get_attributes() ) {
811 $cart_key = WC()->cart->add_to_cart( $variation_id, $quantity, 0, array(), $cart_item_data );
812 $cart_product_count++;
813 }
814 }
815 } else {
816
817 $product_childrens = $_product->get_children();
818
819 $variation_product = false;
820 $variation_product_id = 0;
821
822 foreach ( $product_childrens as $key => $v_id ) {
823
824 $_var_product = wc_get_product( $v_id );
825
826 if ( $_var_product && $_var_product->is_in_stock() && 'publish' === $_var_product->get_status() ) {
827 $variation_product_id = $v_id;
828 $variation_product = $_var_product;
829 break;
830 }
831 }
832
833 if ( $variation_product ) {
834 $_product_price = $variation_product->get_price();
835
836 $custom_price = $this->calculate_discount( '', $discount_type, $discount_value, $_product_price );
837 if ( ! empty( $custom_price ) ) {
838 $cart_item_data['custom_price'] = $custom_price;
839 }
840
841 $cart_key = WC()->cart->add_to_cart( $variation_product_id, $quantity, 0, array(), $cart_item_data );
842 $cart_product_count++;
843 } else {
844 echo '<p>' . esc_html__( 'Variations Not set', 'cartflows' ) . '</p>';
845 }
846 }
847 } else {
848 $cart_key = WC()->cart->add_to_cart( $product_id, $quantity, 0, array(), $cart_item_data );
849
850 if ( false !== $cart_key ) {
851 $cart_product_count++;
852 }
853 }
854 } else {
855 $wrong_product_notice = __( 'This product can\'t be purchased', 'cartflows' );
856 wc_add_notice( $wrong_product_notice );
857 }
858 }
859
860 $products_new[ $index ] = array(
861 'cart_item_key' => $cart_key,
862 );
863 }
864
865 /* Set checkout products data */
866 wcf()->utils->set_selcted_checkout_products( $checkout_id, $products_new );
867
868 /* Since 1.2.2 */
869 do_action( 'cartflows_checkout_after_configure_cart', $checkout_id );
870 }
871
872 $this->set_active_checkout_cookie_data( $checkout_id );
873 }
874 }
875 }
876
877 /**
878 * Set the cookie to identify the active checkout.
879 *
880 * @param int $checkout_id Current page ID.
881 *
882 * @return void
883 */
884 public function set_active_checkout_cookie_data( $checkout_id ) {
885
886 $cart_data = WC()->cart->get_cart();
887 $expiration_time = 30;
888 setcookie( CARTFLOWS_ACTIVE_CHECKOUT, $checkout_id, time() + $expiration_time * MINUTE_IN_SECONDS, '/', COOKIE_DOMAIN, CARTFLOWS_HTTPS, true ); //phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.cookies_setcookie
889
890 $user_key = WC()->session->get_customer_id();
891
892 set_transient( 'wcf_user_' . $user_key . '_checkout_' . $checkout_id, $cart_data, $expiration_time * MINUTE_IN_SECONDS );
893 }
894
895 /**
896 * Load shortcode data.
897 *
898 * @return void
899 */
900 public function shortcode_load_data() {
901 if ( _is_wcf_checkout_type() ) {
902
903 add_action( 'wp_enqueue_scripts', array( $this, 'shortcode_scripts' ), 21 );
904
905 add_action( 'wp_enqueue_scripts', array( $this, 'compatibility_scripts' ), 101 );
906
907 $this->before_checkout_shortcode_actions();
908
909 global $post;
910
911 $checkout_id = $post->ID;
912
913 do_action( 'cartflows_checkout_before_shortcode', $checkout_id );
914 }
915 }
916
917 /**
918 * Render checkout ID hidden field.
919 *
920 * @return void
921 */
922 public function before_checkout_shortcode_actions() {
923 /* Show notices if cart has errors */
924 add_action( 'woocommerce_cart_has_errors', 'woocommerce_output_all_notices' );
925
926 // Outputting the hidden field in checkout page.
927 add_action( 'woocommerce_after_order_notes', array( $this, 'checkout_shortcode_post_id' ), 99 );
928 add_action( 'woocommerce_login_form_end', array( $this, 'checkout_shortcode_post_id' ), 99 );
929
930 // Astra removes this actions so need to add it again.
931 add_action( 'woocommerce_checkout_billing', array( WC()->checkout, 'checkout_form_billing' ) );
932 add_action( 'woocommerce_checkout_shipping', array( WC()->checkout, 'checkout_form_shipping' ) );
933
934 remove_action( 'woocommerce_before_checkout_form', 'woocommerce_checkout_coupon_form' );
935
936 add_action( 'woocommerce_checkout_order_review', array( $this, 'display_custom_coupon_field' ) );
937
938 add_filter( 'woocommerce_cart_totals_coupon_html', array( $this, 'remove_coupon_text' ) );
939
940 add_filter( 'woocommerce_order_button_text', array( $this, 'place_order_button_text' ), 99, 1 );
941
942 add_filter( 'woocommerce_checkout_fields', array( $this, 'checkout_fields_actions' ), 10, 1 );
943
944 // Auto-check "Ship to a different address?" when shipping URL params are present,
945 // so the pre-filled shipping fields are visible on page load.
946 add_filter( 'woocommerce_ship_to_different_address_checked', array( $this, 'maybe_check_ship_to_different_address' ) );
947
948 // Add file field type support.
949 add_filter( 'woocommerce_form_field_file', array( $this, 'render_file_field' ), 10, 4 );
950
951 $this->update_the_checkout_strings();
952 $this->add_customized_shipping_section();
953 }
954
955 /**
956 * Render file upload field for checkout.
957 *
958 * @param string $field Field HTML.
959 * @param string $key Field key.
960 * @param array $args Field arguments.
961 * @param string $value Field value.
962 * @return string
963 */
964 public function render_file_field( $field, $key, $args, $value ) {
965
966 $required = ! empty( $args['required'] ) ? ' <abbr class="required">*</abbr>' : '';
967 $label = isset( $args['label'] ) ? $args['label'] : '';
968 $label_class = ! empty( $args['label_class'] ) ? implode( ' ', $args['label_class'] ) : '';
969 $field_class = ! empty( $args['class'] ) ? implode( ' ', $args['class'] ) : '';
970 $validate = ! empty( $args['required'] ) ? 'validate-required' : '';
971
972 $attrs = isset( $args['custom_attributes'] ) && is_array( $args['custom_attributes'] )
973 ? $args['custom_attributes']
974 : array();
975
976 $max_size = ! empty( $attrs['file_size'] ) ? absint( $attrs['file_size'] ) : 5;
977 $raw_types = isset( $attrs['accepted_file_types'] ) ? $attrs['accepted_file_types'] : array();
978
979 /**
980 * Master frontend allowlist
981 */
982 $master_types = Cartflows_Helper::get_allowed_file_extensions();
983
984 /**
985 * Normalize admin-restricted file types.
986 */
987 $restricted = array();
988
989 if ( ! empty( $raw_types ) ) {
990 foreach ( (array) $raw_types as $item ) {
991 if ( is_array( $item ) && isset( $item['value'] ) ) {
992 $restricted[] = strtolower( trim( $item['value'] ) );
993 } elseif ( is_string( $item ) ) {
994 foreach ( array_map( 'trim', explode( ',', $item ) ) as $ext ) {
995 if ( '' !== $ext ) {
996 $restricted[] = strtolower( $ext );
997 }
998 }
999 }
1000 }
1001 }
1002
1003 $allowed_types = empty( $restricted )
1004 ? $master_types
1005 : array_intersect( $master_types, $restricted );
1006
1007 $accept_parts = array();
1008
1009 foreach ( $allowed_types as $ext ) {
1010 $accept_parts[] = '.' . ltrim( $ext, '.' );
1011 }
1012
1013 $accept_attr = implode( ',', $accept_parts );
1014
1015 $field = '<p class="form-row ' . esc_attr( $field_class . ' ' . $validate ) . '" id="' . esc_attr( $key ) . '_field">';
1016 $field .= '<label for="' . esc_attr( $key ) . '" class="woocommerce-form__label-for-file ' . esc_attr( $label_class ) . '">';
1017 $field .= wp_kses_post( $label ) . $required . '</label>';
1018 $field .= '<span class="woocommerce-input-wrapper wcf-file-input-wrapper">';
1019 $field .= '<input type="file"
1020 id="' . esc_attr( $key ) . '_file"
1021 class="wcf-checkout-file-input"
1022 data-field-key="' . esc_attr( $key ) . '"
1023 accept="' . esc_attr( $accept_attr ) . '"
1024 data-max-size="' . esc_attr( (string) $max_size ) . '" />';
1025 $field .= '<input type="hidden" id="' . esc_attr( $key ) . '" name="' . esc_attr( $key ) . '" class="wcf-checkout-file-url" value="' . esc_attr( $value ) . '" />';
1026 $field .= '<span class="wcf-file-error" id="' . esc_attr( $key ) . '_error"></span>';
1027 $field .= '</span></p>';
1028
1029 return $field;
1030 }
1031
1032 /**
1033 * Add actions to introduced new customized shipping section for all checkout styles.
1034 *
1035 * @since 2.1.0
1036 * @return void
1037 */
1038 public function add_customized_shipping_section() {
1039
1040 add_action( 'woocommerce_checkout_after_customer_details', array( $this, 'add_custom_shipping_section' ), 10 );
1041 }
1042
1043 /**
1044 * Change checkout text.
1045 */
1046 public function update_the_checkout_strings() {
1047
1048 add_filter( 'cartflows_woo_billling_text', array( $this, 'update_billing_text' ), 10, 1 );
1049 add_filter( 'cartflows_woo_shipping_text', array( $this, 'update_shipping_text' ), 10, 1 );
1050 add_filter( 'cartflows_woo_customer_info_text', array( $this, 'update_customer_info_text' ), 10, 1 );
1051 add_filter( 'cartflows_woo_your_order_text', array( $this, 'update_your_order_text' ), 10, 1 );
1052 add_filter( 'cartflows_woo_payment_text', array( $this, 'update_payment_text' ), 10, 1 );
1053 }
1054
1055 /**
1056 * Change Payment text .
1057 *
1058 * @param string $text Payment.
1059 * @return string
1060 */
1061 public function update_payment_text( $text ) {
1062
1063 $checkout_id = $this->get_checkout_id();
1064
1065 if ( $checkout_id ) {
1066
1067 $wcf_payment_text = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-checkout-payment-text' );
1068 if ( ! empty( $wcf_payment_text ) ) {
1069 $text = $wcf_payment_text;
1070 }
1071 }
1072
1073 return $text;
1074 }
1075
1076 /**
1077 * Change Your order text.
1078 *
1079 * @param string $text Your order.
1080 * @return string
1081 */
1082 public function update_your_order_text( $text ) {
1083
1084 $checkout_id = $this->get_checkout_id();
1085
1086 if ( $checkout_id ) {
1087
1088 $wcf_your_order_text = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-checkout-your-order-text' );
1089 if ( ! empty( $wcf_your_order_text ) ) {
1090 $text = $wcf_your_order_text;
1091 }
1092 }
1093
1094 return $text;
1095 }
1096
1097 /**
1098 * Change ship to diff address text .
1099 *
1100 * @param string $text ship to diff address.
1101 * @return string
1102 */
1103 public function update_shipping_text( $text ) {
1104
1105 $checkout_id = $this->get_checkout_id();
1106
1107 if ( $checkout_id ) {
1108
1109 $wcf_shipping_details_text = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-checkout-shipping-details-text' );
1110 if ( ! empty( $wcf_shipping_details_text ) ) {
1111 $text = $wcf_shipping_details_text;
1112 }
1113 }
1114
1115 return $text;
1116 }
1117
1118 /**
1119 * Change billing details text .
1120 *
1121 * @param string $text billing details.
1122 * @return string
1123 */
1124 public function update_billing_text( $text ) {
1125
1126 $checkout_id = $this->get_checkout_id();
1127
1128 if ( $checkout_id ) {
1129
1130 $wcf_billing_details_text = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-checkout-billing-details-text' );
1131
1132 if ( ! empty( $wcf_billing_details_text ) ) {
1133 $text = $wcf_billing_details_text;
1134 }
1135 }
1136
1137 return $text;
1138 }
1139
1140 /**
1141 * Change customer information text .
1142 *
1143 * @param string $text customer information.
1144 * @return string
1145 */
1146 public function update_customer_info_text( $text ) {
1147
1148 $checkout_id = $this->get_checkout_id();
1149
1150 if ( $checkout_id ) {
1151
1152 $wcf_customer_info_text = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-checkout-customer-info-text' );
1153
1154 if ( ! empty( $wcf_customer_info_text ) ) {
1155 $text = $wcf_customer_info_text;
1156 }
1157 }
1158
1159 return $text;
1160 }
1161
1162 /**
1163 * Get checkout id.
1164 */
1165 public function get_checkout_id() {
1166
1167 $checkout_id = get_the_ID();
1168
1169 if ( ! $checkout_id && isset( Cartflows_Woo_Hooks::$ajax_data['_wcf_checkout_id'] ) ) {
1170
1171 $checkout_id = intval( Cartflows_Woo_Hooks::$ajax_data['_wcf_checkout_id'] );
1172 }
1173
1174 if ( ! $checkout_id && isset( $_GET['wcf_checkout_id'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
1175
1176 $checkout_id = intval( $_GET['wcf_checkout_id'] ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended
1177 }
1178
1179 return $checkout_id;
1180 }
1181
1182 /**
1183 * Checkout fields actions.
1184 *
1185 * @param array $checkout_fields checkout fields.
1186 * @since 1.10.0
1187 */
1188 public function checkout_fields_actions( $checkout_fields ) {
1189
1190 $checkout_fields = Cartflows_Checkout_Fields::get_instance()->add_three_column_layout_fields( $checkout_fields );
1191
1192 $checkout_fields = $this->prefill_checkout_fields( $checkout_fields );
1193
1194 return $checkout_fields;
1195 }
1196
1197 /**
1198 * Prefill the checkout fields if available in url.
1199 *
1200 * @param array $checkout_fields checkout fields array.
1201 */
1202 public function prefill_checkout_fields( $checkout_fields ) {
1203 //phpcs:disable WordPress.Security.NonceVerification.Recommended
1204 if ( is_auto_prefill_checkout_fields_enabled() && ! empty( $_GET ) ) {
1205
1206 $billing_fields = isset( $checkout_fields['billing'] ) ? $checkout_fields['billing'] : array();
1207 $shipping_fields = isset( $checkout_fields['shipping'] ) ? $checkout_fields['shipping'] : array();
1208
1209 foreach ( $billing_fields as $key => $field ) {
1210 $field_value = isset( $_GET[ $key ] ) && ! empty( $_GET[ $key ] ) ? sanitize_text_field( wp_unslash( $_GET[ $key ] ) ) : '';
1211
1212 if ( ! empty( $field_value ) ) {
1213 $checkout_fields['billing'][ $key ]['default'] = $field_value;
1214 }
1215 }
1216
1217 foreach ( $shipping_fields as $key => $field ) {
1218 $field_value = isset( $_GET[ $key ] ) && ! empty( $_GET[ $key ] ) ? sanitize_text_field( wp_unslash( $_GET[ $key ] ) ) : '';
1219
1220 if ( ! empty( $field_value ) ) {
1221 $checkout_fields['shipping'][ $key ]['default'] = $field_value;
1222 }
1223 }
1224
1225 // WC bakes the country into the state field definition during
1226 // initialize_checkout_fields(). If that ran before our early-registered
1227 // woocommerce_checkout_get_value filter could return the URL param country,
1228 // the wrong country's states would be used. Override it here.
1229 $billing_country = isset( $_GET['billing_country'] ) && ! empty( $_GET['billing_country'] )
1230 ? sanitize_text_field( wp_unslash( $_GET['billing_country'] ) ) : '';
1231
1232 $shipping_country = isset( $_GET['shipping_country'] ) && ! empty( $_GET['shipping_country'] )
1233 ? sanitize_text_field( wp_unslash( $_GET['shipping_country'] ) ) : '';
1234
1235 if ( $billing_country && isset( $checkout_fields['billing']['billing_state'] ) ) {
1236 $checkout_fields['billing']['billing_state']['country'] = $billing_country;
1237 }
1238
1239 if ( $shipping_country && isset( $checkout_fields['shipping']['shipping_state'] ) ) {
1240 $checkout_fields['shipping']['shipping_state']['country'] = $shipping_country;
1241 }
1242 }
1243
1244 return $checkout_fields;
1245 //phpcs:enable WordPress.Security.NonceVerification.Recommended
1246 }
1247
1248 /**
1249 * Return URL param value for a checkout field so that it takes priority
1250 * over saved customer/session data.
1251 *
1252 * WC_Checkout::get_value() checks this filter before falling back to the
1253 * logged-in customer's saved address, so hooking here is the only reliable
1254 * way to pre-fill fields for logged-in users via checkout links.
1255 *
1256 * @since x.x.x
1257 * @param mixed $value The current value (null = not yet set).
1258 * @param string $input The field key (e.g. 'billing_first_name').
1259 * @return mixed URL param value when present, original $value otherwise.
1260 */
1261 public function prefill_checkout_get_value( $value, $input ) {
1262 //phpcs:disable WordPress.Security.NonceVerification.Recommended
1263 if ( is_auto_prefill_checkout_fields_enabled() && ! empty( $_GET[ $input ] ) ) {
1264 return sanitize_text_field( wp_unslash( $_GET[ $input ] ) );
1265 }
1266 //phpcs:enable WordPress.Security.NonceVerification.Recommended
1267 return $value;
1268 }
1269
1270 /**
1271 * Pre-check the "Ship to a different address?" checkbox when any shipping_*
1272 * URL parameter is present, so the pre-filled shipping fields are visible
1273 * on page load instead of hidden inside the collapsed section.
1274 *
1275 * @since x.x.x
1276 * @param int $checked 1 if the checkbox should be pre-checked, 0 otherwise.
1277 * @return int
1278 */
1279 public function maybe_check_ship_to_different_address( $checked ) {
1280 //phpcs:disable WordPress.Security.NonceVerification.Recommended
1281 if ( is_auto_prefill_checkout_fields_enabled() ) {
1282 foreach ( array_keys( $_GET ) as $key ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1283 if ( 0 === strpos( $key, 'shipping_' ) && ! empty( $_GET[ $key ] ) ) {
1284 return 1;
1285 }
1286 }
1287 }
1288 //phpcs:enable WordPress.Security.NonceVerification.Recommended
1289 return $checked;
1290 }
1291
1292 /**
1293 * Render checkout ID hidden field.
1294 *
1295 * @param array $checkout checkout session data.
1296 * @return void
1297 */
1298 public function checkout_shortcode_post_id( $checkout ) {
1299 global $post;
1300
1301 $checkout_id = 0;
1302
1303 if ( _is_wcf_checkout_type() ) {
1304 $checkout_id = $post->ID;
1305 }
1306
1307 $flow_id = get_post_meta( $checkout_id, 'wcf-flow-id', true );
1308
1309 echo '<input type="hidden" class="input-hidden _wcf_flow_id" name="_wcf_flow_id" value="' . intval( $flow_id ) . '">';
1310 echo '<input type="hidden" class="input-hidden _wcf_checkout_id" name="_wcf_checkout_id" value="' . intval( $checkout_id ) . '">';
1311 }
1312
1313 /**
1314 * Load shortcode scripts.
1315 *
1316 * @return void
1317 */
1318 public function shortcode_scripts() {
1319 wp_enqueue_style( 'wcf-checkout-template', wcf()->utils->get_css_url( 'checkout-template' ), '', CARTFLOWS_VER );
1320
1321 wp_enqueue_script(
1322 'wcf-checkout-template',
1323 wcf()->utils->get_js_url( 'checkout-template' ),
1324 array( 'jquery' ),
1325 CARTFLOWS_VER,
1326 true
1327 );
1328
1329 do_action( 'cartflows_checkout_scripts' );
1330
1331 $checkout_dynamic_css = apply_filters( 'cartflows_checkout_enable_dynamic_css', true );
1332
1333 if ( $checkout_dynamic_css ) {
1334
1335 global $post;
1336
1337 $checkout_id = $post->ID;
1338
1339 $style = get_post_meta( $checkout_id, 'wcf-dynamic-css', true );
1340
1341 $css_version = get_post_meta( $checkout_id, 'wcf-dynamic-css-version', true );
1342
1343 // Regenerate the dynamic css only when key is not exist or version does not match.
1344 if ( empty( $style ) || CARTFLOWS_ASSETS_VERSION !== $css_version ) {
1345 $style = $this->generate_style();
1346 update_post_meta( $checkout_id, 'wcf-dynamic-css', wp_slash( $style ) );
1347 update_post_meta( $checkout_id, 'wcf-dynamic-css-version', CARTFLOWS_ASSETS_VERSION );
1348 }
1349
1350 CartFlows_Font_Families::render_fonts( $checkout_id );
1351
1352 wp_add_inline_style( 'wcf-checkout-template', $style );
1353 }
1354 }
1355
1356 /**
1357 * Load compatibility scripts.
1358 *
1359 * @return void
1360 */
1361 public function compatibility_scripts() {
1362 global $post;
1363
1364 $checkout_id = 0;
1365
1366 if ( _is_wcf_checkout_type() ) {
1367 $checkout_id = $post->ID;
1368 }
1369
1370 // Add DIVI Compatibility css if DIVI theme is enabled.
1371 if (
1372 Cartflows_Compatibility::get_instance()->is_divi_enabled() ||
1373 Cartflows_Compatibility::get_instance()->is_divi_builder_enabled( $checkout_id )
1374 ) {
1375 wp_enqueue_style( 'wcf-checkout-template-divi', wcf()->utils->get_css_url( 'checkout-template-divi' ), '', CARTFLOWS_VER );
1376 }
1377
1378 // Add Flatsome Compatibility css if Flatsome theme is enabled.
1379 if ( Cartflows_Compatibility::get_instance()->is_flatsome_enabled() ) {
1380 wp_enqueue_style( 'wcf-checkout-template-flatsome', wcf()->utils->get_css_url( 'checkout-template-flatsome' ), '', CARTFLOWS_VER );
1381 }
1382
1383 // Add The7 Compatibility css if The7 theme is enabled.
1384 if ( Cartflows_Compatibility::get_instance()->is_the_seven_enabled() ) {
1385 wp_enqueue_style( 'wcf-checkout-template-the-seven', wcf()->utils->get_css_url( 'checkout-template-the-seven' ), '', CARTFLOWS_VER );
1386 }
1387 }
1388
1389 /**
1390 * Generate styles.
1391 *
1392 * @return string
1393 */
1394 public function generate_style() {
1395 global $post;
1396
1397 $checkout_id = 0;
1398
1399 if ( _is_wcf_checkout_type() ) {
1400 $checkout_id = $post->ID;
1401 }
1402
1403 /*Output css variable */
1404 $output = '';
1405
1406 $flow_id = wcf()->utils->is_step_post_type() ? wcf()->utils->get_flow_id() : 0;
1407 $enable_design_setting = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-enable-design-settings' );
1408 $enable_place_order_button_lock = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-checkout-place-order-button-lock' );
1409
1410 if ( 'yes' === $enable_design_setting ) {
1411
1412 $checkout_layout = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-checkout-layout' );
1413
1414 $primary_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-primary-color' );
1415
1416 $base_font_family = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-base-font-family' );
1417
1418 $header_logo_width = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-header-logo-width' );
1419
1420 $r = '';
1421 $g = '';
1422 $b = '';
1423
1424 $field_tb_padding = '';
1425 $field_lr_padding = '';
1426
1427 $field_heading_color = '';
1428 $field_color = '';
1429 $field_bg_color = '';
1430 $field_border_color = '';
1431 $field_label_color = '';
1432 $submit_tb_padding = '';
1433 $submit_lr_padding = '';
1434 $hl_bg_color = '';
1435 $field_input_size = '';
1436 $box_border_color = '';
1437 $section_bg_color = '';
1438 $submit_button_height = '';
1439 $submit_color = '';
1440 $submit_bg_color = $primary_color;
1441 $submit_border_color = $primary_color;
1442
1443 $submit_hover_color = '';
1444 $submit_bg_hover_color = $primary_color;
1445 $submit_border_hover_color = $primary_color;
1446
1447 $section_heading_color = '';
1448
1449 $is_advance_option = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-advance-options-fields' );
1450
1451 $button_font_family = '';
1452 $button_font_weight = '';
1453 $input_font_family = '';
1454 $input_font_weight = '';
1455 $heading_font_family = '';
1456 $heading_font_weight = '';
1457 $base_font_family = $base_font_family;
1458
1459 if ( 'yes' == $is_advance_option ) {
1460
1461 /**
1462 * Get Font Family and Font Weight weight values
1463 */
1464 $section_bg_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-section-bg-color' );
1465
1466 $heading_font_family = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-heading-font-family' );
1467 $heading_font_weight = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-heading-font-weight' );
1468 $section_heading_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-heading-color' );
1469 $button_font_family = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-button-font-family' );
1470 $button_font_weight = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-button-font-weight' );
1471 $input_font_family = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-input-font-family' );
1472 $input_font_weight = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-input-font-weight' );
1473 $field_tb_padding = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-field-tb-padding' );
1474 $field_lr_padding = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-field-lr-padding' );
1475 $field_input_size = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-input-field-size' );
1476
1477 $field_heading_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-field-heading-color' );
1478
1479 $field_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-field-color' );
1480
1481 $field_bg_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-field-bg-color' );
1482
1483 $field_border_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-field-border-color' );
1484
1485 $field_label_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-field-label-color' );
1486
1487 $submit_tb_padding = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-submit-tb-padding' );
1488
1489 $submit_lr_padding = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-submit-lr-padding' );
1490
1491 $submit_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-submit-color' );
1492
1493 $submit_bg_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-submit-bg-color', $primary_color );
1494
1495 $submit_border_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-submit-border-color', $primary_color );
1496
1497 $submit_border_hover_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-submit-border-hover-color', $primary_color );
1498
1499 $submit_hover_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-submit-hover-color' );
1500
1501 $submit_bg_hover_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-submit-bg-hover-color', $primary_color );
1502
1503 $hl_bg_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-hl-bg-color' );
1504
1505 $box_border_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-box-border-color' );
1506
1507 $submit_button_height = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-input-button-size' );
1508
1509 /**
1510 * Get font values
1511 */
1512
1513 if ( 'custom' == $submit_button_height ) {
1514 $submit_button_height = '38px';
1515 }
1516
1517 if ( 'custom' == $field_input_size ) {
1518 $field_input_size = '38px';
1519 }
1520 }
1521 if ( isset( $primary_color ) ) {
1522
1523 list($r, $g, $b) = sscanf( $primary_color, '#%02x%02x%02x' );
1524 }
1525
1526 /** Override the global colors and assign it to the default variables */
1527 $flow_global_style = ! empty( $flow_id ) ? wcf()->options->get_flow_meta_value( $flow_id, 'wcf-enable-gcp-styling', 'no' ) : false;
1528
1529 if ( 'yes' === $flow_global_style ) {
1530 $primary_color = ! empty( $flow_id ) ? get_post_meta( $flow_id, 'wcf-gcp-primary-color', true ) : '';
1531 $field_color = ! empty( $flow_id ) ? get_post_meta( $flow_id, 'wcf-gcp-text-color', true ) : '';
1532 $section_heading_color = ! empty( $flow_id ) ? get_post_meta( $flow_id, 'wcf-gcp-accent-color', true ) : '';
1533 }
1534
1535 /** Override the global colors and assign it to the default variables */
1536
1537 $submit_btn_bg_color = ( $submit_bg_color ) ? $submit_bg_color : $primary_color;
1538 $submit_btn_bg_hover_color = ( $submit_bg_hover_color ) ? $submit_bg_hover_color : $primary_color;
1539
1540 $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 { ';
1541 $output .= ! empty( $primary_color ) ? '--wcf-primary-color: ' . $primary_color . ';' : '';
1542 $output .= ! empty( $section_heading_color ) ? '--wcf-heading-color: ' . $section_heading_color . ';' : '';
1543 $output .= ! empty( $submit_btn_bg_color ) ? '--wcf-btn-bg-color: ' . $submit_btn_bg_color . ';' : '';
1544 $output .= ! empty( $submit_btn_bg_hover_color ) ? '--wcf-btn-bg-hover-color: ' . $submit_btn_bg_hover_color . ';' : '';
1545 $output .= ! empty( $submit_color ) ? '--wcf-btn-text-color: ' . $submit_color . ';' : '';
1546 $output .= ! empty( $submit_hover_color ) ? '--wcf-btn-hover-text-color: ' . $submit_hover_color . ';' : '';
1547 $output .= ! empty( $field_label_color ) ? '--wcf-field-label-color: ' . $field_label_color . ';' : '';
1548 $output .= ! empty( $field_bg_color ) ? '--wcf-field-bg-color: ' . $field_bg_color . ';' : '';
1549 $output .= ! empty( $field_border_color ) ? '--wcf-field-border-color:' . $field_border_color . ';' : '';
1550 $output .= ! empty( $field_color ) ? '--wcf-field-text-color: ' . $field_color . ';' : '';
1551 $output .= '}';
1552
1553 // Add instant layout styles.
1554 if ( Cartflows_Helper::is_instant_layout_enabled() ) {
1555
1556 $instant_checkout_left_column_bg_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-instant-checkout-left-side-bg-color' );
1557 $instant_checkout_right_column_bg_color = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-instant-checkout-right-side-bg-color' );
1558
1559 $submit_btn_bg_color = ( $submit_bg_color ) ? $submit_bg_color : $primary_color;
1560 $submit_btn_bg_hover_color = ( $submit_bg_hover_color ) ? $submit_bg_hover_color : $primary_color;
1561
1562 $output .= 'body .wcf-embed-checkout-form.wcf-embed-checkout-form-instant-checkout { ';
1563
1564 $output .= ! empty( $primary_color ) ? '--wcf-primary-color: ' . $primary_color . ';' : '';
1565 $output .= ! empty( $section_heading_color ) ? '--wcf-heading-color: ' . $section_heading_color . ';' : '';
1566 $output .= ! empty( $heading_font_weight ) ? '--wcf-heading-font-weight: ' . $heading_font_weight . ';' : '';
1567
1568 $output .= ! empty( $field_label_color ) ? '--wcf-field-label-color: ' . $field_label_color . ';' : '';
1569 $output .= ! empty( $field_input_size ) ? '--wcf-field-min-height: ' . $field_input_size . ';' : '';
1570 $output .= ! empty( $field_bg_color ) ? '--wcf-field-bg-color: ' . $field_bg_color . ';' : '';
1571 $output .= ! empty( $field_border_color ) ? '--wcf-field-border-color:' . $field_border_color . ';' : '';
1572 $output .= ! empty( $field_color ) ? '--wcf-field-text-color: ' . $field_color . ';' : '';
1573
1574 $output .= ! empty( $submit_btn_bg_color ) ? '--wcf-btn-bg-color: ' . $submit_btn_bg_color . ';' : '';
1575 $output .= ! empty( $submit_btn_bg_hover_color ) ? '--wcf-btn-bg-hover-color: ' . $submit_btn_bg_hover_color . ';' : '';
1576 $output .= ! empty( $submit_color ) ? '--wcf-btn-text-color: ' . $submit_color . ';' : '';
1577 $output .= ! empty( $submit_hover_color ) ? '--wcf-btn-hover-text-color: ' . $submit_hover_color . ';' : '';
1578
1579 $output .= ! empty( $hl_bg_color ) ? '--wcf-payment-section-desc-bg-color: ' . $hl_bg_color . ';' : '';
1580
1581 $output .= ! empty( $instant_checkout_left_column_bg_color ) ? '--wcf-ic-left-column-bg-color: ' . $instant_checkout_left_column_bg_color . ';' : '';
1582 $output .= ! empty( $instant_checkout_right_column_bg_color ) ? '--wcf-ic-right-column-bg-color: ' . $instant_checkout_right_column_bg_color . ';' : '';
1583
1584 $output .= '}';
1585 }
1586
1587 if (
1588 Cartflows_Compatibility::get_instance()->is_divi_enabled() ||
1589 Cartflows_Compatibility::get_instance()->is_divi_builder_enabled( $checkout_id )
1590 ) {
1591
1592 include CARTFLOWS_CHECKOUT_DIR . 'includes/checkout-dynamic-divi-css.php';
1593 } else {
1594 include CARTFLOWS_CHECKOUT_DIR . 'includes/checkout-dynamic-css.php';
1595 }
1596 }
1597
1598
1599 if ( 'yes' === $enable_place_order_button_lock ) {
1600 // If enabled then add the below css to show the lock icon on place order button.
1601 $output .= '
1602 .wcf-embed-checkout-form .woocommerce #payment #place_order:before{
1603 content: "\e902";
1604 font-family: "cartflows-icon" !important;
1605 margin-right: 10px;
1606 font-size: 16px;
1607 font-weight: 500;
1608 top: 0px;
1609 position: relative;
1610 opacity: 1;
1611 display: block;
1612 }';
1613 }
1614
1615 return $output;
1616 }
1617
1618 /**
1619 * Save checkout fields.
1620 *
1621 * @param int $order_id order id.
1622 * @param array $posted posted data.
1623 * @return void
1624 */
1625 public function save_checkout_fields( $order_id, $posted ) {
1626 //phpcs:disable WordPress.Security.NonceVerification
1627 $checkout_id = '';
1628 $flow_id = '';
1629 if ( isset( $_POST['_wcf_checkout_id'] ) ) {
1630 $checkout_id = intval( $_POST['_wcf_checkout_id'] );
1631 $flow_id = isset( $_POST['_wcf_flow_id'] ) ? intval( $_POST['_wcf_flow_id'] ) : 0;
1632
1633 } elseif ( isset( $_GET['wcf_checkout_id'] ) ) {
1634 $checkout_id = intval( $_GET['wcf_checkout_id'] );
1635 $flow_id = wcf()->utils->get_flow_id_from_step_id( $checkout_id );
1636 } elseif ( isset( $posted['_wcf_checkout_id'] ) ) {
1637 $checkout_id = intval( $posted['_wcf_checkout_id'] );
1638 $flow_id = isset( $posted['_wcf_flow_id'] ) ? intval( $posted['_wcf_flow_id'] ) : wcf()->utils->get_flow_id_from_step_id( $checkout_id );
1639 }
1640
1641 if ( empty( $checkout_id ) && empty( $flow_id ) ) {
1642 $meta_data = $this->get_cartflows_checkout_id_and_flow_id_from_cart();
1643
1644 if ( ! empty( $meta_data ) && is_array( $meta_data ) ) {
1645 $checkout_id = $meta_data['checkout_id'];
1646 $flow_id = $meta_data['flow_id'];
1647 }
1648 }
1649
1650 $order = wc_get_order( $order_id );
1651 // Ensure $order is a WC_Order instance.
1652 if ( ! $order instanceof WC_Order ) {
1653 return;
1654 }
1655 $this->store_flow_metadata_on_order( $checkout_id, $flow_id, $order );
1656
1657 // Save file upload custom field values to order meta.
1658 if ( $checkout_id ) {
1659 $this->save_file_field_meta( (int) $checkout_id, $order );
1660 }
1661 //phpcs:enable WordPress.Security.NonceVerification
1662 }
1663
1664 /**
1665 * Save file upload field values to order meta.
1666 *
1667 * @param int $checkout_id The checkout step ID.
1668 * @param WC_Order $order The order object.
1669 * @return void
1670 */
1671 private function save_file_field_meta( $checkout_id, $order ) {
1672
1673 $field_groups = array(
1674 'wcf_field_order_billing',
1675 'wcf_field_order_shipping',
1676 );
1677
1678 foreach ( $field_groups as $meta_key ) {
1679 $saved_fields = get_post_meta( $checkout_id, $meta_key, true );
1680
1681 if ( ! is_array( $saved_fields ) ) {
1682 continue;
1683 }
1684
1685 foreach ( $saved_fields as $field_key => $field_data ) {
1686 if ( ! isset( $field_data['type'] ) || 'file' !== $field_data['type'] ) {
1687 continue;
1688 }
1689
1690 // phpcs:ignore WordPress.Security.NonceVerification.Missing
1691 if ( ! empty( $_POST[ $field_key ] ) ) {
1692
1693 $file_url = esc_url_raw( wp_unslash( $_POST[ $field_key ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
1694
1695 if ( ! empty( $file_url ) ) {
1696 $order->update_meta_data( $field_key, $file_url );
1697 }
1698 }
1699 }
1700 }
1701
1702 $order->save();
1703 }
1704
1705 /**
1706 * Add the checkout ID and Flow ID to the order meta data.
1707 *
1708 * @param WC_Order $order The order object.
1709 * @return void
1710 */
1711 public function add_order_meta_for_store_api_checkout( $order ) {
1712 // Ensure $order is a WC_Order instance.
1713 if ( ! $order instanceof WC_Order ) {
1714 return;
1715 }
1716
1717 $checkout_id = '';
1718 $flow_id = '';
1719 $meta_data = $this->get_cartflows_checkout_id_and_flow_id_from_cart();
1720
1721 if ( empty( $meta_data ) ) {
1722 // Store Checkout products carry no cart item meta and express checkout sends no POST data.
1723 $meta_data = $this->get_store_checkout_id_and_flow_id();
1724 }
1725
1726 if ( ! empty( $meta_data ) && is_array( $meta_data ) ) {
1727 $checkout_id = $meta_data['checkout_id'];
1728 $flow_id = $meta_data['flow_id'];
1729 }
1730
1731 $this->store_flow_metadata_on_order( $checkout_id, $flow_id, $order );
1732 }
1733
1734 /**
1735 * Retrieve the checkout ID and flow ID of the configured Store Checkout.
1736 * Returns null when no Store Checkout is set or it has no checkout step.
1737 *
1738 * @return array{checkout_id: string, flow_id: string}|null
1739 */
1740 private function get_store_checkout_id_and_flow_id() {
1741 $store_checkout = Cartflows_Helper::get_global_setting( '_cartflows_store_checkout' );
1742 $flow_id = is_scalar( $store_checkout ) ? intval( $store_checkout ) : 0;
1743
1744 if ( empty( $flow_id ) ) {
1745 return null;
1746 }
1747
1748 $steps = get_post_meta( $flow_id, 'wcf-steps', true );
1749
1750 if ( ! is_array( $steps ) ) {
1751 return null;
1752 }
1753
1754 foreach ( $steps as $step ) {
1755 if ( isset( $step['id'], $step['type'] ) && 'checkout' === $step['type'] ) {
1756 return array(
1757 'checkout_id' => (string) $step['id'],
1758 'flow_id' => (string) $flow_id,
1759 );
1760 }
1761 }
1762
1763 return null;
1764 }
1765
1766 /**
1767 * Retrieve CartFlows checkout ID and flow ID stored in cart item meta.
1768 * If no matching cart item or cart is empty, returns null.
1769 *
1770 * @return array{checkout_id: string, flow_id: string}|null
1771 */
1772 private function get_cartflows_checkout_id_and_flow_id_from_cart() {
1773 if ( WC()->cart instanceof WC_Cart ) {
1774 $checkout_id = '';
1775 $flow_id = '';
1776 $cart_contents = WC()->cart->get_cart_contents();
1777 if ( empty( $cart_contents ) ) {
1778 return null;
1779 }
1780
1781 foreach ( $cart_contents as $cart_item ) {
1782 if ( ! isset( $cart_item['wcf_product_data'] ) ) {
1783 continue;
1784 }
1785
1786 $checkout_id = (string) $cart_item['wcf_product_data']['wcf_checkout_id'];
1787 $flow_id = (string) $cart_item['wcf_product_data']['wcf_flow_id'];
1788
1789 // if both found, stop scanning further items.
1790 if ( ! empty( $checkout_id ) && ! empty( $flow_id ) ) {
1791 break;
1792 }
1793 }
1794
1795 if ( ! empty( $flow_id ) && ! empty( $checkout_id ) ) {
1796 return array(
1797 'checkout_id' => $checkout_id,
1798 'flow_id' => $flow_id,
1799 );
1800 }
1801 }
1802
1803 return null;
1804 }
1805
1806 /**
1807 * Add checkout ID and flow ID as order meta on a given order.
1808 *
1809 * Accepts either a WC_Order object. Validates that
1810 * the provided post IDs match the expected post types (CARTFLOWS_FLOW_POST_TYPE
1811 * and CARTFLOWS_STEP_POST_TYPE) before saving meta.
1812 *
1813 * @param int|string $checkout_id Checkout (step) post ID (or empty).
1814 * @param int|string $flow_id Flow post ID (or empty).
1815 * @param WC_Order|bool $order WC_Order object, or null.
1816 * @return void
1817 */
1818 private function store_flow_metadata_on_order( $checkout_id, $flow_id, $order ) {
1819 if ( empty( $flow_id ) || empty( $checkout_id ) ) {
1820 // nothing to store.
1821 return;
1822 }
1823 // Ensure $order is a WC_Order instance.
1824 if ( ! $order instanceof WC_Order ) {
1825 return;
1826 }
1827
1828 // Convert to integers to get post type.
1829 $checkout_id = absint( $checkout_id );
1830 $flow_id = absint( $flow_id );
1831
1832 if ( CARTFLOWS_FLOW_POST_TYPE === get_post_type( $flow_id ) ) {
1833 $order->update_meta_data( '_wcf_flow_id', (string) $flow_id );
1834 }
1835
1836 if ( CARTFLOWS_STEP_POST_TYPE === get_post_type( $checkout_id ) ) {
1837 $order->update_meta_data( '_wcf_checkout_id', (string) $checkout_id );
1838 }
1839
1840 $order->save();
1841 }
1842
1843 /**
1844 * Enable Logo In Header Of Checkout Page
1845 *
1846 * @return void
1847 */
1848 public function enable_logo_in_header() {
1849 global $post;
1850
1851 $checkout_id = 0;
1852
1853 if ( _is_wcf_checkout_type() ) {
1854 $checkout_id = $post->ID;
1855 }
1856
1857 $header_logo_image = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-header-logo-image' );
1858 $add_image_markup = '';
1859
1860 if ( isset( $header_logo_image ) && ! empty( $header_logo_image ) ) {
1861 $add_image_markup = '<div class="wcf-checkout-header-image">';
1862 $add_image_markup .= '<img src="' . esc_url( $header_logo_image ) . '" />';
1863 $add_image_markup .= '</div>';
1864 }
1865
1866 echo wp_kses_post( $add_image_markup );
1867 }
1868
1869 /**
1870 * Add text to the bootom of the checkout page.
1871 *
1872 * @return void
1873 */
1874 public function show_cartflows_copyright_message() {
1875 $output_string = '';
1876
1877 $output_string .= '<div class="wcf-footer-primary">';
1878 $output_string .= '<div class="wcf-footer-content">';
1879 $output_string .= '<p class="wcf-footer-message">';
1880 $output_string .= 'Checkout powered by CartFlows';
1881 $output_string .= '</p>';
1882 $output_string .= '</div>';
1883 $output_string .= '</div>';
1884
1885 echo wp_kses_post( $output_string );
1886 }
1887
1888 /**
1889 * Redirect users to our checkout if hidden param
1890 *
1891 * @param string $redirect redirect url.
1892 * @param object $user user.
1893 * @return string
1894 */
1895 public function after_login_redirect( $redirect, $user ) {
1896 // We are calling this function on WooCommerce action where nonce is verified.
1897 //phpcs:disable WordPress.Security.NonceVerification.Missing
1898 if ( isset( $_POST['_wcf_checkout_id'] ) ) {
1899
1900 $checkout_id = intval( $_POST['_wcf_checkout_id'] );
1901
1902 $redirect = get_permalink( $checkout_id );
1903 }
1904 //phpcs:enable WordPress.Security.NonceVerification.Missing
1905
1906 return $redirect;
1907 }
1908
1909 /**
1910 * Display coupon code field after review order fields.
1911 */
1912 public function display_custom_coupon_field() {
1913
1914 if ( ! $this->is_custom_coupon_field_enabled() ) {
1915 return;
1916 }
1917
1918 $coupon_field = array(
1919 'field_text' => __( 'Coupon Code', 'cartflows' ),
1920 'button_text' => __( 'Apply', 'cartflows' ),
1921 'class' => '',
1922 );
1923
1924 $coupon_field = apply_filters( 'cartflows_coupon_field_options', $coupon_field );
1925
1926 ob_start();
1927 do_action( 'cartflows_before_custom_field_html', $coupon_field );
1928 ?>
1929 <div class="wcf-custom-coupon-field <?php echo esc_attr( $coupon_field['class'] ); ?>" id="wcf_custom_coupon_field">
1930 <div class="wcf-coupon-col-1">
1931 <span>
1932 <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="">
1933 </span>
1934 </div>
1935 <div class="wcf-coupon-col-2">
1936 <span>
1937 <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>
1938 </span>
1939 </div>
1940 </div>
1941 <?php
1942 do_action( 'cartflows_after_custom_field_html', $coupon_field );
1943 // wp_kses_post will not work as it removing input tags hence ignoring below rule.
1944 echo ob_get_clean(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1945 }
1946
1947 /**
1948 * Apply filter to change class of remove coupon field.
1949 *
1950 * @param string $coupon coupon.
1951 * @return string
1952 */
1953 public function remove_coupon_text( $coupon ) {
1954 $coupon = str_replace( 'woocommerce-remove-coupon', 'wcf-remove-coupon', $coupon );
1955 return $coupon;
1956 }
1957 /**
1958 * Apply filter to change the placeholder text of coupon field.
1959 *
1960 * @return string
1961 */
1962 public function coupon_field_placeholder() {
1963 return apply_filters( 'cartflows_coupon_field_placeholder', __( 'Coupon Code', 'cartflows' ) );
1964 }
1965
1966 /**
1967 * Apply filter to change the button text of coupon field.
1968 *
1969 * @return string
1970 */
1971 public function coupon_button_text() {
1972 return apply_filters( 'cartflows_coupon_button_text', __( 'Apply', 'cartflows' ) );
1973 }
1974
1975 /**
1976 * Added ajax nonce to localize variable.
1977 *
1978 * @param array $vars localize variables.
1979 */
1980 public function add_localize_vars( $vars ) {
1981 $vars['wcf_validate_coupon_nonce'] = wp_create_nonce( 'wcf-apply-coupon' );
1982
1983 $vars['wcf_validate_remove_coupon_nonce'] = wp_create_nonce( 'wcf-remove-coupon' );
1984
1985 $vars['wcf_validate_remove_cart_product_nonce'] = wp_create_nonce( 'wcf-remove-cart-product' );
1986
1987 $vars['check_email_exist_nonce'] = wp_create_nonce( 'check-email-exist' );
1988
1989 $vars['woocommerce_login_nonce'] = wp_create_nonce( 'woocommerce-login' );
1990
1991 $vars['wcf_file_upload_nonce'] = wp_create_nonce( 'wcf-file-upload' );
1992
1993 $vars['allow_persistence'] = apply_filters( 'cartflows_allow_persistence', 'yes' );
1994
1995 $vars['is_logged_in'] = is_user_logged_in();
1996
1997 $vars['email_validation_msgs'] = array(
1998 'error_msg' => __( 'Entered email address is not a valid email.', 'cartflows' ),
1999 'success_msg' => __( 'This email is already registered. Please enter the password to continue.', 'cartflows' ),
2000 );
2001 $vars['field_validation_msgs'] = array(
2002 'number_field' => __( 'Value must be between ', 'cartflows' ),
2003 );
2004
2005 $vars['order_review_toggle_texts'] = array(
2006 'toggle_show_text' => $this->get_order_review_toggle_texts(),
2007 'toggle_hide_text' => $this->get_order_review_toggle_texts( 'hide_text' ),
2008 );
2009
2010 $checkout_id = _get_wcf_checkout_id();
2011
2012 $checkout_id = $checkout_id ? $checkout_id : 0;
2013
2014 $vars['field_validation'] = array(
2015 'is_enabled' => wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-enable-checkout-field-validation-text' ),
2016 'error_msg' => wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-checkout-field-validation-text' ),
2017 );
2018
2019 // Detect whether any file upload fields exist on checkout.
2020 $has_file_field = false;
2021 if ( $checkout_id ) {
2022 $billing_fields = get_post_meta( $checkout_id, 'wcf_field_order_billing', true );
2023 $shipping_fields = get_post_meta( $checkout_id, 'wcf_field_order_shipping', true );
2024 $all_fields = array_merge( is_array( $billing_fields ) ? $billing_fields : array(), is_array( $shipping_fields ) ? $shipping_fields : array() );
2025
2026 foreach ( $all_fields as $field_data ) {
2027 if ( isset( $field_data['type'] ) && 'file' === $field_data['type'] && ! empty( $field_data['enabled'] ) ) {
2028 $has_file_field = true;
2029 break;
2030 }
2031 }
2032 }
2033 $vars['has_file_field'] = $has_file_field;
2034
2035 return $vars;
2036 }
2037
2038 /**
2039 * Calculate discount for product.
2040 *
2041 * @param string $discount_coupon discount coupon.
2042 * @param string $discount_type discount type.
2043 * @param int $discount_value discount value.
2044 * @param int $_product_price product price.
2045 * @return int
2046 * @since 1.1.5
2047 */
2048 public function calculate_discount( $discount_coupon, $discount_type, $discount_value, $_product_price ) {
2049 $custom_price = '';
2050
2051 if ( ! empty( $discount_type ) ) {
2052 if ( 'discount_percent' === $discount_type ) {
2053
2054 if ( $discount_value > 0 ) {
2055 $custom_price = $_product_price - ( ( $_product_price * $discount_value ) / 100 );
2056 }
2057 } elseif ( 'discount_price' === $discount_type ) {
2058
2059 if ( $discount_value > 0 ) {
2060 $custom_price = $_product_price - $discount_value;
2061 }
2062 } elseif ( 'coupon' === $discount_type ) {
2063
2064 if ( ! empty( $discount_coupon ) ) {
2065 WC()->cart->add_discount( $discount_coupon );
2066 }
2067 }
2068 }
2069
2070 return $custom_price;
2071 }
2072
2073 /**
2074 * Preserve the custom item price added by Variations & Quantity feature
2075 *
2076 * @param Wc_cart $cart_object cart object.
2077 * @since 1.0.0
2078 */
2079 public function custom_price_to_cart_item( $cart_object ) {
2080 if ( apply_filters( 'cartflows_update_product_custom_price', wp_doing_ajax() ) && ! WC()->session->__isset( 'reload_checkout' ) ) {
2081
2082 foreach ( $cart_object->cart_contents as $key => $value ) {
2083
2084 if ( isset( $value['custom_price'] ) ) {
2085
2086 $custom_price = floatval( $value['custom_price'] );
2087 $value['data']->set_price( $custom_price );
2088 }
2089 }
2090 }
2091 }
2092
2093 /**
2094 * Get random product for test mode.
2095 */
2096 public function get_random_products() {
2097
2098 $products = array();
2099
2100 $args = array(
2101 'posts_per_page' => 1,
2102 'orderby' => 'rand',
2103 'post_type' => 'product',
2104 'meta_query' => array( //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
2105 // Exclude out of stock products.
2106 array(
2107 'key' => '_stock_status',
2108 'value' => 'outofstock',
2109 'compare' => 'NOT IN',
2110 ),
2111 ),
2112 'tax_query' => array( //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
2113 array(
2114 'taxonomy' => 'product_type',
2115 'field' => 'slug',
2116 'terms' => 'simple',
2117 ),
2118 ),
2119 );
2120
2121 $random_product = get_posts( $args );
2122
2123 if ( isset( $random_product[0]->ID ) ) {
2124 $products = array(
2125 array(
2126 'product' => $random_product[0]->ID,
2127 'unique_id' => wcf()->utils->get_unique_id(),
2128 'add_to_cart' => true,
2129 ),
2130 );
2131 }
2132
2133 return $products;
2134 }
2135
2136 /**
2137 * Change the Shipping error messages HTML
2138 *
2139 * @param string $message shipping message.
2140 *
2141 * @return string
2142 */
2143 public function change_shipping_message_html( $message ) {
2144
2145 $checkout_id = _get_wcf_checkout_id();
2146
2147 if ( ! $checkout_id ) {
2148
2149 $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
2150 }
2151
2152 if ( empty( $checkout_id ) ) {
2153 return $message;
2154 }
2155
2156 $enabled_custom_shipping_message = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-custom-no-shipping-method-message-toggle' );
2157
2158 if ( 'yes' === $enabled_custom_shipping_message ) {
2159 $custom_shipping_message = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-no-shipping-method-message' );
2160
2161 $message = ! empty( $custom_shipping_message ) ? $custom_shipping_message : $message;
2162
2163 }
2164 return "<span class='wcf-shipping-error-msg'>" . $message . '</span>';
2165 }
2166
2167 /**
2168 * Update cart total on button and order review mobile sction.
2169 *
2170 * @param string $fragments shipping message.
2171 *
2172 * @return array $fragments updated Woo fragments.
2173 */
2174 public function add_updated_cart_price( $fragments ) {
2175
2176 $checkout_id = _get_wcf_checkout_id();
2177
2178 if ( ! $checkout_id ) {
2179
2180 $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
2181 }
2182
2183 if ( empty( $checkout_id ) ) {
2184 return $fragments;
2185 }
2186 ob_start();
2187 echo "<div class='wcf-customer-shipping'>";
2188 $this->wcf_cart_totals_shipping_html();
2189 echo '</div>';
2190 $wcf_shipping_method_html = ob_get_clean();
2191
2192 ob_start();
2193 $this->wcf_order_review();
2194 $wcf_order_review = ob_get_clean();
2195
2196 ob_start();
2197 $this->show_selected_shipping_method();
2198 $selected_shipping_html = ob_get_clean();
2199
2200 $fragments['.woocommerce-checkout-review-order-table'] = $wcf_order_review;
2201 $fragments['.wcf-embed-checkout-form .wcf-shipping-methods-wrapper'] = $wcf_shipping_method_html;
2202 $fragments['.wcf-embed-checkout-form .woocommerce-checkout-review-order-table .cart-shipping'] = $selected_shipping_html;
2203 $fragments['.wcf-embed-checkout-form .wcf-customer-shipping'] = $wcf_shipping_method_html;
2204 $fragments['.wcf-embed-checkout-form .wcf-order-review-total'] = "<div class='wcf-order-review-total'>" . WC()->cart->get_total() . '</div>';
2205
2206
2207 return $fragments;
2208 }
2209
2210 /**
2211 * Array of order review toggler text.
2212 *
2213 * @param string $text array key to get specific value.
2214 *
2215 * @return string
2216 */
2217 public function get_order_review_toggle_texts( $text = 'show_text' ) {
2218
2219 $toggle_texts = apply_filters(
2220 'cartflows_order_review_toggle_texts',
2221 array(
2222 'show_text' => esc_html__( 'Show Order Summary', 'cartflows' ),
2223 'hide_text' => esc_html__( 'Hide Order Summary', 'cartflows' ),
2224 )
2225 );
2226
2227 return $toggle_texts[ $text ];
2228 }
2229
2230 /**
2231 * Get WC shipping methods HTML for modern Checkout.
2232 */
2233 public function wcf_cart_totals_shipping_html() {
2234
2235 // Return if WooCommerce is not active. Also check for wc is exists or not.
2236 if ( ! wcf()->is_woo_active || ! function_exists( 'WC' ) ) {
2237 return;
2238 }
2239
2240 $packages = WC()->shipping()->get_packages();
2241 if ( empty( $packages ) ) {
2242 WC()->cart->calculate_totals();
2243 $packages = WC()->shipping()->get_packages();
2244 }
2245 $first = true;
2246
2247 foreach ( $packages as $i => $package ) {
2248 $chosen_method = isset( WC()->session->chosen_shipping_methods[ $i ] ) ? WC()->session->chosen_shipping_methods[ $i ] : '';
2249 $product_names = array();
2250
2251 if ( count( $packages ) > 1 ) {
2252 foreach ( $package['contents'] as $item_id => $values ) {
2253 $product_names[ $item_id ] = $values['data']->get_name() . ' &times;' . $values['quantity'];
2254 }
2255 $product_names = apply_filters( 'woocommerce_shipping_package_details_array', $product_names, $package );
2256 }
2257
2258 include CARTFLOWS_CHECKOUT_DIR . 'templates/checkout/shipping-methods.php';
2259 do_action( 'woocommerce_review_order_after_shipping' );
2260
2261 $first = false;
2262 }
2263 }
2264
2265 /**
2266 * Get WC order review table HTML for modern Checkout.
2267 */
2268 public function wcf_order_review() {
2269
2270 // Return if Woo is not installed.
2271 if ( ! wcf()->is_woo_active ) {
2272 return;
2273 }
2274
2275 $checkout_id = _get_wcf_checkout_id();
2276
2277 if ( ! $checkout_id ) {
2278 $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
2279 }
2280 $checkout_layout = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-checkout-layout' );
2281 $path_to_order_review_template = apply_filters( 'cartflows_get_order_review_template_path', CARTFLOWS_CHECKOUT_DIR . 'templates/checkout/order-review-table.php', $checkout_layout );
2282
2283
2284 include $path_to_order_review_template;
2285 }
2286
2287 /**
2288 * Check for the CartFlows coupon field is enabled or disabled.
2289 */
2290 public function is_custom_coupon_field_enabled() {
2291
2292 $enabled = false;
2293
2294 $coupon_enabled = apply_filters( 'woocommerce_coupons_enabled', true );
2295 $show_coupon = apply_filters( 'cartflows_show_coupon_field', true );
2296
2297 if ( $coupon_enabled && $show_coupon ) {
2298 $enabled = true;
2299 }
2300
2301 return $enabled;
2302 }
2303
2304 /**
2305 * Add custom shipping method section if not multistep checkout.
2306 *
2307 * @since 2.1.0
2308 * @return void
2309 */
2310 public function add_custom_shipping_section() {
2311 // Return if the current page is not a CartFlows Checkout page or if any plugin's styles are set to be skipped.
2312 if ( ! ( _is_wcf_checkout_type() || _is_wcf_doing_checkout_ajax() ) || ! apply_filters( 'cartflows_should_render_custom_shipping', true ) ) {
2313 return;
2314 }
2315 ob_start();
2316 echo "<div class='wcf-customer-shipping'>";
2317 $this->wcf_cart_totals_shipping_html();
2318 echo '</div>';
2319 ob_end_flush();
2320 }
2321
2322 /**
2323 * Render the order review shipping totals as a row that is collapsed by default.
2324 * The cost stays visible so the order total reconciles without duplicating the shipping selector.
2325 *
2326 * @since 3.3.0
2327 * @return void
2328 */
2329 public static function collapsible_shipping_totals_html() {
2330
2331 $packages = WC()->shipping()->get_packages();
2332 $package_label = apply_filters( 'wcf_woocommerce_shipping_package_name', esc_html__( 'Shipping', 'cartflows' ) );
2333 $package_title = wp_strip_all_tags( $package_label );
2334 $has_rates = false;
2335
2336 foreach ( $packages as $package ) {
2337 if ( ! empty( $package['rates'] ) ) {
2338 $has_rates = true;
2339 break;
2340 }
2341 }
2342
2343 if ( ! $has_rates ) {
2344 ?>
2345 <tr class="woocommerce-shipping-totals shipping">
2346 <th><?php echo wp_kses_post( $package_label ); ?></th>
2347 <td data-title="<?php echo esc_attr( $package_title ); ?>">
2348 <?php echo wp_kses_post( self::get_shipping_unavailable_message() ); ?>
2349 </td>
2350 </tr>
2351 <?php
2352 return;
2353 }
2354
2355 $chosen_methods = WC()->session->get( 'chosen_shipping_methods' );
2356 $chosen_methods = is_array( $chosen_methods ) ? $chosen_methods : array();
2357 $show_package_name = count( $packages ) > 1;
2358 ?>
2359 <tr class="woocommerce-shipping-totals shipping wcf-shipping-totals-summary">
2360 <th>
2361 <button type="button" class="wcf-shipping-toggle" aria-expanded="false">
2362 <span class="wcf-shipping-toggle-label"><?php echo wp_kses_post( $package_label ); ?></span>
2363 <span class="wcf-shipping-toggle-icon cartflows-icon cartflows-cheveron-down" aria-hidden="true"></span>
2364 </button>
2365 </th>
2366 <td data-title="<?php echo esc_attr( $package_title ); ?>">
2367 <?php echo wp_kses_post( WC()->cart->get_cart_shipping_total() ); ?>
2368 </td>
2369 </tr>
2370 <tr class="wcf-shipping-totals-details" style="display: none;">
2371 <td colspan="2">
2372 <?php foreach ( $packages as $index => $package ) : ?>
2373 <?php
2374 $chosen_method = isset( $chosen_methods[ $index ] ) ? $chosen_methods[ $index ] : '';
2375 $destination = WC()->countries->get_formatted_address( $package['destination'], ', ' );
2376 ?>
2377 <div class="wcf-shipping-package">
2378 <?php if ( $show_package_name ) : ?>
2379 <?php /* translators: %d: shipping package number */ ?>
2380 <span class="wcf-shipping-package-name"><?php echo esc_html( sprintf( _x( 'Shipping %d', 'shipping packages', 'cartflows' ), $index + 1 ) ); ?></span>
2381 <?php endif; ?>
2382 <ul class="wcf-shipping-package-methods">
2383 <?php foreach ( $package['rates'] as $method ) : ?>
2384 <li class="<?php echo esc_attr( $method->id === $chosen_method ? 'wcf-shipping-method-chosen' : '' ); ?>">
2385 <?php echo wp_kses_post( wc_cart_totals_shipping_method_label( $method ) ); ?>
2386 </li>
2387 <?php endforeach; ?>
2388 </ul>
2389 <?php if ( $destination ) : ?>
2390 <p class="woocommerce-shipping-destination">
2391 <?php
2392 // Translators: %s shipping destination.
2393 printf( esc_html__( 'Shipping to %s.', 'cartflows' ), '<strong>' . esc_html( $destination ) . '</strong>' );
2394 ?>
2395 </p>
2396 <?php endif; ?>
2397 </div>
2398 <?php endforeach; ?>
2399 </td>
2400 </tr>
2401 <?php
2402 }
2403
2404 /**
2405 * Get the message shown when no shipping rate is available for the cart.
2406 *
2407 * @since 3.3.0
2408 * @return string
2409 */
2410 public static function get_shipping_unavailable_message() {
2411
2412 $destination = '';
2413
2414 foreach ( WC()->shipping()->get_packages() as $package ) {
2415 $destination = WC()->countries->get_formatted_address( $package['destination'], ', ' );
2416 break;
2417 }
2418
2419 if ( ! WC()->customer->has_calculated_shipping() || ! $destination ) {
2420 return apply_filters( 'woocommerce_shipping_may_be_available_html', __( 'Enter your address to view shipping options.', 'cartflows' ) );
2421 }
2422
2423 return 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' ) );
2424 }
2425
2426 /**
2427 * Show Selected Shipping Method in the order review table.
2428 *
2429 * @since 2.1.0
2430 * @return void
2431 */
2432 public static function show_selected_shipping_method() {
2433 if ( ( _is_wcf_checkout_type() || _is_wcf_doing_checkout_ajax() ) && WC()->cart->needs_shipping() && WC()->cart->show_shipping() ) :
2434 $shipping_method = WC()->session->get( 'chosen_shipping_methods' );
2435 $shipping_method = is_array( $shipping_method ) ? sanitize_text_field( $shipping_method[0] ) : '';
2436 $shipping_cost = '';
2437
2438 // Use WC_Shipping to get the shipping packages.
2439 $packages = WC()->shipping()->get_packages();
2440 if ( ! empty( $packages ) ) {
2441 foreach ( $packages as $package ) {
2442 $formatted_destination = WC()->countries->get_formatted_address( $package['destination'], ', ' );
2443 $has_calculated_shipping = WC()->customer->has_calculated_shipping();
2444 $available_methods = $package['rates'];
2445 if ( ! empty( $available_methods ) && is_array( $available_methods ) && $shipping_method && isset( $available_methods[ $shipping_method ] ) ) {
2446 $rate = $available_methods[ $shipping_method ];
2447 $shipping_cost = wc_cart_totals_shipping_method_label( $rate );
2448 } elseif ( ! $has_calculated_shipping || ! $formatted_destination ) {
2449 $shipping_cost = apply_filters( 'woocommerce_shipping_may_be_available_html', __( 'Enter your address to view shipping options.', 'cartflows' ) );
2450 }
2451 }
2452 } else {
2453 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' ) );
2454 }
2455
2456 ?>
2457 <tr class="cart-shipping">
2458 <th><?php esc_html_e( 'Shipping', 'cartflows' ); ?></th>
2459 <td>
2460 <?php
2461 if ( empty( $shipping_cost ) ) {
2462 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' ) ) );
2463 } else {
2464 echo wp_kses_post( $shipping_cost );
2465 }
2466 ?>
2467 </td>
2468 </tr>
2469 <?php
2470 endif;
2471 }
2472 }
2473
2474
2475 /**
2476 * Kicking this off by calling 'get_instance()' method
2477 */
2478 Cartflows_Checkout_Markup::get_instance();
2479