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