plugin = $plugin; } public function register() { add_action( 'admin_menu', array( $this, 'add_menu' ) ); add_action( 'admin_init', array( $this, 'register_settings' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) ); add_action( 'wp_ajax_divi_powers_save_settings', array( $this, 'ajax_save_settings' ) ); add_action( 'wp_ajax_divi_powers_save_admin_overview_state', array( $this, 'ajax_save_admin_overview_state' ) ); add_filter( 'admin_body_class', array( $this, 'filter_admin_body_class' ) ); } public function add_menu() { add_menu_page( __( '5D Powers for Divi', '5d-powers-for-divi' ), __( '5D Powers for Divi', '5d-powers-for-divi' ), 'manage_options', 'divi-powers', array( $this, 'render_page' ), 'dashicons-superhero-alt', 58 ); if ( ! Divi_Powers_Plugin::is_pro_active() ) { add_submenu_page( 'divi-powers', __( '5D Powers for Divi Pro', '5d-powers-for-divi' ), __( 'Upgrade to Pro', '5d-powers-for-divi' ), 'manage_options', 'divi-powers-upgrade', array( $this, 'render_upgrade_page' ) ); } } public function register_settings() { register_setting( 'divi_powers_settings_group', Divi_Powers_Plugin::OPTION_KEY, array( 'type' => 'array', 'sanitize_callback' => array( $this, 'sanitize_settings' ), 'default' => Divi_Powers_Plugin::defaults(), ) ); } /** Preserve dormant add-on values while updating every currently registered switch. */ public function sanitize_settings( $input ) { $input = is_array( $input ) ? $input : array(); $current = get_option( Divi_Powers_Plugin::OPTION_KEY, array() ); $current = is_array( $current ) ? $current : array(); $defaults = Divi_Powers_Plugin::defaults(); foreach ( array_keys( $defaults ) as $key ) { $current[ $key ] = empty( $input[ $key ] ) ? 0 : 1; } return $current; } public function enqueue_assets( $hook_suffix ) { unset( $hook_suffix ); $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( current_user_can( 'manage_options' ) ) { wp_enqueue_style( 'divi-powers-admin-navigation', DIVI_POWERS_URL . 'assets/css/admin-navigation.css', array(), DIVI_POWERS_VERSION ); wp_enqueue_script( 'divi-powers-admin-navigation', DIVI_POWERS_URL . 'assets/js/admin-navigation.js', array(), DIVI_POWERS_VERSION, true ); wp_localize_script( 'divi-powers-admin-navigation', 'DiviPowersProductNav', array( 'isProductPage' => 0 === strpos( $page, 'divi-powers' ), 'pageSlug' => $page, 'moreLabel' => __( 'More', '5d-powers-for-divi' ), 'allPowersLabel' => __( 'All powers', '5d-powers-for-divi' ), 'searchLabel' => __( 'Search powers', '5d-powers-for-divi' ), 'searchPlaceholder' => __( 'Search by name or purpose…', '5d-powers-for-divi' ), 'allLabel' => __( 'All', '5d-powers-for-divi' ), 'enabledLabel' => __( 'Enabled', '5d-powers-for-divi' ), 'disabledLabel' => __( 'Disabled', '5d-powers-for-divi' ), 'openSettingsLabel' => __( 'Open settings', '5d-powers-for-divi' ), 'noResultsLabel' => __( 'No powers match those filters.', '5d-powers-for-divi' ), 'powerSingularLabel' => __( 'power', '5d-powers-for-divi' ), 'powerPluralLabel' => __( 'powers', '5d-powers-for-divi' ), 'dismissedOverviews' => 0 === strpos( $page, 'divi-powers' ) ? $this->dismissed_admin_overviews() : array(), 'overviewNonce' => wp_create_nonce( self::ADMIN_OVERVIEWS_NONCE ), 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'hideOverviewLabel' => __( 'Hide this overview', '5d-powers-for-divi' ), 'showOverviewLabel' => __( 'Show overview', '5d-powers-for-divi' ), 'overviewHiddenLabel' => __( 'overview hidden', '5d-powers-for-divi' ), 'overviewSaveErrorLabel' => __( 'The overview preference could not be saved. Try again.', '5d-powers-for-divi' ), 'groups' => $this->admin_groups(), 'featureRoutes' => $this->feature_routes(), ) ); } if ( 0 !== strpos( $page, 'divi-powers' ) ) { return; } wp_enqueue_style( 'divi-powers-admin', DIVI_POWERS_URL . 'assets/css/admin.css', array(), DIVI_POWERS_VERSION ); wp_enqueue_script( 'divi-powers-confirmation-dialog', DIVI_POWERS_URL . 'assets/js/confirmation-dialog.js', array(), DIVI_POWERS_VERSION, true ); wp_localize_script( 'divi-powers-confirmation-dialog', 'DiviPowersDialogConfig', array( 'eyebrow' => __( 'Please confirm', '5d-powers-for-divi' ), 'title' => __( 'Continue with this action?', '5d-powers-for-divi' ), 'inputLabel' => __( 'New value', '5d-powers-for-divi' ), 'cancelLabel' => __( 'Cancel', '5d-powers-for-divi' ), 'confirmLabel' => __( 'Continue', '5d-powers-for-divi' ), ) ); wp_enqueue_script( 'divi-powers-admin-notices', DIVI_POWERS_URL . 'assets/js/admin-notices.js', array( 'wp-i18n' ), DIVI_POWERS_VERSION, true ); wp_set_script_translations( 'divi-powers-admin-notices', '5d-powers-for-divi' ); wp_enqueue_script( 'divi-powers-admin-autosave', DIVI_POWERS_URL . 'assets/js/admin-autosave.js', array( 'wp-i18n' ), DIVI_POWERS_VERSION, true ); wp_set_script_translations( 'divi-powers-admin-autosave', '5d-powers-for-divi' ); wp_add_inline_script( 'divi-powers-admin-autosave', 'window.DiviPowersAdminAutosave = ' . wp_json_encode( array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'powersNonce' => wp_create_nonce( 'divi_powers_save_settings' ), 'savingText' => __( 'Saving…', '5d-powers-for-divi' ), 'savedText' => __( 'Saved', '5d-powers-for-divi' ), 'errorText' => __( 'Could not save. Please try again.', '5d-powers-for-divi' ), ) ), 'before' ); } /** Prevent a flash of the flat menu before the accessible organizer runs. */ public function filter_admin_body_class( $classes ) { if ( ! current_user_can( 'manage_options' ) ) { return $classes; } $classes = trim( (string) $classes ); if ( false === strpos( ' ' . $classes . ' ', ' dp-admin-navigation-pending ' ) ) { $classes .= ( '' === $classes ? '' : ' ' ) . 'dp-admin-navigation-pending'; } $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( 0 === strpos( $page, 'divi-powers' ) && false === strpos( ' ' . $classes . ' ', ' dp-admin-product-page ' ) ) { $classes .= ' dp-admin-product-page'; } return $classes; } /** Persist one administrator's collapsed/expanded overview preference. */ public function ajax_save_admin_overview_state() { check_ajax_referer( self::ADMIN_OVERVIEWS_NONCE, 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => __( 'You do not have permission to change this admin preference.', '5d-powers-for-divi' ) ), 403 ); } $section = isset( $_POST['section'] ) ? sanitize_key( wp_unslash( $_POST['section'] ) ) : ''; if ( ! $this->valid_admin_overview_key( $section ) ) { wp_send_json_error( array( 'message' => __( 'That overview section is not valid.', '5d-powers-for-divi' ) ), 400 ); } $collapsed = isset( $_POST['collapsed'] ) && '1' === sanitize_text_field( wp_unslash( $_POST['collapsed'] ) ); $dismissed = $this->dismissed_admin_overviews(); if ( $collapsed ) { $dismissed[] = $section; $dismissed = array_slice( array_values( array_unique( $dismissed ) ), 0, 60 ); } else { $dismissed = array_values( array_diff( $dismissed, array( $section ) ) ); } sort( $dismissed ); update_user_meta( get_current_user_id(), self::ADMIN_OVERVIEWS_META, $dismissed ); wp_send_json_success( array( 'dismissed' => $dismissed ) ); } /** Return the current administrator's saved overview identifiers. */ private function dismissed_admin_overviews() { $saved = get_user_meta( get_current_user_id(), self::ADMIN_OVERVIEWS_META, true ); $saved = is_array( $saved ) ? $saved : array(); $output = array(); foreach ( $saved as $section ) { $section = sanitize_key( (string) $section ); if ( $this->valid_admin_overview_key( $section ) ) { $output[] = $section; } } return array_values( array_unique( $output ) ); } /** Keep stored overview keys scoped to known Divi Powers admin routes. */ private function valid_admin_overview_key( $section ) { return is_string( $section ) && strlen( $section ) <= 120 && 1 === preg_match( '/^divi-powers(?:-[a-z0-9-]+)?--[a-z0-9_-]+$/', $section ); } /** Shared taxonomy for the toolbar, WordPress submenu, and Feature Manager. */ private function admin_groups() { $groups = array( array( 'key' => 'build', 'label' => __( 'Build', '5d-powers-for-divi' ), 'title' => __( 'Build & Design', '5d-powers-for-divi' ), 'icon' => '✦', 'description' => __( 'Create engaging Divi pages, reusable layouts, interactions, and funnels.', '5d-powers-for-divi' ), 'items' => array( 'divi-powers-getting-started', 'divi-powers-plus-funnel', 'divi-powers-design-library' ), 'features' => array( 'dismissible', 'trigger_target', 'content_toggle', 'motion_3d', 'media_scroller', 'design_library', 'builder_bridge', 'builder_drawer_fallback', 'plus_funnel' ), ), array( 'key' => 'sell', 'label' => __( 'Sell', '5d-powers-for-divi' ), 'title' => __( 'Commerce & Checkout', '5d-powers-for-divi' ), 'icon' => '◆', 'description' => __( 'Improve checkout, guide customers to the right products, and control cart and order behavior.', '5d-powers-for-divi' ), 'items' => array( 'divi-powers-plus-checkout', 'divi-powers-commerce-utilities' ), 'features' => array( 'commerce_utilities', 'plus_checkout' ), ), array( 'key' => 'optimize', 'label' => __( 'Optimize', '5d-powers-for-divi' ), 'title' => __( 'Optimize & Grow', '5d-powers-for-divi' ), 'icon' => '↗', 'description' => __( 'Connect your marketing tools and learn what helps customers complete a purchase.', '5d-powers-for-divi' ), 'items' => array(), 'features' => array( 'mailchimp_tags' ), ), array( 'key' => 'members', 'label' => __( 'Members', '5d-powers-for-divi' ), 'title' => __( 'Members & Access', '5d-powers-for-divi' ), 'icon' => '◉', 'description' => __( 'Personalize pages and navigation for signed-in customers and members.', '5d-powers-for-divi' ), 'items' => array(), 'features' => array( 'current_user_shortcodes' ), ), array( 'key' => 'site', 'label' => __( 'Site', '5d-powers-for-divi' ), 'title' => __( 'Site Experience', '5d-powers-for-divi' ), 'icon' => '▣', 'description' => __( 'Add useful editing shortcuts and improve everyday site management.', '5d-powers-for-divi' ), 'items' => array(), 'features' => array( 'admin_bar_auto_hide', 'admin_bar_links', 'duplicate_content' ), ), array( 'key' => 'account', 'label' => __( 'Account', '5d-powers-for-divi' ), 'title' => __( 'Connections & Account', '5d-powers-for-divi' ), 'icon' => '⚙', 'description' => __( 'Connect an AI assistant, manage optional services, support, and your product edition.', '5d-powers-for-divi' ), 'items' => array( 'divi-powers-ai-connector', 'divi-powers-support', 'divi-powers-upgrade' ), 'features' => array( 'ai_connection', 'cloud_catalog', 'model_registry' ), ), ); $groups = apply_filters( 'divi_powers_admin_groups', $groups ); return is_array( $groups ) ? array_values( $groups ) : array(); } /** Destination URLs for Powers with their own settings workspace. */ private function feature_routes() { $routes = array( 'cloud_catalog' => admin_url( 'admin.php?page=divi-powers-design-library' ), 'model_registry' => admin_url( 'admin.php?page=divi-powers-ai-connector' ), 'ai_connection' => admin_url( 'admin.php?page=divi-powers-ai-connector' ), 'design_library' => admin_url( 'admin.php?page=divi-powers-design-library' ), 'builder_bridge' => admin_url( 'admin.php?page=divi-powers-ai-connector' ), 'commerce_utilities' => admin_url( 'admin.php?page=divi-powers-commerce-utilities' ), 'plus_checkout' => admin_url( 'admin.php?page=divi-powers-plus-checkout' ), 'plus_funnel' => admin_url( 'admin.php?page=divi-powers-plus-funnel' ), ); $routes = apply_filters( 'divi_powers_feature_routes', $routes ); return is_array( $routes ) ? $routes : array(); } public function render_page() { if ( ! current_user_can( 'manage_options' ) ) { return; } $settings = $this->plugin->settings(); $features = Divi_Powers_Plugin::feature_definitions(); $divi_available = function_exists( 'et_builder_d5_enabled' ); $divi_5_enabled = $divi_available && et_builder_d5_enabled(); $integration_ok = class_exists( '\ET\Builder\VisualBuilder\Assets\PackageBuildManager' ); $setup = class_exists( 'Divi_Powers_Onboarding' ) ? Divi_Powers_Onboarding::setup_summary() : array( 'percent' => 0, 'done' => 0, 'total' => 3, 'starter_ok' => false ); $setup_complete = ! empty( $setup['total'] ) && (int) $setup['done'] >= (int) $setup['total']; $cards = $this->dashboard_cards( $setup ); $highlights = $this->edition_highlights(); $statuses = $this->status_definitions( $features, $settings, $divi_available, $divi_5_enabled, $integration_ok ); $next_step = $this->next_step(); ?>
__( 'You do not have permission to change 5D Powers for Divi settings.', '5d-powers-for-divi' ) ), 403 ); } $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : ''; if ( ! wp_verify_nonce( $nonce, 'divi_powers_save_settings' ) ) { wp_send_json_error( array( 'message' => __( 'Your settings session has expired. Refresh the page and try again.', '5d-powers-for-divi' ) ), 403 ); } $option_key = Divi_Powers_Plugin::OPTION_KEY; $input = isset( $_POST[ $option_key ] ) && is_array( $_POST[ $option_key ] ) ? wp_unslash( $_POST[ $option_key ] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized field-by-field by sanitize_settings() immediately below. $settings = $this->sanitize_settings( $input ); update_option( $option_key, $settings ); wp_send_json_success( array( 'settings' => $settings ) ); } private function edition_highlights() { $highlights = array( 'tone' => 'lite', 'eyebrow' => __( 'INCLUDED WITH FREE', '5d-powers-for-divi' ), 'title' => __( 'Unlimited basic funnels are included with Free.', '5d-powers-for-divi' ), 'description' => __( 'Create funnels with editable Divi pages, WooCommerce checkout, and basic activity reporting. Upgrade to Pro when you need advanced funnel management, testing, and reporting.', '5d-powers-for-divi' ), 'items' => array( __( 'Commerce Utilities', '5d-powers-for-divi' ), __( 'Three Checkout Layouts', '5d-powers-for-divi' ), __( 'Unlimited Basic Funnels', '5d-powers-for-divi' ), __( 'Basic Funnel Activity', '5d-powers-for-divi' ), ), 'url' => admin_url( 'admin.php?page=divi-powers-plus-funnel' ), 'link' => __( 'Open Free funnels', '5d-powers-for-divi' ), ); $highlights = apply_filters( 'divi_powers_edition_highlights', $highlights ); return is_array( $highlights ) ? $highlights : array(); } private function render_edition_highlights( $highlights ) { if ( empty( $highlights['title'] ) ) { return; } $tone = ! empty( $highlights['tone'] ) ? sanitize_key( $highlights['tone'] ) : 'lite'; $items = ! empty( $highlights['items'] ) && is_array( $highlights['items'] ) ? $highlights['items'] : array(); ?>