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 +1826 -83 1.1.0 → 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,40 @@
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 + /**
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 + /**
25 79 * Checkout Fields
26 80 *
27 81 * @var checkout_fields
28 82 */
@@ -28,11 +82,49 @@
28 82 */
29 83 private static $checkout_fields = null;
30 84
31 85 /**
32 - * Returns an option from the database for
33 - * the admin settings page.
86 + * Facebook pixel global data
34 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 + *
35 127 * @param string $key The option key.
36 128 * @param mixed $default Option default value if option is not available.
37 129 * @param boolean $network_override Whether to allow the network admin setting to be overridden on subsites.
38 130 * @return string Return the option value
@@ -51,14 +143,16 @@
51 143
52 144 /**
53 145 * Updates an option from the admin settings page.
54 146 *
147 + * Note: Use this function to access any properties to front-end of the website.
148 + *
55 149 * @param string $key The option key.
56 150 * @param mixed $value The value to update.
57 151 * @param bool $network Whether to allow the network admin setting to be overridden on subsites.
58 152 * @return mixed
59 153 */
60 - static public function update_admin_settings_option( $key, $value, $network = false ) {
154 + public static function update_admin_settings_option( $key, $value, $network = false ) {
61 155
62 156 // Update the site-wide option since we're in the network admin.
63 157 if ( $network && is_multisite() ) {
64 158 update_site_option( $key, $value );
@@ -64,17 +158,228 @@
64 158 update_site_option( $key, $value );
65 159 } else {
66 160 update_option( $key, $value );
67 161 }
162 + }
68 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;
69 181 }
70 182
71 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 + /**
72 377 * Get zapier settings.
73 378 *
74 - * @return array.
379 + * @return array
75 380 */
76 - static public function get_common_settings() {
381 + public static function get_common_settings() {
77 382
78 383 if ( null === self::$common ) {
79 384
80 385 $common_default = apply_filters(
@@ -79,16 +384,25 @@
79 384
80 385 $common_default = apply_filters(
81 386 'cartflows_common_settings_default',
82 387 array(
83 - 'disallow_indexing' => 'disable',
84 - 'global_checkout' => '',
388 + 'global_checkout' => '',
389 + 'override_global_checkout' => 'enable',
390 + 'override_store_order_pay' => 'disable',
391 + 'disallow_indexing' => 'disable',
392 + 'default_page_builder' => 'elementor',
85 393 )
86 394 );
87 395
88 - $common = Cartflows_Helper::get_admin_settings_option( '_cartflows_common', false, true );
396 + $common = self::get_admin_settings_option( '_cartflows_common', false, false );
89 397
90 - 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 + }
91 405 }
92 406
93 407 return self::$common;
94 408 }
@@ -93,8 +407,110 @@
93 407 return self::$common;
94 408 }
95 409
96 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 + /**
97 513 * Get Checkout field.
98 514 *
99 515 * @param string $key Field key.
100 516 * @param int $post_id Post id.
@@ -99,9 +515,9 @@
99 515 * @param string $key Field key.
100 516 * @param int $post_id Post id.
101 517 * @return array.
102 518 */
103 - static public function get_checkout_fields( $key, $post_id ) {
519 + public static function get_checkout_fields( $key, $post_id ) {
104 520
105 521 $saved_fields = get_post_meta( $post_id, 'wcf_fields_' . $key, true );
106 522
107 523 if ( ! $saved_fields ) {
@@ -122,152 +538,1479 @@
122 538 return $fields;
123 539 }
124 540
125 541 /**
126 - * Add Checkout field.
542 + * Get Optin fields.
127 543 *
128 - * @param string $type Field type.
129 - * @param string $field_key Field key.
130 - * @param array $field_data Field data.
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.
131 589 * @param int $post_id Post id.
132 - * @return boolean.
590 + * @return array.
133 591 */
134 - static public function add_checkout_field( $type, $field_key, $field_data = array(), $post_id ) {
592 + public static function get_optin_fields( $key, $post_id ) {
135 593
136 - $fields = self::get_checkout_fields( $type, $post_id );
594 + $saved_fields = get_post_meta( $post_id, 'wcf_fields_' . $key, true );
137 595
138 - $fields[ $field_key ] = $field_data;
596 + if ( ! $saved_fields ) {
597 + $saved_fields = array();
598 + }
139 599
140 - update_post_meta( $post_id, 'wcf_fields_' . $type, $fields );
600 + $fields = array_filter( $saved_fields );
141 601
142 - return true;
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;
143 612 }
144 613
145 614 /**
146 - * Get checkout fields settings.
615 + * Get meta options
147 616 *
148 - * @param string $type Field type.
149 - * @param string $field_key Field key.
150 - * @param int $post_id Post id.
151 - * @return array.
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.
152 622 */
153 - static public function delete_checkout_field( $type, $field_key, $post_id ) {
623 + public static function get_meta_option( $post_id, $key, $default = '' ) {
154 624
155 - $fields = self::get_checkout_fields( $type, $post_id );
625 + $value = get_post_meta( $post_id, $key, true );
156 626
157 - if ( isset( $fields[ $field_key ] ) ) {
158 - unset( $fields[ $field_key ] );
627 + if ( ! $value ) {
628 + $value = $default;
159 629 }
160 630
161 - update_post_meta( $post_id, 'wcf_fields_' . $type, $fields );
631 + return $value;
632 + }
162 633
163 - return true;
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 ] );
164 646 }
165 647
166 648 /**
167 - * Get checkout fields settings.
649 + * Check if Step has product assigned.
168 650 *
169 - * @return array.
651 + * @since 1.0.0
652 + * @param int $step_id step ID.
653 + *
654 + * @access public
170 655 */
171 - static public function get_checkout_fields_settings() {
656 + public static function has_product_assigned( $step_id ) {
172 657
173 - if ( null === self::$checkout_fields ) {
174 - $checkout_fields_default = array(
175 - 'enable_customization' => 'disable',
176 - 'enable_billing_fields' => 'disable',
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',
177 733 );
178 734
179 - $billing_fields = self::get_checkout_fields( 'billing' );
735 + $facebook = self::get_admin_settings_option( '_cartflows_facebook', false, false );
180 736
181 - if ( is_array( $billing_fields ) && ! empty( $billing_fields ) ) {
737 + $facebook = wp_parse_args( $facebook, $facebook_default );
182 738
183 - foreach ( $billing_fields as $key => $value ) {
739 + self::$facebook = apply_filters( 'cartflows_facebook_settings_default', $facebook );
184 740
185 - $checkout_fields_default[ $key ] = 'enable';
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 );
186 1050 }
187 1051 }
1052 + } else {
1053 + // All flows.
1054 + $flows = $this->get_stored_page_builder_templates( $page_builder_slug );
1055 + }
188 1056
189 - $checkout_fields = Cartflows_Helper::get_admin_settings_option( '_wcf_checkout_fields', false, true );
1057 + return $flows;
1058 + }
190 1059
191 - self::$checkout_fields = wp_parse_args( $checkout_fields, $checkout_fields_default );
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 ];
192 1081 }
193 1082
194 - return self::$checkout_fields;
1083 + return '';
195 1084 }
196 1085
197 1086 /**
198 - * Get meta options
1087 + * Create Edit page link for the widgets.
199 1088 *
200 - * @since 1.0.0
201 - * @param int $post_id Product ID.
202 - * @param string $key Meta Key.
203 - * @param string $default Default value.
204 - * @return string Meta Value.
1089 + * @since 1.6.15
1090 + * @param string $tab The Tab which has to display.
1091 + * @access public
205 1092 */
206 - static public function get_meta_option( $post_id, $key, $default = '' ) {
1093 + public static function get_current_page_edit_url( $tab ) {
207 1094
208 - $value = get_post_meta( $post_id, $key, true );
1095 + $url = add_query_arg(
1096 + array(
1097 + 'wcf-tab' => $tab,
1098 + ),
1099 + get_edit_post_link()
1100 + );
209 1101
210 - if ( ! $value ) {
211 - $value = $default;
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 );
212 1158 }
213 1159
214 - return $value;
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;
215 1176 }
216 1177
217 1178 /**
218 - * Save meta option
1179 + * Get flows from json files.
219 1180 *
220 - * @since 1.0.0
221 - * @param int $post_id Product ID.
222 - * @param array $args Arguments array.
1181 + * @param string $page_builder_slug Selected page builder slug.
223 1182 */
224 - static public function save_meta_option( $post_id, $args = array() ) {
1183 + public function get_stored_page_builder_templates( $page_builder_slug ) {
1184 + $flows = array();
225 1185
226 - if ( is_array( $args ) && ! empty( $args ) ) {
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 + }
227 1207
228 - foreach ( $args as $key => $value ) {
1208 + return $flows;
1209 + }
229 1210
230 - update_post_meta( $post_id, $key, $value );
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;
231 1281 }
232 1282 }
1283 +
1284 + return false;
233 1285 }
234 1286
235 1287 /**
236 - * Check if Elementor page builder is installed
1288 + * Maybe update flow step order.
237 1289 *
238 - * @since 1.0.0
1290 + * @param int $flow_id flow id.
1291 + * @param array $flow_steps step list.
239 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
240 1825 * @access public
241 1826 */
242 - static public function _is_elementor_installed() {
243 - $path = 'elementor/elementor.php';
244 - $plugins = get_plugins();
1827 + public static function get_rollback_versions() {
245 1828
246 - return isset( $plugins[ $path ] );
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;
247 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 ) {
248 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 +
249 1900 /**
250 - * Check if Step has product assigned.
1901 + * Get Rollback versions.
251 1902 *
252 - * @since 1.0.0
253 - * @param int $step_id step ID.
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.
254 1929 *
255 - * @access public
1930 + * @return array<string> Allowed file extensions.
256 1931 */
257 - static public function has_product_assigned( $step_id ) {
1932 + public static function get_allowed_file_extensions() {
1933 + return array( 'jpg', 'jpeg', 'png', 'gif', 'webp', 'pdf', 'mp3', 'm4a', 'wav', 'mp4', 'm4v', 'mov' );
1934 + }
258 1935
259 - $step_type = get_post_meta( $step_id, 'wcf-step-type', true );
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 + }
260 1948
261 - if ( 'checkout' == $step_type ) {
262 - $product = get_post_meta( $step_id, 'wcf-checkout-products', true );
263 - } else {
264 - $product = get_post_meta( $step_id, 'wcf-offer-product', true );
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 + }
265 1958 }
266 1959
267 - if ( ! empty( $product ) ) {
268 - return true;
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();
269 1992 }
270 - return false;
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 + }
271 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;
272 2015 }
273 2016 }