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 / classes / class-cartflows-helper.php

class-cartflows-helper.php in CartFlows – Funnel Builder & Checkout Plugin for WooCommerce 3.3.0, at classes/class-cartflows-helper.php

2,017 lines 52.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * CARTFLOWS Helper.
4 *
5 * @package CARTFLOWS
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit; // Exit if accessed directly.
10 }
11
12 /**
13 * Class Cartflows_Helper.
14 */
15 class Cartflows_Helper {
16
17 /**
18 * Instance
19 *
20 * @since 1.0.0
21 * @var object Class object.
22 * @access private
23 */
24 private static $instance;
25
26 /**
27 * Initiator
28 *
29 * @since 1.0.0
30 * @return object initialized object of class.
31 */
32 public static function get_instance() {
33 if ( ! isset( self::$instance ) ) {
34 self::$instance = new self();
35 }
36 return self::$instance;
37 }
38
39 /**
40 * Common global data
41 *
42 * @var zapier
43 */
44 private static $common = null;
45
46 /**
47 * Common Debug data
48 *
49 * @var debug_data
50 */
51 private static $debug_data = null;
52
53
54 /**
55 * Permalink settings
56 *
57 * @var permalink_setting
58 */
59 private static $permalink_setting = null;
60
61 /**
62 * Google Analytics Settings
63 *
64 * @var permalink_setting
65 */
66 private static $google_analytics_settings = null;
67
68 /**
69 * Installed Plugins
70 *
71 * @since 1.1.4
72 *
73 * @access private
74 * @var array Installed plugins list.
75 */
76 private static $installed_plugins = null;
77
78 /**
79 * Checkout Fields
80 *
81 * @var checkout_fields
82 */
83 private static $checkout_fields = null;
84
85 /**
86 * Facebook pixel global data
87 *
88 * @var faceboook
89 */
90 private static $facebook = null;
91
92 /**
93 * Facebook pixel global data
94 *
95 * @var tiktok
96 */
97 private static $tiktok = null;
98
99 /**
100 * Pinterest tag global data
101 *
102 * @var tiktok
103 */
104 private static $pinterest = null;
105
106 /**
107 * Google Ads settings
108 *
109 * @since 2.1.0
110 * @var array|null
111 */
112 private static $google_ads_settings = null;
113
114 /**
115 * Snapchat pixel global data
116 *
117 * @since 2.1.0
118 * @var array|null
119 */
120 private static $snapchat = null;
121
122 /**
123 * Returns an option from the database for the admin settings page.
124 *
125 * Note: Note: Use this function to access any properties to front-end of the website.
126 *
127 * @param string $key The option key.
128 * @param mixed $default Option default value if option is not available.
129 * @param boolean $network_override Whether to allow the network admin setting to be overridden on subsites.
130 * @return string Return the option value
131 */
132 public static function get_admin_settings_option( $key, $default = false, $network_override = false ) {
133
134 // Get the site-wide option if we're in the network admin.
135 if ( $network_override && is_multisite() ) {
136 $value = get_site_option( $key, $default );
137 } else {
138 $value = get_option( $key, $default );
139 }
140
141 return $value;
142 }
143
144 /**
145 * Updates an option from the admin settings page.
146 *
147 * Note: Use this function to access any properties to front-end of the website.
148 *
149 * @param string $key The option key.
150 * @param mixed $value The value to update.
151 * @param bool $network Whether to allow the network admin setting to be overridden on subsites.
152 * @return mixed
153 */
154 public static function update_admin_settings_option( $key, $value, $network = false ) {
155
156 // Update the site-wide option since we're in the network admin.
157 if ( $network && is_multisite() ) {
158 update_site_option( $key, $value );
159 } else {
160 update_option( $key, $value );
161 }
162 }
163
164 /**
165 * Get single setting
166 *
167 * @since 1.1.4
168 *
169 * @param string $key Option key.
170 * @param string $default Option default value if not exist.
171 * @return mixed
172 */
173 public static function get_common_setting( $key = '', $default = '' ) {
174 $settings = self::get_common_settings();
175
176 if ( $settings && array_key_exists( $key, $settings ) ) {
177 return $settings[ $key ];
178 }
179
180 return $default;
181 }
182
183 /**
184 * This function retrieves global option from database
185 *
186 * @param string $key option meta_key.
187 * @return mixed
188 * @since X.X.X
189 */
190 public static function get_global_setting( $key ) {
191 $default_global = apply_filters(
192 'cartflows_global_settings_default',
193 array(
194 '_cartflows_store_checkout' => false,
195 )
196 );
197
198 $value = get_option( $key, false );
199
200 if ( empty( $value ) && isset( $default_global[ $key ] ) ) {
201 $value = $default_global[ $key ];
202 }
203
204 return $value;
205 }
206
207 /**
208 * Get single debug options
209 *
210 * @since 1.1.4
211 *
212 * @param string $key Option key.
213 * @param string $default Option default value if not exist.
214 * @return mixed
215 */
216 public static function get_debug_setting( $key = '', $default = '' ) {
217 $debug_data = self::get_debug_settings();
218
219 if ( $debug_data && array_key_exists( $key, $debug_data ) ) {
220 return $debug_data[ $key ];
221 }
222
223 return $default;
224 }
225
226 /**
227 * Get required plugins for page builder
228 *
229 * @since 1.1.4
230 *
231 * @param string $page_builder_slug Page builder slug.
232 * @param string $default Default page builder.
233 * @return array selected page builder required plugins list.
234 */
235 public static function get_required_plugins_for_page_builder( $page_builder_slug = '', $default = 'elementor' ) {
236 $plugins = self::get_plugins_groupby_page_builders();
237
238 if ( array_key_exists( $page_builder_slug, $plugins ) ) {
239 return $plugins[ $page_builder_slug ];
240 }
241
242 return $plugins[ $default ];
243 }
244
245 /**
246 * Get Plugins list by page builder.
247 *
248 * @since 1.1.4
249 *
250 * @return array Required Plugins list.
251 */
252 public static function get_plugins_groupby_page_builders() {
253
254 $divi_status = self::get_plugin_status( 'divi-builder/divi-builder.php' );
255 $theme_status = 'not-installed';
256 if ( $divi_status ) {
257 if ( true === Cartflows_Compatibility::get_instance()->is_divi_theme_installed() ) {
258 $theme_status = 'installed';
259 if ( false === Cartflows_Compatibility::get_instance()->is_divi_enabled() ) {
260 $theme_status = 'deactivate';
261 $divi_status = 'activate';
262 } else {
263 $divi_status = '';
264 }
265 }
266 }
267
268 $plugins = array(
269 'elementor' => array(
270 'title' => 'Elementor',
271 'plugins' => array(
272 array(
273 'slug' => 'elementor', // For download from wp.org.
274 'init' => 'elementor/elementor.php',
275 'status' => self::get_plugin_status( 'elementor/elementor.php' ),
276 ),
277 ),
278 ),
279 'gutenberg' => array(
280 'title' => 'Spectra',
281 'plugins' => array(
282 array(
283 'slug' => 'ultimate-addons-for-gutenberg', // For download from wp.org.
284 'init' => 'ultimate-addons-for-gutenberg/ultimate-addons-for-gutenberg.php',
285 'status' => self::get_plugin_status( 'ultimate-addons-for-gutenberg/ultimate-addons-for-gutenberg.php' ),
286 ),
287 ),
288 ),
289 'divi' => array(
290 'title' => 'Divi',
291 'theme-status' => $theme_status,
292 'plugin-status' => $divi_status,
293 'plugins' => array(
294 array(
295 'slug' => 'divi-builder', // For download from wp.org.
296 'init' => 'divi-builder/divi-builder.php',
297 'status' => $divi_status,
298 ),
299 ),
300 ),
301 'beaver-builder' => array(
302 'title' => 'Beaver Builder',
303 'plugins' => array(),
304 ),
305 );
306
307 // Check Pro Exist.
308 if ( file_exists( WP_PLUGIN_DIR . '/bb-plugin/fl-builder.php' ) && ! is_plugin_active( 'beaver-builder-lite-version/fl-builder.php' ) ) {
309 $plugins['beaver-builder']['plugins'][] = array(
310 'slug' => 'bb-plugin',
311 'init' => 'bb-plugin/fl-builder.php',
312 'status' => self::get_plugin_status( 'bb-plugin/fl-builder.php' ),
313 );
314 } else {
315 $plugins['beaver-builder']['plugins'][] = array(
316 'slug' => 'beaver-builder-lite-version', // For download from wp.org.
317 'init' => 'beaver-builder-lite-version/fl-builder.php',
318 'status' => self::get_plugin_status( 'beaver-builder-lite-version/fl-builder.php' ),
319 );
320 }
321
322 if ( file_exists( WP_PLUGIN_DIR . '/bb-ultimate-addon/bb-ultimate-addon.php' ) && ! is_plugin_active( 'ultimate-addons-for-beaver-builder-lite/bb-ultimate-addon.php' ) ) {
323 $plugins['beaver-builder']['plugins'][] = array(
324 'slug' => 'bb-ultimate-addon',
325 'init' => 'bb-ultimate-addon/bb-ultimate-addon.php',
326 'status' => self::get_plugin_status( 'bb-ultimate-addon/bb-ultimate-addon.php' ),
327 );
328 } else {
329 $plugins['beaver-builder']['plugins'][] = array(
330 'slug' => 'ultimate-addons-for-beaver-builder-lite', // For download from wp.org.
331 'init' => 'ultimate-addons-for-beaver-builder-lite/bb-ultimate-addon.php',
332 'status' => self::get_plugin_status( 'ultimate-addons-for-beaver-builder-lite/bb-ultimate-addon.php' ),
333 );
334 }
335
336 return $plugins;
337 }
338
339 /**
340 * Determine the status of a given plugin.
341 *
342 * Returns 'install' if the plugin is not installed,
343 * 'activate' if installed but not active,
344 * 'inactive' if installed and active but $send_active is not true,
345 * or 'active' if installed and active and $send_active is true.
346 *
347 * @since 1.1.4
348 *
349 * @param string $plugin_init_file Plugin init file (e.g., 'plugin-folder/plugin-file.php').
350 * @param bool $send_active If true, returns 'active' for active plugins; otherwise returns 'inactive'.
351 * @return string The status: 'install', 'activate', 'inactive', or 'active'.
352 */
353 public static function get_plugin_status( $plugin_init_file, $send_active = false ) {
354
355 if ( null == self::$installed_plugins ) {
356 self::$installed_plugins = get_plugins();
357 }
358
359 if ( $send_active && is_plugin_active( $plugin_init_file ) ) {
360 return 'active';
361 }
362
363 $plugin_status = '';
364
365 if ( ! isset( self::$installed_plugins[ $plugin_init_file ] ) ) {
366 $plugin_status = 'install';
367 } elseif ( ! is_plugin_active( $plugin_init_file ) ) {
368 $plugin_status = 'activate';
369 } else {
370 $plugin_status = 'inactive';
371 }
372
373 return $plugin_status;
374 }
375
376 /**
377 * Get zapier settings.
378 *
379 * @return array
380 */
381 public static function get_common_settings() {
382
383 if ( null === self::$common ) {
384
385 $common_default = apply_filters(
386 'cartflows_common_settings_default',
387 array(
388 'global_checkout' => '',
389 'override_global_checkout' => 'enable',
390 'override_store_order_pay' => 'disable',
391 'disallow_indexing' => 'disable',
392 'default_page_builder' => 'elementor',
393 )
394 );
395
396 $common = self::get_admin_settings_option( '_cartflows_common', false, false );
397
398 $common = wp_parse_args( $common, $common_default );
399
400 if ( ! did_action( 'wp' ) ) {
401 return $common;
402 } else {
403 self::$common = $common;
404 }
405 }
406
407 return self::$common;
408 }
409
410 /**
411 * Get debug settings data.
412 *
413 * @return array
414 */
415 public static function get_debug_settings() {
416
417 if ( null === self::$debug_data ) {
418
419 $debug_data_default = apply_filters(
420 'cartflows_debug_settings_default',
421 array(
422 'allow_minified_files' => 'disable',
423 )
424 );
425
426 $debug_data = self::get_admin_settings_option( '_cartflows_debug_data', false, false );
427
428 $debug_data = wp_parse_args( $debug_data, $debug_data_default );
429
430 if ( ! did_action( 'wp' ) ) {
431 return $debug_data;
432 } else {
433 self::$debug_data = $debug_data;
434 }
435 }
436
437 return self::$debug_data;
438 }
439
440
441 /**
442 * Get debug settings data.
443 *
444 * @return array.
445 */
446 public static function get_permalink_settings() {
447
448 if ( null === self::$permalink_setting ) {
449
450 $permalink_default = apply_filters(
451 'cartflows_permalink_settings_default',
452 array(
453 'permalink' => CARTFLOWS_STEP_PERMALINK_SLUG,
454 'permalink_flow_base' => CARTFLOWS_FLOW_PERMALINK_SLUG,
455 'permalink_structure' => '',
456
457 )
458 );
459
460 $permalink_data = self::get_admin_settings_option( '_cartflows_permalink', false, false );
461
462 $permalink_data = wp_parse_args( $permalink_data, $permalink_default );
463
464 if ( ! did_action( 'wp' ) ) {
465 return $permalink_data;
466 } else {
467 self::$permalink_setting = $permalink_data;
468 }
469 }
470
471 return self::$permalink_setting;
472 }
473
474
475 /**
476 * Get debug settings data.
477 *
478 * @return array.
479 */
480 public static function get_google_analytics_settings() {
481
482 if ( null === self::$google_analytics_settings ) {
483
484 $google_analytics_settings_default = apply_filters(
485 'cartflows_google_analytics_settings_default',
486 array(
487 'enable_google_analytics' => 'disable',
488 'enable_google_analytics_for_site' => 'disable',
489 'google_analytics_id' => '',
490 'enable_begin_checkout' => 'enable',
491 'enable_add_to_cart' => 'enable',
492 'enable_optin_lead' => 'enable',
493 'enable_add_payment_info' => 'enable',
494 'enable_purchase_event' => 'enable',
495 )
496 );
497
498 $google_analytics_settings_data = self::get_admin_settings_option( '_cartflows_google_analytics', false, true );
499
500 $google_analytics_settings_data = wp_parse_args( $google_analytics_settings_data, $google_analytics_settings_default );
501
502 if ( ! did_action( 'wp' ) ) {
503 return $google_analytics_settings_data;
504 } else {
505 self::$google_analytics_settings = $google_analytics_settings_data;
506 }
507 }
508
509 return self::$google_analytics_settings;
510 }
511
512 /**
513 * Get Checkout field.
514 *
515 * @param string $key Field key.
516 * @param int $post_id Post id.
517 * @return array.
518 */
519 public static function get_checkout_fields( $key, $post_id ) {
520
521 $saved_fields = get_post_meta( $post_id, 'wcf_fields_' . $key, true );
522
523 if ( ! $saved_fields ) {
524 $saved_fields = array();
525 }
526
527 $fields = array_filter( $saved_fields );
528
529 if ( empty( $fields ) ) {
530 if ( 'billing' === $key || 'shipping' === $key ) {
531
532 $fields = WC()->countries->get_address_fields( WC()->countries->get_base_country(), $key . '_' );
533
534 update_post_meta( $post_id, 'wcf_fields_' . $key, $fields );
535 }
536 }
537
538 return $fields;
539 }
540
541 /**
542 * Get Optin fields.
543 *
544 * @return array.
545 */
546 public static function get_optin_default_fields() {
547
548 $optin_fields = array(
549 'billing_first_name' => array(
550 'label' => __( 'First name', 'cartflows' ),
551 'required' => true,
552 'class' => array(
553 'form-row-first',
554 ),
555 'autocomplete' => 'given-name',
556 'priority' => 10,
557 ),
558 'billing_last_name' => array(
559 'label' => __( 'Last name', 'cartflows' ),
560 'required' => true,
561 'class' => array(
562 'form-row-last',
563 ),
564 'autocomplete' => 'family-name',
565 'priority' => 20,
566 ),
567 'billing_email' => array(
568 'label' => __( 'Email address', 'cartflows' ),
569 'required' => true,
570 'type' => 'email',
571 'class' => array(
572 'form-row-wide',
573 ),
574 'validate' => array(
575 'email',
576 ),
577 'autocomplete' => 'email username',
578 'priority' => 30,
579 ),
580 );
581
582 return $optin_fields;
583 }
584
585 /**
586 * Get Optin field.
587 *
588 * @param string $key Field key.
589 * @param int $post_id Post id.
590 * @return array.
591 */
592 public static function get_optin_fields( $key, $post_id ) {
593
594 $saved_fields = get_post_meta( $post_id, 'wcf_fields_' . $key, true );
595
596 if ( ! $saved_fields ) {
597 $saved_fields = array();
598 }
599
600 $fields = array_filter( $saved_fields );
601
602 if ( empty( $fields ) ) {
603 if ( 'billing' === $key ) {
604
605 $fields = self::get_optin_default_fields();
606
607 update_post_meta( $post_id, 'wcf_fields_' . $key, $fields );
608 }
609 }
610
611 return $fields;
612 }
613
614 /**
615 * Get meta options
616 *
617 * @since 1.0.0
618 * @param int $post_id Product ID.
619 * @param string $key Meta Key.
620 * @param string $default Default value.
621 * @return string Meta Value.
622 */
623 public static function get_meta_option( $post_id, $key, $default = '' ) {
624
625 $value = get_post_meta( $post_id, $key, true );
626
627 if ( ! $value ) {
628 $value = $default;
629 }
630
631 return $value;
632 }
633
634 /**
635 * Check if Elementor page builder is installed
636 *
637 * @since 1.0.0
638 *
639 * @access public
640 */
641 public static function is_elementor_installed() {
642 $path = 'elementor/elementor.php';
643 $plugins = get_plugins();
644
645 return isset( $plugins[ $path ] );
646 }
647
648 /**
649 * Check if Step has product assigned.
650 *
651 * @since 1.0.0
652 * @param int $step_id step ID.
653 *
654 * @access public
655 */
656 public static function has_product_assigned( $step_id ) {
657
658 $step_type = get_post_meta( $step_id, 'wcf-step-type', true );
659
660 $has_product_assigned = false;
661
662 if ( 'checkout' === $step_type ) {
663 $product = get_post_meta( $step_id, 'wcf-checkout-products', true );
664
665 if ( ! empty( $product ) && isset( $product[0]['product'] ) ) {
666 $has_product_assigned = true;
667 }
668 } elseif ( 'optin' === $step_type ) {
669 $product = get_post_meta( $step_id, 'wcf-optin-product', true );
670 if ( ! empty( $product ) && ! empty( $product[0] ) ) {
671 $has_product_assigned = true;
672 }
673 } else {
674 $product = get_post_meta( $step_id, 'wcf-offer-product', true );
675 if ( ! empty( $product ) && ! empty( $product[0] ) ) {
676 $has_product_assigned = true;
677 }
678 }
679
680 return $has_product_assigned;
681 }
682
683 /**
684 * Get attributes for cartflows wrap.
685 *
686 * @since 1.1.4
687 *
688 * @access public
689 */
690 public static function get_cartflows_container_atts() {
691
692 $attributes = apply_filters(
693 'cartflows_container_atts',
694 array()
695 );
696 $atts_string = '';
697
698 foreach ( $attributes as $key => $value ) {
699
700 if ( ! $value ) {
701 continue;
702 }
703
704 if ( true === $value ) {
705 $atts_string .= esc_html( $key ) . ' ';
706 } else {
707 $atts_string .= sprintf( '%s="%s" ', esc_html( $key ), esc_attr( $value ) );
708 }
709 }
710
711 return $atts_string;
712 }
713
714 /**
715 * Get facebook pixel settings.
716 *
717 * @return facebook array.
718 */
719 public static function get_facebook_settings() {
720
721 if ( null === self::$facebook ) {
722
723 $facebook_default = array(
724 'facebook_pixel_id' => '',
725 'facebook_pixel_view_content' => 'enable',
726 'facebook_pixel_add_to_cart' => 'enable',
727 'facebook_pixel_initiate_checkout' => 'enable',
728 'facebook_pixel_add_payment_info' => 'enable',
729 'facebook_pixel_purchase_complete' => 'enable',
730 'facebook_pixel_optin_lead' => 'enable',
731 'facebook_pixel_tracking' => 'disable',
732 'facebook_pixel_tracking_for_site' => 'disable',
733 );
734
735 $facebook = self::get_admin_settings_option( '_cartflows_facebook', false, false );
736
737 $facebook = wp_parse_args( $facebook, $facebook_default );
738
739 self::$facebook = apply_filters( 'cartflows_facebook_settings_default', $facebook );
740
741 }
742
743 return self::$facebook;
744 }
745
746 /**
747 * Get tiktok pixel settings.
748 *
749 * @return tiktok tiktok settings array.
750 */
751 public static function get_tiktok_settings() {
752
753 if ( null === self::$tiktok ) {
754
755 $tiktok_default = array(
756 'tiktok_pixel_id' => '',
757 'enable_tiktok_begin_checkout' => 'disable',
758 'enable_tiktok_add_to_cart' => 'disable',
759 'enable_tiktok_view_content' => 'disable',
760 'enable_tiktok_add_payment_info' => 'disable',
761 'enable_tiktok_purchase_event' => 'disable',
762 'enable_tiktok_optin_lead' => 'disable',
763 'tiktok_pixel_tracking' => 'disable',
764 'tiktok_pixel_tracking_for_site' => 'disable',
765 );
766
767 $tiktok = self::get_admin_settings_option( '_cartflows_tiktok', false, false );
768
769 $tiktok = wp_parse_args( $tiktok, $tiktok_default );
770
771 self::$tiktok = apply_filters( 'cartflows_tiktok_settings_default', $tiktok );
772
773 }
774
775 return self::$tiktok;
776 }
777
778 /**
779 * Get pinterest tag settings.
780 *
781 * @return pinterest pinterest settings array.
782 */
783 public static function get_pinterest_settings() {
784
785 if ( null === self::$pinterest ) {
786
787 $pinterest_default = array(
788 'pinterest_tag_id' => '',
789 'enable_pinterest_consent' => 'disable',
790 'enable_pinterest_begin_checkout' => 'disable',
791 'enable_pinterest_add_to_cart' => 'disable',
792 'enable_pinterest_add_payment_info' => 'disable',
793 'enable_pinterest_purchase_event' => 'disable',
794 'enable_pinterest_signup' => 'disable',
795 'enable_pinterest_optin_lead' => 'disable',
796 'pinterest_tag_tracking' => 'disable',
797 'pinterest_tag_tracking_for_site' => 'disable',
798 );
799
800 $pinterest = self::get_admin_settings_option( '_cartflows_pinterest', false, false );
801
802 $pinterest = wp_parse_args( $pinterest, $pinterest_default );
803
804 self::$pinterest = apply_filters( 'cartflows_pinterest_settings_default', $pinterest );
805
806 }
807
808 return self::$pinterest;
809 }
810
811 /**
812 * Get debug settings data.
813 *
814 * @since 2.1.0
815 * @return array $google_ads_settings The Google Ads settings array.
816 */
817 public static function get_google_ads_settings() {
818
819 if ( null === self::$google_ads_settings ) {
820
821
822 $google_ads_settings_default = apply_filters(
823 'cartflows_google_ads_settings_default',
824 array(
825 'google_ads_id' => '',
826 'google_ads_label' => '',
827 'enable_google_ads_begin_checkout' => 'disable',
828 'enable_google_ads_add_to_cart' => 'disable',
829 'enable_google_ads_view_content' => 'disable',
830 'enable_google_ads_add_payment_info' => 'disable',
831 'enable_google_ads_purchase_event' => 'disable',
832 'enable_google_ads_optin_lead' => 'disable',
833 'google_ads_tracking' => 'disable',
834 'google_ads_for_site' => 'disable',
835 )
836 );
837
838 $google_ads_settings_data = self::get_admin_settings_option( '_cartflows_google_ads', false, true );
839
840 $google_ads_settings_data = wp_parse_args( $google_ads_settings_data, $google_ads_settings_default );
841
842 if ( ! did_action( 'wp' ) ) {
843 return $google_ads_settings_data;
844 } else {
845 self::$google_ads_settings = $google_ads_settings_data;
846 }
847 }
848
849 return self::$google_ads_settings;
850 }
851
852 /**
853 * Get snapchat pixel settings.
854 *
855 * @return snapchat snapchat settings array.
856 */
857 public static function get_snapchat_settings() {
858
859 if ( null === self::$snapchat ) {
860
861 $snapchat_default = array(
862 'snapchat_pixel_id' => '',
863 'enable_snapchat_begin_checkout' => 'disable',
864 'enable_snapchat_add_to_cart' => 'disable',
865 'enable_snapchat_view_content' => 'disable',
866 'enable_snapchat_purchase_event' => 'disable',
867 'enable_snapchat_optin_lead' => 'disable',
868 'enable_snapchat_subscribe_event' => 'disable',
869 'snapchat_pixel_tracking' => 'disable',
870 'snapchat_pixel_for_site' => 'disable',
871 );
872
873 $snapchat = self::get_admin_settings_option( '_cartflows_snapchat', false, false );
874
875 $snapchat = wp_parse_args( $snapchat, $snapchat_default );
876
877 self::$snapchat = apply_filters( 'cartflows_snapchat_settings_default', $snapchat );
878
879 }
880
881 return self::$snapchat;
882 }
883
884 /**
885 * Get the image url of size.
886 *
887 * @param int $post_id post id.
888 * @param array $key key.
889 * @param string $size image size.
890 *
891 * @return array
892 */
893 public static function get_image_url( $post_id, $key, $size = false ) {
894
895 $url = get_post_meta( $post_id, $key, true );
896 $img_obj = get_post_meta( $post_id, $key . '-obj', true );
897
898 if ( is_array( $img_obj ) && ! empty( $img_obj ) && false !== $size ) {
899
900 $url = ! empty( $img_obj['url'][ $size ] ) ? $img_obj['url'][ $size ] : $url;
901 }
902
903 return $url;
904 }
905
906 /**
907 * Download File Into Uploads Directory
908 *
909 * @since 1.6.15
910 *
911 * @param string $file Download File URL.
912 * @param array $overrides Upload file arguments.
913 * @param int $timeout_seconds Timeout in downloading the XML file in seconds.
914 * @return array Downloaded file data.
915 */
916 public static function download_file( $file = '', $overrides = array(), $timeout_seconds = 300 ) {
917
918 // Gives us access to the download_url() and wp_handle_sideload() functions.
919 require_once ABSPATH . 'wp-admin/includes/file.php';
920
921 // Download file to temp dir.
922 $temp_file = download_url( $file, $timeout_seconds );
923
924 // WP Error.
925 if ( is_wp_error( $temp_file ) ) {
926 return array(
927 'success' => false,
928 'data' => $temp_file->get_error_message(),
929 );
930 }
931
932 // Array based on $_FILE as seen in PHP file uploads.
933 $file_args = array(
934 'name' => basename( $file ),
935 'tmp_name' => $temp_file,
936 'error' => 0,
937 'size' => filesize( $temp_file ),
938 );
939
940 $defaults = array(
941
942 // Tells WordPress to not look for the POST form
943 // fields that would normally be present as
944 // we downloaded the file from a remote server, so there
945 // will be no form fields
946 // Default is true.
947 'test_form' => false,
948
949 // Setting this to false lets WordPress allow empty files, not recommended.
950 // Default is true.
951 'test_size' => true,
952
953 // A properly uploaded file will pass this test. There should be no reason to override this one.
954 'test_upload' => true,
955
956 'mimes' => array(
957 'xml' => 'text/xml',
958 'json' => 'text/plain',
959 ),
960 );
961
962 $overrides = wp_parse_args( $overrides, $defaults );
963
964 // Move the temporary file into the uploads directory.
965 $results = wp_handle_sideload( $file_args, $overrides );
966
967 if ( isset( $results['error'] ) ) {
968 return array(
969 'success' => false,
970 'data' => $results,
971 );
972 }
973
974 // Success.
975 return array(
976 'success' => true,
977 'data' => $results,
978 );
979 }
980
981 /**
982 * Get an instance of WP_Filesystem_Direct.
983 *
984 * @since 1.6.15
985 * @return object A WP_Filesystem_Direct instance.
986 */
987 public static function get_filesystem() {
988 global $wp_filesystem;
989
990 require_once ABSPATH . '/wp-admin/includes/file.php';
991
992 WP_Filesystem();
993
994 return $wp_filesystem;
995 }
996
997 /**
998 * Get all flow and steps
999 *
1000 * @since 1.6.15
1001 * @return array
1002 */
1003 public function get_all_flows_and_steps() {
1004 $all_flows = array(
1005 'elementor' => array(),
1006 'divi' => array(),
1007 'gutenberg' => array(),
1008 'beaver-builder' => array(),
1009 );
1010 foreach ( $all_flows as $slug => $value ) {
1011 $all_flows[ $slug ] = $this->get_flows_and_steps( $slug );
1012 }
1013
1014 return $all_flows;
1015 }
1016
1017 /**
1018 * Get flow and steps
1019 *
1020 * @since 1.6.15
1021 *
1022 * @param string $page_builder_slug Page builder slug.
1023 * @param string $templates templates category to fetch.
1024 *
1025 * @return array
1026 */
1027 public function get_flows_and_steps( $page_builder_slug = '', $templates = '' ) {
1028 $page_builder_slug = ( ! empty( $page_builder_slug ) ) ? $page_builder_slug : wcf()->get_site_slug();
1029
1030 $suffix = 'store-checkout' === $templates ? 'store-checkout-' : '';
1031
1032 $pages_count = get_site_option( 'cartflows-' . $suffix . $page_builder_slug . '-requests', 0 );
1033
1034 $flows = array();
1035 if ( $pages_count ) {
1036 for ( $page_no = 1; $page_no <= $pages_count; $page_no++ ) {
1037
1038 $data = get_site_option( 'cartflows-' . $suffix . $page_builder_slug . '-flows-and-steps-' . $page_no, '' );
1039
1040 if ( ! empty( $data ) ) {
1041
1042 $data = 'string' === gettype( $data ) ? json_decode( $data ) : $data;
1043
1044 foreach ( $data as $key => $flow ) {
1045 $flows[] = $flow;
1046 }
1047 } else {
1048 // Return store templates from JSON files if no data found.
1049 $flows = $this->get_stored_page_builder_templates( $page_builder_slug );
1050 }
1051 }
1052 } else {
1053 // All flows.
1054 $flows = $this->get_stored_page_builder_templates( $page_builder_slug );
1055 }
1056
1057 return $flows;
1058 }
1059
1060 /**
1061 * Get page builder name
1062 *
1063 * @since 1.1.4
1064 *
1065 * @param string $page_builder Page builder slug.
1066 * @return mixed
1067 */
1068 public static function get_page_builder_name( $page_builder = '' ) {
1069
1070 $pb_data = array(
1071 'elementor' => 'Elementor',
1072 'gutenberg' => 'Spectra',
1073 'beaver-builder' => 'Beaver Builder',
1074 'divi' => 'Divi',
1075 'bricks-builder' => 'Bricks',
1076 );
1077
1078 if ( isset( $pb_data[ $page_builder ] ) ) {
1079
1080 return $pb_data[ $page_builder ];
1081 }
1082
1083 return '';
1084 }
1085
1086 /**
1087 * Create Edit page link for the widgets.
1088 *
1089 * @since 1.6.15
1090 * @param string $tab The Tab which has to display.
1091 * @access public
1092 */
1093 public static function get_current_page_edit_url( $tab ) {
1094
1095 $url = add_query_arg(
1096 array(
1097 'wcf-tab' => $tab,
1098 ),
1099 get_edit_post_link()
1100 );
1101
1102 return $url;
1103 }
1104
1105 /**
1106 * Get product price.
1107 *
1108 * @param object $product product data.
1109 */
1110 public static function get_product_original_price( $product ) {
1111
1112 $custom_price = '';
1113 $product_id = 0;
1114
1115 if ( $product->is_type( 'variable' ) ) {
1116
1117 $default_attributes = $product->get_default_attributes();
1118
1119 if ( ! empty( $default_attributes ) ) {
1120
1121 foreach ( $product->get_children() as $c_in => $variation_id ) {
1122
1123 if ( 0 === $c_in ) {
1124 $product_id = $variation_id;
1125 }
1126
1127 $single_variation = new \WC_Product_Variation( $variation_id );
1128
1129 if ( $default_attributes == $single_variation->get_attributes() ) {
1130
1131 $product_id = $variation_id;
1132 break;
1133 }
1134 }
1135 } else {
1136
1137 $product_childrens = $product->get_children();
1138
1139 if ( is_array( $product_childrens ) && ! empty( $product_childrens ) ) {
1140
1141 foreach ( $product_childrens as $c_in => $c_id ) {
1142
1143 $_child_product = wc_get_product( $c_id );
1144
1145 if ( $_child_product->is_in_stock() && 'publish' === $_child_product->get_status() ) {
1146 $product_id = $c_id;
1147 break;
1148 }
1149 }
1150 } else {
1151
1152 // Return if no childrens found.
1153 return;
1154 }
1155 }
1156
1157 $product = wc_get_product( $product_id );
1158 }
1159
1160 if ( $product && $product->is_type( 'bundle' ) && method_exists( $product, 'get_bundle_price' ) ) {
1161 /*
1162 * A bundle's own price is only its container base price, which is 0 or empty as soon
1163 * as the bundled items are priced individually — leaving the step showing just a
1164 * currency symbol. Use the bundle's minimum price instead: the "from" price the
1165 * customer would pay, mirroring how a variable product resolves to a variation price
1166 * above. Kept scalar because callers run discount maths on this value.
1167 */
1168 return $product->get_bundle_price( 'min', false );
1169 }
1170
1171 if ( $product ) {
1172 $custom_price = $product->get_price( 'edit' );
1173 }
1174
1175 return $custom_price;
1176 }
1177
1178 /**
1179 * Get flows from json files.
1180 *
1181 * @param string $page_builder_slug Selected page builder slug.
1182 */
1183 public function get_stored_page_builder_templates( $page_builder_slug ) {
1184 $flows = array();
1185
1186 $dir = CARTFLOWS_DIR . 'admin-core/assets/importer-data';
1187 $list_files = \list_files( $dir );
1188 if ( ! empty( $list_files ) ) {
1189 $list_files = array_map( 'basename', $list_files );
1190 foreach ( $list_files as $file_key => $file_name ) {
1191 if ( false !== strpos( $file_name, 'cartflows-' . $page_builder_slug . '-flows-and-steps' ) ) {
1192 // file_get_contents is fine for local files. https://github.com/WordPress/WordPress-Coding-Standards/pull/1374/files#diff-400e43bc09c24262b43f26fce487fdabR43-R52.
1193 $data = json_decode( file_get_contents( $dir . '/' . $file_name ), true ); //phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
1194 if ( ! empty( $data ) ) {
1195 /*
1196 Commented.
1197 // $option_name = str_replace( '.json', '', $file_name );
1198 // update_site_option( $option_name, json_decode( $data, true ) );
1199 */
1200 foreach ( $data as $key => $flow ) {
1201 $flows[] = $flow;
1202 }
1203 }
1204 }
1205 }
1206 }
1207
1208 return $flows;
1209 }
1210
1211 /**
1212 * Get meta keys to exclude.
1213 *
1214 * @param int $step_id post id.
1215 */
1216 public function get_meta_keys_to_exclude_from_import( $step_id = 0 ) {
1217
1218 $meta_keys = array(
1219 '_wp_old_slug',
1220 'wcf-checkout-products',
1221 'wcf-optin-product',
1222 'wcf-testing',
1223 );
1224
1225 if ( $step_id && 'yes' === get_post_meta( $step_id, 'cartflows_imported_step', true ) ) {
1226
1227 $meta_keys = array_merge(
1228 $meta_keys,
1229 array(
1230 'wcf_fields_billing',
1231 'wcf_fields_shipping',
1232 )
1233 );
1234 }
1235
1236 return apply_filters(
1237 'cartflows_admin_exclude_import_meta_keys',
1238 $meta_keys
1239 );
1240 }
1241
1242 /**
1243 * Get allowed meta key prefixes for import.
1244 *
1245 * Security: Only meta keys matching these prefixes are permitted during template import.
1246 * This prevents supply-chain attacks from writing arbitrary meta keys.
1247 *
1248 * @since 2.2.2
1249 * @return array List of allowed meta key prefixes.
1250 */
1251 public function get_allowed_import_meta_key_prefixes() {
1252 $prefixes = array(
1253 'wcf-',
1254 'wcf_',
1255 'cartflows_',
1256 '_wcf-',
1257 '_wcf_',
1258 '_cartflows_',
1259 '_elementor_',
1260 '_fl_builder_',
1261 '_wp_page_template',
1262 '_thumbnail_id',
1263 );
1264
1265 return apply_filters( 'cartflows_allowed_import_meta_key_prefixes', $prefixes );
1266 }
1267
1268 /**
1269 * Check if a meta key is allowed for import.
1270 *
1271 * @since 2.2.2
1272 * @param string $meta_key The meta key to check.
1273 * @return bool True if the meta key is allowed.
1274 */
1275 public function is_meta_key_allowed_for_import( $meta_key ) {
1276 $allowed_prefixes = $this->get_allowed_import_meta_key_prefixes();
1277
1278 foreach ( $allowed_prefixes as $prefix ) {
1279 if ( 0 === strpos( $meta_key, $prefix ) ) {
1280 return true;
1281 }
1282 }
1283
1284 return false;
1285 }
1286
1287 /**
1288 * Maybe update flow step order.
1289 *
1290 * @param int $flow_id flow id.
1291 * @param array $flow_steps step list.
1292 *
1293 * @return array flow_steps step list.
1294 */
1295 public function maybe_update_flow_steps( $flow_id, $flow_steps ) {
1296
1297 if ( absint( self::get_global_setting( '_cartflows_store_checkout' ) ) === $flow_id ) {
1298 $key = array_search( 'thankyou', wp_list_pluck( $flow_steps, 'type' ), true );
1299 if ( ! $key ) {
1300 return $flow_steps;
1301 } else {
1302 $thankyou = array_splice( $flow_steps, intval( $key ), 1 );
1303 $flow_steps[] = $thankyou[0];
1304 }
1305 }
1306
1307 return $flow_steps;
1308 }
1309
1310 /**
1311 * Prepare cusom field settings array
1312 *
1313 * @param array $data_array field array.
1314 * @param string $key field key.
1315 * @param array $field_args field args.
1316 * @param array $fields_type field type.
1317 * @param string $step_type step type.
1318 *
1319 * @return array field data array.
1320 */
1321 public function prepare_custom_field_settings( $data_array, $key, $field_args, $fields_type, $step_type = 'checkout' ) {
1322
1323 if ( 'checkout' === $step_type ) {
1324 $name = 'billing' === $fields_type ? 'wcf_field_order_billing[' . $key . ']' : 'wcf_field_order_shipping[' . $key . ']';
1325 } else {
1326 $name = 'wcf-optin-fields-billing[' . $key . ']';
1327 }
1328
1329 $type = $field_args['type'];
1330 $is_checkbox = 'checkbox' == $type ? true : false;
1331 $is_radiobutton = 'radio' == $type ? true : false;
1332 $is_select = 'select' == $type ? true : false;
1333 $is_file = 'file' == $type ? true : false;
1334 $is_custom_field = isset( $field_args['custom'] ) && true === (bool) $field_args['custom'] ? true : false;
1335
1336 $data_array['field_options'] = array(
1337 'enable-field' => array(
1338 'label' => __( 'Enable Field', 'cartflows' ),
1339 'name' => $name . '[enabled]',
1340 'value' => $field_args['enabled'],
1341 ),
1342 'select-width' => array(
1343 'type' => 'select',
1344 'label' => __( 'Field Width', 'cartflows' ),
1345 'name' => $name . '[width]',
1346 'value' => $field_args['width'],
1347 'options' => array(
1348 array(
1349 'value' => '33',
1350 'label' => esc_html__( '33%', 'cartflows' ),
1351 ),
1352 array(
1353 'value' => '50',
1354 'label' => esc_html__( '50%', 'cartflows' ),
1355 ),
1356 array(
1357 'value' => '100',
1358 'label' => esc_html__( '100%', 'cartflows' ),
1359 ),
1360 ),
1361
1362 ),
1363 'field-label' => array(
1364 'type' => 'text',
1365 'label' => __( 'Field Label', 'cartflows' ),
1366 'name' => $name . '[label]',
1367 'value' => $field_args['label'],
1368 ),
1369 'field-name' => array(
1370 'label' => __( 'Field ID', 'cartflows' ),
1371 'name' => $name . '[key]',
1372 'value' => $field_args['key'],
1373 'readonly' => true,
1374 'tooltip' => __( 'Copy this field id to use in Order Custom Field rule of dynamic offers.', 'cartflows' ),
1375 ),
1376
1377 'field-default' => $is_checkbox ?
1378 array(
1379 'type' => 'select',
1380 'label' => __( 'Default', 'cartflows' ),
1381 'name' => $name . '[default]',
1382 'value' => $field_args['default'],
1383 'options' => array(
1384 array(
1385 'value' => '1',
1386 'label' => esc_html__( 'Checked', 'cartflows' ),
1387 ),
1388 array(
1389 'value' => '0',
1390 'label' => esc_html__( 'Un-Checked', 'cartflows' ),
1391 ),
1392 ),
1393 ) :
1394
1395 array(
1396 'type' => 'text',
1397 'label' => __( 'Default', 'cartflows' ),
1398 'name' => $name . '[default]',
1399 'value' => $field_args['default'],
1400 ),
1401 );
1402
1403 if ( $is_select || $is_radiobutton ) {
1404
1405 $data_array['field_options']['select-options'] = array(
1406 'type' => 'text',
1407 'label' => __( 'Options', 'cartflows' ),
1408 'name' => $name . '[options]',
1409 'value' => $field_args['options'],
1410 );
1411 }
1412
1413 if ( in_array( $field_args['type'], array( 'datetime-local', 'date', 'time' ), true ) ) {
1414
1415 switch ( $field_args['type'] ) {
1416 case 'datetime-local':
1417 $date_placeholder = 'yyyy-mm-dd hh:mm';
1418 break;
1419 case 'date':
1420 $date_placeholder = 'yyyy-mm-dd';
1421 break;
1422 case 'time':
1423 $date_placeholder = 'hh:mm';
1424 break;
1425 default:
1426 $date_placeholder = 'yyyy-mm-dd hh:mm';
1427 }
1428
1429 $data_array['field_options']['field-min-date'] = array(
1430 'type' => 'text',
1431 'label' => __( 'Min Date', 'cartflows' ),
1432 'name' => $name . '[custom_attributes][min]',
1433 'value' => $field_args['custom_attributes']['min'],
1434 'placeholder' => $date_placeholder,
1435 );
1436 $data_array['field_options']['field-max-date'] = array(
1437 'type' => 'text',
1438 'label' => __( 'Max Date', 'cartflows' ),
1439 'name' => $name . '[custom_attributes][max]',
1440 'value' => $field_args['custom_attributes']['max'],
1441 'placeholder' => $date_placeholder,
1442 );
1443
1444 $data_array['field_options']['field-default']['placeholder'] = $date_placeholder;
1445 }
1446
1447 if ( ! in_array( $type, array( 'checkbox', 'select', 'radio', 'datetime-local', 'date', 'time', 'number', 'file' ), true ) ) {
1448 $data_array['field_options']['field-placeholder'] = array(
1449 'type' => 'text',
1450 'label' => __( 'Placeholder', 'cartflows' ),
1451 'name' => $name . '[placeholder]',
1452 'value' => $field_args['placeholder'],
1453 );
1454 }
1455
1456 if ( 'number' === $type ) {
1457 $data_array['field_options']['field-min'] = array(
1458 'type' => 'number',
1459 'label' => __( 'Min Number', 'cartflows' ),
1460 'name' => $name . '[custom_attributes][min]',
1461 'value' => $field_args['custom_attributes']['min'],
1462 );
1463 $data_array['field_options']['field-max'] = array(
1464 'type' => 'number',
1465 'label' => __( 'Max Number', 'cartflows' ),
1466 'name' => $name . '[custom_attributes][max]',
1467 'value' => $field_args['custom_attributes']['max'],
1468 );
1469 }
1470
1471 // Remove default field option for file type as files cannot have default values.
1472 if ( $is_file ) {
1473 unset( $data_array['field_options']['field-default'] );
1474
1475 $data_array['field_options']['field-file-size'] = array(
1476 'type' => 'number',
1477 'label' => __( 'File Size (MB)', 'cartflows' ),
1478 'name' => $name . '[custom_attributes][file_size]',
1479 'value' => isset( $field_args['custom_attributes']['file_size'] ) ? $field_args['custom_attributes']['file_size'] : 5,
1480 'min' => 0,
1481 'tooltip' => __( 'Enter maximum file size allowed. Default is 5MB.', 'cartflows' ),
1482 );
1483
1484 // Normalize stored value: could be a comma-separated string or an array of {value, label} objects from a previous Select2Field save.
1485 $stored_types = isset( $field_args['custom_attributes']['accepted_file_types'] )
1486 ? $field_args['custom_attributes']['accepted_file_types']
1487 : '';
1488
1489 $normalized_value = self::sanitize_accepted_file_types( $stored_types );
1490
1491 $data_array['field_options']['field-accepted-file-types'] = array(
1492 'type' => 'text',
1493 'label' => __( 'Accepted File Types', 'cartflows' ),
1494 'name' => $name . '[custom_attributes][accepted_file_types]',
1495 'value' => $normalized_value,
1496 'tooltip' => __( 'Enter comma-separated file extensions (e.g. jpg, png, pdf).', 'cartflows' ),
1497 );
1498 }
1499
1500 if ( $is_custom_field ) {
1501 $data_array['field_options']['show-in-email'] = array(
1502 'type' => 'checkbox',
1503 'label' => __( 'Show In Email', 'cartflows' ),
1504 'name' => $name . '[show_in_email]',
1505 'value' => $field_args['show_in_email'],
1506 );
1507 }
1508
1509 $data_array['field_options']['required-field'] = array(
1510 'label' => __( 'Required', 'cartflows' ),
1511 'name' => $name . '[required]',
1512 'value' => $field_args['required'],
1513 );
1514
1515 if ( 'checkout' === $step_type ) {
1516 $data_array['field_options']['collapsed-field'] = array(
1517 'type' => 'checkbox',
1518 'label' => __( 'Collapsible', 'cartflows' ),
1519 'name' => $name . '[optimized]',
1520 'value' => $field_args['optimized'],
1521 );
1522 }
1523
1524 return $data_array;
1525 }
1526
1527 /**
1528 * Get step edit link.
1529 *
1530 * @param int $step_id Step id.
1531 */
1532 public static function get_page_builder_edit_link( $step_id ) {
1533
1534 $edit_step = get_edit_post_link( $step_id, 'edit' );
1535 $view_step = get_permalink( $step_id );
1536 $page_builder = self::get_common_setting( 'default_page_builder' );
1537 $page_builder_edit = $edit_step;
1538
1539 switch ( $page_builder ) {
1540 case 'beaver-builder':
1541 if ( is_plugin_active( 'beaver-builder-lite-version/fl-builder.php' ) ) {
1542 $page_builder_edit = strpos( $view_step, '?' ) ? $view_step . '&fl_builder' : $view_step . '?fl_builder';
1543 }
1544 break;
1545 case 'elementor':
1546 if ( is_plugin_active( 'elementor/elementor.php' ) ) {
1547 $page_builder_edit = admin_url( 'post.php?post=' . $step_id . '&action=elementor' );
1548 }
1549 break;
1550 case 'bricks-builder':
1551 if ( Cartflows_Compatibility::is_bricks_enabled() ) {
1552 $page_builder_edit = strpos( $view_step, '?' ) ? $view_step . '&bricks=run' : $view_step . '?bricks=run';
1553 }
1554 break;
1555 }
1556
1557 return $page_builder_edit;
1558 }
1559
1560 /**
1561 * Get CartFlows Global Color Pallet CSS_Vars data.
1562 *
1563 * @since 2.0.0.
1564 * @return array Array of GCP vars slugs and label,
1565 */
1566 public static function get_gcp_vars() {
1567 return array(
1568 'wcf-gcp-primary-color' => __( 'CartFlows Primary Color', 'cartflows' ),
1569 'wcf-gcp-secondary-color' => __( 'CartFlows Secondary Color', 'cartflows' ),
1570 'wcf-gcp-text-color' => __( 'CartFlows Text Color', 'cartflows' ),
1571 'wcf-gcp-accent-color' => __( 'CartFlows Heading/Accent Color', 'cartflows' ),
1572 );
1573 }
1574
1575 /**
1576 * Generate GCP styles CSS and assign the colors to the GCP CSS VARs.
1577 *
1578 * @param int $flow_id The current flow ID.
1579 * @return array $gcp_vars Array of generated CSS.
1580 *
1581 * @since 2.0.0
1582 */
1583 public static function generate_gcp_css_style( $flow_id = 0 ) {
1584
1585 $gcp_vars = '';
1586
1587 if ( empty( $flow_id ) ) {
1588 return $gcp_vars;
1589 }
1590
1591 $gcp_vars_array = array_keys( self::get_gcp_vars() );
1592
1593 foreach ( $gcp_vars_array as $slug ) {
1594 // Gather value of global color VAR.
1595 $color_value = wcf()->options->get_flow_meta_value( $flow_id, $slug, '' );
1596
1597 if ( empty( $color_value ) ) {
1598 continue;
1599 }
1600
1601 // Convert it into the CSS var.
1602 $gcp_vars .= '--' . $slug . ': ' . $color_value . '; ';
1603 }
1604
1605 return $gcp_vars;
1606 }
1607
1608 /**
1609 * Generate the array of CSS vars to add in the Gutenberg color pallet.
1610 *
1611 * @param int $flow_id The current Flow ID.
1612 * @param string $builder The current page builder.
1613 * @return array $new_color_palette Prepared array of CSS vars.
1614 *
1615 * @since 2.0.0
1616 */
1617 public static function generate_css_var_array( $flow_id = 0, $builder = 'gutenberg' ) {
1618
1619 if ( empty( $flow_id ) ) {
1620 $flow_id = wcf()->utils->get_flow_id();
1621 }
1622 $new_color_palette = array();
1623 // Default Color Pallet used as a separator.
1624 if ( 'gutenberg' === $builder ) {
1625 $new_color_palette[] = array(
1626 'name' => 'CartFlows Separator',
1627 'slug' => 'wcf-gcp-separator',
1628 'color' => '#ff0000',
1629 );
1630 } elseif ( 'elementor' === $builder ) {
1631 $new_color_palette['wcfgcpseparator'] = array(
1632 'id' => 'wcfgcpseparator',
1633 'title' => 'CartFlows Separator',
1634 'value' => '#ff0000',
1635 );
1636 }
1637
1638 $cf_gcp_data = self::get_gcp_vars();
1639
1640 // Prepare new colors css vars.
1641 foreach ( $cf_gcp_data as $slug => $label ) {
1642
1643 $color_value = get_post_meta( $flow_id, $slug, true );
1644
1645 if ( empty( $color_value ) ) {
1646 continue;
1647 }
1648
1649 // Add CartFlows Global Color Pallets CSS vars options.
1650 if ( 'gutenberg' === $builder ) {
1651 $new_color_palette[] = array(
1652 'name' => $label,
1653 'slug' => $slug,
1654 'color' => $color_value,
1655 );
1656 } elseif ( 'elementor' === $builder ) {
1657 $slug = str_replace( '-', '', $slug );
1658 $new_color_palette[ $slug ] = array(
1659 'id' => $slug,
1660 'title' => $label,
1661 'value' => $color_value,
1662 );
1663 }
1664 }
1665
1666 return $new_color_palette;
1667 }
1668
1669 /**
1670 * Check is the Global Color pallet is enabled or not.
1671 *
1672 * @param int $flow_id Current flow id.
1673 * @return boolean
1674 */
1675 public static function is_gcp_styling_enabled( $flow_id ) {
1676
1677 if ( empty( $flow_id ) ) {
1678 return false;
1679 }
1680
1681 return 'yes' === wcf()->options->get_flow_meta_value( $flow_id, 'wcf-enable-gcp-styling', 'no' );
1682 }
1683
1684 /**
1685 * Function to check to show the products tab in the store checkout or not.
1686 *
1687 * @return bool
1688 * @since 2.0.4
1689 */
1690 public static function display_product_tab_in_store_checkout() {
1691 return apply_filters( 'cartflows_show_store_checkout_product_tab', false );
1692 }
1693
1694 /**
1695 * Common UTM defaults shared across PHP and JS — single source of truth.
1696 *
1697 * @return array<string,string>
1698 */
1699 public static function get_utm_defaults() {
1700 return array(
1701 'source' => 'dashboard',
1702 'medium' => 'free-cartflows',
1703 );
1704 }
1705
1706 /**
1707 * Build a cartflows.com URL with the common UTM parameters appended.
1708 * Single source of truth for utm_source and utm_medium defaults.
1709 *
1710 * @param string $base_url Destination URL.
1711 * @param array<string,string> $args Optional UTM overrides (utm_source, utm_medium, utm_campaign, utm_content, utm_term).
1712 * @return string The final URL — UTM-stamped, partner-aware, BSF-referer-aware.
1713 */
1714 public static function get_utm_url( $base_url, $args = array() ) {
1715
1716 $partner_id = get_option( 'cartflows_partner_url_param', '' );
1717 $partner_id = is_string( $partner_id ) ? sanitize_text_field( $partner_id ) : '';
1718
1719 if ( ! empty( $partner_id ) ) {
1720 return esc_url( add_query_arg( array( 'cf' => $partner_id ), $base_url ) );
1721 }
1722
1723 $defaults = self::get_utm_defaults();
1724 $utm_args = wp_parse_args(
1725 $args,
1726 array(
1727 'utm_source' => $defaults['source'],
1728 'utm_medium' => $defaults['medium'],
1729 )
1730 );
1731
1732 $url = add_query_arg( $utm_args, $base_url );
1733
1734 // BSF analytics overrides utm_source when a cross-product referer is set; no-op otherwise.
1735 if ( class_exists( '\BSF_UTM_Analytics' ) && is_callable( '\BSF_UTM_Analytics::get_utm_ready_link' ) ) {
1736 $url = \BSF_UTM_Analytics::get_utm_ready_link( $url, 'cartflows' );
1737 }
1738
1739 return esc_url( $url );
1740 }
1741
1742 /**
1743 * Build a CartFlows upgrade-to-pro / pricing URL with UTM params.
1744 * Thin wrapper around get_utm_url() with utm_campaign defaulted to 'go-pro'.
1745 *
1746 * @param string $page Page slug appended to the domain (used when $custom_url is empty).
1747 * @param string $custom_url Optional full URL to use instead of building from $page.
1748 * @param array<string,string> $args Optional UTM overrides.
1749 * @return string The modified URL.
1750 */
1751 public static function get_upgrade_to_pro_link( $page = 'pricing', $custom_url = '', $args = array() ) {
1752
1753 $base_url = CARTFLOWS_DOMAIN_URL . $page . '/';
1754 $url = empty( $custom_url ) ? $base_url : $custom_url;
1755
1756 $args = wp_parse_args( $args, array( 'utm_campaign' => 'go-pro' ) );
1757
1758 return self::get_utm_url( $url, $args );
1759 }
1760
1761 /**
1762 * Build a CartFlows knowledgebase / docs URL with UTM params.
1763 * Thin wrapper around get_utm_url() with utm_campaign defaulted to 'docs'.
1764 *
1765 * @param string $base_url Destination docs URL.
1766 * @param array<string,string> $args Optional UTM overrides.
1767 * @return string The modified URL.
1768 */
1769 public static function get_kb_doc_link( $base_url, $args = array() ) {
1770
1771 $args = wp_parse_args( $args, array( 'utm_campaign' => 'docs' ) );
1772
1773 return self::get_utm_url( $base_url, $args );
1774 }
1775
1776 /**
1777 * Get current page's template
1778 *
1779 * @param int $post_id The current page id.
1780 * @return string
1781 *
1782 * @since 2.1.0
1783 */
1784 public static function get_current_page_template( $post_id = 0 ) {
1785
1786 if ( empty( $post_id ) ) {
1787 $post_id = _get_wcf_step_id();
1788 }
1789
1790 return apply_filters( 'cartflows_page_template', get_post_meta( $post_id, '_wp_page_template', true ) );
1791 }
1792
1793 /**
1794 * Check the Instant layout is enabled or not.
1795 *
1796 * @param int $flow_id Current flow id.
1797 * @return boolean Returns true if instant layout is enabled, false otherwise.
1798 */
1799 public static function is_instant_layout_enabled( $flow_id = 0 ) {
1800
1801 // Get the flow ID if not set.
1802 if ( empty( $flow_id ) ) {
1803 $flow_id = wcf()->utils->get_flow_id();
1804 }
1805
1806 // Return false if flow ID is not set.
1807 if ( empty( $flow_id ) ) {
1808 return false;
1809 }
1810
1811 // Return false if wcf()->options is not set.
1812 if ( ! isset( wcf()->options ) || ! is_object( wcf()->options ) || ! is_callable( array( wcf()->options, 'get_flow_meta_value' ) ) ) {
1813 return false;
1814 }
1815
1816 // Return true or false based on the instant layout style.
1817 return 'yes' === wcf()->options->get_flow_meta_value( $flow_id, 'instant-layout-style', 'no' );
1818 }
1819
1820 /**
1821 * Get Rollback versions.
1822 *
1823 * @since 2.1.6
1824 * @return array
1825 * @access public
1826 */
1827 public static function get_rollback_versions() {
1828
1829 $rollback_versions = get_transient( 'cartflows_rollback_versions_' . CARTFLOWS_VER );
1830
1831 if ( empty( $rollback_versions ) ) {
1832
1833 $max_versions = 10;
1834
1835 require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
1836
1837 $plugin_information = plugins_api(
1838 'plugin_information',
1839 array(
1840 'slug' => 'cartflows',
1841 )
1842 );
1843
1844 if ( empty( $plugin_information->versions ) || ! is_array( $plugin_information->versions ) ) {
1845 return array();
1846 }
1847
1848 krsort( $plugin_information->versions );
1849
1850 $rollback_versions = array();
1851
1852 foreach ( $plugin_information->versions as $version => $download_link ) {
1853
1854 $lowercase_version = strtolower( $version );
1855
1856 $is_valid_rollback_version = ! preg_match( '/(trunk|beta|rc|dev)/i', $lowercase_version );
1857
1858 if ( ! $is_valid_rollback_version ) {
1859 continue;
1860 }
1861
1862 if ( version_compare( $version, CARTFLOWS_VER, '>=' ) ) {
1863 continue;
1864 }
1865
1866 $rollback_versions[] = $version;
1867 }
1868
1869 usort( $rollback_versions, array( __CLASS__, 'sort_rollback_versions' ) );
1870
1871 $rollback_versions = array_slice( $rollback_versions, 0, $max_versions, true );
1872
1873 set_transient( 'cartflows_rollback_versions_' . CARTFLOWS_VER, $rollback_versions, WEEK_IN_SECONDS );
1874 }
1875
1876 return (array) $rollback_versions;
1877 }
1878 /**
1879 * Sort Rollback versions.
1880 *
1881 * @since 2.1.6
1882 * @param string $prev Previous Version.
1883 * @param string $next Next Version.
1884 *
1885 * @return int
1886 */
1887 public static function sort_rollback_versions( $prev, $next ) {
1888
1889 if ( version_compare( $prev, $next, '==' ) ) {
1890 return 0;
1891 }
1892
1893 if ( version_compare( $prev, $next, '>' ) ) {
1894 return -1;
1895 }
1896
1897 return 1;
1898 }
1899
1900 /**
1901 * Get Rollback versions.
1902 *
1903 * @since 2.1.6
1904 * @return array
1905 * @access public
1906 */
1907 public static function get_rollback_versions_options() {
1908
1909 $rollback_versions = self::get_rollback_versions();
1910
1911 $rollback_versions_options = array();
1912
1913 foreach ( $rollback_versions as $version ) {
1914
1915 $version = array(
1916 'label' => $version,
1917 'value' => $version,
1918
1919 );
1920
1921 $rollback_versions_options[] = $version;
1922 }
1923
1924 return $rollback_versions_options;
1925 }
1926
1927 /**
1928 * Get the master allowlist of file extensions for checkout file uploads.
1929 *
1930 * @return array<string> Allowed file extensions.
1931 */
1932 public static function get_allowed_file_extensions() {
1933 return array( 'jpg', 'jpeg', 'png', 'gif', 'webp', 'pdf', 'mp3', 'm4a', 'wav', 'mp4', 'm4v', 'mov' );
1934 }
1935
1936 /**
1937 * Sanitize accepted file types input against the master allowlist.
1938 *
1939 * Strips any extensions not in the allowlist and returns a clean comma-separated string.
1940 *
1941 * @param string $raw_types Comma-separated file extensions from user input.
1942 * @return string Sanitized comma-separated file extensions.
1943 */
1944 public static function sanitize_accepted_file_types( $raw_types ) {
1945 if ( empty( $raw_types ) ) {
1946 return '';
1947 }
1948
1949 $allowed = self::get_allowed_file_extensions();
1950 $input = array_map( 'trim', explode( ',', sanitize_text_field( $raw_types ) ) );
1951 $validated = array();
1952
1953 foreach ( $input as $ext ) {
1954 $ext = strtolower( $ext );
1955 if ( ! empty( $ext ) && in_array( $ext, $allowed, true ) ) {
1956 $validated[] = $ext;
1957 }
1958 }
1959
1960 return implode( ', ', array_unique( $validated ) );
1961 }
1962
1963 /**
1964 * Get the script migration status.
1965 *
1966 * Returns the current status of the custom script migration from old textarea
1967 * fields to new CodeMirror editor fields.
1968 *
1969 * @since 2.2.3
1970 * @return string Migration status: 'pending', 'accepted', 'completed', or 'skipped'.
1971 */
1972 public static function get_script_migration_status() {
1973 return get_option( 'cartflows_script_migration_status', 'pending' );
1974 }
1975
1976 /**
1977 * Set an analytics flag in the consolidated cf_analytics_flags option.
1978 *
1979 * Stores all analytics milestone flags in a single wp_options row.
1980 * Skips the update if the flag is already set (one-time events).
1981 *
1982 * @since 2.2.4
1983 * @param string $key Flag key (e.g. 'first_checkout_configured').
1984 * @param mixed $value Flag value. Default true.
1985 * @param bool $force Whether to overwrite an existing value. Default false.
1986 * @return bool True if the flag was set, false if it already existed.
1987 */
1988 public static function set_analytics_flag( $key, $value = true, $force = false ) {
1989 $flags = get_option( 'cf_analytics_flags', array() );
1990 if ( ! is_array( $flags ) ) {
1991 $flags = array();
1992 }
1993 if ( ! $force && ! empty( $flags[ $key ] ) ) {
1994 return false;
1995 }
1996 $flags[ $key ] = $value;
1997 update_option( 'cf_analytics_flags', $flags, false );
1998 return true;
1999 }
2000
2001 /**
2002 * Get an analytics flag from the consolidated cf_analytics_flags option.
2003 *
2004 * @since 2.2.4
2005 * @param string $key Flag key (e.g. 'first_checkout_configured').
2006 * @param mixed $default Default value if key not found. Default false.
2007 * @return mixed
2008 */
2009 public static function get_analytics_flag( $key, $default = false ) {
2010 $flags = get_option( 'cf_analytics_flags', array() );
2011 if ( ! is_array( $flags ) ) {
2012 return $default;
2013 }
2014 return isset( $flags[ $key ] ) ? $flags[ $key ] : $default;
2015 }
2016 }
2017