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