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 +1665 -107 1.1.10 → 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 */
@@ -21,8 +43,30 @@
21 43 */
22 44 private static $common = null;
23 45
24 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 + /**
25 69 * Installed Plugins
26 70 *
27 71 * @since 1.1.4
28 72 *
@@ -38,11 +82,49 @@
38 82 */
39 83 private static $checkout_fields = null;
40 84
41 85 /**
42 - * Returns an option from the database for
43 - * the admin settings page.
86 + * Facebook pixel global data
44 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 + *
45 127 * @param string $key The option key.
46 128 * @param mixed $default Option default value if option is not available.
47 129 * @param boolean $network_override Whether to allow the network admin setting to be overridden on subsites.
48 130 * @return string Return the option value
@@ -61,14 +143,16 @@
61 143
62 144 /**
63 145 * Updates an option from the admin settings page.
64 146 *
147 + * Note: Use this function to access any properties to front-end of the website.
148 + *
65 149 * @param string $key The option key.
66 150 * @param mixed $value The value to update.
67 151 * @param bool $network Whether to allow the network admin setting to be overridden on subsites.
68 152 * @return mixed
69 153 */
70 - static public function update_admin_settings_option( $key, $value, $network = false ) {
154 + public static function update_admin_settings_option( $key, $value, $network = false ) {
71 155
72 156 // Update the site-wide option since we're in the network admin.
73 157 if ( $network && is_multisite() ) {
74 158 update_site_option( $key, $value );
@@ -74,9 +158,8 @@
74 158 update_site_option( $key, $value );
75 159 } else {
76 160 update_option( $key, $value );
77 161 }
78 -
79 162 }
80 163
81 164 /**
82 165 * Get single setting
@@ -86,9 +169,9 @@
86 169 * @param string $key Option key.
87 170 * @param string $default Option default value if not exist.
88 171 * @return mixed
89 172 */
90 - static public function get_common_setting( $key = '', $default = '' ) {
173 + public static function get_common_setting( $key = '', $default = '' ) {
91 174 $settings = self::get_common_settings();
92 175
93 176 if ( $settings && array_key_exists( $key, $settings ) ) {
94 177 return $settings[ $key ];
@@ -97,8 +180,51 @@
97 180 return $default;
98 181 }
99 182
100 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 + /**
101 227 * Get required plugins for page builder
102 228 *
103 229 * @since 1.1.4
104 230 *
@@ -139,9 +265,9 @@
139 265 }
140 266 }
141 267
142 268 $plugins = array(
143 - 'elementor' => array(
269 + 'elementor' => array(
144 270 'title' => 'Elementor',
145 271 'plugins' => array(
146 272 array(
147 273 'slug' => 'elementor', // For download from wp.org.
@@ -149,9 +275,19 @@
149 275 'status' => self::get_plugin_status( 'elementor/elementor.php' ),
150 276 ),
151 277 ),
152 278 ),
153 - '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(
154 290 'title' => 'Divi',
155 291 'theme-status' => $theme_status,
156 292 'plugin-status' => $divi_status,
157 293 'plugins' => array(
@@ -161,17 +297,16 @@
161 297 'status' => $divi_status,
162 298 ),
163 299 ),
164 300 ),
301 + 'beaver-builder' => array(
302 + 'title' => 'Beaver Builder',
303 + 'plugins' => array(),
304 + ),
165 305 );
166 306
167 - $plugins['beaver-builder'] = array(
168 - 'title' => 'Beaver Builder',
169 - 'plugins' => array(),
170 - );
171 -
172 307 // Check Pro Exist.
173 - 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' ) ) {
174 309 $plugins['beaver-builder']['plugins'][] = array(
175 310 'slug' => 'bb-plugin',
176 311 'init' => 'bb-plugin/fl-builder.php',
177 312 'status' => self::get_plugin_status( 'bb-plugin/fl-builder.php' ),
@@ -183,9 +318,9 @@
183 318 'status' => self::get_plugin_status( 'beaver-builder-lite-version/fl-builder.php' ),
184 319 );
185 320 }
186 321
187 - 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' ) ) {
188 323 $plugins['beaver-builder']['plugins'][] = array(
189 324 'slug' => 'bb-ultimate-addon',
190 325 'init' => 'bb-ultimate-addon/bb-ultimate-addon.php',
191 326 'status' => self::get_plugin_status( 'bb-ultimate-addon/bb-ultimate-addon.php' ),
@@ -201,36 +336,50 @@
201 336 return $plugins;
202 337 }
203 338
204 339 /**
205 - * Get plugin status
340 + * Determine the status of a given plugin.
206 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 + *
207 347 * @since 1.1.4
208 348 *
209 - * @param string $plugin_init_file Plguin init file.
210 - * @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'.
211 352 */
212 - public static function get_plugin_status( $plugin_init_file ) {
353 + public static function get_plugin_status( $plugin_init_file, $send_active = false ) {
213 354
214 355 if ( null == self::$installed_plugins ) {
215 356 self::$installed_plugins = get_plugins();
216 357 }
217 358
359 + if ( $send_active && is_plugin_active( $plugin_init_file ) ) {
360 + return 'active';
361 + }
362 +
363 + $plugin_status = '';
364 +
218 365 if ( ! isset( self::$installed_plugins[ $plugin_init_file ] ) ) {
219 - return 'install';
366 + $plugin_status = 'install';
220 367 } elseif ( ! is_plugin_active( $plugin_init_file ) ) {
221 - return 'activate';
368 + $plugin_status = 'activate';
369 + } else {
370 + $plugin_status = 'inactive';
222 371 }
223 372
224 - return;
373 + return $plugin_status;
225 374 }
226 375
227 376 /**
228 377 * Get zapier settings.
229 378 *
230 - * @return array.
379 + * @return array
231 380 */
232 - static public function get_common_settings() {
381 + public static function get_common_settings() {
233 382
234 383 if ( null === self::$common ) {
235 384
236 385 $common_default = apply_filters(
@@ -235,17 +384,25 @@
235 384
236 385 $common_default = apply_filters(
237 386 'cartflows_common_settings_default',
238 387 array(
239 - 'disallow_indexing' => 'disable',
240 - 'global_checkout' => '',
241 - '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',
242 393 )
243 394 );
244 395
245 - $common = Cartflows_Helper::get_admin_settings_option( '_cartflows_common', false, true );
396 + $common = self::get_admin_settings_option( '_cartflows_common', false, false );
246 397
247 - self::$common = wp_parse_args( $common, $common_default );
398 + $common = wp_parse_args( $common, $common_default );
399 +
400 + if ( ! did_action( 'wp' ) ) {
401 + return $common;
402 + } else {
403 + self::$common = $common;
404 + }
248 405 }
249 406
250 407 return self::$common;
251 408 }
@@ -250,8 +407,110 @@
250 407 return self::$common;
251 408 }
252 409
253 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 + /**
254 513 * Get Checkout field.
255 514 *
256 515 * @param string $key Field key.
257 516 * @param int $post_id Post id.
@@ -256,9 +515,9 @@
256 515 * @param string $key Field key.
257 516 * @param int $post_id Post id.
258 517 * @return array.
259 518 */
260 - static public function get_checkout_fields( $key, $post_id ) {
519 + public static function get_checkout_fields( $key, $post_id ) {
261 520
262 521 $saved_fields = get_post_meta( $post_id, 'wcf_fields_' . $key, true );
263 522
264 523 if ( ! $saved_fields ) {
@@ -279,77 +538,78 @@
279 538 return $fields;
280 539 }
281 540
282 541 /**
283 - * Add Checkout field.
542 + * Get Optin fields.
284 543 *
285 - * @param string $type Field type.
286 - * @param string $field_key Field key.
287 - * @param array $field_data Field data.
288 - * @param int $post_id Post id.
289 - * @return boolean.
544 + * @return array.
290 545 */
291 - static public function add_checkout_field( $type, $field_key, $field_data = array(), $post_id ) {
546 + public static function get_optin_default_fields() {
292 547
293 - $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 + );
294 581
295 - $fields[ $field_key ] = $field_data;
296 -
297 - update_post_meta( $post_id, 'wcf_fields_' . $type, $fields );
298 -
299 - return true;
582 + return $optin_fields;
300 583 }
301 584
302 585 /**
303 - * Get checkout fields settings.
586 + * Get Optin field.
304 587 *
305 - * @param string $type Field type.
306 - * @param string $field_key Field key.
588 + * @param string $key Field key.
307 589 * @param int $post_id Post id.
308 - * @return array.
590 + * @return array.
309 591 */
310 - static public function delete_checkout_field( $type, $field_key, $post_id ) {
592 + public static function get_optin_fields( $key, $post_id ) {
311 593
312 - $fields = self::get_checkout_fields( $type, $post_id );
594 + $saved_fields = get_post_meta( $post_id, 'wcf_fields_' . $key, true );
313 595
314 - if ( isset( $fields[ $field_key ] ) ) {
315 - unset( $fields[ $field_key ] );
596 + if ( ! $saved_fields ) {
597 + $saved_fields = array();
316 598 }
317 599
318 - update_post_meta( $post_id, 'wcf_fields_' . $type, $fields );
600 + $fields = array_filter( $saved_fields );
319 601
320 - return true;
321 - }
602 + if ( empty( $fields ) ) {
603 + if ( 'billing' === $key ) {
322 604
323 - /**
324 - * Get checkout fields settings.
325 - *
326 - * @return array.
327 - */
328 - static public function get_checkout_fields_settings() {
605 + $fields = self::get_optin_default_fields();
329 606
330 - if ( null === self::$checkout_fields ) {
331 - $checkout_fields_default = array(
332 - 'enable_customization' => 'disable',
333 - 'enable_billing_fields' => 'disable',
334 - );
335 -
336 - $billing_fields = self::get_checkout_fields( 'billing' );
337 -
338 - if ( is_array( $billing_fields ) && ! empty( $billing_fields ) ) {
339 -
340 - foreach ( $billing_fields as $key => $value ) {
341 -
342 - $checkout_fields_default[ $key ] = 'enable';
343 - }
607 + update_post_meta( $post_id, 'wcf_fields_' . $key, $fields );
344 608 }
345 -
346 - $checkout_fields = Cartflows_Helper::get_admin_settings_option( '_wcf_checkout_fields', false, true );
347 -
348 - self::$checkout_fields = wp_parse_args( $checkout_fields, $checkout_fields_default );
349 609 }
350 610
351 - return self::$checkout_fields;
611 + return $fields;
352 612 }
353 613
354 614 /**
355 615 * Get meta options
@@ -359,9 +619,9 @@
359 619 * @param string $key Meta Key.
360 620 * @param string $default Default value.
361 621 * @return string Meta Value.
362 622 */
363 - static public function get_meta_option( $post_id, $key, $default = '' ) {
623 + public static function get_meta_option( $post_id, $key, $default = '' ) {
364 624
365 625 $value = get_post_meta( $post_id, $key, true );
366 626
367 627 if ( ! $value ) {
@@ -371,26 +631,8 @@
371 631 return $value;
372 632 }
373 633
374 634 /**
375 - * Save meta option
376 - *
377 - * @since 1.0.0
378 - * @param int $post_id Product ID.
379 - * @param array $args Arguments array.
380 - */
381 - static public function save_meta_option( $post_id, $args = array() ) {
382 -
383 - if ( is_array( $args ) && ! empty( $args ) ) {
384 -
385 - foreach ( $args as $key => $value ) {
386 -
387 - update_post_meta( $post_id, $key, $value );
388 - }
389 - }
390 - }
391 -
392 - /**
393 635 * Check if Elementor page builder is installed
394 636 *
395 637 * @since 1.0.0
396 638 *
@@ -395,9 +637,9 @@
395 637 * @since 1.0.0
396 638 *
397 639 * @access public
398 640 */
399 - static public function _is_elementor_installed() {
641 + public static function is_elementor_installed() {
400 642 $path = 'elementor/elementor.php';
401 643 $plugins = get_plugins();
402 644
403 645 return isset( $plugins[ $path ] );
@@ -410,23 +652,33 @@
410 652 * @param int $step_id step ID.
411 653 *
412 654 * @access public
413 655 */
414 - static public function has_product_assigned( $step_id ) {
656 + public static function has_product_assigned( $step_id ) {
415 657
416 658 $step_type = get_post_meta( $step_id, 'wcf-step-type', true );
417 659
418 - if ( 'checkout' == $step_type ) {
660 + $has_product_assigned = false;
661 +
662 + if ( 'checkout' === $step_type ) {
419 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 + }
420 673 } else {
421 674 $product = get_post_meta( $step_id, 'wcf-offer-product', true );
675 + if ( ! empty( $product ) && ! empty( $product[0] ) ) {
676 + $has_product_assigned = true;
677 + }
422 678 }
423 679
424 - if ( ! empty( $product ) ) {
425 - return true;
426 - }
427 - return false;
428 -
680 + return $has_product_assigned;
429 681 }
430 682
431 683 /**
432 684 * Get attributes for cartflows wrap.
@@ -434,11 +686,14 @@
434 686 * @since 1.1.4
435 687 *
436 688 * @access public
437 689 */
438 - static public function get_cartflows_container_atts() {
690 + public static function get_cartflows_container_atts() {
439 691
440 - $attributes = apply_filters( 'cartflows_container_atts', array() );
692 + $attributes = apply_filters(
693 + 'cartflows_container_atts',
694 + array()
695 + );
441 696 $atts_string = '';
442 697
443 698 foreach ( $attributes as $key => $value ) {
444 699
@@ -453,6 +708,1309 @@
453 708 }
454 709 }
455 710
456 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;
457 2015 }
458 2016 }