PluginProbe
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce / 3.2.0
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce v3.2.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
cartflows / admin-core / inc / admin-helper.php

admin-helper.php in CartFlows – Funnel Builder & Checkout Plugin for WooCommerce 3.2.0, at admin-core/inc/admin-helper.php

1,302 lines 37.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * CartFlows Admin Helper.
4 *
5 * @package CartFlows
6 */
7
8 namespace CartflowsAdmin\AdminCore\Inc;
9
10 use Automattic\WooCommerce\Utilities\OrderUtil;
11
12 // Exit if accessed directly.
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit;
15 }
16
17 /**
18 * Class AdminHelper.
19 */
20 class AdminHelper {
21
22 /**
23 * Meta_options.
24 *
25 * @var object instance
26 */
27 public static $meta_options = array();
28 /**
29 * Common.
30 *
31 * @var object instance
32 */
33 public static $common = null;
34
35 /**
36 * Permalink setting.
37 *
38 * @var object instance
39 */
40 public static $permalink_setting = null;
41
42 /**
43 * Facebook.
44 *
45 * @var object instance
46 */
47 public static $facebook = null;
48
49 /**
50 * TikTok.
51 *
52 * @var object instance
53 */
54 public static $tiktok = null;
55
56 /**
57 * Snapchat.
58 *
59 * @var object instance
60 */
61 public static $snapchat = null;
62
63 /**
64 * Google_analytics_settings.
65 *
66 * @var object instance
67 */
68 public static $google_analytics_settings = null;
69
70 /**
71 * Google_analytics_settings.
72 *
73 * @var object instance
74 */
75 public static $pinterest = null;
76
77 /**
78 * Google_ads_settings.
79 *
80 * @since 2.1.0
81 * @var object instance
82 */
83 public static $google_ads_settings_data = null;
84
85 /**
86 * Options.
87 *
88 * @var object instance
89 */
90 public static $options = null;
91
92 /**
93 * Get flow meta options.
94 *
95 * @param int $post_id post id.
96 * @return array.
97 */
98 public static function get_flow_meta_options( $post_id ) {
99
100 if ( ! isset( self::$meta_options[ $post_id ] ) ) {
101
102 /**
103 * Set metabox options
104 */
105
106 $default_meta = wcf()->options->get_flow_fields( $post_id );
107 $stored_meta = get_post_meta( $post_id );
108
109 /**
110 * Get options
111 */
112 self::$meta_options[ $post_id ] = self::get_prepared_meta_options( $default_meta, $stored_meta );
113 }
114
115 return self::$meta_options[ $post_id ];
116 }
117
118 /**
119 * Get step meta options.
120 *
121 * @param int $step_id step id.
122 * @return array.
123 */
124 public static function get_step_meta_options( $step_id ) {
125
126 if ( ! isset( self::$meta_options[ $step_id ] ) ) {
127
128 $step_type = wcf_get_step_type( $step_id );
129 $step_fields = array();
130 $step_tabs = array();
131
132 $default_meta = self::get_step_default_meta( $step_type, $step_id );
133
134 $stored_meta = get_post_meta( $step_id );
135
136 $prepared_options = self::get_prepared_meta_options( $default_meta, $stored_meta );
137
138 $prepared_options = apply_filters( 'cartflows_admin_' . $step_type . '_step_meta_fields', $prepared_options, $step_id );
139
140 $step_tabs = apply_filters( 'cartflows_admin_' . $step_type . '_step_tabs', $step_tabs );
141
142 /**
143 * Get options
144 */
145 self::$meta_options[ $step_id ]['type'] = $step_type;
146 self::$meta_options[ $step_id ]['tabs'] = $step_tabs;
147 self::$meta_options[ $step_id ]['options'] = $prepared_options;
148 }
149
150 return self::$meta_options[ $step_id ];
151 }
152
153 /**
154 * Merge default and saved meta options.
155 *
156 * @param array $default_meta Default meta.
157 * @param array $stored_meta Saved meta.
158 * @return array.
159 */
160 public static function get_prepared_meta_options( $default_meta, $stored_meta ) {
161
162 $meta_options = array();
163
164 // Set stored and override defaults.
165 foreach ( $default_meta as $key => $value ) {
166
167 $meta_options[ $key ] = ( isset( $stored_meta[ $key ][0] ) ) ? maybe_unserialize( $stored_meta[ $key ][0] ) : $default_meta[ $key ]['default'];
168 }
169
170 return $meta_options;
171 }
172
173 /**
174 * Get Common settings.
175 *
176 * @return array
177 */
178 public static function get_common_settings() {
179
180 $options = array();
181
182 $common_default = apply_filters(
183 'cartflows_common_settings_default',
184 array(
185 'global_checkout' => '',
186 'override_global_checkout' => 'enable',
187 'disallow_indexing' => 'disable',
188 'default_page_builder' => 'elementor',
189 )
190 );
191
192 $common = self::get_admin_settings_option( '_cartflows_common', false, false );
193
194 $common = wp_parse_args( $common, $common_default );
195
196 foreach ( $common as $key => $data ) {
197 $options[ '_cartflows_common[' . $key . ']' ] = $data;
198 }
199
200 return $options;
201 }
202
203 /**
204 * Get admin settings.
205 *
206 * Note: Use this function to access any properties to backend-end of the website i:e in admin-core.
207 *
208 * @param string $key key.
209 * @param bool $default key.
210 * @param bool $network_override key.
211 *
212 * @return array.
213 */
214 public static function get_admin_settings_option( $key, $default = false, $network_override = false ) {
215
216 // Get the site-wide option if we're in the network admin.
217 if ( $network_override && is_multisite() ) {
218 $value = get_site_option( $key, $default );
219 } else {
220 $value = get_option( $key, $default );
221 }
222
223 return $value;
224 }
225
226 /**
227 * Update admin settings.
228 *
229 * Note: Use this function to access any properties to backend-end of the website i:e in admin-core.
230 *
231 * @param string $key key.
232 * @param mixed $value key.
233 * @param bool $network network.
234 * @return void
235 */
236 public static function update_admin_settings_option( $key, $value, $network = false ) {
237
238 // Update the site-wide option since we're in the network admin.
239 if ( $network && is_multisite() ) {
240 update_site_option( $key, $value );
241 } else {
242 update_option( $key, $value );
243 }
244 }
245
246 /**
247 * Get Common settings.
248 *
249 * @return array
250 */
251 public static function get_permalink_settings() {
252
253 $options = array();
254
255 $permalink_default = apply_filters(
256 'cartflows_permalink_settings_default',
257 array(
258 'permalink' => CARTFLOWS_STEP_PERMALINK_SLUG,
259 'permalink_flow_base' => CARTFLOWS_FLOW_PERMALINK_SLUG,
260 'permalink_structure' => '',
261
262 )
263 );
264
265 $permalink_data = self::get_admin_settings_option( '_cartflows_permalink', false, true );
266
267 $permalink_data = wp_parse_args( $permalink_data, $permalink_default );
268
269 foreach ( $permalink_data as $key => $data ) {
270 $options[ '_cartflows_permalink[' . $key . ']' ] = $data;
271 }
272
273 return $options;
274 }
275
276 /**
277 * Get Common settings.
278 *
279 * @return array
280 */
281 public static function get_facebook_settings() {
282
283 $options = array();
284
285 $facebook_default = array(
286 'facebook_pixel_id' => '',
287 'facebook_pixel_add_to_cart' => 'enable',
288 'facebook_pixel_view_content' => 'enable',
289 'facebook_pixel_initiate_checkout' => 'enable',
290 'facebook_pixel_add_payment_info' => 'enable',
291 'facebook_pixel_purchase_complete' => 'enable',
292 'facebook_pixel_optin_lead' => 'enable',
293 'facebook_pixel_tracking' => 'disable',
294 'facebook_pixel_tracking_for_site' => 'disable',
295 );
296
297 $facebook = self::get_admin_settings_option( '_cartflows_facebook', false, true );
298
299 $facebook = wp_parse_args( $facebook, $facebook_default );
300
301 $facebook = apply_filters( 'cartflows_facebook_settings_default', $facebook );
302
303 foreach ( $facebook as $key => $data ) {
304 $options[ '_cartflows_facebook[' . $key . ']' ] = $data;
305 }
306
307 return $options;
308 }
309
310 /**
311 * Get Common settings.
312 *
313 * @return array
314 */
315 public static function get_google_analytics_settings() {
316
317 $options = array();
318
319 $google_analytics_settings_default = apply_filters(
320 'cartflows_google_analytics_settings_default',
321 array(
322 'enable_google_analytics' => 'disable',
323 'enable_google_analytics_for_site' => 'disable',
324 'google_analytics_id' => '',
325 'enable_begin_checkout' => 'disable',
326 'enable_add_to_cart' => 'disable',
327 'enable_add_payment_info' => 'disable',
328 'enable_purchase_event' => 'disable',
329 )
330 );
331
332 $google_analytics_settings_data = self::get_admin_settings_option( '_cartflows_google_analytics', false, false );
333
334 $google_analytics_settings_data = wp_parse_args( $google_analytics_settings_data, $google_analytics_settings_default );
335
336 foreach ( $google_analytics_settings_data as $key => $data ) {
337 $options[ '_cartflows_google_analytics[' . $key . ']' ] = $data;
338 }
339
340 return $options;
341 }
342
343 /**
344 * Get Common settings.
345 *
346 * @return array
347 */
348 public static function get_tiktok_settings() {
349
350 $options = array();
351
352 $tiktok_default = array(
353 'tiktok_pixel_id' => '',
354 'enable_tiktok_begin_checkout' => 'disable',
355 'enable_tiktok_add_to_cart' => 'disable',
356 'enable_tiktok_view_content' => 'disable',
357 'enable_tiktok_add_payment_info' => 'disable',
358 'enable_tiktok_purchase_event' => 'disable',
359 'enable_tiktok_optin_lead' => 'disable',
360 'tiktok_pixel_tracking' => 'disable',
361 'tiktok_pixel_tracking_for_site' => 'disable',
362 );
363
364 $tiktok = self::get_admin_settings_option( '_cartflows_tiktok', false, false );
365
366 $tiktok = wp_parse_args( $tiktok, $tiktok_default );
367
368 $tiktok = apply_filters( 'cartflows_tiktok_settings_default', $tiktok );
369
370 foreach ( $tiktok as $key => $data ) {
371 $options[ '_cartflows_tiktok[' . $key . ']' ] = $data;
372 }
373
374 return $options;
375 }
376
377 /**
378 * Get Common settings of pinterest.
379 *
380 * @return array
381 */
382 public static function get_pinterest_settings() {
383
384 $options = array();
385
386 $pinterest_default = array(
387 'pinterest_tag_id' => '',
388 'enable_pinterest_consent' => 'disable',
389 'enable_pinterest_begin_checkout' => 'disable',
390 'enable_pinterest_add_to_cart' => 'disable',
391 'enable_pinterest_add_payment_info' => 'disable',
392 'enable_pinterest_purchase_event' => 'disable',
393 'enable_pinterest_optin_lead' => 'disable',
394 'enable_pinterest_signup' => 'disable',
395 'pinterest_tag_tracking' => 'disable',
396 'pinterest_tag_tracking_for_site' => 'disable',
397 );
398
399 $pinterest = self::get_admin_settings_option( '_cartflows_pinterest', false, false );
400
401 $pinterest = wp_parse_args( $pinterest, $pinterest_default );
402
403 $pinterest = apply_filters( 'cartflows_pinterest_settings_default', $pinterest );
404
405 foreach ( $pinterest as $key => $data ) {
406 $options[ '_cartflows_pinterest[' . $key . ']' ] = $data;
407 }
408
409 return $options;
410 }
411
412 /**
413 * Get Common settings.
414 *
415 * @since 2.1.0
416 * @return array
417 */
418 public static function get_google_ads_settings() {
419
420 $options = array();
421
422 $google_ads_settings_default = apply_filters(
423 'cartflows_google_ads_settings_default',
424 array(
425 'google_ads_id' => '',
426 'google_ads_label' => '',
427 'enable_google_ads_begin_checkout' => 'disable',
428 'enable_google_ads_add_to_cart' => 'disable',
429 'enable_google_ads_view_content' => 'disable',
430 'enable_google_ads_add_payment_info' => 'disable',
431 'enable_google_ads_purchase_event' => 'disable',
432 'enable_google_ads_optin_lead' => 'disable',
433 'google_ads_tracking' => 'disable',
434 'google_ads_for_site' => 'disable',
435 )
436 );
437
438 $google_ads_settings_data = self::get_admin_settings_option( '_cartflows_google_ads', false, false );
439
440 $google_ads_settings_data = wp_parse_args( $google_ads_settings_data, $google_ads_settings_default );
441
442 foreach ( $google_ads_settings_data as $key => $data ) {
443 $options[ '_cartflows_google_ads[' . $key . ']' ] = $data;
444 }
445
446 return $options;
447 }
448
449 /**
450 * Get Snapchat settings.
451 *
452 * @since 2.1.0
453 * @return array
454 */
455 public static function get_snapchat_settings() {
456
457 $options = array();
458
459 $snapchat_settings_default = apply_filters(
460 'cartflows_snapchat_settings_default',
461 array(
462 'snapchat_pixel_id' => '',
463 'enable_snapchat_begin_checkout' => 'disable',
464 'enable_snapchat_add_to_cart' => 'disable',
465 'enable_snapchat_view_content' => 'disable',
466 'enable_snapchat_purchase_event' => 'disable',
467 'enable_snapchat_optin_lead' => 'disable',
468 'enable_snapchat_subscribe_event' => 'disable',
469 'snapchat_pixel_tracking' => 'disable',
470 'snapchat_pixel_for_site' => 'disable',
471 )
472 );
473
474 $snapchat_settings_data = self::get_admin_settings_option( '_cartflows_snapchat', false, false );
475
476 $snapchat_settings_data = wp_parse_args( $snapchat_settings_data, $snapchat_settings_default );
477
478 foreach ( $snapchat_settings_data as $key => $data ) {
479 $options[ '_cartflows_snapchat[' . $key . ']' ] = $data;
480 }
481
482 return $options;
483 }
484
485 /**
486 * Get User role settings.
487 *
488 * @return array
489 */
490 public static function get_user_role_management_settings() {
491 global $wp_roles;
492
493 $options = array();
494
495 // Get all user roles.
496 $roles_names_array = array_keys( $wp_roles->get_names() );
497 $roles_names_array = array_diff( $roles_names_array, array( 'administrator' ) );
498 $user_role_management_default = array();
499
500 foreach ( $roles_names_array as $role_name ) {
501 $user_role_management_default[ $role_name ] = 'no_access';
502 }
503
504 $user_role_management = self::get_admin_settings_option( '_cartflows_roles', false, false );
505
506 $user_role_management = wp_parse_args( $user_role_management, $user_role_management_default );
507
508 $user_role_management = apply_filters( 'cartflows_user_role_default_settings', $user_role_management );
509
510 foreach ( $user_role_management as $key => $data ) {
511 $options[ '_cartflows_roles[' . $key . ']' ] = $data;
512 }
513
514 return $options;
515 }
516
517 /**
518 * Get Google Auto-Address Fields settings.
519 *
520 * @return array
521 */
522 public static function get_google_auto_fields_settings() {
523 $options = array();
524
525 $google_auto_fields_setting_default = apply_filters(
526 'cartflows_google_auto_fields_setting_default',
527 array(
528 'google_map_api_key' => '',
529 )
530 );
531
532 $google_auto_fields_settings_data = self::get_admin_settings_option( '_cartflows_google_auto_address', false, false );
533
534 $google_auto_fields_settings_data = wp_parse_args( $google_auto_fields_settings_data, $google_auto_fields_setting_default );
535
536 foreach ( $google_auto_fields_settings_data as $key => $data ) {
537 $options[ '_cartflows_google_auto_address[' . $key . ']' ] = $data;
538 }
539
540 return $options;
541 }
542
543 /**
544 * Get Global CSS & Scripts settings.
545 *
546 * @return array.
547 */
548 public static function get_global_scripts_settings() {
549 $options = array();
550
551 $default = array(
552 'global_css' => '',
553 'global_js' => '',
554 );
555
556 $global_scripts = self::get_admin_settings_option( '_cartflows_global_scripts', false, false );
557
558 $global_scripts = wp_parse_args( $global_scripts, $default );
559
560 foreach ( $global_scripts as $key => $data ) {
561 $options[ '_cartflows_global_scripts[' . $key . ']' ] = $data;
562 }
563
564 return $options;
565 }
566
567 /**
568 * Clear Page Builder Cache
569 */
570 public static function clear_cache() {
571
572 // Clear 'Elementor' file cache.
573 if ( class_exists( '\Elementor\Plugin' ) ) {
574 \Elementor\Plugin::$instance->files_manager->clear_cache();
575 }
576 }
577
578 /**
579 * Get Flows count.
580 */
581 public static function get_flows_count() {
582
583 $flow_posts = get_posts(
584 array(
585 'posts_per_page' => 4,
586 'post_type' => CARTFLOWS_FLOW_POST_TYPE,
587 'post_status' => array( 'publish', 'pending', 'draft', 'future', 'private' ),
588 )
589 );
590
591 return count( $flow_posts );
592 }
593
594 /**
595 * Font family field.
596 *
597 * @return array field.
598 */
599 public static function get_font_family() {
600
601 $font_family[0] = array(
602 'value' => '',
603 'label' => __( 'Default', 'cartflows' ),
604 );
605
606 $system_font_family = array();
607 $google_font_family = array();
608
609 foreach ( \CartFlows_Font_Families::get_system_fonts() as $name => $variants ) {
610 array_push(
611 $system_font_family,
612 array(
613 'value' => $name,
614 'label' => esc_attr( $name ),
615 )
616 );
617 }
618
619 $font_family[1] = array(
620 'label' => __( 'System Fonts', 'cartflows' ),
621 'options' => $system_font_family,
622 );
623
624 foreach ( \CartFlows_Font_Families::get_google_fonts() as $name => $single_font ) {
625 $variants = wcf_get_prop( $single_font, 'variants' );
626 $category = wcf_get_prop( $single_font, 'category' );
627 $font_value = '\'' . esc_attr( $name ) . '\', ' . esc_attr( $category );
628 array_push(
629 $google_font_family,
630 array(
631 'value' => $font_value,
632 'label' => esc_attr( $name ),
633 )
634 );
635 }
636
637 $font_family[2] = array(
638 'label' => __( 'Google Fonts', 'cartflows' ),
639 'options' => $google_font_family,
640 );
641
642 return $font_family;
643 }
644
645 /**
646 * Get step default meta.
647 *
648 * @param string $step_type type.
649 * @param int $step_id id.
650 */
651 public static function get_step_default_meta( $step_type, $step_id ) {
652
653 $step_default_fields = array();
654
655 switch ( $step_type ) {
656 case 'landing':
657 $step_default_fields = wcf()->options->get_landing_fields( $step_id );
658 break;
659
660 case 'checkout':
661 $step_default_fields = wcf()->options->get_checkout_fields( $step_id );
662 break;
663
664 case 'thankyou':
665 $step_default_fields = wcf()->options->get_thankyou_fields( $step_id );
666 break;
667
668 case 'optin':
669 $step_default_fields = wcf()->options->get_optin_fields( $step_id );
670 break;
671
672 default:
673 break;
674 }
675 $step_default_fields = apply_filters( 'cartflows_admin_' . $step_type . '_step_default_meta_fields', $step_default_fields, $step_id );
676 return $step_default_fields;
677 }
678
679 /**
680 * Get options.
681 */
682 public static function get_options() {
683
684 $general_settings = self::get_common_settings();
685 $permalink_settings = self::get_permalink_settings();
686 $fb_settings = self::get_facebook_settings();
687 $tik_settings = self::get_tiktok_settings();
688 $ga_settings = self::get_google_analytics_settings();
689 $pin_settings = self::get_pinterest_settings();
690 $gads_settings = self::get_google_ads_settings();
691 $snap_settings = self::get_snapchat_settings();
692 $urm_settings = self::get_user_role_management_settings();
693 $auto_fields = self::get_google_auto_fields_settings();
694 $global_scripts = self::get_global_scripts_settings();
695 $options = array_merge( $general_settings, $permalink_settings, $fb_settings, $tik_settings, $ga_settings, $gads_settings, $pin_settings, $snap_settings, $urm_settings, $auto_fields, $global_scripts );
696 $options = apply_filters( 'cartflows_admin_global_data_options', $options );
697
698 return $options;
699 }
700
701
702
703 /**
704 * Prepare step data.
705 *
706 * @param int $flow_id Flow id.
707 * @param array $meta_options Meta data.
708 *
709 * @return array
710 */
711 public static function prepare_step_data( $flow_id, $meta_options ) {
712
713 $steps = $meta_options['wcf-steps'];
714
715 if ( is_array( $steps ) && ! empty( $steps ) ) {
716
717 foreach ( $steps as $in => $step ) {
718 $step_id = $step['id'];
719 $steps[ $in ]['title'] = get_the_title( $step_id );
720 $steps[ $in ]['is_product_assigned'] = \Cartflows_Helper::has_product_assigned( $step_id );
721 $steps[ $in ]['step_disabled'] = get_post_meta( $step_id, 'wcf-disable-step', true ) === 'yes';
722
723 $steps[ $in ]['actions'] = self::get_step_actions( $flow_id, $step_id );
724 $steps[ $in ]['menu_actions'] = self::get_step_actions( $flow_id, $step_id, 'menu' );
725
726 $steps[ $in ]['page_builder_edit'] = \Cartflows_Helper::get_page_builder_edit_link( $step_id );
727
728 /* Add variation data */
729 if ( ! empty( $steps[ $in ]['ab-test-variations'] ) ) {
730
731 $ab_test_variations = $steps[ $in ]['ab-test-variations'];
732
733 foreach ( $ab_test_variations as $variation_in => $variation ) {
734
735 $ab_test_variations[ $variation_in ]['title'] = get_the_title( $variation['id'] );
736 $ab_test_variations[ $variation_in ]['actions'] = self::get_ab_test_step_actions( $flow_id, $variation['id'] );
737 $ab_test_variations[ $variation_in ]['menu_actions'] = self::get_ab_test_step_actions( $flow_id, $variation['id'], 'menu' );
738 $ab_test_variations[ $variation_in ]['is_product_assigned'] = \Cartflows_Helper::has_product_assigned( $variation['id'] );
739 $ab_test_variations[ $variation_in ]['step_disabled'] = get_post_meta( $variation['id'], 'wcf-disable-step', true ) === 'yes';
740 }
741
742 $steps[ $in ]['ab-test-variations'] = $ab_test_variations;
743 }
744
745 if ( ! empty( $steps[ $in ]['ab-test-archived-variations'] ) ) {
746
747 $ab_test_archived_variations = $steps[ $in ]['ab-test-archived-variations'];
748
749 foreach ( $ab_test_archived_variations as $variation_in => $variation ) {
750
751 // Don't add hidden archived steps.
752 if ( get_post_meta( $variation['id'], 'wcf-hide-step', true ) ) {
753 unset( $ab_test_archived_variations[ $variation_in ] );
754 continue;
755 }
756
757 $ab_test_archived_variations[ $variation_in ]['actions'] = self::get_ab_test_step_archived_actions( $flow_id, $variation['id'], $variation['deleted'] );
758 $ab_test_archived_variations[ $variation_in ]['hide'] = get_post_meta( $variation['id'], 'wcf-hide-step', true );
759 }
760
761 $steps[ $in ]['ab-test-archived-variations'] = array_values( $ab_test_archived_variations );
762 }
763 }
764
765 $steps = apply_filters( 'cartflows_admin_flows_step_data', $steps );
766 }
767
768 return $steps;
769 }
770
771 /**
772 * Get step actions.
773 *
774 * @param int $flow_id Flow id.
775 * @param int $step_id Step id.
776 * @param string $type type.
777 *
778 * @return array
779 */
780 public static function get_step_actions( $flow_id, $step_id, $type = 'inline' ) {
781
782 if ( 'menu' === $type ) {
783 $actions = array(
784 'clone' => array(
785 'slug' => 'clone',
786 'class' => 'wcf-step-clone',
787 'icon_class' => 'dashicons dashicons-admin-page',
788 'text' => __( 'Duplicate', 'cartflows' ),
789 'pro' => true,
790 'link' => '#',
791 'ajaxcall' => 'cartflows_clone_step',
792 ),
793 'abtest' => array(
794 'slug' => 'abtest',
795 'class' => 'wcf-step-abtest',
796 'icon_class' => 'dashicons dashicons-forms',
797 'text' => __( 'A/B Test', 'cartflows' ),
798 'pro' => true,
799 'link' => '#',
800 ),
801 'delete' => array(
802 'slug' => 'delete',
803 'class' => 'wcf-step-delete',
804 'icon_class' => 'dashicons dashicons-trash',
805 'text' => __( 'Delete', 'cartflows' ),
806 'link' => '#',
807 'ajaxcall' => 'cartflows_delete_step',
808 ),
809 );
810
811 if ( current_user_can( 'cartflows_manage_settings' ) ) {
812 // Show Automation action only if suretriggers is connected.
813 $is_suretriggers_connected = _is_suretriggers_connected();
814 $automation_link = $is_suretriggers_connected ? '#' : admin_url( 'admin.php?page=' . CARTFLOWS_SLUG . '&path=automations' );
815
816 $actions = array_merge(
817 array(
818 'automation' => array(
819 'slug' => 'automation',
820 'class' => 'wcf-step-automation',
821 'icon_class' => 'dashicons dashicons-editor-code',
822 'text' => __( 'Automation', 'cartflows' ),
823 'pro' => false,
824 'link' => $automation_link,
825 'tag' => ! $is_suretriggers_connected ? __( '(Connect)', 'cartflows' ) : '',
826 'ajaxcall' => 'cartflows_automation_step',
827 ),
828 ),
829 $actions
830 );
831 }
832 } else {
833 $actions = array(
834 'view' => array(
835 'slug' => 'view',
836 'class' => 'wcf-step-view',
837 'icon_class' => 'dashicons dashicons-visibility',
838 'target' => 'blank',
839 'link' => get_permalink( $step_id ),
840 ),
841 'edit' => array(
842 'slug' => 'edit',
843 'class' => 'wcf-step-edit',
844 'icon_class' => 'dashicons dashicons-edit',
845 'link' => \Cartflows_Helper::get_page_builder_edit_link( $step_id ),
846 ),
847 );
848 }
849 return $actions;
850 }
851
852 /**
853 * Get step actions.
854 *
855 * @param int $flow_id Flow id.
856 * @param int $step_id Step id.
857 * @param string $type type.
858 *
859 * @return array
860 */
861 public static function get_ab_test_step_actions( $flow_id, $step_id, $type = 'inline' ) {
862
863 if ( 'menu' === $type ) {
864
865 $actions = array(
866 'clone' => array(
867 'slug' => 'clone',
868 'class' => 'wcf-ab-test-step-clone',
869 'icon_class' => 'dashicons dashicons-admin-page',
870 'text' => __( 'Duplicate', 'cartflows' ),
871 'link' => '#',
872 'pro' => true,
873 'ajaxcall' => 'cartflows_clone_ab_test_step',
874 ),
875 'delete' => array(
876 'slug' => 'delete',
877 'class' => 'wcf-ab-test-step-delete',
878 'icon_class' => 'dashicons dashicons-trash',
879 'text' => __( 'Delete', 'cartflows' ),
880 'link' => '#',
881 'ajaxcall' => 'cartflows_delete_ab_test_step',
882 ),
883 'archived' => array(
884 'slug' => 'archived',
885 'class' => 'wcf-ab-test-step-archived',
886 'icon_class' => 'dashicons dashicons-archive',
887 'text' => __( 'Archive', 'cartflows' ),
888 'link' => '#',
889 ),
890 'winner' => array(
891 'slug' => 'winner',
892 'class' => 'wcf-declare-winner',
893 'icon_class' => 'dashicons dashicons-yes-alt',
894 'text' => __( 'Declare as Winner', 'cartflows' ),
895 'link' => '#',
896 ),
897 );
898
899 } else {
900 $action_slug = apply_filters( 'cartflows_admin_action_slug', 'wcf-edit-step', $flow_id );
901 $actions = array(
902 'view' => array(
903 'slug' => 'view',
904 'class' => 'wcf-step-view',
905 'icon_class' => 'dashicons dashicons-visibility',
906 'target' => 'blank',
907 'link' => get_permalink( $step_id ),
908 ),
909 'edit' => array(
910 'slug' => 'edit',
911 'class' => 'wcf-step-edit',
912 'icon_class' => 'dashicons dashicons-edit',
913 'link' => \Cartflows_Helper::get_page_builder_edit_link( $step_id ),
914 ),
915 );
916 }
917
918 return $actions;
919 }
920
921 /**
922 * Get ab test step action.
923 *
924 * @param int $flow_id Flow id.
925 * @param int $step_id Step id.
926 * @param bool $deleted Step deleted or archived.
927 * @return array
928 */
929 public static function get_ab_test_step_archived_actions( $flow_id, $step_id, $deleted ) {
930
931 if ( $deleted ) {
932 $actions = array(
933 'archive-hide' => array(
934 'slug' => 'hide',
935 'class' => 'wcf-step-archive-hide',
936 'icon_class' => '',
937 'target' => 'blank',
938 'before_text' => __( 'Deleted variation can\'t be restored.', 'cartflows' ),
939 'text' => __( 'Hide', 'cartflows' ),
940 'link' => '#',
941 ),
942 'archive-delete' => array(
943 'slug' => 'deleteArch',
944 'class' => 'wcf-step-archive-delete',
945 'icon_class' => '',
946 'target' => 'blank',
947 'text' => __( 'Delete', 'cartflows' ),
948 'link' => '#',
949 ),
950 );
951 } else {
952
953 $actions = array(
954 'archive-restore' => array(
955 'slug' => 'restore',
956 'class' => 'wcf-step-archive-restore',
957 'icon_class' => '',
958 'target' => 'blank',
959 'text' => __( 'Restore', 'cartflows' ),
960 'link' => '#',
961 ),
962 'archive-delete' => array(
963 'slug' => 'delete',
964 'class' => 'wcf-step-archive-delete',
965 'icon_class' => '',
966 'target' => 'blank',
967 'text' => __( 'Delete', 'cartflows' ),
968 'link' => '#',
969 ),
970 );
971 }
972
973 return $actions;
974 }
975
976 /**
977 * Calculate earning.
978 *
979 * @param string $start_date start date.
980 * @param string $end_date end date.
981 * @param string $flow_id flow id.
982 * @param string $screen_type screen_type.
983 * @param string $comparison_range_type comparison range type - 'previous-period' | 'previous-year'.
984 *
985 * @return array
986 */
987 public static function get_earnings( $start_date, $end_date, $flow_id = '', $screen_type = 'funnels', $comparison_range_type = '' ) {
988
989 $currency_symbol = function_exists( 'get_woocommerce_currency_symbol' ) ? get_woocommerce_currency_symbol() : '';
990
991 $start_date = $start_date ? $start_date : gmdate( 'Y-m-d' );
992 $end_date = $end_date ? $end_date : gmdate( 'Y-m-d' );
993
994 $start_date = gmdate( 'Y-m-d H:i:s', strtotime( $start_date . '00:00:00' ) );
995 $end_date = gmdate( 'Y-m-d H:i:s', strtotime( $end_date . '23:59:59' ) );
996
997
998 if ( _is_cartflows_pro() && is_wcf_pro_plan() ) {
999 // Return All Stats.
1000 return apply_filters(
1001 'cartflows_home_page_analytics',
1002 array(
1003 'order_currency' => $currency_symbol,
1004 'total_orders' => '0',
1005 'total_revenue' => '0',
1006 'total_bump_revenue' => '0',
1007 'total_offers_revenue' => '0',
1008 'total_visits' => '0',
1009 ),
1010 $start_date,
1011 $end_date,
1012 $flow_id,
1013 $screen_type,
1014 $comparison_range_type
1015 );
1016 }
1017
1018 $orders = self::get_orders_by_flow( $start_date, $end_date, $flow_id );
1019 $gross_sale = 0;
1020 $order_count = 0;
1021
1022 if ( ! empty( $orders ) ) {
1023
1024 foreach ( $orders as $order ) {
1025
1026 $order_id = $order->ID;
1027 $order = wc_get_order( $order_id );
1028 $order_total = $order ? $order->get_total() : 0;
1029
1030 $order_count++;
1031
1032 if ( $order && ! $order->has_status( 'cancelled' ) ) {
1033 $gross_sale += (float) $order_total;
1034 }
1035 }
1036 }
1037
1038 // Return All Stats.
1039 return array(
1040 'order_currency' => $currency_symbol,
1041 'total_orders' => $order_count,
1042 'total_revenue_raw' => $gross_sale,
1043 'total_revenue' => str_replace( '&nbsp;', '', wc_price( (float) $gross_sale ) ),
1044 'total_bump_revenue' => '0',
1045 'total_offers_revenue' => '0',
1046 'total_visits' => '0',
1047 );
1048 }
1049
1050
1051
1052 /**
1053 * Get orders data for flow.
1054 *
1055 * @since 1.6.15
1056 *
1057 * @param string $start_date start date.
1058 * @param string $end_date end date.
1059 * @param string $flow_id flow id.
1060 * @return wc_order object.
1061 */
1062 public static function get_orders_by_flow( $start_date, $end_date, $flow_id = '' ) {
1063
1064 global $wpdb;
1065
1066 if ( class_exists( '\Automattic\WooCommerce\Utilities\OrderUtil' ) && OrderUtil::custom_orders_table_usage_is_enabled() ) {
1067 // HPOS usage is enabled.
1068 $conditions = array(
1069 'tb1.type' => 'shop_order',
1070 );
1071
1072 $order_date_key = 'date_created_gmt';
1073 $order_status_key = 'status';
1074 $order_id_key = 'order_id';
1075 $order_table = $wpdb->prefix . 'wc_orders';
1076 $order_meta_table = $wpdb->prefix . 'wc_orders_meta';
1077
1078 } else {
1079 // Traditional CPT-based orders are in use.
1080
1081 $conditions = array(
1082 'tb1.post_type' => 'shop_order',
1083 );
1084
1085 $order_date_key = 'post_date';
1086 $order_status_key = 'post_status';
1087 $order_id_key = 'post_id';
1088 $order_table = $wpdb->prefix . 'posts';
1089 $order_meta_table = $wpdb->prefix . 'postmeta';
1090 }
1091
1092 $where = self::get_items_query_where( $conditions );
1093
1094 if ( ! empty( $flow_id ) ) {
1095 $where .= $wpdb->prepare( ' AND tb2.meta_value = %s', $flow_id );
1096 }
1097
1098 // Security: Use $wpdb->prepare() for date parameters to prevent SQL injection.
1099 $where .= $wpdb->prepare(
1100 ' AND ( tb1.' . $order_date_key . " BETWEEN IF (tb2.meta_key='wcf-analytics-reset-date'>%s, tb2.meta_key, %s) AND %s )", // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
1101 $start_date,
1102 $start_date,
1103 $end_date
1104 );
1105 $where .= " AND ( ( tb2.meta_key = '_wcf_flow_id' ) OR ( tb2.meta_key = '_cartflows_parent_flow_id' ) )";
1106 $where .= ' AND tb1.' . $order_status_key . " IN ( 'wc-completed', 'wc-processing' )";
1107
1108 $query = 'SELECT tb1.ID, DATE( tb1.' . $order_date_key . ' ) date, tb2.meta_value FROM ' . $order_table . ' tb1
1109 INNER JOIN ' . $order_meta_table . ' tb2
1110 ON tb1.ID = tb2.' . $order_id_key . ' '
1111 . $where;
1112
1113 return $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery
1114 }
1115
1116 /**
1117 * Prepare where items for query.
1118 *
1119 * @param array $conditions conditions to prepare WHERE query.
1120 * @return string
1121 */
1122 public static function get_items_query_where( $conditions ) {
1123
1124 global $wpdb;
1125
1126 $where_conditions = array();
1127
1128 foreach ( $conditions as $key => $condition ) {
1129
1130 if ( false !== stripos( $key, 'IN' ) ) {
1131 $where_conditions[] = $key . $wpdb->prepare( '( %s )', $condition );
1132 } else {
1133 $where_conditions[] = $key . $wpdb->prepare( '= %s', $condition );
1134 }
1135 }
1136
1137 if ( ! empty( $where_conditions ) ) {
1138 return 'WHERE 1 = 1 AND ' . implode( ' AND ', $where_conditions );
1139 } else {
1140 return '';
1141 }
1142 }
1143
1144 /**
1145 * Check is error in the received response.
1146 *
1147 * @param object $response Received API Response.
1148 * @return array $result Error result.
1149 * @since 1.11.8
1150 */
1151 public static function has_api_error( $response ) {
1152
1153 $result = array(
1154 'error' => false,
1155 'error_message' => __( 'Ooops! Something went wrong. Please open a support ticket from the website.', 'cartflows' ),
1156 'call_to_action' => __( 'No error found.', 'cartflows' ),
1157 'error_code' => 0,
1158 );
1159
1160 if ( is_wp_error( $response ) ) {
1161
1162 $msg = $response->get_error_message();
1163 $error_code = $response->get_error_code();
1164
1165 if ( 'http_request_failed' === $error_code ) {
1166 /* translators: %1$s: HTML, %2$s: HTML */
1167 $msg = $msg . '<br>' . sprintf( __( 'Request timeout error. Please check if the firewall or any security plugin is blocking the outgoing HTTP/HTTPS requests to templates.cartflows.com or not. %1$1sTo resolve this issue, please check this %2$2sarticle%3$3s.', 'cartflows' ), '<br><br>', '<a target="_blank" href="' . esc_url( \Cartflows_Helper::get_kb_doc_link( 'https://cartflows.com/docs/request-timeout-error-while-importing-the-flow-step-templates/' ) ) . '">', '</a>' );
1168 }
1169
1170 $result['error'] = true;
1171 $result['call_to_action'] = $msg;
1172 $result['error_code'] = $error_code;
1173
1174 } elseif ( ! empty( wp_remote_retrieve_response_code( $response ) ) && ! in_array( wp_remote_retrieve_response_code( $response ), array( 200, 201, 204 ), true ) ) {
1175
1176 $error_message = ! empty( wp_remote_retrieve_response_message( $response ) ) ? wp_remote_retrieve_response_message( $response ) : '';
1177 $error_body = ! empty( wp_remote_retrieve_body( $response ) ) ? wp_kses( wp_remote_retrieve_body( $response ), '<p>' ) : '';
1178
1179 if ( false !== strpos( $error_body, 'MalCare' ) ) {
1180 /* translators: %1$s: HTML, %2$s: HTML, %3$s: HTML */
1181 $error_message = $error_message . '<br>' . sprintf( __( 'Sorry for the inconvenience, but your website seems to be having trouble connecting to our server. %1$s Please open a technical %2$ssupport ticket%3$s and share the server\'s outgoing IP address.', 'cartflows' ), '<br><br>', '<a href="' . esc_url( \Cartflows_Helper::get_utm_url( 'https://cartflows.com/support', array( 'utm_campaign' => 'support' ) ) ) . '" target="_blank">', '</a>' );
1182 $ip_address = self::get_valid_ip_address();
1183 $result['message'] = ! empty( $ip_address ) ? __( 'Server\'s outgoing IP address: ', 'cartflows' ) . $ip_address : '';
1184 }
1185
1186 $result['error'] = true;
1187 $result['call_to_action'] = $error_message;
1188 $result['error_code'] = wp_remote_retrieve_response_code( $response );
1189 } else {
1190 $result['response_code'] = wp_remote_retrieve_response_code( $response );
1191 }
1192
1193 return $result;
1194 }
1195
1196 /**
1197 * Convert the array format in the WooCommerce's expected format for display.
1198 * Received array format is array( 0=>key, 1=>Key_value ) expected is array( key=>key_value )
1199 * This is because of the core rules structure which we have created.
1200 *
1201 * @param array $options Array of key & value in the array format.
1202 * @return array $options
1203 */
1204 public static function sanitize_array_values( $options ) {
1205
1206 if ( is_array( $options ) ) {
1207
1208 $sanitized_options = array();
1209
1210 foreach ( $options as $option_key => $option_value ) {
1211 $sanitized_options[ trim( $option_value ) ] = trim( $option_value );
1212 }
1213
1214 $options = $sanitized_options;
1215 }
1216
1217 return $options;
1218 }
1219
1220 /**
1221 * Get the IP address of the user's server.
1222 * To check weather the IP is not blocked by any firewall.
1223 *
1224 * Note: This IP address is not stored any where in the database.
1225 *
1226 * @return string A valid IP address.
1227 */
1228 public static function get_valid_ip_address() {
1229
1230 if ( isset( $_SERVER['HTTP_X_REAL_IP'] ) ) { //phpcs:ignore WordPressVIPMinimum.Variables.ServerVariables.UserControlledHeaders
1231 return self::validate_ip_address( sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_REAL_IP'] ) ) );
1232 } elseif ( isset( $_SERVER['REMOTE_ADDR'] ) ) { //phpcs:ignore WordPressVIPMinimum.Variables.ServerVariables.UserControlledHeaders
1233 return self::validate_ip_address( sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) ); //phpcs:ignore WordPressVIPMinimum.Variables.ServerVariables.UserControlledHeaders, WordPressVIPMinimum.Variables.RestrictedVariables.cache_constraints___SERVER__REMOTE_ADDR__
1234 } elseif ( isset( $_SERVER['SERVER_ADDR'] ) ) { //phpcs:ignore WordPressVIPMinimum.Variables.ServerVariables.UserControlledHeaders
1235 return self::validate_ip_address( sanitize_text_field( wp_unslash( $_SERVER['SERVER_ADDR'] ) ) );
1236 } else {
1237 return '';
1238 }
1239 }
1240
1241 /**
1242 * Validate the provided IP address safe to printing.
1243 *
1244 * @param string $ip_address IP address to validate.
1245 * @return string $ip_address Validated IP address for display/use.
1246 */
1247 public static function validate_ip_address( $ip_address ) {
1248
1249 $ip_address = filter_var( $ip_address, FILTER_VALIDATE_IP, array() );
1250
1251 return $ip_address ? $ip_address : '';
1252 }
1253
1254 /**
1255 * Track funnel creation method for analytics.
1256 *
1257 * @param string $creation_method The method used to create the funnel (e.g., 'scratch', 'ready_made_template').
1258 * @return void
1259 */
1260 public static function track_funnel_creation_method( $creation_method ) {
1261 $funnel_creation_stats = get_option(
1262 'cartflows_funnel_creation_method',
1263 array(
1264 'scratch' => 0,
1265 'ready_made_template' => 0,
1266 )
1267 );
1268
1269 if ( isset( $funnel_creation_stats[ $creation_method ] ) ) {
1270 $funnel_creation_stats[ $creation_method ]++;
1271 }
1272
1273 update_option( 'cartflows_funnel_creation_method', $funnel_creation_stats );
1274 }
1275
1276 /**
1277 * Determine whether to show the CodeMirror code editor fields.
1278 *
1279 * Returns true when the custom script migration status is 'completed',
1280 * meaning users should see the new separate JS and CSS code editors
1281 * instead of the old combined textarea.
1282 *
1283 * @since 2.2.2
1284 * @return bool
1285 */
1286 public static function should_show_code_editor() {
1287 return 'completed' === \CartFlows_Helper::get_script_migration_status();
1288 }
1289
1290 /**
1291 * Get the field type for custom script fields based on migration status.
1292 *
1293 * Returns 'code' after migration is completed, 'textarea' otherwise.
1294 *
1295 * @since 2.2.2
1296 * @return string 'code' or 'textarea'
1297 */
1298 public static function get_custom_script_field_type() {
1299 return self::should_show_code_editor() ? 'code' : 'textarea';
1300 }
1301 }
1302