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

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