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
cartflows / admin-core / inc / admin-menu.php

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

1,224 lines 44.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 Menu.
4 *
5 * @package CartFlows
6 */
7
8 namespace CartflowsAdmin\AdminCore\Inc;
9
10 use CartflowsAdmin\AdminCore\Inc\AdminHelper;
11 use CartflowsAdmin\AdminCore\Inc\LogStatus;
12
13
14 // Exit if accessed directly.
15 if ( ! defined( 'ABSPATH' ) ) {
16 exit;
17 }
18
19 /**
20 * Class Admin_Menu.
21 */
22 class AdminMenu {
23
24 /**
25 * Instance
26 *
27 * @access private
28 * @var object Class object.
29 * @since 1.0.0
30 */
31 private static $instance;
32
33 /**
34 * For Gutenberg
35 *
36 * @var $is_gutenberg_editor_active
37 */
38 private $is_gutenberg_editor_active = false;
39
40 /**
41 * Initiator
42 *
43 * @since 1.0.0
44 * @return object initialized object of class.
45 */
46 public static function get_instance() {
47 if ( ! isset( self::$instance ) ) {
48 self::$instance = new self();
49 }
50 return self::$instance;
51 }
52
53 /**
54 * Instance
55 *
56 * @access private
57 * @var string Class object.
58 * @since 1.0.0
59 */
60 private $menu_slug;
61
62 /**
63 * Constructor
64 *
65 * @since 1.0.0
66 */
67 public function __construct() {
68 $this->menu_slug = 'cartflows';
69
70 $this->initialize_hooks();
71 }
72
73 /**
74 * Init Hooks.
75 *
76 * @since 1.0.0
77 * @return void
78 */
79 public function initialize_hooks() {
80
81 add_action( 'admin_menu', array( $this, 'setup_menu' ) );
82 add_action( 'admin_init', array( $this, 'settings_admin_scripts' ) );
83 add_action( 'admin_init', array( $this, 'add_capabilities_to_admin' ) );
84
85 /* Flow content view */
86 add_action( 'cartflows_render_admin_page_content', array( $this, 'render_content' ), 10, 2 );
87
88 add_action( 'edit_form_after_title', array( $this, 'new_admin_flow_setting_redirection' ), 10, 1 );
89
90 /* To check the status of gutenberg */
91 add_action( 'enqueue_block_editor_assets', array( $this, 'set_block_editor_status' ) );
92 add_action( 'admin_footer', array( $this, 'back_to_new_step_ui_for_gutenberg' ), 999 );
93
94 add_action( 'admin_notices', array( $this, 'back_to_new_step_ui_for_classic_editor' ) );
95
96 add_action( 'wp_ajax_cartflows_fetch_whats_new_data', array( $this, 'fetch_whats_new_data' ) );
97 }
98
99 /**
100 * Display admin notices.
101 *
102 * @since 1.0.0
103 *
104 * @return void
105 */
106 public function back_to_new_step_ui_for_classic_editor() {
107
108 if ( CARTFLOWS_STEP_POST_TYPE !== get_post_type() ) {
109 return;
110 }
111
112 $flow_id = get_post_meta( get_the_id(), 'wcf-flow-id', true );
113 $step_id = isset( $_GET['post'] ) ? intval( $_GET['post'] ) : 0; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
114
115 if ( $flow_id && $step_id ) {
116 $path = \Cartflows_Helper::get_global_setting( '_cartflows_store_checkout' ) === $flow_id ? 'store-checkout' : 'flows';
117
118 $flow_redirect_url = esc_url( admin_url() . 'admin.php?page=' . $this->menu_slug . '&path= ' . $path . '&action=wcf-edit-flow&flow_id=' . $flow_id ); // phpcs:igmore Generic.Strings.UnnecessaryStringConcat.Found.
119 ?>
120 <div class="wcf-notice-back-edit-step">
121 <p>
122 <a href="<?php echo esc_url( $flow_redirect_url ); ?>" class="button button-primary button-hero wcf-header-back-button" style="text-decoration: none;">
123 <?php esc_html_e( 'Edit Funnel', 'cartflows' ); ?>
124 </a>
125 </p>
126 </div>
127 <?php
128 }
129 }
130
131 /**
132 * Set status true for gutenberg.
133 *
134 * @return void
135 */
136 public function set_block_editor_status() {
137
138 if ( ! current_user_can( 'cartflows_manage_flows_steps' ) ) {
139 return;
140 }
141
142 // Set gutenberg status here.
143 $this->is_gutenberg_editor_active = true;
144 }
145
146 /**
147 * Back to flow button gutenberg template
148 *
149 * @return void
150 */
151 public function back_to_new_step_ui_for_gutenberg() {
152
153 // Exit if block editor is not enabled.
154 if ( ! $this->is_gutenberg_editor_active ) {
155 return;
156 }
157
158 wp_enqueue_script( 'cartflows-admin-common-script', CARTFLOWS_ADMIN_CORE_URL . 'assets/js/common.js', array( 'jquery' ), CARTFLOWS_VER, false );
159
160 if ( CARTFLOWS_STEP_POST_TYPE !== get_post_type() ) {
161 return;
162 }
163
164 $flow_id = get_post_meta( get_the_id(), 'wcf-flow-id', true );
165 $step_id = isset( $_GET['post'] ) ? intval( $_GET['post'] ) : 0; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
166
167 if ( $flow_id && $step_id ) {
168 $path = \Cartflows_Helper::get_global_setting( '_cartflows_store_checkout' ) === $flow_id ? 'store-checkout' : 'flows';
169 $flow_redirect_url = esc_url( admin_url() . 'admin.php?page=' . $this->menu_slug . '&path=' . $path . '&action=wcf-edit-flow&flow_id=' . $flow_id );
170 ?>
171 <script id="wcf-gutenberg-back-step-button" type="text/html">
172 <div class="wcf-notice-back-edit-step gutenberg-button" style="display: flex; margin: 2px 5px;">
173 <a href="<?php echo esc_url( $flow_redirect_url ); ?>" class="button button-primary button-large wcf-header-back-button" style="text-decoration: none; font-size: 13px; line-height: 2.5;"><?php esc_html_e( 'Edit Funnel', 'cartflows' ); ?></a>
174 </div>
175 </script>
176 <script>
177 window.addEventListener('load', function () {
178 ( function( window, wp ){
179 var link = document.querySelector('a.components-button.edit-post-fullscreen-mode-close');
180 if (link) {
181 link.setAttribute('href', "<?php echo htmlspecialchars_decode( esc_url( $flow_redirect_url ) );//phpcs:ignore ?>")
182 }
183
184 } )( window, wp )
185 });
186 </script>
187 <?php
188 }
189 }
190
191
192 /**
193 * Back to flow button
194 *
195 * @param array $meta meta data.
196 *
197 * @return void
198 */
199 public function new_admin_flow_setting_redirection( $meta ) {
200
201 $flow_id = isset( $_GET['post'] ) ? intval( $_GET['post'] ) : 0; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
202
203 $post_type = get_post_type();
204
205 if ( CARTFLOWS_FLOW_POST_TYPE === $post_type && 0 !== $flow_id ) {
206
207 $redirect_url = esc_url( admin_url() . 'admin.php?page=' . $this->menu_slug . '&path=flows&action=wcf-edit-flow&flow_id=' . $flow_id );
208 $btn_markup = '<div class="wcf-flow-editing-action" style="padding: 50px;text-align: center;">';
209 $btn_markup .= '<a class="button button-primary button-hero" href="' . $redirect_url . '">' . __( 'Go to Funnel Editing', 'cartflows' ) . '</a>';
210 $btn_markup .= '</div>';
211
212 echo wp_kses_post( $btn_markup );
213 }
214 }
215
216 /**
217 * Initialize after Cartflows pro get loaded.
218 */
219 public function settings_admin_scripts() {
220
221 // Enqueue admin scripts.
222 // Ignoring nonce verification as using SuperGlobal variables on WordPress hooks.
223 if ( isset( $_GET['page'] ) && ( 'cartflows' === sanitize_text_field( $_GET['page'] ) || false !== strpos( sanitize_text_field( $_GET['page'] ), 'cartflows_' ) ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
224
225 add_action( 'admin_enqueue_scripts', array( $this, 'styles_scripts' ) );
226
227 // Twemoji rewrites emoji text nodes inside our React tree, detaching nodes React still tracks and crashing the editor.
228 remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
229 remove_action( 'admin_print_styles', 'print_emoji_styles' );
230 }
231 }
232
233 /**
234 * Add submenu to admin menu.
235 *
236 * @since 1.0.0
237 */
238 public function setup_menu() {
239 global $submenu;
240
241 if ( current_user_can( 'cartflows_manage_flows_steps' ) ) {
242 $parent_slug = $this->menu_slug;
243 $capability = 'cartflows_manage_flows_steps';
244 $menu_priority = apply_filters( 'cartflows_menu_priority', ! defined( 'ASTRA_THEME_VERSION' ) ? 40 : 3 );
245
246 add_menu_page(
247 'CartFlows',
248 'CartFlows',
249 $capability,
250 $parent_slug,
251 array( $this, 'render' ),
252 'data:image/svg+xml;base64,' . base64_encode( file_get_contents( CARTFLOWS_DIR . 'assets/images/cartflows-icon.svg' ) ),
253 $menu_priority
254 );
255
256 // Add settings menu.
257 add_submenu_page(
258 $parent_slug,
259 __( 'Funnels', 'cartflows' ),
260 __( 'Funnels', 'cartflows' ),
261 $capability,
262 'admin.php?page=' . $this->menu_slug . '&path=flows'
263 );
264
265 $global_checkout_id = absint( \Cartflows_Helper::get_global_setting( '_cartflows_store_checkout' ) );
266 $global_checkout_param = ( empty( $global_checkout_id ) ) ? 'path=store-checkout' : 'path=store-checkout&action=wcf-edit-flow&flow_id=' . $global_checkout_id;
267
268 add_submenu_page(
269 $parent_slug,
270 __( 'Store Checkout', 'cartflows' ),
271 __( 'Store Checkout', 'cartflows' ),
272 $capability,
273 'admin.php?page=' . $this->menu_slug . '&' . $global_checkout_param
274 );
275
276 add_submenu_page(
277 $parent_slug,
278 __( 'Analytics', 'cartflows' ),
279 __( 'Analytics', 'cartflows' ),
280 $capability,
281 'admin.php?page=' . $this->menu_slug . '&path=analytics'
282 );
283
284 // Persistent re-entry point until the guided setup is finished.
285 if ( current_user_can( 'manage_options' ) && '1' !== get_option( 'wcf_setup_complete', false ) ) {
286 $exit_step = get_option( 'wcf_exit_setup_step', '' );
287 $resume_link = 'index.php?page=cartflows-onboarding' . ( ! empty( $exit_step ) && is_string( $exit_step ) ? '&step=' . sanitize_key( $exit_step ) : '' );
288
289 add_submenu_page(
290 $parent_slug,
291 __( 'Resume Setup', 'cartflows' ),
292 __( 'Resume Setup', 'cartflows' ),
293 $capability,
294 $resume_link
295 );
296 }
297
298 if ( current_user_can( 'cartflows_manage_settings' ) ) {
299
300 add_submenu_page(
301 $parent_slug,
302 __( 'Learn', 'cartflows' ),
303 __( 'Learn', 'cartflows' ),
304 $capability,
305 'admin.php?page=' . $this->menu_slug . '&path=learn'
306 );
307
308 if ( ! _is_cartflows_pro() ) {
309 add_submenu_page(
310 $parent_slug,
311 __( 'Get CartFlows Pro', 'cartflows' ),
312 __( 'Get CartFlows Pro', 'cartflows' ),
313 $capability,
314 \Cartflows_Helper::get_upgrade_to_pro_link()
315 );
316 }
317 }
318
319 // Rename to Home menu.
320 // Disable phpcs since we need to override submenu name.
321 $submenu[ $parent_slug ][0][0] = __( 'Dashboard', 'cartflows' ); //phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
322 }
323 }
324
325 /**
326 * Add custom capabilities to Admin user.
327 */
328 public function add_capabilities_to_admin() {
329
330 global $wp_roles;
331 // Add custom capabilities to admin by default.
332 $capabilities = array(
333 'cartflows_manage_settings',
334 'cartflows_manage_flows_steps',
335 );
336
337 foreach ( $capabilities as $cap ) {
338 $wp_roles->add_cap( 'administrator', $cap );
339 }
340 }
341
342 /**
343 * Renders the admin settings.
344 *
345 * @since 1.0.0
346 * @return void
347 */
348 public function render() {
349 $menu_page_slug = ( isset( $_GET['page'] ) ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : CARTFLOWS_SETTINGS; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
350 $page_action = '';
351
352 if ( isset( $_GET['action'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
353 $page_action = sanitize_text_field( wp_unslash( $_GET['action'] ) ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended
354 $page_action = str_replace( '_', '-', $page_action );
355 }
356
357 include_once CARTFLOWS_ADMIN_CORE_DIR . 'views/admin-base.php';
358 }
359
360 /**
361 * Renders the admin settings content.
362 *
363 * @since 1.0.0
364 * @param sting $menu_page_slug current page name.
365 * @param sting $page_action current page action.
366 *
367 * @return void
368 */
369 public function render_content( $menu_page_slug, $page_action ) {
370
371 if ( 'cartflows' === $menu_page_slug ) {
372 if ( $this->is_current_page( 'cartflows' ) ) {
373 include_once CARTFLOWS_ADMIN_CORE_DIR . 'views/settings-app.php';
374 } elseif ( $this->is_current_page( 'cartflows', array( 'wcf-edit-flow' ) ) ) {
375 include_once CARTFLOWS_ADMIN_CORE_DIR . 'views/editor-app.php';
376 } elseif ( $this->is_current_page( 'cartflows', array( 'wcf-license' ) ) && _is_cartflows_pro() ) {
377 do_action( 'cartflows_admin_log', 'wcf-license' );
378 } else {
379 include_once CARTFLOWS_ADMIN_CORE_DIR . 'views/404-error.php';
380 }
381 }
382 }
383
384 /**
385 * Enqueues the needed CSS/JS for the builder's admin settings page.
386 *
387 * @since 1.0.0
388 */
389 public function styles_scripts() {
390
391 $admin_slug = 'cartflows-admin';
392
393 // Load the custom font for admin area.
394 wp_enqueue_style( $admin_slug . '-font', 'https://fonts.googleapis.com/css2?family=Figtree:wght@400;500;600;700&display=swap', array(), CARTFLOWS_VER );
395
396 // Styles.
397 wp_enqueue_style( $admin_slug . '-common', CARTFLOWS_ADMIN_CORE_URL . 'assets/css/common.css', array(), CARTFLOWS_VER );
398 wp_style_add_data( $admin_slug . '-common', 'rtl', 'replace' );
399
400 wp_enqueue_script( $admin_slug . '-common-script', CARTFLOWS_ADMIN_CORE_URL . 'assets/js/common.js', array( 'jquery' ), CARTFLOWS_VER, false );
401
402 wp_enqueue_script( $admin_slug . '-suretriggers-integration', CARTFLOWS_SURETRIGGERS_INTEGRATION_BASE_URL . 'js/v2/embed.js', array(), CARTFLOWS_VER, true );
403
404 $current_flow_steps = array();
405 $flow_id = isset( $_GET['flow_id'] ) ? intval( $_GET['flow_id'] ) : 0; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
406 if ( $flow_id ) {
407 $current_flow_steps = AdminHelper::get_flow_meta_options( $flow_id );
408 }
409
410 $product_src = esc_url_raw(
411 add_query_arg(
412 array(
413 'post_type' => 'product',
414 'wcf-woo-iframe' => 'true',
415 ),
416 admin_url( 'post-new.php' )
417 )
418 );
419
420
421 $page_builder = \Cartflows_Helper::get_common_setting( 'default_page_builder' );
422 $page_builder_name = \Cartflows_Helper::get_page_builder_name( $page_builder );
423
424 $global_checkout_id = \Cartflows_Helper::get_global_setting( '_cartflows_store_checkout' );
425
426 $flow_action = 'wcf-edit-flow';
427 $step_action = 'wcf-edit-step';
428
429 $flows_and_steps = \Cartflows_Helper::get_instance()->get_flows_and_steps();
430
431 $cf_pro_status = $this->get_cartflows_pro_plugin_status();
432 $cf_pro_type_inactive = '';
433 if ( 'inactive' === $cf_pro_status || 'not-installed' === $cf_pro_status ) {
434
435 if ( ! function_exists( 'get_plugins' ) ) {
436 require_once ABSPATH . 'wp-admin/includes/plugin.php';
437 }
438
439 $plugin = get_plugins();
440 $cf_pro_type_inactive = isset( $plugin['cartflows-pro/cartflows-pro.php']['Name'] ) ? $plugin['cartflows-pro/cartflows-pro.php']['Name'] : '';
441 }
442
443 $order_url = '#';
444
445 if ( wcf()->is_woo_active && class_exists( '\Automattic\WooCommerce\Utilities\OrderUtil' ) ) {
446 $order_url = \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled() ? admin_url( 'admin.php?page=wc-orders' ) : admin_url( 'edit.php?post_type=shop_order' );
447 }
448
449 $localize = apply_filters(
450 'cartflows_admin_localized_vars',
451 array(
452 'current_user' => ! empty( wp_get_current_user()->user_firstname ) ? wp_get_current_user()->user_firstname : wp_get_current_user()->display_name,
453 'cf_pro_status' => $cf_pro_status,
454 'cf_pro_type' => 'free',
455 'cf_pro_type_inactive' => $cf_pro_type_inactive,
456 'woocommerce_status' => $this->get_plugin_status( 'woocommerce/woocommerce.php' ),
457 'default_page_builder' => $page_builder,
458 'required_plugins' => \Cartflows_Helper::get_plugins_groupby_page_builders(),
459 'required_plugins_data' => $this->get_required_plugins_data(),
460 'is_any_required_plugins_missing' => $this->get_any_required_plugins_status(),
461 'admin_base_slug' => $this->menu_slug,
462 'admin_base_url' => admin_url(),
463 'is_onboarding_completed' => '1' === get_option( 'wcf_setup_complete', false ),
464 'title_length' => apply_filters(
465 'cartflows_flows_steps_title_length',
466 array(
467 'max' => 50,
468 'display_length' => 40,
469 )
470 ),
471 'plugin_dir' => CARTFLOWS_URL,
472 'admin_url' => admin_url( 'admin.php' ),
473 'ajax_url' => admin_url( 'admin-ajax.php' ),
474 'is_rtl' => is_rtl(),
475 'home_slug' => $this->menu_slug,
476 'is_pro' => _is_cartflows_pro(),
477 'is_file_mod_disabled' => function_exists( 'wcf_file_mod_disabled' ) ? wcf_file_mod_disabled() : false, // Keep false by default if function not exists.
478 'page_builder' => $page_builder,
479 'page_builder_name' => $page_builder_name,
480 'global_checkout' => \Cartflows_Helper::get_common_setting( 'global_checkout' ),
481 'flows_count' => 1, // Removing the flow count condition.
482 'currentFlowSteps' => $current_flow_steps,
483 // Delete this code after 3 major update. Added in 1.10.4.
484 'license_status' => \_is_cartflows_pro_license_activated(),
485 'license_popup_url' => admin_url( 'admin.php?page=cartflows&path=settings&tab=license' ),
486 'store_checkout_show_product_tab' => \Cartflows_Helper::display_product_tab_in_store_checkout(),
487 'cf_domain_url' => CARTFLOWS_DOMAIN_URL,
488 'cf_upgrade_to_pro_url' => \Cartflows_Helper::get_upgrade_to_pro_link(),
489 'logo_url' => esc_url_raw( CARTFLOWS_URL . 'assets/images/cartflows-logo.svg' ),
490 'create_product_src' => $product_src,
491 'cf_font_family' => AdminHelper::get_font_family(),
492 'flows_and_steps' => ! empty( $flows_and_steps ) ? $flows_and_steps : '',
493 'store_checkout_flows_and_steps' => \Cartflows_Helper::get_instance()->get_flows_and_steps( '', 'store-checkout' ),
494 'woo_currency' => function_exists( 'get_woocommerce_currency_symbol' ) ? get_woocommerce_currency_symbol() : '',
495 'woo_currency_pos' => function_exists( 'wc' ) ? get_option( 'woocommerce_currency_pos', 'left' ) : 'left',
496 'woo_decimal_sep' => function_exists( 'wc_get_price_decimal_separator' ) ? wc_get_price_decimal_separator() : '.',
497 'woo_thousand_sep' => function_exists( 'wc_get_price_thousand_separator' ) ? wc_get_price_thousand_separator() : ',',
498 'woo_decimals' => function_exists( 'wc_get_price_decimals' ) ? wc_get_price_decimals() : 2,
499 'template_library_url' => wcf()->get_site_url(),
500 'image_placeholder' => esc_url_raw( CARTFLOWS_URL . 'admin-core/assets/images/image-placeholder.png' ),
501 'ai_connected' => class_exists( '\Cartflows_Ai_Auth' ) ? \Cartflows_Ai_Auth::get_instance()->get_auth_status() : false,
502 'can_manage_ai_auth' => current_user_can( 'manage_options' ),
503 'google_fonts' => \CartFlows_Font_Families::get_google_fonts(),
504 'system_fonts' => \CartFlows_Font_Families::get_system_fonts(),
505 'font_weights' => array(
506 '100' => __( 'Thin 100', 'cartflows' ),
507 '200' => __( 'Extra-Light 200', 'cartflows' ),
508 '300' => __( 'Light 300', 'cartflows' ),
509 '400' => __( 'Normal 400', 'cartflows' ),
510 '500' => __( 'Medium 500', 'cartflows' ),
511 '600' => __( 'Semi-Bold 600', 'cartflows' ),
512 '700' => __( 'Bold 700', 'cartflows' ),
513 '800' => __( 'Extra-Bold 800', 'cartflows' ),
514 '900' => __( 'Ultra-Bold 900', 'cartflows' ),
515 ),
516 'global_checkout_id' => $global_checkout_id ? absint( $global_checkout_id ) : '',
517 'flow_action' => $flow_action,
518 'step_action' => $step_action,
519 'old_global_checkout' => get_option( '_cartflows_old_global_checkout', false ),
520 'woopayments_status' => $this->get_plugin_status( 'woocommerce-payments/woocommerce-payments.php' ),
521 'ca_status' => $this->get_plugin_status( 'woo-cart-abandonment-recovery/woo-cart-abandonment-recovery.php' ),
522 'suretriggers_status' => $this->get_plugin_status( 'suretriggers/suretriggers.php' ),
523 'moderncart_status' => $this->get_plugin_status( 'modern-cart/modern-cart.php' ),
524 'cpsw_connection_status' => 'success' === get_option( 'cpsw_test_con_status', false ) || 'success' === get_option( 'cpsw_con_status', false ),
525 'current_user_can_manage_cartflows' => current_user_can( 'cartflows_manage_settings' ),
526 'is_set_report_email_ids' => get_option( 'cartflows_stats_report_email_ids', false ),
527 'woo_order_url' => $order_url,
528 'integrations' => $this->get_recommendation_integrations(),
529 'plugin_installer_nonce' => wp_create_nonce( 'updates' ),
530 'instant_checkout_notice_status' => \Cartflows_Helper::get_admin_settings_option( 'wcf-instant-checkout-notice-skipped', false, false ),
531 'cartflows_admin_notices' => $this->cartflows_admin_notices(),
532 'whats_new_rss_feed' => $this->get_whats_new_rss_feeds_data(),
533 'cartflows_current_version' => CARTFLOWS_VER,
534 'cartflows_previous_versions' => \Cartflows_Helper::get_rollback_versions_options(),
535 'rollback_url' => esc_url( add_query_arg( 'version', 'VERSION', wp_nonce_url( admin_url( 'admin-post.php?action=cartflows_rollback' ), 'cartflows_rollback' ) ) ),
536 'utm_defaults' => \Cartflows_Helper::get_utm_defaults(),
537 /**
538 * Filters whether to hide already-added products from the checkout product search dropdown.
539 *
540 * When `true`, products already present in the current checkout step's product list are
541 * filtered out of the search results, preventing duplicate selection through the UI.
542 *
543 * Note: this is a UI-level restriction only. The server-side save path (REST/AJAX) is
544 * not gated by this filter, so duplicates submitted directly to the save endpoint will
545 * still be persisted.
546 *
547 * @since x.x.x
548 *
549 * @param bool $restrict Whether to hide already-added products in the search dropdown. Default false.
550 */
551 'restrict_duplicate_checkout_products' => apply_filters( 'cartflows_restrict_duplicate_checkout_products', false ),
552 )
553 );
554
555 // Load the codemirror for code editor.
556 wp_enqueue_code_editor(
557 array(
558 'type' => 'text/javascript',
559 )
560 );
561 wp_enqueue_code_editor(
562 array(
563 'type' => 'text/css',
564 )
565 );
566
567 wp_enqueue_script( 'wp-code-editor' );
568 wp_enqueue_style( 'wp-codemirror' );
569
570 if ( $this->is_current_page( $this->menu_slug ) ) {
571 $this->settings_app_scripts( $localize );
572 } elseif ( $this->is_current_page( 'cartflows', array( 'wcf-edit-flow' ) ) ) {
573 wp_enqueue_media();
574 wp_enqueue_editor(); // Require for Order Bump Desc WP Editor/ tinymce field.
575 $this->editor_app_scripts( $localize );
576 }
577 }
578
579 /**
580 * Get recommendation integrations.
581 *
582 * @since 1.11.8
583 *
584 * @return array
585 */
586 public function cartflows_admin_notices() {
587 $notices = array();
588
589 $notices = $this->page_builder_plugin_notices( $notices );
590 $notices = apply_filters( 'cartflows_admin_notices', $notices );
591
592 return $notices;
593 }
594
595 /**
596 * Get builder plugin notices.
597 *
598 * @since 1.11.8
599 * @param array $notices Notices array.
600 * @return array
601 */
602 public function page_builder_plugin_notices( $notices ) {
603 $required_plugins_missing = $this->get_any_required_plugins_status();
604 $default_page_builder = \Cartflows_Helper::get_common_setting( 'default_page_builder' );
605 $required_plugins = array();
606 if ( isset( \Cartflows_Helper::get_plugins_groupby_page_builders()[ $default_page_builder ] ) ) {
607 $required_plugins = \Cartflows_Helper::get_plugins_groupby_page_builders()[ $default_page_builder ];
608 }
609
610 $page_builder_plugins = isset( $required_plugins['plugins'] ) ? $required_plugins['plugins'] : array();
611 $any_inactive = false;
612 if ( ! empty( $page_builder_plugins ) ) {
613 foreach ( $page_builder_plugins as $plugin ) {
614 if ( 'activated' !== $plugin['status'] ) {
615 $any_inactive = true;
616 break; // Stop checking if we found an active plugin.
617 }
618 }
619 }
620
621 // Get the titles.
622 $titles = $this->generate_plugin_titles( $page_builder_plugins );
623
624 if ( 'yes' === $required_plugins_missing && $any_inactive ) {
625 $notices[] = '<div class="wcf-payment-gateway-notice--text"><p class="text-sm text-yellow-700">' . wp_kses_post(
626 sprintf(
627 // Translators: %1$s is the required page builder title, %2$s is the opening anchor tag to plugins.php, %3$s is the closing anchor tag, %4$s is the plugin title.
628 __( 'The default page builder is set to %1$s. Please %2$sinstall & activate%3$s the %4$s to start editing the steps.', 'cartflows' ), //phpcs:ignore
629 '<span class="capitalize">' . esc_html( $required_plugins['title'] ) . '</span>',
630 '<span class="font-medium"><a href="' . esc_url( admin_url() . 'plugins.php' ) . '" class="underline text-yellow-700 hover:text-yellow-600" target="_blank">',
631 '</a></span>',
632 '<span class="capitalize">' . esc_html( implode( ', ', $titles ) ) . '</span>'
633 )
634 ) . '</p></div>';
635
636 }
637 return $notices;
638 }
639
640 /**
641 * Get required plugin titles from their slugs.
642 *
643 * @since 2.1.6
644 * @param array $plugins plugins array.
645 * @return array
646 */
647 public function generate_plugin_titles( $plugins ) {
648 $titles = array();
649
650 if ( ! is_array( $plugins ) || empty( $plugins ) ) {
651 return $titles;
652 }
653
654 foreach ( $plugins as $plugin ) {
655
656 if ( 'ultimate-addons-for-gutenberg' === $plugin['slug'] ) {
657 $title = 'Spectra'; // Don't add this for translation as it is a plugin name.
658 } else {
659 $slug_parts = explode( '-', $plugin['slug'] );
660 $title = implode( ' ', array_map( 'ucfirst', $slug_parts ) ); // Convert each part to title case.
661 }
662
663 $titles[] = $title;
664 }
665
666 return $titles;
667 }
668
669
670 /**
671 * Get required plugin status.
672 */
673 public function get_required_plugins_data() {
674
675 $missing_plugins_data = array();
676
677 $page_builder_plugins = \Cartflows_Helper::get_plugins_groupby_page_builders();
678
679 foreach ( $page_builder_plugins as $slug => $data ) {
680
681 $missing_plugins_data[ $slug ] = 'no';
682 $current_page_builder_data = $page_builder_plugins[ $slug ];
683
684 foreach ( $current_page_builder_data['plugins'] as $plugin ) {
685 if ( 'activate' === $plugin['status'] || 'install' === $plugin['status'] ) {
686 $missing_plugins_data[ $slug ] = 'yes';
687 }
688 }
689
690 // Divi.
691 if ( 'divi' === $slug ) {
692 if ( 'activate' === $current_page_builder_data['theme-status'] ) {
693 $missing_plugins_data[ $slug ] = 'yes';
694 }
695 }
696 }
697
698 return $missing_plugins_data;
699 }
700 /**
701 * Get required plugin status
702 */
703 public function get_any_required_plugins_status() {
704
705 $default_page_builder = \Cartflows_Helper::get_common_setting( 'default_page_builder' );
706 $any_inactive = 'no';
707
708 $page_builder_plugins = \Cartflows_Helper::get_plugins_groupby_page_builders();
709
710 if ( isset( $page_builder_plugins[ $default_page_builder ] ) ) {
711
712 $current_page_builder_data = $page_builder_plugins[ $default_page_builder ];
713
714 foreach ( $current_page_builder_data['plugins'] as $plugin ) {
715 if ( 'activate' === $plugin['status'] || 'install' === $plugin['status'] ) {
716 $any_inactive = 'yes';
717 }
718 }
719
720 // Divi.
721 if ( 'divi' === $default_page_builder ) {
722 if ( 'activate' === $current_page_builder_data['theme-status'] ) {
723 $any_inactive = 'yes';
724 }
725 }
726 }
727
728 return $any_inactive;
729 }
730
731 /**
732 * Get CartFlows plugin status
733 *
734 * @since 1.11.8
735 *
736 * @return string
737 */
738 public function get_cartflows_pro_plugin_status() {
739
740 $status = $this->get_plugin_status( 'cartflows-pro/cartflows-pro.php' );
741
742 if ( 'active' === $status && ! _is_cartflows_pro() ) {
743
744 $status = 'inactive';
745 }
746
747 return $status;
748 }
749
750 /**
751 * Get plugin status
752 *
753 * @since 1.1.4
754 *
755 * @param string $plugin_init_file Plguin init file.
756 * @return mixed
757 */
758 public function get_plugin_status( $plugin_init_file ) {
759
760 $installed_plugins = get_plugins();
761
762 if ( ! isset( $installed_plugins[ $plugin_init_file ] ) ) {
763 return 'not-installed';
764 } elseif ( is_plugin_active( $plugin_init_file ) ) {
765 return 'active';
766 } else {
767 return 'inactive';
768 }
769 }
770
771 /**
772 * Get plugin status
773 *
774 * @since 1.1.4
775 *
776 * @param string $theme_name Theme slug file.
777 * @return string
778 */
779 public function get_themes_status( $theme_name ) {
780
781 $theme = wp_get_theme();
782
783 // Theme installed and activate.
784 if ( $theme_name === $theme->name || $theme_name === $theme->parent_theme ) {
785 return 'active';
786 }
787
788 // Theme installed but not activate.
789 foreach ( (array) wp_get_themes() as $theme_dir => $theme ) {
790 if ( $theme_name === $theme->name || $theme_name === $theme->parent_theme ) {
791 return 'inactive';
792 }
793 }
794
795 // If none of the above conditions are found then the theme is not installed.
796 return 'not-installed';
797 }
798
799 /**
800 * Settings app scripts
801 *
802 * @param array $localize Variable names.
803 */
804 public function settings_app_scripts( $localize ) {
805 $handle = 'wcf-react-settings';
806 $build_path = CARTFLOWS_ADMIN_CORE_DIR . 'assets/build/';
807 $build_url = CARTFLOWS_ADMIN_CORE_URL . 'assets/build/';
808 $script_asset_path = $build_path . 'settings-app.asset.php';
809 $script_info = file_exists( $script_asset_path )
810 ? include $script_asset_path
811 : array(
812 'dependencies' => array(),
813 'version' => CARTFLOWS_VER,
814 );
815
816 $script_dep = array_merge( $script_info['dependencies'], array( 'updates' ) );
817
818 wp_register_script(
819 $handle,
820 $build_url . 'settings-app.js',
821 $script_dep,
822 $script_info['version'],
823 true
824 );
825
826 wp_register_style(
827 $handle,
828 $build_url . 'settings-app.css',
829 array(),
830 CARTFLOWS_VER
831 );
832
833 // Store Checkout renders the canvas inside this app too, so it needs the React Flow base styles.
834 wp_register_style(
835 'wcf-canvas-base-style',
836 $build_url . 'style-editor-app.css',
837 array(),
838 CARTFLOWS_VER
839 );
840 wp_enqueue_style( 'wcf-canvas-base-style' );
841
842 wp_enqueue_script( $handle );
843
844 wp_set_script_translations( $handle, 'cartflows' );
845
846 wp_enqueue_style( $handle );
847 wp_style_add_data( $handle, 'rtl', 'replace' );
848
849 $localize['is_flows_limit'] = false; // Removed the flow count condition.
850
851 $localize = $this->debugger_scripts( $localize );
852
853 $localize['save_learn_completed_nonce'] = wp_create_nonce( 'cartflows_save_learn_completed' );
854 $localize['activate_plugin_nonce'] = wp_create_nonce( 'cartflows_activate_plugin' );
855
856 wp_localize_script( $handle, 'cartflows_admin', $localize );
857 }
858
859 /**
860 * Settings app scripts
861 *
862 * @param array $localize Variable names.
863 */
864 public function editor_app_scripts( $localize ) {
865 $handle = 'wcf-editor-app';
866 $build_path = CARTFLOWS_ADMIN_CORE_DIR . 'assets/build/';
867 $build_url = CARTFLOWS_ADMIN_CORE_URL . 'assets/build/';
868 $script_asset_path = $build_path . 'editor-app.asset.php';
869 $script_info = file_exists( $script_asset_path )
870 ? include $script_asset_path
871 : array(
872 'dependencies' => array(),
873 'version' => CARTFLOWS_VER,
874 );
875
876 wp_register_script(
877 $handle,
878 $build_url . 'editor-app.js',
879 $script_info['dependencies'],
880 $script_info['version'],
881 true
882 );
883
884 wp_register_style(
885 $handle,
886 $build_url . 'editor-app.css',
887 array(),
888 CARTFLOWS_VER
889 );
890
891 wp_register_style(
892 'wcf-canvas-base-style',
893 $build_url . 'style-editor-app.css',
894 array(),
895 CARTFLOWS_VER
896 );
897 // Enqueue the registered style.
898 wp_enqueue_style( 'wcf-canvas-base-style' );
899
900 wp_enqueue_script( $handle );
901
902 wp_set_script_translations( $handle, 'cartflows' );
903
904 wp_enqueue_style( $handle );
905 wp_style_add_data( $handle, 'rtl', 'replace' );
906
907 $localize['flow_id'] = isset( $_GET['flow_id'] ) ? intval( $_GET['flow_id'] ) : 0; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
908
909 $step_id = isset( $_GET['step_id'] ) ? intval( $_GET['step_id'] ) : false; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
910
911 if ( $step_id ) {
912
913 $meta_options = AdminHelper::get_step_meta_options( $step_id );
914 $localize['options'] = $meta_options['options'];
915 }
916
917 wp_localize_script( $handle, 'cartflows_admin', $localize );
918 }
919
920 /**
921 * Debugger scripts.
922 *
923 * @param array $localize Variable names.
924 */
925 public function debugger_scripts( $localize ) {
926
927 $logs = LogStatus::get_instance()->get_log_files();
928 $viewed_log = '';
929 $viewed_log_file = '';
930
931 // Calling this function on CartFlows action hook. Hence ignoring nonce.
932 if ( ! empty( $_REQUEST['log_file'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
933
934 $filename = sanitize_text_field( wp_unslash( $_REQUEST['log_file'] ) ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended
935
936 if ( isset( $logs[ $filename ] ) ) {
937 $viewed_log = $filename;
938 $viewed_log_file = $viewed_log . '.log';
939 }
940 } elseif ( ! empty( $logs ) ) {
941 $viewed_log = current( $logs ) ? pathinfo( current( $logs ), PATHINFO_FILENAME ) : '';
942 $viewed_log_file = $viewed_log . '.log';
943 }
944
945 if ( ! empty( $viewed_log_file ) ) {
946 $file_content = file_get_contents( CARTFLOWS_LOG_DIR . $viewed_log_file );
947 $localize['file_content'] = $file_content;
948 $localize['log_key'] = $viewed_log;
949
950 }
951 $localize['logs'] = $logs;
952 return $localize;
953 }
954
955
956 /**
957 * CHeck if it is current page by parameters
958 *
959 * @param string $page_slug Menu name.
960 * @param string $action Menu name.
961 *
962 * @return string page url
963 */
964 public function is_current_page( $page_slug = '', $action = '' ) {
965
966 $page_matched = false;
967
968 if ( empty( $page_slug ) ) {
969 return false;
970 }
971
972 $current_page_slug = isset( $_GET['page'] ) ? sanitize_text_field( $_GET['page'] ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
973 $current_action = isset( $_GET['action'] ) ? sanitize_text_field( $_GET['action'] ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
974
975 if ( ! is_array( $action ) ) {
976 $action = explode( ' ', $action );
977 }
978
979 if ( $page_slug === $current_page_slug && in_array( $current_action, $action, true ) ) {
980 $page_matched = true;
981 }
982
983 return $page_matched;
984 }
985
986 /**
987 * Get CartFlows recommended integrations.
988 *
989 * @return array
990 */
991 public function get_recommendation_integrations() {
992
993 return array(
994 'plugins' => apply_filters(
995 'cartflows_admin_integrated_plugins',
996 array(
997 array(
998 'title' => __( 'WooCommerce', 'cartflows' ),
999 'subtitle' => __( 'WooCommerce is a customizable, open-source ecommerce platform built on WordPress.', 'cartflows' ),
1000 'isPro' => false,
1001 'status' => $this->get_plugin_status( 'woocommerce/woocommerce.php' ),
1002 'slug' => 'woocommerce',
1003 'path' => 'woocommerce/woocommerce.php',
1004 'redirection' => admin_url( 'admin.php?page=wc-admin' ),
1005 'logoPath' => array(
1006 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/woo.svg',
1007 ),
1008 ),
1009 array(
1010 'title' => __( 'Power Coupons', 'cartflows' ),
1011 'subtitle' => __( 'Create powerful coupon rules with advanced conditions, discount types, and usage restrictions for WooCommerce.', 'cartflows' ),
1012 'isPro' => false,
1013 'status' => $this->get_plugin_status( 'power-coupons/power-coupons.php' ),
1014 'slug' => 'power-coupons',
1015 'path' => 'power-coupons/power-coupons.php',
1016 'redirection' => admin_url( 'admin.php?page=power-coupons' ),
1017 'logoPath' => array(
1018 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/power-coupons.svg',
1019 ),
1020 ),
1021 array(
1022 'title' => __( 'Modern Cart for WooCommerce', 'cartflows' ),
1023 'subtitle' => __( 'A fast, customizable cart built to boost conversions, maximise profits, and elevate the shopping experience.', 'cartflows' ),
1024 'isPro' => false,
1025 'status' => $this->get_plugin_status( 'modern-cart/modern-cart.php' ),
1026 'slug' => 'modern-cart',
1027 'path' => 'modern-cart/modern-cart.php',
1028 'redirection' => admin_url( 'admin.php?page=moderncart_settings' ),
1029 'logoPath' => array(
1030 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/modern-cart-for-woocommerce.svg',
1031 ),
1032 'hot_badge' => true,
1033 ),
1034 array(
1035 'title' => __( 'Cart Abandonment Recovery', 'cartflows' ),
1036 'subtitle' => __( 'Recover abandonded carts with ease in less than 10 minutes.', 'cartflows' ),
1037 'isPro' => false,
1038 'status' => $this->get_plugin_status( 'woo-cart-abandonment-recovery/woo-cart-abandonment-recovery.php' ),
1039 'slug' => 'woo-cart-abandonment-recovery',
1040 'path' => 'woo-cart-abandonment-recovery/woo-cart-abandonment-recovery.php',
1041 'redirection' => admin_url( 'admin.php?page=woo-cart-abandonment-recovery' ),
1042 'logoPath' => array(
1043 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/wcar.svg',
1044 ),
1045 'hot_badge' => true,
1046 ),
1047 array(
1048 'title' => __( 'Variation Swatches for WooCommerce', 'cartflows' ),
1049 'subtitle' => __( 'Convert dropdown boxes into highly engaging variation swatches.', 'cartflows' ),
1050 'isPro' => false,
1051 'status' => $this->get_plugin_status( 'variation-swatches-woo/variation-swatches-woo.php' ),
1052 'slug' => 'variation-swatches-woo',
1053 'path' => 'variation-swatches-woo/variation-swatches-woo.php',
1054 'redirection' => admin_url( 'admin.php?page=cfvsw_settings' ),
1055 'logoPath' => array(
1056 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/variation-swatches-woo.svg',
1057 ),
1058 'hot_badge' => true,
1059 ),
1060 array(
1061 'title' => __( 'WooPayments: Integrated WooCommerce Payments', 'cartflows' ),
1062 'subtitle' => __( 'Payments made simple, with no monthly fees – designed exclusively for WooCommerce stores.', 'cartflows' ),
1063 'isPro' => false,
1064 'status' => $this->get_plugin_status( 'woocommerce-payments/woocommerce-payments.php' ),
1065 'redirection' => admin_url( 'admin.php?page=wc-admin&path=/payments/connect' ),
1066 'slug' => 'woocommerce-payments',
1067 'path' => 'woocommerce-payments/woocommerce-payments.php',
1068 'logoPath' => array(
1069 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/woocommere-payments.png',
1070 ),
1071 ),
1072 array(
1073 'title' => __( 'OttoKit', 'cartflows' ),
1074 'subtitle' => __( 'OttoKit helps people automate their work by integrating multiple apps and plugins, allowing them to share data and perform tasks automatically.', 'cartflows' ),
1075 'isPro' => true,
1076 'status' => $this->get_plugin_status( 'suretriggers/suretriggers.php' ),
1077 'slug' => 'suretriggers',
1078 'path' => 'suretriggers/suretriggers.php',
1079 'link' => 'https://ottokit.com/pricing/',
1080 'redirection' => admin_url( 'admin.php?page=suretriggers' ),
1081 'logoPath' => array(
1082 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/suretriggers-emblem.svg',
1083 ),
1084 ),
1085 array(
1086 'title' => __( 'Ultimate addons for Elementor', 'cartflows' ),
1087 'subtitle' => __( 'Powerful Elementor addon with InfoCard, Fancy Heading, Before/After Slider, Price Box, FAQ Schema, WooCommerce widgets & Header-Footer builder.', 'cartflows' ),
1088 'isPro' => false,
1089 'status' => $this->get_plugin_status( 'header-footer-elementor/header-footer-elementor.php' ),
1090 'slug' => 'header-footer-elementor',
1091 'path' => 'header-footer-elementor/header-footer-elementor.php',
1092 'redirection' => admin_url( 'admin.php?page=hfe#dashboard' ),
1093 'logoPath' => array(
1094 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/uael.png',
1095 ),
1096 ),
1097 array(
1098 'title' => __( 'Spectra', 'cartflows' ),
1099 'subtitle' => __( 'Power-up the Gutenberg editor with advanced and powerful blocks.', 'cartflows' ),
1100 'isPro' => false,
1101 'status' => $this->get_plugin_status( 'ultimate-addons-for-gutenberg/ultimate-addons-for-gutenberg.php' ),
1102 'slug' => 'ultimate-addons-for-gutenberg',
1103 'path' => 'ultimate-addons-for-gutenberg/ultimate-addons-for-gutenberg.php',
1104 'redirection' => admin_url( 'options-general.php?page=spectra' ),
1105 'logoPath' => array(
1106 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/spectra.svg',
1107 ),
1108 ),
1109 array(
1110 'title' => __( 'SureForms', 'cartflows' ),
1111 'subtitle' => __( 'Transform your WordPress form-building experience with stunning designs, ai integration, and no-code flexibility.', 'cartflows' ),
1112 'isPro' => false,
1113 'status' => $this->get_plugin_status( 'sureforms/sureforms.php' ),
1114 'slug' => 'sureforms',
1115 'path' => 'sureforms/sureforms.php',
1116 'redirection' => admin_url( 'admin.php?page=sureforms_menu' ),
1117 'logoPath' => array(
1118 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/sureforms.svg',
1119 ),
1120 ),
1121 array(
1122 'title' => __( 'SureMembers', 'cartflows' ),
1123 'subtitle' => __( 'A simple yet powerful way to add content restriction to your website.', 'cartflows' ),
1124 'isPro' => true,
1125 'status' => $this->get_plugin_status( 'suremembers/suremembers.php' ),
1126 'slug' => 'suremembers',
1127 'path' => 'suremembers/suremembers.php',
1128 'link' => 'https://suremembers.com/pricing/',
1129 'redirection' => admin_url( 'edit.php?post_type=wsm_access_group' ),
1130 'logoPath' => array(
1131 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/suremembers.svg',
1132 ),
1133 ),
1134 )
1135 ),
1136 'themes' => apply_filters(
1137 'cartflows_admin_integrated_themes',
1138 array(
1139 array(
1140 'title' => __( 'Astra', 'cartflows' ),
1141 'subtitle' => __( 'Astra is fast, fully customizable & beautiful WordPress theme suitable for blog, personal portfolio, business website and WooCommerce storefront.', 'cartflows' ),
1142 'isPro' => false,
1143 'status' => $this->get_themes_status( 'Astra' ),
1144 'slug' => 'astra',
1145 'redirection' => admin_url( 'admin.php?page=wc-admin' ),
1146 'logoPath' => array(
1147 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/themes/astra-icon.svg',
1148 ),
1149 ),
1150 array(
1151 'title' => __( 'Spectra One', 'cartflows' ),
1152 'subtitle' => __( 'Spectra One is a beautiful and modern WordPress theme built with the Full Site Editing (FSE) feature. It\'s a versatile theme that can be used for blogs, portfolios, businesses, and more.', 'cartflows' ),
1153 'isPro' => false,
1154 'status' => $this->get_themes_status( 'Spectra One' ),
1155 'slug' => 'spectra-one',
1156 'redirection' => admin_url( 'admin.php?page=wc-admin' ),
1157 'logoPath' => array(
1158 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/themes/spectra-one.svg',
1159 ),
1160 ),
1161 )
1162 ),
1163 );
1164 }
1165
1166 /**
1167 * Prepare the array of RSS Feeds of CartFlows for Whats New slide-our pannel.
1168 *
1169 * @since 2.1.6
1170 * @return array The prepared array of RSS feeds.
1171 */
1172 public function get_whats_new_rss_feeds_data() {
1173 return array(
1174 array(
1175 'key' => 'cartflows',
1176 'label' => 'CartFlows',
1177 'url' => add_query_arg(
1178 array(
1179 'action' => 'cartflows_fetch_whats_new_data',
1180 'nonce' => wp_create_nonce( 'cartflows_fetch_whats_new_data' ),
1181 ),
1182 admin_url( 'admin-ajax.php' )
1183 ), // 'https://cartflows.com/whats-new/feed/'
1184 ),
1185 );
1186 }
1187
1188 /**
1189 * Fetch the Whats New RSS feed from the URL.
1190 *
1191 * @since 1.0.3
1192 * @return void
1193 */
1194 public function fetch_whats_new_data() {
1195 if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( $_GET['nonce'] ), 'cartflows_fetch_whats_new_data' ) ) {
1196 // Verify the nonce, if it fails, return an error.
1197 wp_send_json_error( array( 'message' => __( 'Nonce verification failed.', 'cartflows' ) ) );
1198 }
1199
1200 // Security: Require admin capability to access RSS feed proxy.
1201 if ( ! current_user_can( 'cartflows_manage_flows_steps' ) ) {
1202 wp_send_json_error( array( 'message' => __( 'Permission denied.', 'cartflows' ) ) );
1203 }
1204
1205 // Fetch the RSS feed from the URL. This saves us from the CORS issue.
1206 $feed = wp_remote_retrieve_body( wp_safe_remote_get( 'https://cartflows.com/product/cartflows/feed/' ) ); // phpcs:ignore -- This is a valid use case cannot use VIP rules here.
1207
1208 /*
1209 * This is an RSS document, parsed as XML by the What's New client. It must not be
1210 * run through wp_kses_post() — that strips the feed's own elements and leaves the
1211 * client nothing to parse. Declaring the type is what stops a browser sniffing the
1212 * response as HTML; the body itself is fetched over HTTPS from our own domain and
1213 * this endpoint is behind a nonce and a capability check.
1214 */
1215 header( 'Content-Type: application/rss+xml; charset=' . get_option( 'blog_charset' ) );
1216 header( 'X-Content-Type-Options: nosniff' );
1217
1218 echo $feed; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- RSS/XML body, see above.
1219 exit;
1220 }
1221 }
1222
1223 AdminMenu::get_instance();
1224