PluginProbe
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce / 3.1.2
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce v3.1.2
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.1.2, at classes/class-cartflows-helper.php

2,073 lines 54.5 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 ) {
1228 $custom_price = $product->get_price( 'edit' );
1229 }
1230
1231 return $custom_price;
1232 }
1233
1234 /**
1235 * Get flows from json files.
1236 *
1237 * @param string $page_builder_slug Selected page builder slug.
1238 */
1239 public function get_stored_page_builder_templates( $page_builder_slug ) {
1240 $flows = array();
1241
1242 $dir = CARTFLOWS_DIR . 'admin-core/assets/importer-data';
1243 $list_files = \list_files( $dir );
1244 if ( ! empty( $list_files ) ) {
1245 $list_files = array_map( 'basename', $list_files );
1246 foreach ( $list_files as $file_key => $file_name ) {
1247 if ( false !== strpos( $file_name, 'cartflows-' . $page_builder_slug . '-flows-and-steps' ) ) {
1248 // file_get_contents is fine for local files. https://github.com/WordPress/WordPress-Coding-Standards/pull/1374/files#diff-400e43bc09c24262b43f26fce487fdabR43-R52.
1249 $data = json_decode( file_get_contents( $dir . '/' . $file_name ), true ); //phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
1250 if ( ! empty( $data ) ) {
1251 /*
1252 Commented.
1253 // $option_name = str_replace( '.json', '', $file_name );
1254 // update_site_option( $option_name, json_decode( $data, true ) );
1255 */
1256 foreach ( $data as $key => $flow ) {
1257 $flows[] = $flow;
1258 }
1259 }
1260 }
1261 }
1262 }
1263
1264 return $flows;
1265 }
1266
1267 /**
1268 * Get meta keys to exclude.
1269 *
1270 * @param int $step_id post id.
1271 */
1272 public function get_meta_keys_to_exclude_from_import( $step_id = 0 ) {
1273
1274 $meta_keys = array(
1275 '_wp_old_slug',
1276 'wcf-checkout-products',
1277 'wcf-optin-product',
1278 'wcf-testing',
1279 );
1280
1281 if ( $step_id && 'yes' === get_post_meta( $step_id, 'cartflows_imported_step', true ) ) {
1282
1283 $meta_keys = array_merge(
1284 $meta_keys,
1285 array(
1286 'wcf_fields_billing',
1287 'wcf_fields_shipping',
1288 )
1289 );
1290 }
1291
1292 return apply_filters(
1293 'cartflows_admin_exclude_import_meta_keys',
1294 $meta_keys
1295 );
1296 }
1297
1298 /**
1299 * Get allowed meta key prefixes for import.
1300 *
1301 * Security: Only meta keys matching these prefixes are permitted during template import.
1302 * This prevents supply-chain attacks from writing arbitrary meta keys.
1303 *
1304 * @since 2.2.2
1305 * @return array List of allowed meta key prefixes.
1306 */
1307 public function get_allowed_import_meta_key_prefixes() {
1308 $prefixes = array(
1309 'wcf-',
1310 'wcf_',
1311 'cartflows_',
1312 '_wcf-',
1313 '_wcf_',
1314 '_cartflows_',
1315 '_elementor_',
1316 '_fl_builder_',
1317 '_wp_page_template',
1318 '_thumbnail_id',
1319 );
1320
1321 return apply_filters( 'cartflows_allowed_import_meta_key_prefixes', $prefixes );
1322 }
1323
1324 /**
1325 * Check if a meta key is allowed for import.
1326 *
1327 * @since 2.2.2
1328 * @param string $meta_key The meta key to check.
1329 * @return bool True if the meta key is allowed.
1330 */
1331 public function is_meta_key_allowed_for_import( $meta_key ) {
1332 $allowed_prefixes = $this->get_allowed_import_meta_key_prefixes();
1333
1334 foreach ( $allowed_prefixes as $prefix ) {
1335 if ( 0 === strpos( $meta_key, $prefix ) ) {
1336 return true;
1337 }
1338 }
1339
1340 return false;
1341 }
1342
1343 /**
1344 * Maybe update flow step order.
1345 *
1346 * @param int $flow_id flow id.
1347 * @param array $flow_steps step list.
1348 *
1349 * @return array flow_steps step list.
1350 */
1351 public function maybe_update_flow_steps( $flow_id, $flow_steps ) {
1352
1353 if ( absint( self::get_global_setting( '_cartflows_store_checkout' ) ) === $flow_id ) {
1354 $key = array_search( 'thankyou', wp_list_pluck( $flow_steps, 'type' ), true );
1355 if ( ! $key ) {
1356 return $flow_steps;
1357 } else {
1358 $thankyou = array_splice( $flow_steps, intval( $key ), 1 );
1359 $flow_steps[] = $thankyou[0];
1360 }
1361 }
1362
1363 return $flow_steps;
1364 }
1365
1366 /**
1367 * Prepare cusom field settings array
1368 *
1369 * @param array $data_array field array.
1370 * @param string $key field key.
1371 * @param array $field_args field args.
1372 * @param array $fields_type field type.
1373 * @param string $step_type step type.
1374 *
1375 * @return array field data array.
1376 */
1377 public function prepare_custom_field_settings( $data_array, $key, $field_args, $fields_type, $step_type = 'checkout' ) {
1378
1379 if ( 'checkout' === $step_type ) {
1380 $name = 'billing' === $fields_type ? 'wcf_field_order_billing[' . $key . ']' : 'wcf_field_order_shipping[' . $key . ']';
1381 } else {
1382 $name = 'wcf-optin-fields-billing[' . $key . ']';
1383 }
1384
1385 $type = $field_args['type'];
1386 $is_checkbox = 'checkbox' == $type ? true : false;
1387 $is_radiobutton = 'radio' == $type ? true : false;
1388 $is_select = 'select' == $type ? true : false;
1389 $is_file = 'file' == $type ? true : false;
1390 $is_custom_field = isset( $field_args['custom'] ) && true === (bool) $field_args['custom'] ? true : false;
1391
1392 $data_array['field_options'] = array(
1393 'enable-field' => array(
1394 'label' => __( 'Enable Field', 'cartflows' ),
1395 'name' => $name . '[enabled]',
1396 'value' => $field_args['enabled'],
1397 ),
1398 'select-width' => array(
1399 'type' => 'select',
1400 'label' => __( 'Field Width', 'cartflows' ),
1401 'name' => $name . '[width]',
1402 'value' => $field_args['width'],
1403 'options' => array(
1404 array(
1405 'value' => '33',
1406 'label' => esc_html__( '33%', 'cartflows' ),
1407 ),
1408 array(
1409 'value' => '50',
1410 'label' => esc_html__( '50%', 'cartflows' ),
1411 ),
1412 array(
1413 'value' => '100',
1414 'label' => esc_html__( '100%', 'cartflows' ),
1415 ),
1416 ),
1417
1418 ),
1419 'field-label' => array(
1420 'type' => 'text',
1421 'label' => __( 'Field Label', 'cartflows' ),
1422 'name' => $name . '[label]',
1423 'value' => $field_args['label'],
1424 ),
1425 'field-name' => array(
1426 'label' => __( 'Field ID', 'cartflows' ),
1427 'name' => $name . '[key]',
1428 'value' => $field_args['key'],
1429 'readonly' => true,
1430 'tooltip' => __( 'Copy this field id to use in Order Custom Field rule of dynamic offers.', 'cartflows' ),
1431 ),
1432
1433 'field-default' => $is_checkbox ?
1434 array(
1435 'type' => 'select',
1436 'label' => __( 'Default', 'cartflows' ),
1437 'name' => $name . '[default]',
1438 'value' => $field_args['default'],
1439 'options' => array(
1440 array(
1441 'value' => '1',
1442 'label' => esc_html__( 'Checked', 'cartflows' ),
1443 ),
1444 array(
1445 'value' => '0',
1446 'label' => esc_html__( 'Un-Checked', 'cartflows' ),
1447 ),
1448 ),
1449 ) :
1450
1451 array(
1452 'type' => 'text',
1453 'label' => __( 'Default', 'cartflows' ),
1454 'name' => $name . '[default]',
1455 'value' => $field_args['default'],
1456 ),
1457 );
1458
1459 if ( $is_select || $is_radiobutton ) {
1460
1461 $data_array['field_options']['select-options'] = array(
1462 'type' => 'text',
1463 'label' => __( 'Options', 'cartflows' ),
1464 'name' => $name . '[options]',
1465 'value' => $field_args['options'],
1466 );
1467 }
1468
1469 if ( in_array( $field_args['type'], array( 'datetime-local', 'date', 'time' ), true ) ) {
1470
1471 switch ( $field_args['type'] ) {
1472 case 'datetime-local':
1473 $date_placeholder = 'yyyy-mm-dd hh:mm';
1474 break;
1475 case 'date':
1476 $date_placeholder = 'yyyy-mm-dd';
1477 break;
1478 case 'time':
1479 $date_placeholder = 'hh:mm';
1480 break;
1481 default:
1482 $date_placeholder = 'yyyy-mm-dd hh:mm';
1483 }
1484
1485 $data_array['field_options']['field-min-date'] = array(
1486 'type' => 'text',
1487 'label' => __( 'Min Date', 'cartflows' ),
1488 'name' => $name . '[custom_attributes][min]',
1489 'value' => $field_args['custom_attributes']['min'],
1490 'placeholder' => $date_placeholder,
1491 );
1492 $data_array['field_options']['field-max-date'] = array(
1493 'type' => 'text',
1494 'label' => __( 'Max Date', 'cartflows' ),
1495 'name' => $name . '[custom_attributes][max]',
1496 'value' => $field_args['custom_attributes']['max'],
1497 'placeholder' => $date_placeholder,
1498 );
1499
1500 $data_array['field_options']['field-default']['placeholder'] = $date_placeholder;
1501 }
1502
1503 if ( ! in_array( $type, array( 'checkbox', 'select', 'radio', 'datetime-local', 'date', 'time', 'number', 'file' ), true ) ) {
1504 $data_array['field_options']['field-placeholder'] = array(
1505 'type' => 'text',
1506 'label' => __( 'Placeholder', 'cartflows' ),
1507 'name' => $name . '[placeholder]',
1508 'value' => $field_args['placeholder'],
1509 );
1510 }
1511
1512 if ( 'number' === $type ) {
1513 $data_array['field_options']['field-min'] = array(
1514 'type' => 'number',
1515 'label' => __( 'Min Number', 'cartflows' ),
1516 'name' => $name . '[custom_attributes][min]',
1517 'value' => $field_args['custom_attributes']['min'],
1518 );
1519 $data_array['field_options']['field-max'] = array(
1520 'type' => 'number',
1521 'label' => __( 'Max Number', 'cartflows' ),
1522 'name' => $name . '[custom_attributes][max]',
1523 'value' => $field_args['custom_attributes']['max'],
1524 );
1525 }
1526
1527 // Remove default field option for file type as files cannot have default values.
1528 if ( $is_file ) {
1529 unset( $data_array['field_options']['field-default'] );
1530
1531 $data_array['field_options']['field-file-size'] = array(
1532 'type' => 'number',
1533 'label' => __( 'File Size (MB)', 'cartflows' ),
1534 'name' => $name . '[custom_attributes][file_size]',
1535 'value' => isset( $field_args['custom_attributes']['file_size'] ) ? $field_args['custom_attributes']['file_size'] : 5,
1536 'min' => 0,
1537 'tooltip' => __( 'Enter maximum file size allowed. Default is 5MB.', 'cartflows' ),
1538 );
1539
1540 // Normalize stored value: could be a comma-separated string or an array of {value, label} objects from a previous Select2Field save.
1541 $stored_types = isset( $field_args['custom_attributes']['accepted_file_types'] )
1542 ? $field_args['custom_attributes']['accepted_file_types']
1543 : '';
1544
1545 $normalized_value = self::sanitize_accepted_file_types( $stored_types );
1546
1547 $data_array['field_options']['field-accepted-file-types'] = array(
1548 'type' => 'text',
1549 'label' => __( 'Accepted File Types', 'cartflows' ),
1550 'name' => $name . '[custom_attributes][accepted_file_types]',
1551 'value' => $normalized_value,
1552 'tooltip' => __( 'Enter comma-separated file extensions (e.g. jpg, png, pdf).', 'cartflows' ),
1553 );
1554 }
1555
1556 if ( $is_custom_field ) {
1557 $data_array['field_options']['show-in-email'] = array(
1558 'type' => 'checkbox',
1559 'label' => __( 'Show In Email', 'cartflows' ),
1560 'name' => $name . '[show_in_email]',
1561 'value' => $field_args['show_in_email'],
1562 );
1563 }
1564
1565 $data_array['field_options']['required-field'] = array(
1566 'label' => __( 'Required', 'cartflows' ),
1567 'name' => $name . '[required]',
1568 'value' => $field_args['required'],
1569 );
1570
1571 if ( 'checkout' === $step_type ) {
1572 $data_array['field_options']['collapsed-field'] = array(
1573 'type' => 'checkbox',
1574 'label' => __( 'Collapsible', 'cartflows' ),
1575 'name' => $name . '[optimized]',
1576 'value' => $field_args['optimized'],
1577 );
1578 }
1579
1580 return $data_array;
1581 }
1582
1583 /**
1584 * Get step edit link.
1585 *
1586 * @param int $step_id Step id.
1587 */
1588 public static function get_page_builder_edit_link( $step_id ) {
1589
1590 $edit_step = get_edit_post_link( $step_id, 'edit' );
1591 $view_step = get_permalink( $step_id );
1592 $page_builder = self::get_common_setting( 'default_page_builder' );
1593 $page_builder_edit = $edit_step;
1594
1595 switch ( $page_builder ) {
1596 case 'beaver-builder':
1597 if ( is_plugin_active( 'beaver-builder-lite-version/fl-builder.php' ) ) {
1598 $page_builder_edit = strpos( $view_step, '?' ) ? $view_step . '&fl_builder' : $view_step . '?fl_builder';
1599 }
1600 break;
1601 case 'elementor':
1602 if ( is_plugin_active( 'elementor/elementor.php' ) ) {
1603 $page_builder_edit = admin_url( 'post.php?post=' . $step_id . '&action=elementor' );
1604 }
1605 break;
1606 case 'bricks-builder':
1607 if ( Cartflows_Compatibility::is_bricks_enabled() ) {
1608 $page_builder_edit = strpos( $view_step, '?' ) ? $view_step . '&bricks=run' : $view_step . '?bricks=run';
1609 }
1610 break;
1611 }
1612
1613 return $page_builder_edit;
1614 }
1615
1616 /**
1617 * Get CartFlows Global Color Pallet CSS_Vars data.
1618 *
1619 * @since 2.0.0.
1620 * @return array Array of GCP vars slugs and label,
1621 */
1622 public static function get_gcp_vars() {
1623 return array(
1624 'wcf-gcp-primary-color' => __( 'CartFlows Primary Color', 'cartflows' ),
1625 'wcf-gcp-secondary-color' => __( 'CartFlows Secondary Color', 'cartflows' ),
1626 'wcf-gcp-text-color' => __( 'CartFlows Text Color', 'cartflows' ),
1627 'wcf-gcp-accent-color' => __( 'CartFlows Heading/Accent Color', 'cartflows' ),
1628 );
1629 }
1630
1631 /**
1632 * Generate GCP styles CSS and assign the colors to the GCP CSS VARs.
1633 *
1634 * @param int $flow_id The current flow ID.
1635 * @return array $gcp_vars Array of generated CSS.
1636 *
1637 * @since 2.0.0
1638 */
1639 public static function generate_gcp_css_style( $flow_id = 0 ) {
1640
1641 $gcp_vars = '';
1642
1643 if ( empty( $flow_id ) ) {
1644 return $gcp_vars;
1645 }
1646
1647 $gcp_vars_array = array_keys( self::get_gcp_vars() );
1648
1649 foreach ( $gcp_vars_array as $slug ) {
1650 // Gather value of global color VAR.
1651 $color_value = wcf()->options->get_flow_meta_value( $flow_id, $slug, '' );
1652
1653 if ( empty( $color_value ) ) {
1654 continue;
1655 }
1656
1657 // Convert it into the CSS var.
1658 $gcp_vars .= '--' . $slug . ': ' . $color_value . '; ';
1659 }
1660
1661 return $gcp_vars;
1662 }
1663
1664 /**
1665 * Generate the array of CSS vars to add in the Gutenberg color pallet.
1666 *
1667 * @param int $flow_id The current Flow ID.
1668 * @param string $builder The current page builder.
1669 * @return array $new_color_palette Prepared array of CSS vars.
1670 *
1671 * @since 2.0.0
1672 */
1673 public static function generate_css_var_array( $flow_id = 0, $builder = 'gutenberg' ) {
1674
1675 if ( empty( $flow_id ) ) {
1676 $flow_id = wcf()->utils->get_flow_id();
1677 }
1678 $new_color_palette = array();
1679 // Default Color Pallet used as a separator.
1680 if ( 'gutenberg' === $builder ) {
1681 $new_color_palette[] = array(
1682 'name' => 'CartFlows Separator',
1683 'slug' => 'wcf-gcp-separator',
1684 'color' => '#ff0000',
1685 );
1686 } elseif ( 'elementor' === $builder ) {
1687 $new_color_palette['wcfgcpseparator'] = array(
1688 'id' => 'wcfgcpseparator',
1689 'title' => 'CartFlows Separator',
1690 'value' => '#ff0000',
1691 );
1692 }
1693
1694 $cf_gcp_data = self::get_gcp_vars();
1695
1696 // Prepare new colors css vars.
1697 foreach ( $cf_gcp_data as $slug => $label ) {
1698
1699 $color_value = get_post_meta( $flow_id, $slug, true );
1700
1701 if ( empty( $color_value ) ) {
1702 continue;
1703 }
1704
1705 // Add CartFlows Global Color Pallets CSS vars options.
1706 if ( 'gutenberg' === $builder ) {
1707 $new_color_palette[] = array(
1708 'name' => $label,
1709 'slug' => $slug,
1710 'color' => $color_value,
1711 );
1712 } elseif ( 'elementor' === $builder ) {
1713 $slug = str_replace( '-', '', $slug );
1714 $new_color_palette[ $slug ] = array(
1715 'id' => $slug,
1716 'title' => $label,
1717 'value' => $color_value,
1718 );
1719 }
1720 }
1721
1722 return $new_color_palette;
1723 }
1724
1725 /**
1726 * Check is the Global Color pallet is enabled or not.
1727 *
1728 * @param int $flow_id Current flow id.
1729 * @return boolean
1730 */
1731 public static function is_gcp_styling_enabled( $flow_id ) {
1732
1733 if ( empty( $flow_id ) ) {
1734 return false;
1735 }
1736
1737 return 'yes' === wcf()->options->get_flow_meta_value( $flow_id, 'wcf-enable-gcp-styling', 'no' );
1738 }
1739
1740 /**
1741 * Function to check to show the products tab in the store checkout or not.
1742 *
1743 * @return bool
1744 * @since 2.0.4
1745 */
1746 public static function display_product_tab_in_store_checkout() {
1747 return apply_filters( 'cartflows_show_store_checkout_product_tab', false );
1748 }
1749
1750 /**
1751 * Common UTM defaults shared across PHP and JS — single source of truth.
1752 *
1753 * @return array<string,string>
1754 */
1755 public static function get_utm_defaults() {
1756 return array(
1757 'source' => 'dashboard',
1758 'medium' => 'free-cartflows',
1759 );
1760 }
1761
1762 /**
1763 * Build a cartflows.com URL with the common UTM parameters appended.
1764 * Single source of truth for utm_source and utm_medium defaults.
1765 *
1766 * @param string $base_url Destination URL.
1767 * @param array<string,string> $args Optional UTM overrides (utm_source, utm_medium, utm_campaign, utm_content, utm_term).
1768 * @return string The final URL — UTM-stamped, partner-aware, BSF-referer-aware.
1769 */
1770 public static function get_utm_url( $base_url, $args = array() ) {
1771
1772 $partner_id = get_option( 'cartflows_partner_url_param', '' );
1773 $partner_id = is_string( $partner_id ) ? sanitize_text_field( $partner_id ) : '';
1774
1775 if ( ! empty( $partner_id ) ) {
1776 return esc_url( add_query_arg( array( 'cf' => $partner_id ), $base_url ) );
1777 }
1778
1779 $defaults = self::get_utm_defaults();
1780 $utm_args = wp_parse_args(
1781 $args,
1782 array(
1783 'utm_source' => $defaults['source'],
1784 'utm_medium' => $defaults['medium'],
1785 )
1786 );
1787
1788 $url = add_query_arg( $utm_args, $base_url );
1789
1790 // BSF analytics overrides utm_source when a cross-product referer is set; no-op otherwise.
1791 if ( class_exists( '\BSF_UTM_Analytics' ) && is_callable( '\BSF_UTM_Analytics::get_utm_ready_link' ) ) {
1792 $url = \BSF_UTM_Analytics::get_utm_ready_link( $url, 'cartflows' );
1793 }
1794
1795 return esc_url( $url );
1796 }
1797
1798 /**
1799 * Build a CartFlows upgrade-to-pro / pricing URL with UTM params.
1800 * Thin wrapper around get_utm_url() with utm_campaign defaulted to 'go-pro'.
1801 *
1802 * @param string $page Page slug appended to the domain (used when $custom_url is empty).
1803 * @param string $custom_url Optional full URL to use instead of building from $page.
1804 * @param array<string,string> $args Optional UTM overrides.
1805 * @return string The modified URL.
1806 */
1807 public static function get_upgrade_to_pro_link( $page = 'pricing', $custom_url = '', $args = array() ) {
1808
1809 $base_url = CARTFLOWS_DOMAIN_URL . $page . '/';
1810 $url = empty( $custom_url ) ? $base_url : $custom_url;
1811
1812 $args = wp_parse_args( $args, array( 'utm_campaign' => 'go-pro' ) );
1813
1814 return self::get_utm_url( $url, $args );
1815 }
1816
1817 /**
1818 * Build a CartFlows knowledgebase / docs URL with UTM params.
1819 * Thin wrapper around get_utm_url() with utm_campaign defaulted to 'docs'.
1820 *
1821 * @param string $base_url Destination docs URL.
1822 * @param array<string,string> $args Optional UTM overrides.
1823 * @return string The modified URL.
1824 */
1825 public static function get_kb_doc_link( $base_url, $args = array() ) {
1826
1827 $args = wp_parse_args( $args, array( 'utm_campaign' => 'docs' ) );
1828
1829 return self::get_utm_url( $base_url, $args );
1830 }
1831
1832 /**
1833 * Get current page's template
1834 *
1835 * @param int $post_id The current page id.
1836 * @return string
1837 *
1838 * @since 2.1.0
1839 */
1840 public static function get_current_page_template( $post_id = 0 ) {
1841
1842 if ( empty( $post_id ) ) {
1843 $post_id = _get_wcf_step_id();
1844 }
1845
1846 return apply_filters( 'cartflows_page_template', get_post_meta( $post_id, '_wp_page_template', true ) );
1847 }
1848
1849 /**
1850 * Check the Instant layout is enabled or not.
1851 *
1852 * @param int $flow_id Current flow id.
1853 * @return boolean Returns true if instant layout is enabled, false otherwise.
1854 */
1855 public static function is_instant_layout_enabled( $flow_id = 0 ) {
1856
1857 // Get the flow ID if not set.
1858 if ( empty( $flow_id ) ) {
1859 $flow_id = wcf()->utils->get_flow_id();
1860 }
1861
1862 // Return false if flow ID is not set.
1863 if ( empty( $flow_id ) ) {
1864 return false;
1865 }
1866
1867 // Return false if wcf()->options is not set.
1868 if ( ! isset( wcf()->options ) || ! is_object( wcf()->options ) || ! is_callable( array( wcf()->options, 'get_flow_meta_value' ) ) ) {
1869 return false;
1870 }
1871
1872 // Return true or false based on the instant layout style.
1873 return 'yes' === wcf()->options->get_flow_meta_value( $flow_id, 'instant-layout-style', 'no' );
1874 }
1875
1876 /**
1877 * Get Rollback versions.
1878 *
1879 * @since 2.1.6
1880 * @return array
1881 * @access public
1882 */
1883 public static function get_rollback_versions() {
1884
1885 $rollback_versions = get_transient( 'cartflows_rollback_versions_' . CARTFLOWS_VER );
1886
1887 if ( empty( $rollback_versions ) ) {
1888
1889 $max_versions = 10;
1890
1891 require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
1892
1893 $plugin_information = plugins_api(
1894 'plugin_information',
1895 array(
1896 'slug' => 'cartflows',
1897 )
1898 );
1899
1900 if ( empty( $plugin_information->versions ) || ! is_array( $plugin_information->versions ) ) {
1901 return array();
1902 }
1903
1904 krsort( $plugin_information->versions );
1905
1906 $rollback_versions = array();
1907
1908 foreach ( $plugin_information->versions as $version => $download_link ) {
1909
1910 $lowercase_version = strtolower( $version );
1911
1912 $is_valid_rollback_version = ! preg_match( '/(trunk|beta|rc|dev)/i', $lowercase_version );
1913
1914 if ( ! $is_valid_rollback_version ) {
1915 continue;
1916 }
1917
1918 if ( version_compare( $version, CARTFLOWS_VER, '>=' ) ) {
1919 continue;
1920 }
1921
1922 $rollback_versions[] = $version;
1923 }
1924
1925 usort( $rollback_versions, array( __CLASS__, 'sort_rollback_versions' ) );
1926
1927 $rollback_versions = array_slice( $rollback_versions, 0, $max_versions, true );
1928
1929 set_transient( 'cartflows_rollback_versions_' . CARTFLOWS_VER, $rollback_versions, WEEK_IN_SECONDS );
1930 }
1931
1932 return (array) $rollback_versions;
1933 }
1934 /**
1935 * Sort Rollback versions.
1936 *
1937 * @since 2.1.6
1938 * @param string $prev Previous Version.
1939 * @param string $next Next Version.
1940 *
1941 * @return int
1942 */
1943 public static function sort_rollback_versions( $prev, $next ) {
1944
1945 if ( version_compare( $prev, $next, '==' ) ) {
1946 return 0;
1947 }
1948
1949 if ( version_compare( $prev, $next, '>' ) ) {
1950 return -1;
1951 }
1952
1953 return 1;
1954 }
1955
1956 /**
1957 * Get Rollback versions.
1958 *
1959 * @since 2.1.6
1960 * @return array
1961 * @access public
1962 */
1963 public static function get_rollback_versions_options() {
1964
1965 $rollback_versions = self::get_rollback_versions();
1966
1967 $rollback_versions_options = array();
1968
1969 foreach ( $rollback_versions as $version ) {
1970
1971 $version = array(
1972 'label' => $version,
1973 'value' => $version,
1974
1975 );
1976
1977 $rollback_versions_options[] = $version;
1978 }
1979
1980 return $rollback_versions_options;
1981 }
1982
1983 /**
1984 * Get the master allowlist of file extensions for checkout file uploads.
1985 *
1986 * @return array<string> Allowed file extensions.
1987 */
1988 public static function get_allowed_file_extensions() {
1989 return array( 'jpg', 'jpeg', 'png', 'gif', 'webp', 'ico', 'pdf', 'mp3', 'm4a', 'wav', 'mp4', 'm4v', 'mov' );
1990 }
1991
1992 /**
1993 * Sanitize accepted file types input against the master allowlist.
1994 *
1995 * Strips any extensions not in the allowlist and returns a clean comma-separated string.
1996 *
1997 * @param string $raw_types Comma-separated file extensions from user input.
1998 * @return string Sanitized comma-separated file extensions.
1999 */
2000 public static function sanitize_accepted_file_types( $raw_types ) {
2001 if ( empty( $raw_types ) ) {
2002 return '';
2003 }
2004
2005 $allowed = self::get_allowed_file_extensions();
2006 $input = array_map( 'trim', explode( ',', sanitize_text_field( $raw_types ) ) );
2007 $validated = array();
2008
2009 foreach ( $input as $ext ) {
2010 $ext = strtolower( $ext );
2011 if ( ! empty( $ext ) && in_array( $ext, $allowed, true ) ) {
2012 $validated[] = $ext;
2013 }
2014 }
2015
2016 return implode( ', ', array_unique( $validated ) );
2017 }
2018
2019 /**
2020 * Get the script migration status.
2021 *
2022 * Returns the current status of the custom script migration from old textarea
2023 * fields to new CodeMirror editor fields.
2024 *
2025 * @since 2.2.3
2026 * @return string Migration status: 'pending', 'accepted', 'completed', or 'skipped'.
2027 */
2028 public static function get_script_migration_status() {
2029 return get_option( 'cartflows_script_migration_status', 'pending' );
2030 }
2031
2032 /**
2033 * Set an analytics flag in the consolidated cf_analytics_flags option.
2034 *
2035 * Stores all analytics milestone flags in a single wp_options row.
2036 * Skips the update if the flag is already set (one-time events).
2037 *
2038 * @since 2.2.4
2039 * @param string $key Flag key (e.g. 'first_checkout_configured').
2040 * @param mixed $value Flag value. Default true.
2041 * @param bool $force Whether to overwrite an existing value. Default false.
2042 * @return bool True if the flag was set, false if it already existed.
2043 */
2044 public static function set_analytics_flag( $key, $value = true, $force = false ) {
2045 $flags = get_option( 'cf_analytics_flags', array() );
2046 if ( ! is_array( $flags ) ) {
2047 $flags = array();
2048 }
2049 if ( ! $force && ! empty( $flags[ $key ] ) ) {
2050 return false;
2051 }
2052 $flags[ $key ] = $value;
2053 update_option( 'cf_analytics_flags', $flags, false );
2054 return true;
2055 }
2056
2057 /**
2058 * Get an analytics flag from the consolidated cf_analytics_flags option.
2059 *
2060 * @since 2.2.4
2061 * @param string $key Flag key (e.g. 'first_checkout_configured').
2062 * @param mixed $default Default value if key not found. Default false.
2063 * @return mixed
2064 */
2065 public static function get_analytics_flag( $key, $default = false ) {
2066 $flags = get_option( 'cf_analytics_flags', array() );
2067 if ( ! is_array( $flags ) ) {
2068 return $default;
2069 }
2070 return isset( $flags[ $key ] ) ? $flags[ $key ] : $default;
2071 }
2072 }
2073