menu_slug = 'cartflows';
$this->initialize_hooks();
}
/**
* Init Hooks.
*
* @since 1.0.0
* @return void
*/
public function initialize_hooks() {
add_action( 'admin_menu', array( $this, 'setup_menu' ) );
add_action( 'admin_init', array( $this, 'settings_admin_scripts' ) );
add_action( 'admin_init', array( $this, 'add_capabilities_to_admin' ) );
/* Flow content view */
add_action( 'cartflows_render_admin_page_content', array( $this, 'render_content' ), 10, 2 );
add_action( 'edit_form_after_title', array( $this, 'new_admin_flow_setting_redirection' ), 10, 1 );
/* To check the status of gutenberg */
add_action( 'enqueue_block_editor_assets', array( $this, 'set_block_editor_status' ) );
add_action( 'admin_footer', array( $this, 'back_to_new_step_ui_for_gutenberg' ), 999 );
add_action( 'admin_notices', array( $this, 'back_to_new_step_ui_for_classic_editor' ) );
add_action( 'wp_ajax_cartflows_fetch_whats_new_data', array( $this, 'fetch_whats_new_data' ) );
}
/**
* Display admin notices.
*
* @since 1.0.0
*
* @return void
*/
public function back_to_new_step_ui_for_classic_editor() {
if ( CARTFLOWS_STEP_POST_TYPE !== get_post_type() ) {
return;
}
$flow_id = get_post_meta( get_the_id(), 'wcf-flow-id', true );
$step_id = isset( $_GET['post'] ) ? intval( $_GET['post'] ) : 0; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( $flow_id && $step_id ) {
$path = \Cartflows_Helper::get_global_setting( '_cartflows_store_checkout' ) === $flow_id ? 'store-checkout' : 'flows';
$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.
?>
' . wp_kses_post(
sprintf(
// 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.
__( '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
'' . esc_html( $required_plugins['title'] ) . '',
'',
'',
'' . esc_html( implode( ', ', $titles ) ) . ''
)
) . '
';
}
return $notices;
}
/**
* Get required plugin titles from their slugs.
*
* @since 2.1.6
* @param array $plugins plugins array.
* @return array
*/
public function generate_plugin_titles( $plugins ) {
$titles = array();
if ( ! is_array( $plugins ) || empty( $plugins ) ) {
return $titles;
}
foreach ( $plugins as $plugin ) {
if ( 'ultimate-addons-for-gutenberg' === $plugin['slug'] ) {
$title = 'Spectra'; // Don't add this for translation as it is a plugin name.
} else {
$slug_parts = explode( '-', $plugin['slug'] );
$title = implode( ' ', array_map( 'ucfirst', $slug_parts ) ); // Convert each part to title case.
}
$titles[] = $title;
}
return $titles;
}
/**
* Get required plugin status.
*/
public function get_required_plugins_data() {
$missing_plugins_data = array();
$page_builder_plugins = \Cartflows_Helper::get_plugins_groupby_page_builders();
foreach ( $page_builder_plugins as $slug => $data ) {
$missing_plugins_data[ $slug ] = 'no';
$current_page_builder_data = $page_builder_plugins[ $slug ];
foreach ( $current_page_builder_data['plugins'] as $plugin ) {
if ( 'activate' === $plugin['status'] || 'install' === $plugin['status'] ) {
$missing_plugins_data[ $slug ] = 'yes';
}
}
// Divi.
if ( 'divi' === $slug ) {
if ( 'activate' === $current_page_builder_data['theme-status'] ) {
$missing_plugins_data[ $slug ] = 'yes';
}
}
}
return $missing_plugins_data;
}
/**
* Get required plugin status
*/
public function get_any_required_plugins_status() {
$default_page_builder = \Cartflows_Helper::get_common_setting( 'default_page_builder' );
$any_inactive = 'no';
$page_builder_plugins = \Cartflows_Helper::get_plugins_groupby_page_builders();
if ( isset( $page_builder_plugins[ $default_page_builder ] ) ) {
$current_page_builder_data = $page_builder_plugins[ $default_page_builder ];
foreach ( $current_page_builder_data['plugins'] as $plugin ) {
if ( 'activate' === $plugin['status'] || 'install' === $plugin['status'] ) {
$any_inactive = 'yes';
}
}
// Divi.
if ( 'divi' === $default_page_builder ) {
if ( 'activate' === $current_page_builder_data['theme-status'] ) {
$any_inactive = 'yes';
}
}
}
return $any_inactive;
}
/**
* Get CartFlows plugin status
*
* @since 1.11.8
*
* @return string
*/
public function get_cartflows_pro_plugin_status() {
$status = $this->get_plugin_status( 'cartflows-pro/cartflows-pro.php' );
if ( 'active' === $status && ! _is_cartflows_pro() ) {
$status = 'inactive';
}
return $status;
}
/**
* Get plugin status
*
* @since 1.1.4
*
* @param string $plugin_init_file Plguin init file.
* @return mixed
*/
public function get_plugin_status( $plugin_init_file ) {
$installed_plugins = get_plugins();
if ( ! isset( $installed_plugins[ $plugin_init_file ] ) ) {
return 'not-installed';
} elseif ( is_plugin_active( $plugin_init_file ) ) {
return 'active';
} else {
return 'inactive';
}
}
/**
* Get plugin status
*
* @since 1.1.4
*
* @param string $theme_name Theme slug file.
* @return string
*/
public function get_themes_status( $theme_name ) {
$theme = wp_get_theme();
// Theme installed and activate.
if ( $theme_name === $theme->name || $theme_name === $theme->parent_theme ) {
return 'active';
}
// Theme installed but not activate.
foreach ( (array) wp_get_themes() as $theme_dir => $theme ) {
if ( $theme_name === $theme->name || $theme_name === $theme->parent_theme ) {
return 'inactive';
}
}
// If none of the above conditions are found then the theme is not installed.
return 'not-installed';
}
/**
* Settings app scripts
*
* @param array $localize Variable names.
*/
public function settings_app_scripts( $localize ) {
$handle = 'wcf-react-settings';
$build_path = CARTFLOWS_ADMIN_CORE_DIR . 'assets/build/';
$build_url = CARTFLOWS_ADMIN_CORE_URL . 'assets/build/';
$script_asset_path = $build_path . 'settings-app.asset.php';
$script_info = file_exists( $script_asset_path )
? include $script_asset_path
: array(
'dependencies' => array(),
'version' => CARTFLOWS_VER,
);
$script_dep = array_merge( $script_info['dependencies'], array( 'updates' ) );
wp_register_script(
$handle,
$build_url . 'settings-app.js',
$script_dep,
$script_info['version'],
true
);
wp_register_style(
$handle,
$build_url . 'settings-app.css',
array(),
CARTFLOWS_VER
);
// Store Checkout renders the canvas inside this app too, so it needs the React Flow base styles.
wp_register_style(
'wcf-canvas-base-style',
$build_url . 'style-editor-app.css',
array(),
CARTFLOWS_VER
);
wp_enqueue_style( 'wcf-canvas-base-style' );
wp_enqueue_script( $handle );
wp_set_script_translations( $handle, 'cartflows' );
wp_enqueue_style( $handle );
wp_style_add_data( $handle, 'rtl', 'replace' );
$localize['is_flows_limit'] = false; // Removed the flow count condition.
$localize = $this->debugger_scripts( $localize );
$localize['save_learn_completed_nonce'] = wp_create_nonce( 'cartflows_save_learn_completed' );
$localize['activate_plugin_nonce'] = wp_create_nonce( 'cartflows_activate_plugin' );
wp_localize_script( $handle, 'cartflows_admin', $localize );
}
/**
* Settings app scripts
*
* @param array $localize Variable names.
*/
public function editor_app_scripts( $localize ) {
$handle = 'wcf-editor-app';
$build_path = CARTFLOWS_ADMIN_CORE_DIR . 'assets/build/';
$build_url = CARTFLOWS_ADMIN_CORE_URL . 'assets/build/';
$script_asset_path = $build_path . 'editor-app.asset.php';
$script_info = file_exists( $script_asset_path )
? include $script_asset_path
: array(
'dependencies' => array(),
'version' => CARTFLOWS_VER,
);
wp_register_script(
$handle,
$build_url . 'editor-app.js',
$script_info['dependencies'],
$script_info['version'],
true
);
wp_register_style(
$handle,
$build_url . 'editor-app.css',
array(),
CARTFLOWS_VER
);
wp_register_style(
'wcf-canvas-base-style',
$build_url . 'style-editor-app.css',
array(),
CARTFLOWS_VER
);
// Enqueue the registered style.
wp_enqueue_style( 'wcf-canvas-base-style' );
wp_enqueue_script( $handle );
wp_set_script_translations( $handle, 'cartflows' );
wp_enqueue_style( $handle );
wp_style_add_data( $handle, 'rtl', 'replace' );
$localize['flow_id'] = isset( $_GET['flow_id'] ) ? intval( $_GET['flow_id'] ) : 0; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
$step_id = isset( $_GET['step_id'] ) ? intval( $_GET['step_id'] ) : false; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( $step_id ) {
$meta_options = AdminHelper::get_step_meta_options( $step_id );
$localize['options'] = $meta_options['options'];
}
wp_localize_script( $handle, 'cartflows_admin', $localize );
}
/**
* Debugger scripts.
*
* @param array $localize Variable names.
*/
public function debugger_scripts( $localize ) {
$logs = LogStatus::get_instance()->get_log_files();
$viewed_log = '';
$viewed_log_file = '';
// Calling this function on CartFlows action hook. Hence ignoring nonce.
if ( ! empty( $_REQUEST['log_file'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
$filename = sanitize_text_field( wp_unslash( $_REQUEST['log_file'] ) ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( isset( $logs[ $filename ] ) ) {
$viewed_log = $filename;
$viewed_log_file = $viewed_log . '.log';
}
} elseif ( ! empty( $logs ) ) {
$viewed_log = current( $logs ) ? pathinfo( current( $logs ), PATHINFO_FILENAME ) : '';
$viewed_log_file = $viewed_log . '.log';
}
if ( ! empty( $viewed_log_file ) ) {
$file_content = file_get_contents( CARTFLOWS_LOG_DIR . $viewed_log_file );
$localize['file_content'] = $file_content;
$localize['log_key'] = $viewed_log;
}
$localize['logs'] = $logs;
return $localize;
}
/**
* CHeck if it is current page by parameters
*
* @param string $page_slug Menu name.
* @param string $action Menu name.
*
* @return string page url
*/
public function is_current_page( $page_slug = '', $action = '' ) {
$page_matched = false;
if ( empty( $page_slug ) ) {
return false;
}
$current_page_slug = isset( $_GET['page'] ) ? sanitize_text_field( $_GET['page'] ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
$current_action = isset( $_GET['action'] ) ? sanitize_text_field( $_GET['action'] ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( ! is_array( $action ) ) {
$action = explode( ' ', $action );
}
if ( $page_slug === $current_page_slug && in_array( $current_action, $action, true ) ) {
$page_matched = true;
}
return $page_matched;
}
/**
* Get CartFlows recommended integrations.
*
* @return array
*/
public function get_recommendation_integrations() {
return array(
'plugins' => apply_filters(
'cartflows_admin_integrated_plugins',
array(
array(
'title' => __( 'WooCommerce', 'cartflows' ),
'subtitle' => __( 'WooCommerce is a customizable, open-source ecommerce platform built on WordPress.', 'cartflows' ),
'isPro' => false,
'status' => $this->get_plugin_status( 'woocommerce/woocommerce.php' ),
'slug' => 'woocommerce',
'path' => 'woocommerce/woocommerce.php',
'redirection' => admin_url( 'admin.php?page=wc-admin' ),
'logoPath' => array(
'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/woo.svg',
),
),
array(
'title' => __( 'Power Coupons', 'cartflows' ),
'subtitle' => __( 'Create powerful coupon rules with advanced conditions, discount types, and usage restrictions for WooCommerce.', 'cartflows' ),
'isPro' => false,
'status' => $this->get_plugin_status( 'power-coupons/power-coupons.php' ),
'slug' => 'power-coupons',
'path' => 'power-coupons/power-coupons.php',
'redirection' => admin_url( 'admin.php?page=power-coupons' ),
'logoPath' => array(
'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/power-coupons.svg',
),
),
array(
'title' => __( 'Modern Cart for WooCommerce', 'cartflows' ),
'subtitle' => __( 'A fast, customizable cart built to boost conversions, maximise profits, and elevate the shopping experience.', 'cartflows' ),
'isPro' => false,
'status' => $this->get_plugin_status( 'modern-cart/modern-cart.php' ),
'slug' => 'modern-cart',
'path' => 'modern-cart/modern-cart.php',
'redirection' => admin_url( 'admin.php?page=moderncart_settings' ),
'logoPath' => array(
'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/modern-cart-for-woocommerce.svg',
),
'hot_badge' => true,
),
array(
'title' => __( 'Cart Abandonment Recovery', 'cartflows' ),
'subtitle' => __( 'Recover abandonded carts with ease in less than 10 minutes.', 'cartflows' ),
'isPro' => false,
'status' => $this->get_plugin_status( 'woo-cart-abandonment-recovery/woo-cart-abandonment-recovery.php' ),
'slug' => 'woo-cart-abandonment-recovery',
'path' => 'woo-cart-abandonment-recovery/woo-cart-abandonment-recovery.php',
'redirection' => admin_url( 'admin.php?page=woo-cart-abandonment-recovery' ),
'logoPath' => array(
'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/wcar.svg',
),
'hot_badge' => true,
),
array(
'title' => __( 'Variation Swatches for WooCommerce', 'cartflows' ),
'subtitle' => __( 'Convert dropdown boxes into highly engaging variation swatches.', 'cartflows' ),
'isPro' => false,
'status' => $this->get_plugin_status( 'variation-swatches-woo/variation-swatches-woo.php' ),
'slug' => 'variation-swatches-woo',
'path' => 'variation-swatches-woo/variation-swatches-woo.php',
'redirection' => admin_url( 'admin.php?page=cfvsw_settings' ),
'logoPath' => array(
'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/variation-swatches-woo.svg',
),
'hot_badge' => true,
),
array(
'title' => __( 'WooPayments: Integrated WooCommerce Payments', 'cartflows' ),
'subtitle' => __( 'Payments made simple, with no monthly fees – designed exclusively for WooCommerce stores.', 'cartflows' ),
'isPro' => false,
'status' => $this->get_plugin_status( 'woocommerce-payments/woocommerce-payments.php' ),
'redirection' => admin_url( 'admin.php?page=wc-admin&path=/payments/connect' ),
'slug' => 'woocommerce-payments',
'path' => 'woocommerce-payments/woocommerce-payments.php',
'logoPath' => array(
'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/woocommere-payments.png',
),
),
array(
'title' => __( 'OttoKit', 'cartflows' ),
'subtitle' => __( 'OttoKit helps people automate their work by integrating multiple apps and plugins, allowing them to share data and perform tasks automatically.', 'cartflows' ),
'isPro' => true,
'status' => $this->get_plugin_status( 'suretriggers/suretriggers.php' ),
'slug' => 'suretriggers',
'path' => 'suretriggers/suretriggers.php',
'link' => 'https://ottokit.com/pricing/',
'redirection' => admin_url( 'admin.php?page=suretriggers' ),
'logoPath' => array(
'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/suretriggers-emblem.svg',
),
),
array(
'title' => __( 'Ultimate addons for Elementor', 'cartflows' ),
'subtitle' => __( 'Powerful Elementor addon with InfoCard, Fancy Heading, Before/After Slider, Price Box, FAQ Schema, WooCommerce widgets & Header-Footer builder.', 'cartflows' ),
'isPro' => false,
'status' => $this->get_plugin_status( 'header-footer-elementor/header-footer-elementor.php' ),
'slug' => 'header-footer-elementor',
'path' => 'header-footer-elementor/header-footer-elementor.php',
'redirection' => admin_url( 'admin.php?page=hfe#dashboard' ),
'logoPath' => array(
'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/uael.png',
),
),
array(
'title' => __( 'Spectra', 'cartflows' ),
'subtitle' => __( 'Power-up the Gutenberg editor with advanced and powerful blocks.', 'cartflows' ),
'isPro' => false,
'status' => $this->get_plugin_status( 'ultimate-addons-for-gutenberg/ultimate-addons-for-gutenberg.php' ),
'slug' => 'ultimate-addons-for-gutenberg',
'path' => 'ultimate-addons-for-gutenberg/ultimate-addons-for-gutenberg.php',
'redirection' => admin_url( 'options-general.php?page=spectra' ),
'logoPath' => array(
'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/spectra.svg',
),
),
array(
'title' => __( 'SureForms', 'cartflows' ),
'subtitle' => __( 'Transform your WordPress form-building experience with stunning designs, ai integration, and no-code flexibility.', 'cartflows' ),
'isPro' => false,
'status' => $this->get_plugin_status( 'sureforms/sureforms.php' ),
'slug' => 'sureforms',
'path' => 'sureforms/sureforms.php',
'redirection' => admin_url( 'admin.php?page=sureforms_menu' ),
'logoPath' => array(
'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/sureforms.svg',
),
),
array(
'title' => __( 'SureMembers', 'cartflows' ),
'subtitle' => __( 'A simple yet powerful way to add content restriction to your website.', 'cartflows' ),
'isPro' => true,
'status' => $this->get_plugin_status( 'suremembers/suremembers.php' ),
'slug' => 'suremembers',
'path' => 'suremembers/suremembers.php',
'link' => 'https://suremembers.com/pricing/',
'redirection' => admin_url( 'edit.php?post_type=wsm_access_group' ),
'logoPath' => array(
'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/plugins/suremembers.svg',
),
),
)
),
'themes' => apply_filters(
'cartflows_admin_integrated_themes',
array(
array(
'title' => __( 'Astra', 'cartflows' ),
'subtitle' => __( 'Astra is fast, fully customizable & beautiful WordPress theme suitable for blog, personal portfolio, business website and WooCommerce storefront.', 'cartflows' ),
'isPro' => false,
'status' => $this->get_themes_status( 'Astra' ),
'slug' => 'astra',
'redirection' => admin_url( 'admin.php?page=wc-admin' ),
'logoPath' => array(
'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/themes/astra-icon.svg',
),
),
array(
'title' => __( 'Spectra One', 'cartflows' ),
'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' ),
'isPro' => false,
'status' => $this->get_themes_status( 'Spectra One' ),
'slug' => 'spectra-one',
'redirection' => admin_url( 'admin.php?page=wc-admin' ),
'logoPath' => array(
'icon_path' => CARTFLOWS_ADMIN_CORE_URL . 'assets/images/themes/spectra-one.svg',
),
),
)
),
);
}
/**
* Prepare the array of RSS Feeds of CartFlows for Whats New slide-our pannel.
*
* @since 2.1.6
* @return array The prepared array of RSS feeds.
*/
public function get_whats_new_rss_feeds_data() {
return array(
array(
'key' => 'cartflows',
'label' => 'CartFlows',
'url' => add_query_arg(
array(
'action' => 'cartflows_fetch_whats_new_data',
'nonce' => wp_create_nonce( 'cartflows_fetch_whats_new_data' ),
),
admin_url( 'admin-ajax.php' )
), // 'https://cartflows.com/whats-new/feed/'
),
);
}
/**
* Fetch the Whats New RSS feed from the URL.
*
* @since 1.0.3
* @return void
*/
public function fetch_whats_new_data() {
if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( $_GET['nonce'] ), 'cartflows_fetch_whats_new_data' ) ) {
// Verify the nonce, if it fails, return an error.
wp_send_json_error( array( 'message' => __( 'Nonce verification failed.', 'cartflows' ) ) );
}
// Security: Require admin capability to access RSS feed proxy.
if ( ! current_user_can( 'cartflows_manage_flows_steps' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'cartflows' ) ) );
}
// Fetch the RSS feed from the URL. This saves us from the CORS issue.
$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.
/*
* This is an RSS document, parsed as XML by the What's New client. It must not be
* run through wp_kses_post() — that strips the feed's own elements and leaves the
* client nothing to parse. Declaring the type is what stops a browser sniffing the
* response as HTML; the body itself is fetched over HTTPS from our own domain and
* this endpoint is behind a nonce and a capability check.
*/
header( 'Content-Type: application/rss+xml; charset=' . get_option( 'blog_charset' ) );
header( 'X-Content-Type-Options: nosniff' );
echo $feed; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- RSS/XML body, see above.
exit;
}
}
AdminMenu::get_instance();