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

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