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

1,185 lines 42.2 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 'google_fonts' => \CartFlows_Font_Families::get_google_fonts(),
482 'system_fonts' => \CartFlows_Font_Families::get_system_fonts(),
483 'font_weights' => array(
484 '100' => __( 'Thin 100', 'cartflows' ),
485 '200' => __( 'Extra-Light 200', 'cartflows' ),
486 '300' => __( 'Light 300', 'cartflows' ),
487 '400' => __( 'Normal 400', 'cartflows' ),
488 '500' => __( 'Medium 500', 'cartflows' ),
489 '600' => __( 'Semi-Bold 600', 'cartflows' ),
490 '700' => __( 'Bold 700', 'cartflows' ),
491 '800' => __( 'Extra-Bold 800', 'cartflows' ),
492 '900' => __( 'Ultra-Bold 900', 'cartflows' ),
493 ),
494 'global_checkout_id' => $global_checkout_id ? absint( $global_checkout_id ) : '',
495 'flow_action' => $flow_action,
496 'step_action' => $step_action,
497 'old_global_checkout' => get_option( '_cartflows_old_global_checkout', false ),
498 'woopayments_status' => $this->get_plugin_status( 'woocommerce-payments/woocommerce-payments.php' ),
499 'ca_status' => $this->get_plugin_status( 'woo-cart-abandonment-recovery/woo-cart-abandonment-recovery.php' ),
500 'suretriggers_status' => $this->get_plugin_status( 'suretriggers/suretriggers.php' ),
501 'moderncart_status' => $this->get_plugin_status( 'modern-cart/modern-cart.php' ),
502 'cpsw_connection_status' => 'success' === get_option( 'cpsw_test_con_status', false ) || 'success' === get_option( 'cpsw_con_status', false ),
503 'current_user_can_manage_cartflows' => current_user_can( 'cartflows_manage_settings' ),
504 'is_set_report_email_ids' => get_option( 'cartflows_stats_report_email_ids', false ),
505 'cf_docs_data' => get_option( 'cartflows_docs_data', false ),
506 'woo_order_url' => $order_url,
507 'integrations' => $this->get_recommendation_integrations(),
508 'plugin_installer_nonce' => wp_create_nonce( 'updates' ),
509 'instant_checkout_notice_status' => \Cartflows_Helper::get_admin_settings_option( 'wcf-instant-checkout-notice-skipped', false, false ),
510 'cartflows_admin_notices' => $this->cartflows_admin_notices(),
511 'whats_new_rss_feed' => $this->get_whats_new_rss_feeds_data(),
512 'cartflows_current_version' => CARTFLOWS_VER,
513 'cartflows_previous_versions' => \Cartflows_Helper::get_rollback_versions_options(),
514 'rollback_url' => esc_url( add_query_arg( 'version', 'VERSION', wp_nonce_url( admin_url( 'admin-post.php?action=cartflows_rollback' ), 'cartflows_rollback' ) ) ),
515 'utm_defaults' => \Cartflows_Helper::get_utm_defaults(),
516 /**
517 * Filters whether to hide already-added products from the checkout product search dropdown.
518 *
519 * When `true`, products already present in the current checkout step's product list are
520 * filtered out of the search results, preventing duplicate selection through the UI.
521 *
522 * Note: this is a UI-level restriction only. The server-side save path (REST/AJAX) is
523 * not gated by this filter, so duplicates submitted directly to the save endpoint will
524 * still be persisted.
525 *
526 * @since x.x.x
527 *
528 * @param bool $restrict Whether to hide already-added products in the search dropdown. Default false.
529 */
530 'restrict_duplicate_checkout_products' => apply_filters( 'cartflows_restrict_duplicate_checkout_products', false ),
531 )
532 );
533
534 // Load the codemirror for code editor.
535 wp_enqueue_code_editor(
536 array(
537 'type' => 'text/javascript',
538 )
539 );
540 wp_enqueue_code_editor(
541 array(
542 'type' => 'text/css',
543 )
544 );
545
546 wp_enqueue_script( 'wp-code-editor' );
547 wp_enqueue_style( 'wp-codemirror' );
548
549 if ( $this->is_current_page( $this->menu_slug ) ) {
550 $this->settings_app_scripts( $localize );
551 } elseif ( $this->is_current_page( 'cartflows', array( 'wcf-edit-flow' ) ) ) {
552 wp_enqueue_media();
553 wp_enqueue_editor(); // Require for Order Bump Desc WP Editor/ tinymce field.
554 $this->editor_app_scripts( $localize );
555 }
556 }
557
558 /**
559 * Get recommendation integrations.
560 *
561 * @since 1.11.8
562 *
563 * @return array
564 */
565 public function cartflows_admin_notices() {
566 $notices = array();
567
568 $notices = $this->page_builder_plugin_notices( $notices );
569 $notices = apply_filters( 'cartflows_admin_notices', $notices );
570
571 return $notices;
572 }
573
574 /**
575 * Get builder plugin notices.
576 *
577 * @since 1.11.8
578 * @param array $notices Notices array.
579 * @return array
580 */
581 public function page_builder_plugin_notices( $notices ) {
582 $required_plugins_missing = $this->get_any_required_plugins_status();
583 $default_page_builder = \Cartflows_Helper::get_common_setting( 'default_page_builder' );
584 $required_plugins = array();
585 if ( isset( \Cartflows_Helper::get_plugins_groupby_page_builders()[ $default_page_builder ] ) ) {
586 $required_plugins = \Cartflows_Helper::get_plugins_groupby_page_builders()[ $default_page_builder ];
587 }
588
589 $page_builder_plugins = isset( $required_plugins['plugins'] ) ? $required_plugins['plugins'] : array();
590 $any_inactive = false;
591 if ( ! empty( $page_builder_plugins ) ) {
592 foreach ( $page_builder_plugins as $plugin ) {
593 if ( 'activated' !== $plugin['status'] ) {
594 $any_inactive = true;
595 break; // Stop checking if we found an active plugin.
596 }
597 }
598 }
599
600 // Get the titles.
601 $titles = $this->generate_plugin_titles( $page_builder_plugins );
602
603 if ( 'yes' === $required_plugins_missing && $any_inactive ) {
604 $notices[] = '<div class="wcf-payment-gateway-notice--text"><p class="text-sm text-yellow-700">' . wp_kses_post(
605 sprintf(
606 // 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.
607 __( '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
608 '<span class="capitalize">' . esc_html( $required_plugins['title'] ) . '</span>',
609 '<span class="font-medium"><a href="' . esc_url( admin_url() . 'plugins.php' ) . '" class="underline text-yellow-700 hover:text-yellow-600" target="_blank">',
610 '</a></span>',
611 '<span class="capitalize">' . esc_html( implode( ', ', $titles ) ) . '</span>'
612 )
613 ) . '</p></div>';
614
615 }
616 return $notices;
617 }
618
619 /**
620 * Get required plugin titles from their slugs.
621 *
622 * @since 2.1.6
623 * @param array $plugins plugins array.
624 * @return array
625 */
626 public function generate_plugin_titles( $plugins ) {
627 $titles = array();
628
629 if ( ! is_array( $plugins ) || empty( $plugins ) ) {
630 return $titles;
631 }
632
633 foreach ( $plugins as $plugin ) {
634
635 if ( 'ultimate-addons-for-gutenberg' === $plugin['slug'] ) {
636 $title = 'Spectra'; // Don't add this for translation as it is a plugin name.
637 } else {
638 $slug_parts = explode( '-', $plugin['slug'] );
639 $title = implode( ' ', array_map( 'ucfirst', $slug_parts ) ); // Convert each part to title case.
640 }
641
642 $titles[] = $title;
643 }
644
645 return $titles;
646 }
647
648
649 /**
650 * Get required plugin status.
651 */
652 public function get_required_plugins_data() {
653
654 $missing_plugins_data = array();
655
656 $page_builder_plugins = \Cartflows_Helper::get_plugins_groupby_page_builders();
657
658 foreach ( $page_builder_plugins as $slug => $data ) {
659
660 $missing_plugins_data[ $slug ] = 'no';
661 $current_page_builder_data = $page_builder_plugins[ $slug ];
662
663 foreach ( $current_page_builder_data['plugins'] as $plugin ) {
664 if ( 'activate' === $plugin['status'] || 'install' === $plugin['status'] ) {
665 $missing_plugins_data[ $slug ] = 'yes';
666 }
667 }
668
669 // Divi.
670 if ( 'divi' === $slug ) {
671 if ( 'activate' === $current_page_builder_data['theme-status'] ) {
672 $missing_plugins_data[ $slug ] = 'yes';
673 }
674 }
675 }
676
677 return $missing_plugins_data;
678 }
679 /**
680 * Get required plugin status
681 */
682 public function get_any_required_plugins_status() {
683
684 $default_page_builder = \Cartflows_Helper::get_common_setting( 'default_page_builder' );
685 $any_inactive = 'no';
686
687 $page_builder_plugins = \Cartflows_Helper::get_plugins_groupby_page_builders();
688
689 if ( isset( $page_builder_plugins[ $default_page_builder ] ) ) {
690
691 $current_page_builder_data = $page_builder_plugins[ $default_page_builder ];
692
693 foreach ( $current_page_builder_data['plugins'] as $plugin ) {
694 if ( 'activate' === $plugin['status'] || 'install' === $plugin['status'] ) {
695 $any_inactive = 'yes';
696 }
697 }
698
699 // Divi.
700 if ( 'divi' === $default_page_builder ) {
701 if ( 'activate' === $current_page_builder_data['theme-status'] ) {
702 $any_inactive = 'yes';
703 }
704 }
705 }
706
707 return $any_inactive;
708 }
709
710 /**
711 * Get CartFlows plugin status
712 *
713 * @since 1.11.8
714 *
715 * @return string
716 */
717 public function get_cartflows_pro_plugin_status() {
718
719 $status = $this->get_plugin_status( 'cartflows-pro/cartflows-pro.php' );
720
721 if ( 'active' === $status && ! _is_cartflows_pro() ) {
722
723 $status = 'inactive';
724 }
725
726 return $status;
727 }
728
729 /**
730 * Get plugin status
731 *
732 * @since 1.1.4
733 *
734 * @param string $plugin_init_file Plguin init file.
735 * @return mixed
736 */
737 public function get_plugin_status( $plugin_init_file ) {
738
739 $installed_plugins = get_plugins();
740
741 if ( ! isset( $installed_plugins[ $plugin_init_file ] ) ) {
742 return 'not-installed';
743 } elseif ( is_plugin_active( $plugin_init_file ) ) {
744 return 'active';
745 } else {
746 return 'inactive';
747 }
748 }
749
750 /**
751 * Get plugin status
752 *
753 * @since 1.1.4
754 *
755 * @param string $theme_name Theme slug file.
756 * @return string
757 */
758 public function get_themes_status( $theme_name ) {
759
760 $theme = wp_get_theme();
761
762 // Theme installed and activate.
763 if ( $theme_name === $theme->name || $theme_name === $theme->parent_theme ) {
764 return 'active';
765 }
766
767 // Theme installed but not activate.
768 foreach ( (array) wp_get_themes() as $theme_dir => $theme ) {
769 if ( $theme_name === $theme->name || $theme_name === $theme->parent_theme ) {
770 return 'inactive';
771 }
772 }
773
774 // If none of the above conditions are found then the theme is not installed.
775 return 'not-installed';
776 }
777
778 /**
779 * Settings app scripts
780 *
781 * @param array $localize Variable names.
782 */
783 public function settings_app_scripts( $localize ) {
784 $handle = 'wcf-react-settings';
785 $build_path = CARTFLOWS_ADMIN_CORE_DIR . 'assets/build/';
786 $build_url = CARTFLOWS_ADMIN_CORE_URL . 'assets/build/';
787 $script_asset_path = $build_path . 'settings-app.asset.php';
788 $script_info = file_exists( $script_asset_path )
789 ? include $script_asset_path
790 : array(
791 'dependencies' => array(),
792 'version' => CARTFLOWS_VER,
793 );
794
795 $script_dep = array_merge( $script_info['dependencies'], array( 'updates' ) );
796
797 wp_register_script(
798 $handle,
799 $build_url . 'settings-app.js',
800 $script_dep,
801 $script_info['version'],
802 true
803 );
804
805 wp_register_style(
806 $handle,
807 $build_url . 'settings-app.css',
808 array(),
809 CARTFLOWS_VER
810 );
811
812 wp_enqueue_script( $handle );
813
814 wp_set_script_translations( $handle, 'cartflows' );
815
816 wp_enqueue_style( $handle );
817 wp_style_add_data( $handle, 'rtl', 'replace' );
818
819 $localize['is_flows_limit'] = false; // Removed the flow count condition.
820
821 $localize = $this->debugger_scripts( $localize );
822
823 $localize['save_learn_completed_nonce'] = wp_create_nonce( 'cartflows_save_learn_completed' );
824 $localize['activate_plugin_nonce'] = wp_create_nonce( 'cartflows_activate_plugin' );
825
826 wp_localize_script( $handle, 'cartflows_admin', $localize );
827 }
828
829 /**
830 * Settings app scripts
831 *
832 * @param array $localize Variable names.
833 */
834 public function editor_app_scripts( $localize ) {
835 $handle = 'wcf-editor-app';
836 $build_path = CARTFLOWS_ADMIN_CORE_DIR . 'assets/build/';
837 $build_url = CARTFLOWS_ADMIN_CORE_URL . 'assets/build/';
838 $script_asset_path = $build_path . 'editor-app.asset.php';
839 $script_info = file_exists( $script_asset_path )
840 ? include $script_asset_path
841 : array(
842 'dependencies' => array(),
843 'version' => CARTFLOWS_VER,
844 );
845
846 wp_register_script(
847 $handle,
848 $build_url . 'editor-app.js',
849 $script_info['dependencies'],
850 $script_info['version'],
851 true
852 );
853
854 wp_register_style(
855 $handle,
856 $build_url . 'editor-app.css',
857 array(),
858 CARTFLOWS_VER
859 );
860
861 wp_register_style(
862 'wcf-canvas-base-style',
863 $build_url . 'style-editor-app.css',
864 array(),
865 CARTFLOWS_VER
866 );
867 // Enqueue the registered style.
868 wp_enqueue_style( 'wcf-canvas-base-style' );
869
870 wp_enqueue_script( $handle );
871
872 wp_set_script_translations( $handle, 'cartflows' );
873
874 wp_enqueue_style( $handle );
875 wp_style_add_data( $handle, 'rtl', 'replace' );
876
877 $localize['flow_id'] = isset( $_GET['flow_id'] ) ? intval( $_GET['flow_id'] ) : 0; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
878
879 $step_id = isset( $_GET['step_id'] ) ? intval( $_GET['step_id'] ) : false; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
880
881 if ( $step_id ) {
882
883 $meta_options = AdminHelper::get_step_meta_options( $step_id );
884 $localize['options'] = $meta_options['options'];
885 }
886
887 wp_localize_script( $handle, 'cartflows_admin', $localize );
888 }
889
890 /**
891 * Debugger scripts.
892 *
893 * @param array $localize Variable names.
894 */
895 public function debugger_scripts( $localize ) {
896
897 $logs = LogStatus::get_instance()->get_log_files();
898 $viewed_log = '';
899 $viewed_log_file = '';
900
901 // Calling this function on CartFlows action hook. Hence ignoring nonce.
902 if ( ! empty( $_REQUEST['log_file'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
903
904 $filename = sanitize_text_field( wp_unslash( $_REQUEST['log_file'] ) ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended
905
906 if ( isset( $logs[ $filename ] ) ) {
907 $viewed_log = $filename;
908 $viewed_log_file = $viewed_log . '.log';
909 }
910 } elseif ( ! empty( $logs ) ) {
911 $viewed_log = current( $logs ) ? pathinfo( current( $logs ), PATHINFO_FILENAME ) : '';
912 $viewed_log_file = $viewed_log . '.log';
913 }
914
915 if ( ! empty( $viewed_log_file ) ) {
916 $file_content = file_get_contents( CARTFLOWS_LOG_DIR . $viewed_log_file );
917 $localize['file_content'] = $file_content;
918 $localize['log_key'] = $viewed_log;
919
920 }
921 $localize['logs'] = $logs;
922 return $localize;
923 }
924
925
926 /**
927 * CHeck if it is current page by parameters
928 *
929 * @param string $page_slug Menu name.
930 * @param string $action Menu name.
931 *
932 * @return string page url
933 */
934 public function is_current_page( $page_slug = '', $action = '' ) {
935
936 $page_matched = false;
937
938 if ( empty( $page_slug ) ) {
939 return false;
940 }
941
942 $current_page_slug = isset( $_GET['page'] ) ? sanitize_text_field( $_GET['page'] ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
943 $current_action = isset( $_GET['action'] ) ? sanitize_text_field( $_GET['action'] ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
944
945 if ( ! is_array( $action ) ) {
946 $action = explode( ' ', $action );
947 }
948
949 if ( $page_slug === $current_page_slug && in_array( $current_action, $action, true ) ) {
950 $page_matched = true;
951 }
952
953 return $page_matched;
954 }
955
956 /**
957 * Get CartFlows recommended integrations.
958 *
959 * @return array
960 */
961 public function get_recommendation_integrations() {
962
963 return array(
964 'plugins' => apply_filters(
965 'cartflows_admin_integrated_plugins',
966 array(
967 array(
968 'title' => __( 'WooCommerce', 'cartflows' ),
969 'subtitle' => __( 'WooCommerce is a customizable, open-source ecommerce platform built on WordPress.', 'cartflows' ),
970 'isPro' => false,
971 'status' => $this->get_plugin_status( 'woocommerce/woocommerce.php' ),
972 'slug' => 'woocommerce',
973 'path' => 'woocommerce/woocommerce.php',
974 'redirection' => admin_url( 'admin.php?page=wc-admin' ),
975 'logoPath' => array(
976 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/woo.svg',
977 ),
978 ),
979 array(
980 'title' => __( 'Power Coupons', 'cartflows' ),
981 'subtitle' => __( 'Create powerful coupon rules with advanced conditions, discount types, and usage restrictions for WooCommerce.', 'cartflows' ),
982 'isPro' => false,
983 'status' => $this->get_plugin_status( 'power-coupons/power-coupons.php' ),
984 'slug' => 'power-coupons',
985 'path' => 'power-coupons/power-coupons.php',
986 'redirection' => admin_url( 'admin.php?page=power-coupons' ),
987 'logoPath' => array(
988 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/power-coupons.svg',
989 ),
990 ),
991 array(
992 'title' => __( 'Modern Cart for WooCommerce', 'cartflows' ),
993 'subtitle' => __( 'A fast, customizable cart built to boost conversions, maximise profits, and elevate the shopping experience.', 'cartflows' ),
994 'isPro' => false,
995 'status' => $this->get_plugin_status( 'modern-cart/modern-cart.php' ),
996 'slug' => 'modern-cart',
997 'path' => 'modern-cart/modern-cart.php',
998 'redirection' => admin_url( 'admin.php?page=moderncart_settings' ),
999 'logoPath' => array(
1000 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/modern-cart-for-woocommerce.svg',
1001 ),
1002 'hot_badge' => true,
1003 ),
1004 array(
1005 'title' => __( 'Cart Abandonment Recovery', 'cartflows' ),
1006 'subtitle' => __( 'Recover abandonded carts with ease in less than 10 minutes.', 'cartflows' ),
1007 'isPro' => false,
1008 'status' => $this->get_plugin_status( 'woo-cart-abandonment-recovery/woo-cart-abandonment-recovery.php' ),
1009 'slug' => 'woo-cart-abandonment-recovery',
1010 'path' => 'woo-cart-abandonment-recovery/woo-cart-abandonment-recovery.php',
1011 'redirection' => admin_url( 'admin.php?page=woo-cart-abandonment-recovery' ),
1012 'logoPath' => array(
1013 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/wcar.svg',
1014 ),
1015 'hot_badge' => true,
1016 ),
1017 array(
1018 'title' => __( 'Variation Swatches for WooCommerce', 'cartflows' ),
1019 'subtitle' => __( 'Convert dropdown boxes into highly engaging variation swatches.', 'cartflows' ),
1020 'isPro' => false,
1021 'status' => $this->get_plugin_status( 'variation-swatches-woo/variation-swatches-woo.php' ),
1022 'slug' => 'variation-swatches-woo',
1023 'path' => 'variation-swatches-woo/variation-swatches-woo.php',
1024 'redirection' => admin_url( 'admin.php?page=cfvsw_settings' ),
1025 'logoPath' => array(
1026 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/variation-swatches-woo.svg',
1027 ),
1028 'hot_badge' => true,
1029 ),
1030 array(
1031 'title' => __( 'WooPayments: Integrated WooCommerce Payments', 'cartflows' ),
1032 'subtitle' => __( 'Payments made simple, with no monthly fees – designed exclusively for WooCommerce stores.', 'cartflows' ),
1033 'isPro' => false,
1034 'status' => $this->get_plugin_status( 'woocommerce-payments/woocommerce-payments.php' ),
1035 'redirection' => admin_url( 'admin.php?page=wc-admin&path=/payments/connect' ),
1036 'slug' => 'woocommerce-payments',
1037 'path' => 'woocommerce-payments/woocommerce-payments.php',
1038 'logoPath' => array(
1039 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/woocommere-payments.png',
1040 ),
1041 ),
1042 array(
1043 'title' => __( 'OttoKit', 'cartflows' ),
1044 'subtitle' => __( 'OttoKit helps people automate their work by integrating multiple apps and plugins, allowing them to share data and perform tasks automatically.', 'cartflows' ),
1045 'isPro' => true,
1046 'status' => $this->get_plugin_status( 'suretriggers/suretriggers.php' ),
1047 'slug' => 'suretriggers',
1048 'path' => 'suretriggers/suretriggers.php',
1049 'link' => 'https://ottokit.com/pricing/',
1050 'redirection' => admin_url( 'admin.php?page=suretriggers' ),
1051 'logoPath' => array(
1052 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/suretriggers-emblem.svg',
1053 ),
1054 ),
1055 array(
1056 'title' => __( 'Ultimate addons for Elementor', 'cartflows' ),
1057 'subtitle' => __( 'Powerful Elementor addon with InfoCard, Fancy Heading, Before/After Slider, Price Box, FAQ Schema, WooCommerce widgets & Header-Footer builder.', 'cartflows' ),
1058 'isPro' => false,
1059 'status' => $this->get_plugin_status( 'header-footer-elementor/header-footer-elementor.php' ),
1060 'slug' => 'header-footer-elementor',
1061 'path' => 'header-footer-elementor/header-footer-elementor.php',
1062 'redirection' => admin_url( 'admin.php?page=hfe#dashboard' ),
1063 'logoPath' => array(
1064 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/uael.png',
1065 ),
1066 ),
1067 array(
1068 'title' => __( 'Spectra', 'cartflows' ),
1069 'subtitle' => __( 'Power-up the Gutenberg editor with advanced and powerful blocks.', 'cartflows' ),
1070 'isPro' => false,
1071 'status' => $this->get_plugin_status( 'ultimate-addons-for-gutenberg/ultimate-addons-for-gutenberg.php' ),
1072 'slug' => 'ultimate-addons-for-gutenberg',
1073 'path' => 'ultimate-addons-for-gutenberg/ultimate-addons-for-gutenberg.php',
1074 'redirection' => admin_url( 'options-general.php?page=spectra' ),
1075 'logoPath' => array(
1076 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/spectra.svg',
1077 ),
1078 ),
1079 array(
1080 'title' => __( 'SureForms', 'cartflows' ),
1081 'subtitle' => __( 'Transform your WordPress form-building experience with stunning designs, ai integration, and no-code flexibility.', 'cartflows' ),
1082 'isPro' => false,
1083 'status' => $this->get_plugin_status( 'sureforms/sureforms.php' ),
1084 'slug' => 'sureforms',
1085 'path' => 'sureforms/sureforms.php',
1086 'redirection' => admin_url( 'admin.php?page=sureforms_menu' ),
1087 'logoPath' => array(
1088 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/sureforms.svg',
1089 ),
1090 ),
1091 array(
1092 'title' => __( 'SureMembers', 'cartflows' ),
1093 'subtitle' => __( 'A simple yet powerful way to add content restriction to your website.', 'cartflows' ),
1094 'isPro' => true,
1095 'status' => $this->get_plugin_status( 'suremembers/suremembers.php' ),
1096 'slug' => 'suremembers',
1097 'path' => 'suremembers/suremembers.php',
1098 'link' => 'https://suremembers.com/pricing/',
1099 'redirection' => admin_url( 'edit.php?post_type=wsm_access_group' ),
1100 'logoPath' => array(
1101 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/suremembers.svg',
1102 ),
1103 ),
1104 )
1105 ),
1106 'themes' => apply_filters(
1107 'cartflows_admin_integrated_themes',
1108 array(
1109 array(
1110 'title' => __( 'Astra', 'cartflows' ),
1111 'subtitle' => __( 'Astra is fast, fully customizable & beautiful WordPress theme suitable for blog, personal portfolio, business website and WooCommerce storefront.', 'cartflows' ),
1112 'isPro' => false,
1113 'status' => $this->get_themes_status( 'Astra' ),
1114 'slug' => 'astra',
1115 'redirection' => admin_url( 'admin.php?page=wc-admin' ),
1116 'logoPath' => array(
1117 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/themes/astra-icon.svg',
1118 ),
1119 ),
1120 array(
1121 'title' => __( 'Spectra One', 'cartflows' ),
1122 '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' ),
1123 'isPro' => false,
1124 'status' => $this->get_themes_status( 'Spectra One' ),
1125 'slug' => 'spectra-one',
1126 'redirection' => admin_url( 'admin.php?page=wc-admin' ),
1127 'logoPath' => array(
1128 'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/themes/spectra-one.svg',
1129 ),
1130 ),
1131 )
1132 ),
1133 );
1134 }
1135
1136 /**
1137 * Prepare the array of RSS Feeds of CartFlows for Whats New slide-our pannel.
1138 *
1139 * @since 2.1.6
1140 * @return array The prepared array of RSS feeds.
1141 */
1142 public function get_whats_new_rss_feeds_data() {
1143 return array(
1144 array(
1145 'key' => 'cartflows',
1146 'label' => 'CartFlows',
1147 'url' => add_query_arg(
1148 array(
1149 'action' => 'cartflows_fetch_whats_new_data',
1150 'nonce' => wp_create_nonce( 'cartflows_fetch_whats_new_data' ),
1151 ),
1152 admin_url( 'admin-ajax.php' )
1153 ), // 'https://cartflows.com/whats-new/feed/'
1154 ),
1155 );
1156 }
1157
1158 /**
1159 * Fetch the Whats New RSS feed from the URL.
1160 *
1161 * @since 1.0.3
1162 * @return void
1163 */
1164 public function fetch_whats_new_data() {
1165 if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( $_GET['nonce'] ), 'cartflows_fetch_whats_new_data' ) ) {
1166 // Verify the nonce, if it fails, return an error.
1167 wp_send_json_error( array( 'message' => __( 'Nonce verification failed.', 'cartflows' ) ) );
1168 }
1169
1170 // Security: Require admin capability to access RSS feed proxy.
1171 if ( ! current_user_can( 'cartflows_manage_flows_steps' ) ) {
1172 wp_send_json_error( array( 'message' => __( 'Permission denied.', 'cartflows' ) ) );
1173 }
1174
1175 // Fetch the RSS feed from the URL. This saves us from the CORS issue.
1176 $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.
1177
1178 // Security: Set proper content type header and strip script tags to prevent XSS.
1179 echo $feed; // phpcs:ignore -- RSS feed content sanitized via wp_kses_post.
1180 exit;
1181 }
1182 }
1183
1184 AdminMenu::get_instance();
1185