PluginProbe
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce / 3.2.1
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce v3.2.1
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.2.1, at admin-core/inc/admin-menu.php

1,187 lines 42.4 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 }
228
229 /**
230 * Add submenu to admin menu.
231 *
232 * @since 1.0.0
233 */
234 public function setup_menu() {
235 global $submenu;
236
237 if ( current_user_can( 'cartflows_manage_flows_steps' ) ) {
238 $parent_slug = $this->menu_slug;
239 $capability = 'cartflows_manage_flows_steps';
240 $menu_priority = apply_filters( 'cartflows_menu_priority', ! defined( 'ASTRA_THEME_VERSION' ) ? 40 : 3 );
241
242 add_menu_page(
243 'CartFlows',
244 'CartFlows',
245 $capability,
246 $parent_slug,
247 array( $this, 'render' ),
248 'data:image/svg+xml;base64,' . base64_encode( file_get_contents( CARTFLOWS_DIR . 'assets/images/cartflows-icon.svg' ) ),
249 $menu_priority
250 );
251
252 // Add settings menu.
253 add_submenu_page(
254 $parent_slug,
255 __( 'Funnels', 'cartflows' ),
256 __( 'Funnels', 'cartflows' ),
257 $capability,
258 'admin.php?page=' . $this->menu_slug . '&path=flows'
259 );
260
261 $global_checkout_id = absint( \Cartflows_Helper::get_global_setting( '_cartflows_store_checkout' ) );
262 $global_checkout_param = ( empty( $global_checkout_id ) ) ? 'path=store-checkout' : 'path=store-checkout&action=wcf-edit-flow&flow_id=' . $global_checkout_id;
263
264 add_submenu_page(
265 $parent_slug,
266 __( 'Store Checkout', 'cartflows' ),
267 __( 'Store Checkout', 'cartflows' ),
268 $capability,
269 'admin.php?page=' . $this->menu_slug . '&' . $global_checkout_param
270 );
271
272 add_submenu_page(
273 $parent_slug,
274 __( 'Analytics', 'cartflows' ),
275 __( 'Analytics', 'cartflows' ),
276 $capability,
277 'admin.php?page=' . $this->menu_slug . '&path=analytics'
278 );
279
280 if ( current_user_can( 'cartflows_manage_settings' ) ) {
281
282 add_submenu_page(
283 $parent_slug,
284 __( 'Learn', 'cartflows' ),
285 __( 'Learn', 'cartflows' ),
286 $capability,
287 'admin.php?page=' . $this->menu_slug . '&path=learn'
288 );
289
290 if ( ! _is_cartflows_pro() ) {
291 add_submenu_page(
292 $parent_slug,
293 __( 'Get CartFlows Pro', 'cartflows' ),
294 __( 'Get CartFlows Pro', 'cartflows' ),
295 $capability,
296 \Cartflows_Helper::get_upgrade_to_pro_link()
297 );
298 }
299 }
300
301 // Rename to Home menu.
302 // Disable phpcs since we need to override submenu name.
303 $submenu[ $parent_slug ][0][0] = __( 'Dashboard', 'cartflows' ); //phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
304 }
305 }
306
307 /**
308 * Add custom capabilities to Admin user.
309 */
310 public function add_capabilities_to_admin() {
311
312 global $wp_roles;
313 // Add custom capabilities to admin by default.
314 $capabilities = array(
315 'cartflows_manage_settings',
316 'cartflows_manage_flows_steps',
317 );
318
319 foreach ( $capabilities as $cap ) {
320 $wp_roles->add_cap( 'administrator', $cap );
321 }
322 }
323
324 /**
325 * Renders the admin settings.
326 *
327 * @since 1.0.0
328 * @return void
329 */
330 public function render() {
331 $menu_page_slug = ( isset( $_GET['page'] ) ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : CARTFLOWS_SETTINGS; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
332 $page_action = '';
333
334 if ( isset( $_GET['action'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
335 $page_action = sanitize_text_field( wp_unslash( $_GET['action'] ) ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended
336 $page_action = str_replace( '_', '-', $page_action );
337 }
338
339 include_once CARTFLOWS_ADMIN_CORE_DIR . 'views/admin-base.php';
340 }
341
342 /**
343 * Renders the admin settings content.
344 *
345 * @since 1.0.0
346 * @param sting $menu_page_slug current page name.
347 * @param sting $page_action current page action.
348 *
349 * @return void
350 */
351 public function render_content( $menu_page_slug, $page_action ) {
352
353 if ( 'cartflows' === $menu_page_slug ) {
354 if ( $this->is_current_page( 'cartflows' ) ) {
355 include_once CARTFLOWS_ADMIN_CORE_DIR . 'views/settings-app.php';
356 } elseif ( $this->is_current_page( 'cartflows', array( 'wcf-edit-flow' ) ) ) {
357 include_once CARTFLOWS_ADMIN_CORE_DIR . 'views/editor-app.php';
358 } elseif ( $this->is_current_page( 'cartflows', array( 'wcf-log' ) ) ) {
359 include_once CARTFLOWS_ADMIN_CORE_DIR . 'views/debugger.php';
360 } elseif ( $this->is_current_page( 'cartflows', array( 'wcf-license' ) ) && _is_cartflows_pro() ) {
361 do_action( 'cartflows_admin_log', 'wcf-license' );
362 } else {
363 include_once CARTFLOWS_ADMIN_CORE_DIR . 'views/404-error.php';
364 }
365 }
366 }
367
368 /**
369 * Enqueues the needed CSS/JS for the builder's admin settings page.
370 *
371 * @since 1.0.0
372 */
373 public function styles_scripts() {
374
375 $admin_slug = 'cartflows-admin';
376
377 // Load the custom font for admin area.
378 wp_enqueue_style( $admin_slug . '-font', 'https://fonts.googleapis.com/css2?family=Figtree:wght@400;500;600;700&display=swap', array(), CARTFLOWS_VER );
379
380 // Styles.
381 wp_enqueue_style( $admin_slug . '-common', CARTFLOWS_ADMIN_CORE_URL . 'assets/css/common.css', array(), CARTFLOWS_VER );
382 wp_style_add_data( $admin_slug . '-common', 'rtl', 'replace' );
383
384 wp_enqueue_script( $admin_slug . '-common-script', CARTFLOWS_ADMIN_CORE_URL . 'assets/js/common.js', array( 'jquery' ), CARTFLOWS_VER, false );
385
386 wp_enqueue_script( $admin_slug . '-suretriggers-integration', CARTFLOWS_SURETRIGGERS_INTEGRATION_BASE_URL . 'js/v2/embed.js', array(), CARTFLOWS_VER, true );
387
388 $current_flow_steps = array();
389 $flow_id = isset( $_GET['flow_id'] ) ? intval( $_GET['flow_id'] ) : 0; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
390 if ( $flow_id ) {
391 $current_flow_steps = AdminHelper::get_flow_meta_options( $flow_id );
392 }
393
394 $product_src = esc_url_raw(
395 add_query_arg(
396 array(
397 'post_type' => 'product',
398 'wcf-woo-iframe' => 'true',
399 ),
400 admin_url( 'post-new.php' )
401 )
402 );
403
404
405 $page_builder = \Cartflows_Helper::get_common_setting( 'default_page_builder' );
406 $page_builder_name = \Cartflows_Helper::get_page_builder_name( $page_builder );
407
408 $global_checkout_id = \Cartflows_Helper::get_global_setting( '_cartflows_store_checkout' );
409
410 $flow_action = 'wcf-edit-flow';
411 $step_action = 'wcf-edit-step';
412
413 $flows_and_steps = \Cartflows_Helper::get_instance()->get_flows_and_steps();
414
415 $cf_pro_status = $this->get_cartflows_pro_plugin_status();
416 $cf_pro_type_inactive = '';
417 if ( 'inactive' === $cf_pro_status || 'not-installed' === $cf_pro_status ) {
418
419 if ( ! function_exists( 'get_plugins' ) ) {
420 require_once ABSPATH . 'wp-admin/includes/plugin.php';
421 }
422
423 $plugin = get_plugins();
424 $cf_pro_type_inactive = isset( $plugin['cartflows-pro/cartflows-pro.php']['Name'] ) ? $plugin['cartflows-pro/cartflows-pro.php']['Name'] : '';
425 }
426
427 $order_url = '#';
428
429 if ( wcf()->is_woo_active && class_exists( '\Automattic\WooCommerce\Utilities\OrderUtil' ) ) {
430 $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' );
431 }
432
433 $localize = apply_filters(
434 'cartflows_admin_localized_vars',
435 array(
436 'current_user' => ! empty( wp_get_current_user()->user_firstname ) ? wp_get_current_user()->user_firstname : wp_get_current_user()->display_name,
437 'cf_pro_status' => $cf_pro_status,
438 'cf_pro_type' => 'free',
439 'cf_pro_type_inactive' => $cf_pro_type_inactive,
440 'woocommerce_status' => $this->get_plugin_status( 'woocommerce/woocommerce.php' ),
441 'default_page_builder' => $page_builder,
442 'required_plugins' => \Cartflows_Helper::get_plugins_groupby_page_builders(),
443 'required_plugins_data' => $this->get_required_plugins_data(),
444 'is_any_required_plugins_missing' => $this->get_any_required_plugins_status(),
445 'admin_base_slug' => $this->menu_slug,
446 'admin_base_url' => admin_url(),
447 'is_onboarding_completed' => '1' === get_option( 'wcf_setup_complete', false ),
448 'title_length' => apply_filters(
449 'cartflows_flows_steps_title_length',
450 array(
451 'max' => 50,
452 'display_length' => 40,
453 )
454 ),
455 'plugin_dir' => CARTFLOWS_URL,
456 'admin_url' => admin_url( 'admin.php' ),
457 'ajax_url' => admin_url( 'admin-ajax.php' ),
458 'is_rtl' => is_rtl(),
459 'home_slug' => $this->menu_slug,
460 'is_pro' => _is_cartflows_pro(),
461 'is_file_mod_disabled' => function_exists( 'wcf_file_mod_disabled' ) ? wcf_file_mod_disabled() : false, // Keep false by default if function not exists.
462 'page_builder' => $page_builder,
463 'page_builder_name' => $page_builder_name,
464 'global_checkout' => \Cartflows_Helper::get_common_setting( 'global_checkout' ),
465 'flows_count' => 1, // Removing the flow count condition.
466 'currentFlowSteps' => $current_flow_steps,
467 // Delete this code after 3 major update. Added in 1.10.4.
468 'license_status' => \_is_cartflows_pro_license_activated(),
469 'license_popup_url' => admin_url( 'admin.php?page=cartflows&path=settings&tab=license' ),
470 'store_checkout_show_product_tab' => \Cartflows_Helper::display_product_tab_in_store_checkout(),
471 'cf_domain_url' => CARTFLOWS_DOMAIN_URL,
472 'cf_upgrade_to_pro_url' => \Cartflows_Helper::get_upgrade_to_pro_link(),
473 'logo_url' => esc_url_raw( CARTFLOWS_URL . 'assets/images/cartflows-logo.svg' ),
474 'create_product_src' => $product_src,
475 'cf_font_family' => AdminHelper::get_font_family(),
476 'flows_and_steps' => ! empty( $flows_and_steps ) ? $flows_and_steps : '',
477 'store_checkout_flows_and_steps' => \Cartflows_Helper::get_instance()->get_flows_and_steps( '', 'store-checkout' ),
478 'woo_currency' => function_exists( 'get_woocommerce_currency_symbol' ) ? get_woocommerce_currency_symbol() : '',
479 'template_library_url' => wcf()->get_site_url(),
480 'image_placeholder' => esc_url_raw( CARTFLOWS_URL . 'admin-core/assets/images/image-placeholder.png' ),
481 'ai_connected' => class_exists( '\Cartflows_Ai_Auth' ) ? \Cartflows_Ai_Auth::get_instance()->get_auth_status() : false,
482 'can_manage_ai_auth' => current_user_can( 'manage_options' ),
483 'google_fonts' => \CartFlows_Font_Families::get_google_fonts(),
484 'system_fonts' => \CartFlows_Font_Families::get_system_fonts(),
485 'font_weights' => array(
486 '100' => __( 'Thin 100', 'cartflows' ),
487 '200' => __( 'Extra-Light 200', 'cartflows' ),
488 '300' => __( 'Light 300', 'cartflows' ),
489 '400' => __( 'Normal 400', 'cartflows' ),
490 '500' => __( 'Medium 500', 'cartflows' ),
491 '600' => __( 'Semi-Bold 600', 'cartflows' ),
492 '700' => __( 'Bold 700', 'cartflows' ),
493 '800' => __( 'Extra-Bold 800', 'cartflows' ),
494 '900' => __( 'Ultra-Bold 900', 'cartflows' ),
495 ),
496 'global_checkout_id' => $global_checkout_id ? absint( $global_checkout_id ) : '',
497 'flow_action' => $flow_action,
498 'step_action' => $step_action,
499 'old_global_checkout' => get_option( '_cartflows_old_global_checkout', false ),
500 'woopayments_status' => $this->get_plugin_status( 'woocommerce-payments/woocommerce-payments.php' ),
501 'ca_status' => $this->get_plugin_status( 'woo-cart-abandonment-recovery/woo-cart-abandonment-recovery.php' ),
502 'suretriggers_status' => $this->get_plugin_status( 'suretriggers/suretriggers.php' ),
503 'moderncart_status' => $this->get_plugin_status( 'modern-cart/modern-cart.php' ),
504 'cpsw_connection_status' => 'success' === get_option( 'cpsw_test_con_status', false ) || 'success' === get_option( 'cpsw_con_status', false ),
505 'current_user_can_manage_cartflows' => current_user_can( 'cartflows_manage_settings' ),
506 'is_set_report_email_ids' => get_option( 'cartflows_stats_report_email_ids', false ),
507 'cf_docs_data' => get_option( 'cartflows_docs_data', false ),
508 'woo_order_url' => $order_url,
509 'integrations' => $this->get_recommendation_integrations(),
510 'plugin_installer_nonce' => wp_create_nonce( 'updates' ),
511 'instant_checkout_notice_status' => \Cartflows_Helper::get_admin_settings_option( 'wcf-instant-checkout-notice-skipped', false, false ),
512 'cartflows_admin_notices' => $this->cartflows_admin_notices(),
513 'whats_new_rss_feed' => $this->get_whats_new_rss_feeds_data(),
514 'cartflows_current_version' => CARTFLOWS_VER,
515 'cartflows_previous_versions' => \Cartflows_Helper::get_rollback_versions_options(),
516 'rollback_url' => esc_url( add_query_arg( 'version', 'VERSION', wp_nonce_url( admin_url( 'admin-post.php?action=cartflows_rollback' ), 'cartflows_rollback' ) ) ),
517 'utm_defaults' => \Cartflows_Helper::get_utm_defaults(),
518 /**
519 * Filters whether to hide already-added products from the checkout product search dropdown.
520 *
521 * When `true`, products already present in the current checkout step's product list are
522 * filtered out of the search results, preventing duplicate selection through the UI.
523 *
524 * Note: this is a UI-level restriction only. The server-side save path (REST/AJAX) is
525 * not gated by this filter, so duplicates submitted directly to the save endpoint will
526 * still be persisted.
527 *
528 * @since x.x.x
529 *
530 * @param bool $restrict Whether to hide already-added products in the search dropdown. Default false.
531 */
532 'restrict_duplicate_checkout_products' => apply_filters( 'cartflows_restrict_duplicate_checkout_products', false ),
533 )
534 );
535
536 // Load the codemirror for code editor.
537 wp_enqueue_code_editor(
538 array(
539 'type' => 'text/javascript',
540 )
541 );
542 wp_enqueue_code_editor(
543 array(
544 'type' => 'text/css',
545 )
546 );
547
548 wp_enqueue_script( 'wp-code-editor' );
549 wp_enqueue_style( 'wp-codemirror' );
550
551 if ( $this->is_current_page( $this->menu_slug ) ) {
552 $this->settings_app_scripts( $localize );
553 } elseif ( $this->is_current_page( 'cartflows', array( 'wcf-edit-flow' ) ) ) {
554 wp_enqueue_media();
555 wp_enqueue_editor(); // Require for Order Bump Desc WP Editor/ tinymce field.
556 $this->editor_app_scripts( $localize );
557 }
558 }
559
560 /**
561 * Get recommendation integrations.
562 *
563 * @since 1.11.8
564 *
565 * @return array
566 */
567 public function cartflows_admin_notices() {
568 $notices = array();
569
570 $notices = $this->page_builder_plugin_notices( $notices );
571 $notices = apply_filters( 'cartflows_admin_notices', $notices );
572
573 return $notices;
574 }
575
576 /**
577 * Get builder plugin notices.
578 *
579 * @since 1.11.8
580 * @param array $notices Notices array.
581 * @return array
582 */
583 public function page_builder_plugin_notices( $notices ) {
584 $required_plugins_missing = $this->get_any_required_plugins_status();
585 $default_page_builder = \Cartflows_Helper::get_common_setting( 'default_page_builder' );
586 $required_plugins = array();
587 if ( isset( \Cartflows_Helper::get_plugins_groupby_page_builders()[ $default_page_builder ] ) ) {
588 $required_plugins = \Cartflows_Helper::get_plugins_groupby_page_builders()[ $default_page_builder ];
589 }
590
591 $page_builder_plugins = isset( $required_plugins['plugins'] ) ? $required_plugins['plugins'] : array();
592 $any_inactive = false;
593 if ( ! empty( $page_builder_plugins ) ) {
594 foreach ( $page_builder_plugins as $plugin ) {
595 if ( 'activated' !== $plugin['status'] ) {
596 $any_inactive = true;
597 break; // Stop checking if we found an active plugin.
598 }
599 }
600 }
601
602 // Get the titles.
603 $titles = $this->generate_plugin_titles( $page_builder_plugins );
604
605 if ( 'yes' === $required_plugins_missing && $any_inactive ) {
606 $notices[] = '<div class="wcf-payment-gateway-notice--text"><p class="text-sm text-yellow-700">' . wp_kses_post(
607 sprintf(
608 // 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.
609 __( '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
610 '<span class="capitalize">' . esc_html( $required_plugins['title'] ) . '</span>',
611 '<span class="font-medium"><a href="' . esc_url( admin_url() . 'plugins.php' ) . '" class="underline text-yellow-700 hover:text-yellow-600" target="_blank">',
612 '</a></span>',
613 '<span class="capitalize">' . esc_html( implode( ', ', $titles ) ) . '</span>'
614 )
615 ) . '</p></div>';
616
617 }
618 return $notices;
619 }
620
621 /**
622 * Get required plugin titles from their slugs.
623 *
624 * @since 2.1.6
625 * @param array $plugins plugins array.
626 * @return array
627 */
628 public function generate_plugin_titles( $plugins ) {
629 $titles = array();
630
631 if ( ! is_array( $plugins ) || empty( $plugins ) ) {
632 return $titles;
633 }
634
635 foreach ( $plugins as $plugin ) {
636
637 if ( 'ultimate-addons-for-gutenberg' === $plugin['slug'] ) {
638 $title = 'Spectra'; // Don't add this for translation as it is a plugin name.
639 } else {
640 $slug_parts = explode( '-', $plugin['slug'] );
641 $title = implode( ' ', array_map( 'ucfirst', $slug_parts ) ); // Convert each part to title case.
642 }
643
644 $titles[] = $title;
645 }
646
647 return $titles;
648 }
649
650
651 /**
652 * Get required plugin status.
653 */
654 public function get_required_plugins_data() {
655
656 $missing_plugins_data = array();
657
658 $page_builder_plugins = \Cartflows_Helper::get_plugins_groupby_page_builders();
659
660 foreach ( $page_builder_plugins as $slug => $data ) {
661
662 $missing_plugins_data[ $slug ] = 'no';
663 $current_page_builder_data = $page_builder_plugins[ $slug ];
664
665 foreach ( $current_page_builder_data['plugins'] as $plugin ) {
666 if ( 'activate' === $plugin['status'] || 'install' === $plugin['status'] ) {
667 $missing_plugins_data[ $slug ] = 'yes';
668 }
669 }
670
671 // Divi.
672 if ( 'divi' === $slug ) {
673 if ( 'activate' === $current_page_builder_data['theme-status'] ) {
674 $missing_plugins_data[ $slug ] = 'yes';
675 }
676 }
677 }
678
679 return $missing_plugins_data;
680 }
681 /**
682 * Get required plugin status
683 */
684 public function get_any_required_plugins_status() {
685
686 $default_page_builder = \Cartflows_Helper::get_common_setting( 'default_page_builder' );
687 $any_inactive = 'no';
688
689 $page_builder_plugins = \Cartflows_Helper::get_plugins_groupby_page_builders();
690
691 if ( isset( $page_builder_plugins[ $default_page_builder ] ) ) {
692
693 $current_page_builder_data = $page_builder_plugins[ $default_page_builder ];
694
695 foreach ( $current_page_builder_data['plugins'] as $plugin ) {
696 if ( 'activate' === $plugin['status'] || 'install' === $plugin['status'] ) {
697 $any_inactive = 'yes';
698 }
699 }
700
701 // Divi.
702 if ( 'divi' === $default_page_builder ) {
703 if ( 'activate' === $current_page_builder_data['theme-status'] ) {
704 $any_inactive = 'yes';
705 }
706 }
707 }
708
709 return $any_inactive;
710 }
711
712 /**
713 * Get CartFlows plugin status
714 *
715 * @since 1.11.8
716 *
717 * @return string
718 */
719 public function get_cartflows_pro_plugin_status() {
720
721 $status = $this->get_plugin_status( 'cartflows-pro/cartflows-pro.php' );
722
723 if ( 'active' === $status && ! _is_cartflows_pro() ) {
724
725 $status = 'inactive';
726 }
727
728 return $status;
729 }
730
731 /**
732 * Get plugin status
733 *
734 * @since 1.1.4
735 *
736 * @param string $plugin_init_file Plguin init file.
737 * @return mixed
738 */
739 public function get_plugin_status( $plugin_init_file ) {
740
741 $installed_plugins = get_plugins();
742
743 if ( ! isset( $installed_plugins[ $plugin_init_file ] ) ) {
744 return 'not-installed';
745 } elseif ( is_plugin_active( $plugin_init_file ) ) {
746 return 'active';
747 } else {
748 return 'inactive';
749 }
750 }
751
752 /**
753 * Get plugin status
754 *
755 * @since 1.1.4
756 *
757 * @param string $theme_name Theme slug file.
758 * @return string
759 */
760 public function get_themes_status( $theme_name ) {
761
762 $theme = wp_get_theme();
763
764 // Theme installed and activate.
765 if ( $theme_name === $theme->name || $theme_name === $theme->parent_theme ) {
766 return 'active';
767 }
768
769 // Theme installed but not activate.
770 foreach ( (array) wp_get_themes() as $theme_dir => $theme ) {
771 if ( $theme_name === $theme->name || $theme_name === $theme->parent_theme ) {
772 return 'inactive';
773 }
774 }
775
776 // If none of the above conditions are found then the theme is not installed.
777 return 'not-installed';
778 }
779
780 /**
781 * Settings app scripts
782 *
783 * @param array $localize Variable names.
784 */
785 public function settings_app_scripts( $localize ) {
786 $handle = 'wcf-react-settings';
787 $build_path = CARTFLOWS_ADMIN_CORE_DIR . 'assets/build/';
788 $build_url = CARTFLOWS_ADMIN_CORE_URL . 'assets/build/';
789 $script_asset_path = $build_path . 'settings-app.asset.php';
790 $script_info = file_exists( $script_asset_path )
791 ? include $script_asset_path
792 : array(
793 'dependencies' => array(),
794 'version' => CARTFLOWS_VER,
795 );
796
797 $script_dep = array_merge( $script_info['dependencies'], array( 'updates' ) );
798
799 wp_register_script(
800 $handle,
801 $build_url . 'settings-app.js',
802 $script_dep,
803 $script_info['version'],
804 true
805 );
806
807 wp_register_style(
808 $handle,
809 $build_url . 'settings-app.css',
810 array(),
811 CARTFLOWS_VER
812 );
813
814 wp_enqueue_script( $handle );
815
816 wp_set_script_translations( $handle, 'cartflows' );
817
818 wp_enqueue_style( $handle );
819 wp_style_add_data( $handle, 'rtl', 'replace' );
820
821 $localize['is_flows_limit'] = false; // Removed the flow count condition.
822
823 $localize = $this->debugger_scripts( $localize );
824
825 $localize['save_learn_completed_nonce'] = wp_create_nonce( 'cartflows_save_learn_completed' );
826 $localize['activate_plugin_nonce'] = wp_create_nonce( 'cartflows_activate_plugin' );
827
828 wp_localize_script( $handle, 'cartflows_admin', $localize );
829 }
830
831 /**
832 * Settings app scripts
833 *
834 * @param array $localize Variable names.
835 */
836 public function editor_app_scripts( $localize ) {
837 $handle = 'wcf-editor-app';
838 $build_path = CARTFLOWS_ADMIN_CORE_DIR . 'assets/build/';
839 $build_url = CARTFLOWS_ADMIN_CORE_URL . 'assets/build/';
840 $script_asset_path = $build_path . 'editor-app.asset.php';
841 $script_info = file_exists( $script_asset_path )
842 ? include $script_asset_path
843 : array(
844 'dependencies' => array(),
845 'version' => CARTFLOWS_VER,
846 );
847
848 wp_register_script(
849 $handle,
850 $build_url . 'editor-app.js',
851 $script_info['dependencies'],
852 $script_info['version'],
853 true
854 );
855
856 wp_register_style(
857 $handle,
858 $build_url . 'editor-app.css',
859 array(),
860 CARTFLOWS_VER
861 );
862
863 wp_register_style(
864 'wcf-canvas-base-style',
865 $build_url . 'style-editor-app.css',
866 array(),
867 CARTFLOWS_VER
868 );
869 // Enqueue the registered style.
870 wp_enqueue_style( 'wcf-canvas-base-style' );
871
872 wp_enqueue_script( $handle );
873
874 wp_set_script_translations( $handle, 'cartflows' );
875
876 wp_enqueue_style( $handle );
877 wp_style_add_data( $handle, 'rtl', 'replace' );
878
879 $localize['flow_id'] = isset( $_GET['flow_id'] ) ? intval( $_GET['flow_id'] ) : 0; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
880
881 $step_id = isset( $_GET['step_id'] ) ? intval( $_GET['step_id'] ) : false; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
882
883 if ( $step_id ) {
884
885 $meta_options = AdminHelper::get_step_meta_options( $step_id );
886 $localize['options'] = $meta_options['options'];
887 }
888
889 wp_localize_script( $handle, 'cartflows_admin', $localize );
890 }
891
892 /**
893 * Debugger scripts.
894 *
895 * @param array $localize Variable names.
896 */
897 public function debugger_scripts( $localize ) {
898
899 $logs = LogStatus::get_instance()->get_log_files();
900 $viewed_log = '';
901 $viewed_log_file = '';
902
903 // Calling this function on CartFlows action hook. Hence ignoring nonce.
904 if ( ! empty( $_REQUEST['log_file'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
905
906 $filename = sanitize_text_field( wp_unslash( $_REQUEST['log_file'] ) ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended
907
908 if ( isset( $logs[ $filename ] ) ) {
909 $viewed_log = $filename;
910 $viewed_log_file = $viewed_log . '.log';
911 }
912 } elseif ( ! empty( $logs ) ) {
913 $viewed_log = current( $logs ) ? pathinfo( current( $logs ), PATHINFO_FILENAME ) : '';
914 $viewed_log_file = $viewed_log . '.log';
915 }
916
917 if ( ! empty( $viewed_log_file ) ) {
918 $file_content = file_get_contents( CARTFLOWS_LOG_DIR . $viewed_log_file );
919 $localize['file_content'] = $file_content;
920 $localize['log_key'] = $viewed_log;
921
922 }
923 $localize['logs'] = $logs;
924 return $localize;
925 }
926
927
928 /**
929 * CHeck if it is current page by parameters
930 *
931 * @param string $page_slug Menu name.
932 * @param string $action Menu name.
933 *
934 * @return string page url
935 */
936 public function is_current_page( $page_slug = '', $action = '' ) {
937
938 $page_matched = false;
939
940 if ( empty( $page_slug ) ) {
941 return false;
942 }
943
944 $current_page_slug = isset( $_GET['page'] ) ? sanitize_text_field( $_GET['page'] ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
945 $current_action = isset( $_GET['action'] ) ? sanitize_text_field( $_GET['action'] ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
946
947 if ( ! is_array( $action ) ) {
948 $action = explode( ' ', $action );
949 }
950
951 if ( $page_slug === $current_page_slug && in_array( $current_action, $action, true ) ) {
952 $page_matched = true;
953 }
954
955 return $page_matched;
956 }
957
958 /**
959 * Get CartFlows recommended integrations.
960 *
961 * @return array
962 */
963 public function get_recommendation_integrations() {
964
965 return array(
966 'plugins' => apply_filters(
967 'cartflows_admin_integrated_plugins',
968 array(
969 array(
970 'title' => __( 'WooCommerce', 'cartflows' ),
971 'subtitle' => __( 'WooCommerce is a customizable, open-source ecommerce platform built on WordPress.', 'cartflows' ),
972 'isPro' => false,
973 'status' => $this->get_plugin_status( 'woocommerce/woocommerce.php' ),
974 'slug' => 'woocommerce',
975 'path' => 'woocommerce/woocommerce.php',
976 'redirection' => admin_url( 'admin.php?page=wc-admin' ),
977 'logoPath' => array(
978 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/woo.svg',
979 ),
980 ),
981 array(
982 'title' => __( 'Power Coupons', 'cartflows' ),
983 'subtitle' => __( 'Create powerful coupon rules with advanced conditions, discount types, and usage restrictions for WooCommerce.', 'cartflows' ),
984 'isPro' => false,
985 'status' => $this->get_plugin_status( 'power-coupons/power-coupons.php' ),
986 'slug' => 'power-coupons',
987 'path' => 'power-coupons/power-coupons.php',
988 'redirection' => admin_url( 'admin.php?page=power-coupons' ),
989 'logoPath' => array(
990 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/power-coupons.svg',
991 ),
992 ),
993 array(
994 'title' => __( 'Modern Cart for WooCommerce', 'cartflows' ),
995 'subtitle' => __( 'A fast, customizable cart built to boost conversions, maximise profits, and elevate the shopping experience.', 'cartflows' ),
996 'isPro' => false,
997 'status' => $this->get_plugin_status( 'modern-cart/modern-cart.php' ),
998 'slug' => 'modern-cart',
999 'path' => 'modern-cart/modern-cart.php',
1000 'redirection' => admin_url( 'admin.php?page=moderncart_settings' ),
1001 'logoPath' => array(
1002 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/modern-cart-for-woocommerce.svg',
1003 ),
1004 'hot_badge' => true,
1005 ),
1006 array(
1007 'title' => __( 'Cart Abandonment Recovery', 'cartflows' ),
1008 'subtitle' => __( 'Recover abandonded carts with ease in less than 10 minutes.', 'cartflows' ),
1009 'isPro' => false,
1010 'status' => $this->get_plugin_status( 'woo-cart-abandonment-recovery/woo-cart-abandonment-recovery.php' ),
1011 'slug' => 'woo-cart-abandonment-recovery',
1012 'path' => 'woo-cart-abandonment-recovery/woo-cart-abandonment-recovery.php',
1013 'redirection' => admin_url( 'admin.php?page=woo-cart-abandonment-recovery' ),
1014 'logoPath' => array(
1015 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/wcar.svg',
1016 ),
1017 'hot_badge' => true,
1018 ),
1019 array(
1020 'title' => __( 'Variation Swatches for WooCommerce', 'cartflows' ),
1021 'subtitle' => __( 'Convert dropdown boxes into highly engaging variation swatches.', 'cartflows' ),
1022 'isPro' => false,
1023 'status' => $this->get_plugin_status( 'variation-swatches-woo/variation-swatches-woo.php' ),
1024 'slug' => 'variation-swatches-woo',
1025 'path' => 'variation-swatches-woo/variation-swatches-woo.php',
1026 'redirection' => admin_url( 'admin.php?page=cfvsw_settings' ),
1027 'logoPath' => array(
1028 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/variation-swatches-woo.svg',
1029 ),
1030 'hot_badge' => true,
1031 ),
1032 array(
1033 'title' => __( 'WooPayments: Integrated WooCommerce Payments', 'cartflows' ),
1034 'subtitle' => __( 'Payments made simple, with no monthly fees – designed exclusively for WooCommerce stores.', 'cartflows' ),
1035 'isPro' => false,
1036 'status' => $this->get_plugin_status( 'woocommerce-payments/woocommerce-payments.php' ),
1037 'redirection' => admin_url( 'admin.php?page=wc-admin&path=/payments/connect' ),
1038 'slug' => 'woocommerce-payments',
1039 'path' => 'woocommerce-payments/woocommerce-payments.php',
1040 'logoPath' => array(
1041 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/woocommere-payments.png',
1042 ),
1043 ),
1044 array(
1045 'title' => __( 'OttoKit', 'cartflows' ),
1046 'subtitle' => __( 'OttoKit helps people automate their work by integrating multiple apps and plugins, allowing them to share data and perform tasks automatically.', 'cartflows' ),
1047 'isPro' => true,
1048 'status' => $this->get_plugin_status( 'suretriggers/suretriggers.php' ),
1049 'slug' => 'suretriggers',
1050 'path' => 'suretriggers/suretriggers.php',
1051 'link' => 'https://ottokit.com/pricing/',
1052 'redirection' => admin_url( 'admin.php?page=suretriggers' ),
1053 'logoPath' => array(
1054 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/suretriggers-emblem.svg',
1055 ),
1056 ),
1057 array(
1058 'title' => __( 'Ultimate addons for Elementor', 'cartflows' ),
1059 'subtitle' => __( 'Powerful Elementor addon with InfoCard, Fancy Heading, Before/After Slider, Price Box, FAQ Schema, WooCommerce widgets & Header-Footer builder.', 'cartflows' ),
1060 'isPro' => false,
1061 'status' => $this->get_plugin_status( 'header-footer-elementor/header-footer-elementor.php' ),
1062 'slug' => 'header-footer-elementor',
1063 'path' => 'header-footer-elementor/header-footer-elementor.php',
1064 'redirection' => admin_url( 'admin.php?page=hfe#dashboard' ),
1065 'logoPath' => array(
1066 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/uael.png',
1067 ),
1068 ),
1069 array(
1070 'title' => __( 'Spectra', 'cartflows' ),
1071 'subtitle' => __( 'Power-up the Gutenberg editor with advanced and powerful blocks.', 'cartflows' ),
1072 'isPro' => false,
1073 'status' => $this->get_plugin_status( 'ultimate-addons-for-gutenberg/ultimate-addons-for-gutenberg.php' ),
1074 'slug' => 'ultimate-addons-for-gutenberg',
1075 'path' => 'ultimate-addons-for-gutenberg/ultimate-addons-for-gutenberg.php',
1076 'redirection' => admin_url( 'options-general.php?page=spectra' ),
1077 'logoPath' => array(
1078 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/spectra.svg',
1079 ),
1080 ),
1081 array(
1082 'title' => __( 'SureForms', 'cartflows' ),
1083 'subtitle' => __( 'Transform your WordPress form-building experience with stunning designs, ai integration, and no-code flexibility.', 'cartflows' ),
1084 'isPro' => false,
1085 'status' => $this->get_plugin_status( 'sureforms/sureforms.php' ),
1086 'slug' => 'sureforms',
1087 'path' => 'sureforms/sureforms.php',
1088 'redirection' => admin_url( 'admin.php?page=sureforms_menu' ),
1089 'logoPath' => array(
1090 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/sureforms.svg',
1091 ),
1092 ),
1093 array(
1094 'title' => __( 'SureMembers', 'cartflows' ),
1095 'subtitle' => __( 'A simple yet powerful way to add content restriction to your website.', 'cartflows' ),
1096 'isPro' => true,
1097 'status' => $this->get_plugin_status( 'suremembers/suremembers.php' ),
1098 'slug' => 'suremembers',
1099 'path' => 'suremembers/suremembers.php',
1100 'link' => 'https://suremembers.com/pricing/',
1101 'redirection' => admin_url( 'edit.php?post_type=wsm_access_group' ),
1102 'logoPath' => array(
1103 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/suremembers.svg',
1104 ),
1105 ),
1106 )
1107 ),
1108 'themes' => apply_filters(
1109 'cartflows_admin_integrated_themes',
1110 array(
1111 array(
1112 'title' => __( 'Astra', 'cartflows' ),
1113 'subtitle' => __( 'Astra is fast, fully customizable & beautiful WordPress theme suitable for blog, personal portfolio, business website and WooCommerce storefront.', 'cartflows' ),
1114 'isPro' => false,
1115 'status' => $this->get_themes_status( 'Astra' ),
1116 'slug' => 'astra',
1117 'redirection' => admin_url( 'admin.php?page=wc-admin' ),
1118 'logoPath' => array(
1119 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/themes/astra-icon.svg',
1120 ),
1121 ),
1122 array(
1123 'title' => __( 'Spectra One', 'cartflows' ),
1124 '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' ),
1125 'isPro' => false,
1126 'status' => $this->get_themes_status( 'Spectra One' ),
1127 'slug' => 'spectra-one',
1128 'redirection' => admin_url( 'admin.php?page=wc-admin' ),
1129 'logoPath' => array(
1130 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/themes/spectra-one.svg',
1131 ),
1132 ),
1133 )
1134 ),
1135 );
1136 }
1137
1138 /**
1139 * Prepare the array of RSS Feeds of CartFlows for Whats New slide-our pannel.
1140 *
1141 * @since 2.1.6
1142 * @return array The prepared array of RSS feeds.
1143 */
1144 public function get_whats_new_rss_feeds_data() {
1145 return array(
1146 array(
1147 'key' => 'cartflows',
1148 'label' => 'CartFlows',
1149 'url' => add_query_arg(
1150 array(
1151 'action' => 'cartflows_fetch_whats_new_data',
1152 'nonce' => wp_create_nonce( 'cartflows_fetch_whats_new_data' ),
1153 ),
1154 admin_url( 'admin-ajax.php' )
1155 ), // 'https://cartflows.com/whats-new/feed/'
1156 ),
1157 );
1158 }
1159
1160 /**
1161 * Fetch the Whats New RSS feed from the URL.
1162 *
1163 * @since 1.0.3
1164 * @return void
1165 */
1166 public function fetch_whats_new_data() {
1167 if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( $_GET['nonce'] ), 'cartflows_fetch_whats_new_data' ) ) {
1168 // Verify the nonce, if it fails, return an error.
1169 wp_send_json_error( array( 'message' => __( 'Nonce verification failed.', 'cartflows' ) ) );
1170 }
1171
1172 // Security: Require admin capability to access RSS feed proxy.
1173 if ( ! current_user_can( 'cartflows_manage_flows_steps' ) ) {
1174 wp_send_json_error( array( 'message' => __( 'Permission denied.', 'cartflows' ) ) );
1175 }
1176
1177 // Fetch the RSS feed from the URL. This saves us from the CORS issue.
1178 $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.
1179
1180 // Security: Set proper content type header and strip script tags to prevent XSS.
1181 echo $feed; // phpcs:ignore -- RSS feed content sanitized via wp_kses_post.
1182 exit;
1183 }
1184 }
1185
1186 AdminMenu::get_instance();
1187