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

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

2,084 lines 55.1 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 * Prepare response data for facebook.
886 *
887 * @todo Remove this function in 1.6.18 as it is added in cartflows-tracking file.
888 * @param int $order_id order_id.
889 * @param array $offer_data offer data.
890 * @return void
891 */
892 public static function send_fb_response_if_enabled( $order_id, $offer_data = array() ) {
893
894 _deprecated_function( __METHOD__, '1.6.15' );
895
896 // Stop Execution if WooCommerce is not installed & don't set the cookie.
897 if ( ! Cartflows_Loader::get_instance()->is_woo_active ) {
898 return;
899 }
900
901 // @Since 1.6.15 It will only trigger offer purchase event.
902 $fb_settings = self::get_facebook_settings();
903 if ( 'enable' === $fb_settings['facebook_pixel_tracking'] ) {
904 setcookie( 'wcf_order_details', wp_json_encode( self::prepare_purchase_data_fb_response( $order_id, $offer_data ) ), strtotime( '+1 year' ), '/', COOKIE_DOMAIN, CARTFLOWS_HTTPS, true ); //phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.cookies_setcookie
905 }
906 }
907
908 /**
909 * Prepare purchase response for facebook purcase event.
910 *
911 * @todo Remove this function in 1.6.18 as it is added in cartflows-tracking file.
912 *
913 * @param integer $order_id order id.
914 * @param array $offer_data offer data.
915 * @return mixed
916 */
917 public static function prepare_purchase_data_fb_response( $order_id, $offer_data ) {
918
919 _deprecated_function( __METHOD__, '1.6.15' );
920
921 $purchase_data = array();
922
923 if ( empty( $offer_data ) ) {
924 return $purchase_data;
925 }
926
927 if ( ! Cartflows_Loader::get_instance()->is_woo_active ) {
928 return $purchase_data;
929 }
930
931 $order = wc_get_order( $order_id );
932
933 if ( ! $order ) {
934 return $purchase_data;
935 }
936
937 $purchase_data['content_type'] = 'product';
938 $purchase_data['currency'] = wcf()->options->get_checkout_meta_value( $order_id, '_order_currency' );
939 $purchase_data['userAgent'] = wcf()->options->get_checkout_meta_value( $order_id, '_customer_user_agent' );
940 $purchase_data['plugin'] = 'CartFlows-Offer';
941
942 $purchase_data['content_ids'][] = (string) $offer_data['id'];
943 $purchase_data['content_names'][] = $offer_data['name'];
944 $purchase_data['content_category'][] = wp_strip_all_tags( wc_get_product_category_list( $offer_data['id'] ) );
945 $purchase_data['value'] = $offer_data['total'];
946 $purchase_data['transaction_id'] = $order_id;
947
948 return $purchase_data;
949 }
950
951 /**
952 * Get the image url of size.
953 *
954 * @param int $post_id post id.
955 * @param array $key key.
956 * @param string $size image size.
957 *
958 * @return array
959 */
960 public static function get_image_url( $post_id, $key, $size = false ) {
961
962 $url = get_post_meta( $post_id, $key, true );
963 $img_obj = get_post_meta( $post_id, $key . '-obj', true );
964
965 if ( is_array( $img_obj ) && ! empty( $img_obj ) && false !== $size ) {
966
967 $url = ! empty( $img_obj['url'][ $size ] ) ? $img_obj['url'][ $size ] : $url;
968 }
969
970 return $url;
971 }
972
973 /**
974 * Download File Into Uploads Directory
975 *
976 * @since 1.6.15
977 *
978 * @param string $file Download File URL.
979 * @param array $overrides Upload file arguments.
980 * @param int $timeout_seconds Timeout in downloading the XML file in seconds.
981 * @return array Downloaded file data.
982 */
983 public static function download_file( $file = '', $overrides = array(), $timeout_seconds = 300 ) {
984
985 // Gives us access to the download_url() and wp_handle_sideload() functions.
986 require_once ABSPATH . 'wp-admin/includes/file.php';
987
988 // Download file to temp dir.
989 $temp_file = download_url( $file, $timeout_seconds );
990
991 // WP Error.
992 if ( is_wp_error( $temp_file ) ) {
993 return array(
994 'success' => false,
995 'data' => $temp_file->get_error_message(),
996 );
997 }
998
999 // Array based on $_FILE as seen in PHP file uploads.
1000 $file_args = array(
1001 'name' => basename( $file ),
1002 'tmp_name' => $temp_file,
1003 'error' => 0,
1004 'size' => filesize( $temp_file ),
1005 );
1006
1007 $defaults = array(
1008
1009 // Tells WordPress to not look for the POST form
1010 // fields that would normally be present as
1011 // we downloaded the file from a remote server, so there
1012 // will be no form fields
1013 // Default is true.
1014 'test_form' => false,
1015
1016 // Setting this to false lets WordPress allow empty files, not recommended.
1017 // Default is true.
1018 'test_size' => true,
1019
1020 // A properly uploaded file will pass this test. There should be no reason to override this one.
1021 'test_upload' => true,
1022
1023 'mimes' => array(
1024 'xml' => 'text/xml',
1025 'json' => 'text/plain',
1026 ),
1027 );
1028
1029 $overrides = wp_parse_args( $overrides, $defaults );
1030
1031 // Move the temporary file into the uploads directory.
1032 $results = wp_handle_sideload( $file_args, $overrides );
1033
1034 if ( isset( $results['error'] ) ) {
1035 return array(
1036 'success' => false,
1037 'data' => $results,
1038 );
1039 }
1040
1041 // Success.
1042 return array(
1043 'success' => true,
1044 'data' => $results,
1045 );
1046 }
1047
1048 /**
1049 * Get an instance of WP_Filesystem_Direct.
1050 *
1051 * @since 1.6.15
1052 * @return object A WP_Filesystem_Direct instance.
1053 */
1054 public static function get_filesystem() {
1055 global $wp_filesystem;
1056
1057 require_once ABSPATH . '/wp-admin/includes/file.php';
1058
1059 WP_Filesystem();
1060
1061 return $wp_filesystem;
1062 }
1063
1064 /**
1065 * Get all flow and steps
1066 *
1067 * @since 1.6.15
1068 * @return array
1069 */
1070 public function get_all_flows_and_steps() {
1071 $all_flows = array(
1072 'elementor' => array(),
1073 'divi' => array(),
1074 'gutenberg' => array(),
1075 'beaver-builder' => array(),
1076 );
1077 foreach ( $all_flows as $slug => $value ) {
1078 $all_flows[ $slug ] = $this->get_flows_and_steps( $slug );
1079 }
1080
1081 return $all_flows;
1082 }
1083
1084 /**
1085 * Get flow and steps
1086 *
1087 * @since 1.6.15
1088 *
1089 * @param string $page_builder_slug Page builder slug.
1090 * @param string $templates templates category to fetch.
1091 *
1092 * @return array
1093 */
1094 public function get_flows_and_steps( $page_builder_slug = '', $templates = '' ) {
1095 $page_builder_slug = ( ! empty( $page_builder_slug ) ) ? $page_builder_slug : wcf()->get_site_slug();
1096
1097 $suffix = 'store-checkout' === $templates ? 'store-checkout-' : '';
1098
1099 $pages_count = get_site_option( 'cartflows-' . $suffix . $page_builder_slug . '-requests', 0 );
1100
1101 $flows = array();
1102 if ( $pages_count ) {
1103 for ( $page_no = 1; $page_no <= $pages_count; $page_no++ ) {
1104
1105 $data = get_site_option( 'cartflows-' . $suffix . $page_builder_slug . '-flows-and-steps-' . $page_no, '' );
1106
1107 if ( ! empty( $data ) ) {
1108
1109 $data = 'string' === gettype( $data ) ? json_decode( $data ) : $data;
1110
1111 foreach ( $data as $key => $flow ) {
1112 $flows[] = $flow;
1113 }
1114 } else {
1115 // Return store templates from JSON files if no data found.
1116 $flows = $this->get_stored_page_builder_templates( $page_builder_slug );
1117 }
1118 }
1119 } else {
1120 // All flows.
1121 $flows = $this->get_stored_page_builder_templates( $page_builder_slug );
1122 }
1123
1124 return $flows;
1125 }
1126
1127 /**
1128 * Get page builder name
1129 *
1130 * @since 1.1.4
1131 *
1132 * @param string $page_builder Page builder slug.
1133 * @return mixed
1134 */
1135 public static function get_page_builder_name( $page_builder = '' ) {
1136
1137 $pb_data = array(
1138 'elementor' => 'Elementor',
1139 'gutenberg' => 'Spectra',
1140 'beaver-builder' => 'Beaver Builder',
1141 'divi' => 'Divi',
1142 'bricks-builder' => 'Bricks',
1143 );
1144
1145 if ( isset( $pb_data[ $page_builder ] ) ) {
1146
1147 return $pb_data[ $page_builder ];
1148 }
1149
1150 return '';
1151 }
1152
1153 /**
1154 * Create Edit page link for the widgets.
1155 *
1156 * @since 1.6.15
1157 * @param string $tab The Tab which has to display.
1158 * @access public
1159 */
1160 public static function get_current_page_edit_url( $tab ) {
1161
1162 $url = add_query_arg(
1163 array(
1164 'wcf-tab' => $tab,
1165 ),
1166 get_edit_post_link()
1167 );
1168
1169 return $url;
1170 }
1171
1172 /**
1173 * Get product price.
1174 *
1175 * @param object $product product data.
1176 */
1177 public static function get_product_original_price( $product ) {
1178
1179 $custom_price = '';
1180 $product_id = 0;
1181
1182 if ( $product->is_type( 'variable' ) ) {
1183
1184 $default_attributes = $product->get_default_attributes();
1185
1186 if ( ! empty( $default_attributes ) ) {
1187
1188 foreach ( $product->get_children() as $c_in => $variation_id ) {
1189
1190 if ( 0 === $c_in ) {
1191 $product_id = $variation_id;
1192 }
1193
1194 $single_variation = new \WC_Product_Variation( $variation_id );
1195
1196 if ( $default_attributes == $single_variation->get_attributes() ) {
1197
1198 $product_id = $variation_id;
1199 break;
1200 }
1201 }
1202 } else {
1203
1204 $product_childrens = $product->get_children();
1205
1206 if ( is_array( $product_childrens ) && ! empty( $product_childrens ) ) {
1207
1208 foreach ( $product_childrens as $c_in => $c_id ) {
1209
1210 $_child_product = wc_get_product( $c_id );
1211
1212 if ( $_child_product->is_in_stock() && 'publish' === $_child_product->get_status() ) {
1213 $product_id = $c_id;
1214 break;
1215 }
1216 }
1217 } else {
1218
1219 // Return if no childrens found.
1220 return;
1221 }
1222 }
1223
1224 $product = wc_get_product( $product_id );
1225 }
1226
1227 if ( $product && $product->is_type( 'bundle' ) && method_exists( $product, 'get_bundle_price' ) ) {
1228 /*
1229 * A bundle's own price is only its container base price, which is 0 or empty as soon
1230 * as the bundled items are priced individually — leaving the step showing just a
1231 * currency symbol. Use the bundle's minimum price instead: the "from" price the
1232 * customer would pay, mirroring how a variable product resolves to a variation price
1233 * above. Kept scalar because callers run discount maths on this value.
1234 */
1235 return $product->get_bundle_price( 'min', false );
1236 }
1237
1238 if ( $product ) {
1239 $custom_price = $product->get_price( 'edit' );
1240 }
1241
1242 return $custom_price;
1243 }
1244
1245 /**
1246 * Get flows from json files.
1247 *
1248 * @param string $page_builder_slug Selected page builder slug.
1249 */
1250 public function get_stored_page_builder_templates( $page_builder_slug ) {
1251 $flows = array();
1252
1253 $dir = CARTFLOWS_DIR . 'admin-core/assets/importer-data';
1254 $list_files = \list_files( $dir );
1255 if ( ! empty( $list_files ) ) {
1256 $list_files = array_map( 'basename', $list_files );
1257 foreach ( $list_files as $file_key => $file_name ) {
1258 if ( false !== strpos( $file_name, 'cartflows-' . $page_builder_slug . '-flows-and-steps' ) ) {
1259 // file_get_contents is fine for local files. https://github.com/WordPress/WordPress-Coding-Standards/pull/1374/files#diff-400e43bc09c24262b43f26fce487fdabR43-R52.
1260 $data = json_decode( file_get_contents( $dir . '/' . $file_name ), true ); //phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
1261 if ( ! empty( $data ) ) {
1262 /*
1263 Commented.
1264 // $option_name = str_replace( '.json', '', $file_name );
1265 // update_site_option( $option_name, json_decode( $data, true ) );
1266 */
1267 foreach ( $data as $key => $flow ) {
1268 $flows[] = $flow;
1269 }
1270 }
1271 }
1272 }
1273 }
1274
1275 return $flows;
1276 }
1277
1278 /**
1279 * Get meta keys to exclude.
1280 *
1281 * @param int $step_id post id.
1282 */
1283 public function get_meta_keys_to_exclude_from_import( $step_id = 0 ) {
1284
1285 $meta_keys = array(
1286 '_wp_old_slug',
1287 'wcf-checkout-products',
1288 'wcf-optin-product',
1289 'wcf-testing',
1290 );
1291
1292 if ( $step_id && 'yes' === get_post_meta( $step_id, 'cartflows_imported_step', true ) ) {
1293
1294 $meta_keys = array_merge(
1295 $meta_keys,
1296 array(
1297 'wcf_fields_billing',
1298 'wcf_fields_shipping',
1299 )
1300 );
1301 }
1302
1303 return apply_filters(
1304 'cartflows_admin_exclude_import_meta_keys',
1305 $meta_keys
1306 );
1307 }
1308
1309 /**
1310 * Get allowed meta key prefixes for import.
1311 *
1312 * Security: Only meta keys matching these prefixes are permitted during template import.
1313 * This prevents supply-chain attacks from writing arbitrary meta keys.
1314 *
1315 * @since 2.2.2
1316 * @return array List of allowed meta key prefixes.
1317 */
1318 public function get_allowed_import_meta_key_prefixes() {
1319 $prefixes = array(
1320 'wcf-',
1321 'wcf_',
1322 'cartflows_',
1323 '_wcf-',
1324 '_wcf_',
1325 '_cartflows_',
1326 '_elementor_',
1327 '_fl_builder_',
1328 '_wp_page_template',
1329 '_thumbnail_id',
1330 );
1331
1332 return apply_filters( 'cartflows_allowed_import_meta_key_prefixes', $prefixes );
1333 }
1334
1335 /**
1336 * Check if a meta key is allowed for import.
1337 *
1338 * @since 2.2.2
1339 * @param string $meta_key The meta key to check.
1340 * @return bool True if the meta key is allowed.
1341 */
1342 public function is_meta_key_allowed_for_import( $meta_key ) {
1343 $allowed_prefixes = $this->get_allowed_import_meta_key_prefixes();
1344
1345 foreach ( $allowed_prefixes as $prefix ) {
1346 if ( 0 === strpos( $meta_key, $prefix ) ) {
1347 return true;
1348 }
1349 }
1350
1351 return false;
1352 }
1353
1354 /**
1355 * Maybe update flow step order.
1356 *
1357 * @param int $flow_id flow id.
1358 * @param array $flow_steps step list.
1359 *
1360 * @return array flow_steps step list.
1361 */
1362 public function maybe_update_flow_steps( $flow_id, $flow_steps ) {
1363
1364 if ( absint( self::get_global_setting( '_cartflows_store_checkout' ) ) === $flow_id ) {
1365 $key = array_search( 'thankyou', wp_list_pluck( $flow_steps, 'type' ), true );
1366 if ( ! $key ) {
1367 return $flow_steps;
1368 } else {
1369 $thankyou = array_splice( $flow_steps, intval( $key ), 1 );
1370 $flow_steps[] = $thankyou[0];
1371 }
1372 }
1373
1374 return $flow_steps;
1375 }
1376
1377 /**
1378 * Prepare cusom field settings array
1379 *
1380 * @param array $data_array field array.
1381 * @param string $key field key.
1382 * @param array $field_args field args.
1383 * @param array $fields_type field type.
1384 * @param string $step_type step type.
1385 *
1386 * @return array field data array.
1387 */
1388 public function prepare_custom_field_settings( $data_array, $key, $field_args, $fields_type, $step_type = 'checkout' ) {
1389
1390 if ( 'checkout' === $step_type ) {
1391 $name = 'billing' === $fields_type ? 'wcf_field_order_billing[' . $key . ']' : 'wcf_field_order_shipping[' . $key . ']';
1392 } else {
1393 $name = 'wcf-optin-fields-billing[' . $key . ']';
1394 }
1395
1396 $type = $field_args['type'];
1397 $is_checkbox = 'checkbox' == $type ? true : false;
1398 $is_radiobutton = 'radio' == $type ? true : false;
1399 $is_select = 'select' == $type ? true : false;
1400 $is_file = 'file' == $type ? true : false;
1401 $is_custom_field = isset( $field_args['custom'] ) && true === (bool) $field_args['custom'] ? true : false;
1402
1403 $data_array['field_options'] = array(
1404 'enable-field' => array(
1405 'label' => __( 'Enable Field', 'cartflows' ),
1406 'name' => $name . '[enabled]',
1407 'value' => $field_args['enabled'],
1408 ),
1409 'select-width' => array(
1410 'type' => 'select',
1411 'label' => __( 'Field Width', 'cartflows' ),
1412 'name' => $name . '[width]',
1413 'value' => $field_args['width'],
1414 'options' => array(
1415 array(
1416 'value' => '33',
1417 'label' => esc_html__( '33%', 'cartflows' ),
1418 ),
1419 array(
1420 'value' => '50',
1421 'label' => esc_html__( '50%', 'cartflows' ),
1422 ),
1423 array(
1424 'value' => '100',
1425 'label' => esc_html__( '100%', 'cartflows' ),
1426 ),
1427 ),
1428
1429 ),
1430 'field-label' => array(
1431 'type' => 'text',
1432 'label' => __( 'Field Label', 'cartflows' ),
1433 'name' => $name . '[label]',
1434 'value' => $field_args['label'],
1435 ),
1436 'field-name' => array(
1437 'label' => __( 'Field ID', 'cartflows' ),
1438 'name' => $name . '[key]',
1439 'value' => $field_args['key'],
1440 'readonly' => true,
1441 'tooltip' => __( 'Copy this field id to use in Order Custom Field rule of dynamic offers.', 'cartflows' ),
1442 ),
1443
1444 'field-default' => $is_checkbox ?
1445 array(
1446 'type' => 'select',
1447 'label' => __( 'Default', 'cartflows' ),
1448 'name' => $name . '[default]',
1449 'value' => $field_args['default'],
1450 'options' => array(
1451 array(
1452 'value' => '1',
1453 'label' => esc_html__( 'Checked', 'cartflows' ),
1454 ),
1455 array(
1456 'value' => '0',
1457 'label' => esc_html__( 'Un-Checked', 'cartflows' ),
1458 ),
1459 ),
1460 ) :
1461
1462 array(
1463 'type' => 'text',
1464 'label' => __( 'Default', 'cartflows' ),
1465 'name' => $name . '[default]',
1466 'value' => $field_args['default'],
1467 ),
1468 );
1469
1470 if ( $is_select || $is_radiobutton ) {
1471
1472 $data_array['field_options']['select-options'] = array(
1473 'type' => 'text',
1474 'label' => __( 'Options', 'cartflows' ),
1475 'name' => $name . '[options]',
1476 'value' => $field_args['options'],
1477 );
1478 }
1479
1480 if ( in_array( $field_args['type'], array( 'datetime-local', 'date', 'time' ), true ) ) {
1481
1482 switch ( $field_args['type'] ) {
1483 case 'datetime-local':
1484 $date_placeholder = 'yyyy-mm-dd hh:mm';
1485 break;
1486 case 'date':
1487 $date_placeholder = 'yyyy-mm-dd';
1488 break;
1489 case 'time':
1490 $date_placeholder = 'hh:mm';
1491 break;
1492 default:
1493 $date_placeholder = 'yyyy-mm-dd hh:mm';
1494 }
1495
1496 $data_array['field_options']['field-min-date'] = array(
1497 'type' => 'text',
1498 'label' => __( 'Min Date', 'cartflows' ),
1499 'name' => $name . '[custom_attributes][min]',
1500 'value' => $field_args['custom_attributes']['min'],
1501 'placeholder' => $date_placeholder,
1502 );
1503 $data_array['field_options']['field-max-date'] = array(
1504 'type' => 'text',
1505 'label' => __( 'Max Date', 'cartflows' ),
1506 'name' => $name . '[custom_attributes][max]',
1507 'value' => $field_args['custom_attributes']['max'],
1508 'placeholder' => $date_placeholder,
1509 );
1510
1511 $data_array['field_options']['field-default']['placeholder'] = $date_placeholder;
1512 }
1513
1514 if ( ! in_array( $type, array( 'checkbox', 'select', 'radio', 'datetime-local', 'date', 'time', 'number', 'file' ), true ) ) {
1515 $data_array['field_options']['field-placeholder'] = array(
1516 'type' => 'text',
1517 'label' => __( 'Placeholder', 'cartflows' ),
1518 'name' => $name . '[placeholder]',
1519 'value' => $field_args['placeholder'],
1520 );
1521 }
1522
1523 if ( 'number' === $type ) {
1524 $data_array['field_options']['field-min'] = array(
1525 'type' => 'number',
1526 'label' => __( 'Min Number', 'cartflows' ),
1527 'name' => $name . '[custom_attributes][min]',
1528 'value' => $field_args['custom_attributes']['min'],
1529 );
1530 $data_array['field_options']['field-max'] = array(
1531 'type' => 'number',
1532 'label' => __( 'Max Number', 'cartflows' ),
1533 'name' => $name . '[custom_attributes][max]',
1534 'value' => $field_args['custom_attributes']['max'],
1535 );
1536 }
1537
1538 // Remove default field option for file type as files cannot have default values.
1539 if ( $is_file ) {
1540 unset( $data_array['field_options']['field-default'] );
1541
1542 $data_array['field_options']['field-file-size'] = array(
1543 'type' => 'number',
1544 'label' => __( 'File Size (MB)', 'cartflows' ),
1545 'name' => $name . '[custom_attributes][file_size]',
1546 'value' => isset( $field_args['custom_attributes']['file_size'] ) ? $field_args['custom_attributes']['file_size'] : 5,
1547 'min' => 0,
1548 'tooltip' => __( 'Enter maximum file size allowed. Default is 5MB.', 'cartflows' ),
1549 );
1550
1551 // Normalize stored value: could be a comma-separated string or an array of {value, label} objects from a previous Select2Field save.
1552 $stored_types = isset( $field_args['custom_attributes']['accepted_file_types'] )
1553 ? $field_args['custom_attributes']['accepted_file_types']
1554 : '';
1555
1556 $normalized_value = self::sanitize_accepted_file_types( $stored_types );
1557
1558 $data_array['field_options']['field-accepted-file-types'] = array(
1559 'type' => 'text',
1560 'label' => __( 'Accepted File Types', 'cartflows' ),
1561 'name' => $name . '[custom_attributes][accepted_file_types]',
1562 'value' => $normalized_value,
1563 'tooltip' => __( 'Enter comma-separated file extensions (e.g. jpg, png, pdf).', 'cartflows' ),
1564 );
1565 }
1566
1567 if ( $is_custom_field ) {
1568 $data_array['field_options']['show-in-email'] = array(
1569 'type' => 'checkbox',
1570 'label' => __( 'Show In Email', 'cartflows' ),
1571 'name' => $name . '[show_in_email]',
1572 'value' => $field_args['show_in_email'],
1573 );
1574 }
1575
1576 $data_array['field_options']['required-field'] = array(
1577 'label' => __( 'Required', 'cartflows' ),
1578 'name' => $name . '[required]',
1579 'value' => $field_args['required'],
1580 );
1581
1582 if ( 'checkout' === $step_type ) {
1583 $data_array['field_options']['collapsed-field'] = array(
1584 'type' => 'checkbox',
1585 'label' => __( 'Collapsible', 'cartflows' ),
1586 'name' => $name . '[optimized]',
1587 'value' => $field_args['optimized'],
1588 );
1589 }
1590
1591 return $data_array;
1592 }
1593
1594 /**
1595 * Get step edit link.
1596 *
1597 * @param int $step_id Step id.
1598 */
1599 public static function get_page_builder_edit_link( $step_id ) {
1600
1601 $edit_step = get_edit_post_link( $step_id, 'edit' );
1602 $view_step = get_permalink( $step_id );
1603 $page_builder = self::get_common_setting( 'default_page_builder' );
1604 $page_builder_edit = $edit_step;
1605
1606 switch ( $page_builder ) {
1607 case 'beaver-builder':
1608 if ( is_plugin_active( 'beaver-builder-lite-version/fl-builder.php' ) ) {
1609 $page_builder_edit = strpos( $view_step, '?' ) ? $view_step . '&fl_builder' : $view_step . '?fl_builder';
1610 }
1611 break;
1612 case 'elementor':
1613 if ( is_plugin_active( 'elementor/elementor.php' ) ) {
1614 $page_builder_edit = admin_url( 'post.php?post=' . $step_id . '&action=elementor' );
1615 }
1616 break;
1617 case 'bricks-builder':
1618 if ( Cartflows_Compatibility::is_bricks_enabled() ) {
1619 $page_builder_edit = strpos( $view_step, '?' ) ? $view_step . '&bricks=run' : $view_step . '?bricks=run';
1620 }
1621 break;
1622 }
1623
1624 return $page_builder_edit;
1625 }
1626
1627 /**
1628 * Get CartFlows Global Color Pallet CSS_Vars data.
1629 *
1630 * @since 2.0.0.
1631 * @return array Array of GCP vars slugs and label,
1632 */
1633 public static function get_gcp_vars() {
1634 return array(
1635 'wcf-gcp-primary-color' => __( 'CartFlows Primary Color', 'cartflows' ),
1636 'wcf-gcp-secondary-color' => __( 'CartFlows Secondary Color', 'cartflows' ),
1637 'wcf-gcp-text-color' => __( 'CartFlows Text Color', 'cartflows' ),
1638 'wcf-gcp-accent-color' => __( 'CartFlows Heading/Accent Color', 'cartflows' ),
1639 );
1640 }
1641
1642 /**
1643 * Generate GCP styles CSS and assign the colors to the GCP CSS VARs.
1644 *
1645 * @param int $flow_id The current flow ID.
1646 * @return array $gcp_vars Array of generated CSS.
1647 *
1648 * @since 2.0.0
1649 */
1650 public static function generate_gcp_css_style( $flow_id = 0 ) {
1651
1652 $gcp_vars = '';
1653
1654 if ( empty( $flow_id ) ) {
1655 return $gcp_vars;
1656 }
1657
1658 $gcp_vars_array = array_keys( self::get_gcp_vars() );
1659
1660 foreach ( $gcp_vars_array as $slug ) {
1661 // Gather value of global color VAR.
1662 $color_value = wcf()->options->get_flow_meta_value( $flow_id, $slug, '' );
1663
1664 if ( empty( $color_value ) ) {
1665 continue;
1666 }
1667
1668 // Convert it into the CSS var.
1669 $gcp_vars .= '--' . $slug . ': ' . $color_value . '; ';
1670 }
1671
1672 return $gcp_vars;
1673 }
1674
1675 /**
1676 * Generate the array of CSS vars to add in the Gutenberg color pallet.
1677 *
1678 * @param int $flow_id The current Flow ID.
1679 * @param string $builder The current page builder.
1680 * @return array $new_color_palette Prepared array of CSS vars.
1681 *
1682 * @since 2.0.0
1683 */
1684 public static function generate_css_var_array( $flow_id = 0, $builder = 'gutenberg' ) {
1685
1686 if ( empty( $flow_id ) ) {
1687 $flow_id = wcf()->utils->get_flow_id();
1688 }
1689 $new_color_palette = array();
1690 // Default Color Pallet used as a separator.
1691 if ( 'gutenberg' === $builder ) {
1692 $new_color_palette[] = array(
1693 'name' => 'CartFlows Separator',
1694 'slug' => 'wcf-gcp-separator',
1695 'color' => '#ff0000',
1696 );
1697 } elseif ( 'elementor' === $builder ) {
1698 $new_color_palette['wcfgcpseparator'] = array(
1699 'id' => 'wcfgcpseparator',
1700 'title' => 'CartFlows Separator',
1701 'value' => '#ff0000',
1702 );
1703 }
1704
1705 $cf_gcp_data = self::get_gcp_vars();
1706
1707 // Prepare new colors css vars.
1708 foreach ( $cf_gcp_data as $slug => $label ) {
1709
1710 $color_value = get_post_meta( $flow_id, $slug, true );
1711
1712 if ( empty( $color_value ) ) {
1713 continue;
1714 }
1715
1716 // Add CartFlows Global Color Pallets CSS vars options.
1717 if ( 'gutenberg' === $builder ) {
1718 $new_color_palette[] = array(
1719 'name' => $label,
1720 'slug' => $slug,
1721 'color' => $color_value,
1722 );
1723 } elseif ( 'elementor' === $builder ) {
1724 $slug = str_replace( '-', '', $slug );
1725 $new_color_palette[ $slug ] = array(
1726 'id' => $slug,
1727 'title' => $label,
1728 'value' => $color_value,
1729 );
1730 }
1731 }
1732
1733 return $new_color_palette;
1734 }
1735
1736 /**
1737 * Check is the Global Color pallet is enabled or not.
1738 *
1739 * @param int $flow_id Current flow id.
1740 * @return boolean
1741 */
1742 public static function is_gcp_styling_enabled( $flow_id ) {
1743
1744 if ( empty( $flow_id ) ) {
1745 return false;
1746 }
1747
1748 return 'yes' === wcf()->options->get_flow_meta_value( $flow_id, 'wcf-enable-gcp-styling', 'no' );
1749 }
1750
1751 /**
1752 * Function to check to show the products tab in the store checkout or not.
1753 *
1754 * @return bool
1755 * @since 2.0.4
1756 */
1757 public static function display_product_tab_in_store_checkout() {
1758 return apply_filters( 'cartflows_show_store_checkout_product_tab', false );
1759 }
1760
1761 /**
1762 * Common UTM defaults shared across PHP and JS — single source of truth.
1763 *
1764 * @return array<string,string>
1765 */
1766 public static function get_utm_defaults() {
1767 return array(
1768 'source' => 'dashboard',
1769 'medium' => 'free-cartflows',
1770 );
1771 }
1772
1773 /**
1774 * Build a cartflows.com URL with the common UTM parameters appended.
1775 * Single source of truth for utm_source and utm_medium defaults.
1776 *
1777 * @param string $base_url Destination URL.
1778 * @param array<string,string> $args Optional UTM overrides (utm_source, utm_medium, utm_campaign, utm_content, utm_term).
1779 * @return string The final URL — UTM-stamped, partner-aware, BSF-referer-aware.
1780 */
1781 public static function get_utm_url( $base_url, $args = array() ) {
1782
1783 $partner_id = get_option( 'cartflows_partner_url_param', '' );
1784 $partner_id = is_string( $partner_id ) ? sanitize_text_field( $partner_id ) : '';
1785
1786 if ( ! empty( $partner_id ) ) {
1787 return esc_url( add_query_arg( array( 'cf' => $partner_id ), $base_url ) );
1788 }
1789
1790 $defaults = self::get_utm_defaults();
1791 $utm_args = wp_parse_args(
1792 $args,
1793 array(
1794 'utm_source' => $defaults['source'],
1795 'utm_medium' => $defaults['medium'],
1796 )
1797 );
1798
1799 $url = add_query_arg( $utm_args, $base_url );
1800
1801 // BSF analytics overrides utm_source when a cross-product referer is set; no-op otherwise.
1802 if ( class_exists( '\BSF_UTM_Analytics' ) && is_callable( '\BSF_UTM_Analytics::get_utm_ready_link' ) ) {
1803 $url = \BSF_UTM_Analytics::get_utm_ready_link( $url, 'cartflows' );
1804 }
1805
1806 return esc_url( $url );
1807 }
1808
1809 /**
1810 * Build a CartFlows upgrade-to-pro / pricing URL with UTM params.
1811 * Thin wrapper around get_utm_url() with utm_campaign defaulted to 'go-pro'.
1812 *
1813 * @param string $page Page slug appended to the domain (used when $custom_url is empty).
1814 * @param string $custom_url Optional full URL to use instead of building from $page.
1815 * @param array<string,string> $args Optional UTM overrides.
1816 * @return string The modified URL.
1817 */
1818 public static function get_upgrade_to_pro_link( $page = 'pricing', $custom_url = '', $args = array() ) {
1819
1820 $base_url = CARTFLOWS_DOMAIN_URL . $page . '/';
1821 $url = empty( $custom_url ) ? $base_url : $custom_url;
1822
1823 $args = wp_parse_args( $args, array( 'utm_campaign' => 'go-pro' ) );
1824
1825 return self::get_utm_url( $url, $args );
1826 }
1827
1828 /**
1829 * Build a CartFlows knowledgebase / docs URL with UTM params.
1830 * Thin wrapper around get_utm_url() with utm_campaign defaulted to 'docs'.
1831 *
1832 * @param string $base_url Destination docs URL.
1833 * @param array<string,string> $args Optional UTM overrides.
1834 * @return string The modified URL.
1835 */
1836 public static function get_kb_doc_link( $base_url, $args = array() ) {
1837
1838 $args = wp_parse_args( $args, array( 'utm_campaign' => 'docs' ) );
1839
1840 return self::get_utm_url( $base_url, $args );
1841 }
1842
1843 /**
1844 * Get current page's template
1845 *
1846 * @param int $post_id The current page id.
1847 * @return string
1848 *
1849 * @since 2.1.0
1850 */
1851 public static function get_current_page_template( $post_id = 0 ) {
1852
1853 if ( empty( $post_id ) ) {
1854 $post_id = _get_wcf_step_id();
1855 }
1856
1857 return apply_filters( 'cartflows_page_template', get_post_meta( $post_id, '_wp_page_template', true ) );
1858 }
1859
1860 /**
1861 * Check the Instant layout is enabled or not.
1862 *
1863 * @param int $flow_id Current flow id.
1864 * @return boolean Returns true if instant layout is enabled, false otherwise.
1865 */
1866 public static function is_instant_layout_enabled( $flow_id = 0 ) {
1867
1868 // Get the flow ID if not set.
1869 if ( empty( $flow_id ) ) {
1870 $flow_id = wcf()->utils->get_flow_id();
1871 }
1872
1873 // Return false if flow ID is not set.
1874 if ( empty( $flow_id ) ) {
1875 return false;
1876 }
1877
1878 // Return false if wcf()->options is not set.
1879 if ( ! isset( wcf()->options ) || ! is_object( wcf()->options ) || ! is_callable( array( wcf()->options, 'get_flow_meta_value' ) ) ) {
1880 return false;
1881 }
1882
1883 // Return true or false based on the instant layout style.
1884 return 'yes' === wcf()->options->get_flow_meta_value( $flow_id, 'instant-layout-style', 'no' );
1885 }
1886
1887 /**
1888 * Get Rollback versions.
1889 *
1890 * @since 2.1.6
1891 * @return array
1892 * @access public
1893 */
1894 public static function get_rollback_versions() {
1895
1896 $rollback_versions = get_transient( 'cartflows_rollback_versions_' . CARTFLOWS_VER );
1897
1898 if ( empty( $rollback_versions ) ) {
1899
1900 $max_versions = 10;
1901
1902 require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
1903
1904 $plugin_information = plugins_api(
1905 'plugin_information',
1906 array(
1907 'slug' => 'cartflows',
1908 )
1909 );
1910
1911 if ( empty( $plugin_information->versions ) || ! is_array( $plugin_information->versions ) ) {
1912 return array();
1913 }
1914
1915 krsort( $plugin_information->versions );
1916
1917 $rollback_versions = array();
1918
1919 foreach ( $plugin_information->versions as $version => $download_link ) {
1920
1921 $lowercase_version = strtolower( $version );
1922
1923 $is_valid_rollback_version = ! preg_match( '/(trunk|beta|rc|dev)/i', $lowercase_version );
1924
1925 if ( ! $is_valid_rollback_version ) {
1926 continue;
1927 }
1928
1929 if ( version_compare( $version, CARTFLOWS_VER, '>=' ) ) {
1930 continue;
1931 }
1932
1933 $rollback_versions[] = $version;
1934 }
1935
1936 usort( $rollback_versions, array( __CLASS__, 'sort_rollback_versions' ) );
1937
1938 $rollback_versions = array_slice( $rollback_versions, 0, $max_versions, true );
1939
1940 set_transient( 'cartflows_rollback_versions_' . CARTFLOWS_VER, $rollback_versions, WEEK_IN_SECONDS );
1941 }
1942
1943 return (array) $rollback_versions;
1944 }
1945 /**
1946 * Sort Rollback versions.
1947 *
1948 * @since 2.1.6
1949 * @param string $prev Previous Version.
1950 * @param string $next Next Version.
1951 *
1952 * @return int
1953 */
1954 public static function sort_rollback_versions( $prev, $next ) {
1955
1956 if ( version_compare( $prev, $next, '==' ) ) {
1957 return 0;
1958 }
1959
1960 if ( version_compare( $prev, $next, '>' ) ) {
1961 return -1;
1962 }
1963
1964 return 1;
1965 }
1966
1967 /**
1968 * Get Rollback versions.
1969 *
1970 * @since 2.1.6
1971 * @return array
1972 * @access public
1973 */
1974 public static function get_rollback_versions_options() {
1975
1976 $rollback_versions = self::get_rollback_versions();
1977
1978 $rollback_versions_options = array();
1979
1980 foreach ( $rollback_versions as $version ) {
1981
1982 $version = array(
1983 'label' => $version,
1984 'value' => $version,
1985
1986 );
1987
1988 $rollback_versions_options[] = $version;
1989 }
1990
1991 return $rollback_versions_options;
1992 }
1993
1994 /**
1995 * Get the master allowlist of file extensions for checkout file uploads.
1996 *
1997 * @return array<string> Allowed file extensions.
1998 */
1999 public static function get_allowed_file_extensions() {
2000 return array( 'jpg', 'jpeg', 'png', 'gif', 'webp', 'ico', 'pdf', 'mp3', 'm4a', 'wav', 'mp4', 'm4v', 'mov' );
2001 }
2002
2003 /**
2004 * Sanitize accepted file types input against the master allowlist.
2005 *
2006 * Strips any extensions not in the allowlist and returns a clean comma-separated string.
2007 *
2008 * @param string $raw_types Comma-separated file extensions from user input.
2009 * @return string Sanitized comma-separated file extensions.
2010 */
2011 public static function sanitize_accepted_file_types( $raw_types ) {
2012 if ( empty( $raw_types ) ) {
2013 return '';
2014 }
2015
2016 $allowed = self::get_allowed_file_extensions();
2017 $input = array_map( 'trim', explode( ',', sanitize_text_field( $raw_types ) ) );
2018 $validated = array();
2019
2020 foreach ( $input as $ext ) {
2021 $ext = strtolower( $ext );
2022 if ( ! empty( $ext ) && in_array( $ext, $allowed, true ) ) {
2023 $validated[] = $ext;
2024 }
2025 }
2026
2027 return implode( ', ', array_unique( $validated ) );
2028 }
2029
2030 /**
2031 * Get the script migration status.
2032 *
2033 * Returns the current status of the custom script migration from old textarea
2034 * fields to new CodeMirror editor fields.
2035 *
2036 * @since 2.2.3
2037 * @return string Migration status: 'pending', 'accepted', 'completed', or 'skipped'.
2038 */
2039 public static function get_script_migration_status() {
2040 return get_option( 'cartflows_script_migration_status', 'pending' );
2041 }
2042
2043 /**
2044 * Set an analytics flag in the consolidated cf_analytics_flags option.
2045 *
2046 * Stores all analytics milestone flags in a single wp_options row.
2047 * Skips the update if the flag is already set (one-time events).
2048 *
2049 * @since 2.2.4
2050 * @param string $key Flag key (e.g. 'first_checkout_configured').
2051 * @param mixed $value Flag value. Default true.
2052 * @param bool $force Whether to overwrite an existing value. Default false.
2053 * @return bool True if the flag was set, false if it already existed.
2054 */
2055 public static function set_analytics_flag( $key, $value = true, $force = false ) {
2056 $flags = get_option( 'cf_analytics_flags', array() );
2057 if ( ! is_array( $flags ) ) {
2058 $flags = array();
2059 }
2060 if ( ! $force && ! empty( $flags[ $key ] ) ) {
2061 return false;
2062 }
2063 $flags[ $key ] = $value;
2064 update_option( 'cf_analytics_flags', $flags, false );
2065 return true;
2066 }
2067
2068 /**
2069 * Get an analytics flag from the consolidated cf_analytics_flags option.
2070 *
2071 * @since 2.2.4
2072 * @param string $key Flag key (e.g. 'first_checkout_configured').
2073 * @param mixed $default Default value if key not found. Default false.
2074 * @return mixed
2075 */
2076 public static function get_analytics_flag( $key, $default = false ) {
2077 $flags = get_option( 'cf_analytics_flags', array() );
2078 if ( ! is_array( $flags ) ) {
2079 return $default;
2080 }
2081 return isset( $flags[ $key ] ) ? $flags[ $key ] : $default;
2082 }
2083 }
2084