features = $features; } /** * Register the admin menu. * * @return void */ public function register_menu() { $dashboard_title = __( 'Dashboard', 'edminboost-admin-customization' ); $this->bind_admin_page_title( add_menu_page( $dashboard_title, __( 'EdminBoost', 'edminboost-admin-customization' ), EDMINBOOST_Settings::CAPABILITY, self::PAGE_SLUG, array( $this, 'render_admin_page' ), 'dashicons-performance', self::ADMIN_MENU_POSITION ), $dashboard_title ); $this->register_plugin_submenu_page( self::PAGE_SLUG, $dashboard_title, $dashboard_title, self::PAGE_SLUG, array( $this, 'render_admin_page' ) ); $this->register_plugin_submenu_page( self::PAGE_SLUG, __( 'Layouts', 'edminboost-admin-customization' ), __( 'Layouts', 'edminboost-admin-customization' ), self::PAGE_SLUG . EDMINBOOST_Command_Center::PAGE_PRESETS, array( $this, 'render_presets_page' ) ); $this->register_plugin_submenu_page( self::PAGE_SLUG, __( 'Theme', 'edminboost-admin-customization' ), __( 'Theme', 'edminboost-admin-customization' ), self::PAGE_SLUG . EDMINBOOST_Command_Center::PAGE_APPEARANCE, array( $this, 'render_appearance_page' ) ); $this->register_plugin_submenu_page( self::PAGE_SLUG, __( 'Top Bar', 'edminboost-admin-customization' ), __( 'Top Bar', 'edminboost-admin-customization' ), self::PAGE_SLUG . EDMINBOOST_Command_Center::PAGE_MAPPER, array( $this, 'render_mapper_page' ) ); $this->register_plugin_submenu_page( self::PAGE_SLUG, __( 'Menu Studio', 'edminboost-admin-customization' ), __( 'Menu Studio', 'edminboost-admin-customization' ), self::PAGE_SLUG . EDMINBOOST_Command_Center::PAGE_MENU_STUDIO, array( $this, 'render_menu_studio_page' ) ); // Tab-only pages — registered but not shown in the sidebar. $this->register_plugin_submenu_page( null, __( 'Productivity', 'edminboost-admin-customization' ), __( 'Productivity', 'edminboost-admin-customization' ), self::PAGE_SLUG . EDMINBOOST_Command_Center::PAGE_PRODUCTIVITY, array( $this, 'render_productivity_page' ) ); $this->register_plugin_submenu_page( null, __( 'Security', 'edminboost-admin-customization' ), __( 'Security', 'edminboost-admin-customization' ), self::PAGE_SLUG . EDMINBOOST_Command_Center::PAGE_SECURITY, array( $this, 'render_security_page' ) ); $this->register_plugin_submenu_page( null, __( 'Performance', 'edminboost-admin-customization' ), __( 'Performance', 'edminboost-admin-customization' ), self::PAGE_SLUG . EDMINBOOST_Command_Center::PAGE_PERFORMANCE, array( $this, 'render_performance_page' ) ); if ( apply_filters( 'edminboost_should_register_licensed_admin_menus', false ) ) { /** * Register optional admin menus (premium package adds Settings and White Label when licensed). * * @param EDMINBOOST_Admin $admin Admin instance. */ do_action( 'edminboost_register_admin_menus', $this ); } // Legacy slugs — redirect to Dashboard (not shown in sidebar). $this->register_plugin_submenu_page( null, $dashboard_title, $dashboard_title, self::PAGE_SLUG . EDMINBOOST_Command_Center::PAGE_ONBOARDING, array( $this, 'render_onboarding_page' ) ); $this->register_plugin_submenu_page( null, __( 'Theme', 'edminboost-admin-customization' ), __( 'Theme', 'edminboost-admin-customization' ), self::PAGE_SLUG . EDMINBOOST_Command_Center::PAGE_BEHAVIOR, array( $this, 'render_behavior_page' ) ); } /** * Register a plugin submenu page and bind its document title. * * @param string|null $parent_slug Parent menu slug (null for tab-only pages). * @param string $page_title Document title for the screen. * @param string $menu_title Sidebar menu label. * @param string $menu_slug Unique page slug. * @param callable $callback Page render callback. * @return void */ private function register_plugin_submenu_page( $parent_slug, $page_title, $menu_title, $menu_slug, $callback ) { $this->bind_admin_page_title( add_submenu_page( $parent_slug, $page_title, $menu_title, EDMINBOOST_Settings::CAPABILITY, $menu_slug, $callback ), $page_title ); } /** * Ensure admin-header.php receives a string page title (PHP 8.1+). * * Hidden submenu pages (null parent) are not resolved by get_admin_page_title(). * * @param string|false $hook_suffix Hook suffix from add_menu_page / add_submenu_page. * @param string $page_title Document title when the screen loads. * @return void */ private function bind_admin_page_title( $hook_suffix, $page_title ) { if ( ! $hook_suffix ) { return; } add_action( "load-{$hook_suffix}", static function () use ( $page_title ) { global $title; $title = $page_title; } ); } /** * Enforce canonical EdminBoost submenu order after Menu Studio reordering. * * @return void */ public function normalize_plugin_submenu() { global $submenu; if ( empty( $submenu[ self::PAGE_SLUG ] ) || ! is_array( $submenu[ self::PAGE_SLUG ] ) ) { return; } $dashboard_label = __( 'Dashboard', 'edminboost-admin-customization' ); $items_by_slug = array(); foreach ( $submenu[ self::PAGE_SLUG ] as $item ) { if ( empty( $item[2] ) ) { continue; } $slug = (string) $item[2]; if ( ! isset( $items_by_slug[ $slug ] ) ) { $items_by_slug[ $slug ] = $item; } } $ordered = array(); $seen = array(); foreach ( EDMINBOOST_Command_Center::get_page_links() as $link ) { $slug = (string) $link['slug']; if ( ! isset( $items_by_slug[ $slug ] ) ) { continue; } $item = $items_by_slug[ $slug ]; if ( self::PAGE_SLUG === $slug ) { $item[0] = $dashboard_label; } $ordered[] = $item; $seen[ $slug ] = true; } foreach ( $submenu[ self::PAGE_SLUG ] as $item ) { if ( empty( $item[2] ) ) { $ordered[] = $item; continue; } $slug = (string) $item[2]; if ( isset( $seen[ $slug ] ) ) { continue; } $ordered[] = $item; } $submenu[ self::PAGE_SLUG ] = $ordered; } /** * Register plugin settings. * * @return void */ public function register_settings() { register_setting( EDMINBOOST_Settings::SETTINGS_GROUP, EDMINBOOST_Settings::OPTION_NAME, array( 'type' => 'array', 'sanitize_callback' => array( 'EDMINBOOST_Settings', 'sanitize' ), 'default' => EDMINBOOST_Settings::get_defaults(), 'show_in_rest' => false, ) ); } /** * Get plugin settings. * * @return array */ public function get_settings() { return EDMINBOOST_Settings::get(); } /** * Render the Dashboard hub page. * * @return void */ public function render_admin_page() { if ( ! current_user_can( EDMINBOOST_Settings::CAPABILITY ) ) { return; } $cc_settings = EDMINBOOST_Command_Center::get_settings(); $current_page = self::PAGE_SLUG; include EDMINBOOST_PLUGIN_DIR . 'admin/partials/edminboost-admin-page.php'; } /** * Redirect legacy onboarding URL to Dashboard. * * @return void */ public function render_onboarding_page() { if ( ! current_user_can( EDMINBOOST_Settings::CAPABILITY ) ) { return; } wp_safe_redirect( admin_url( 'admin.php?page=' . self::PAGE_SLUG ) ); exit; } /** * Render the layout mapper page. * * @return void */ public function render_mapper_page() { $this->render_command_center_page( 'admin/partials/edminboost-mapper-page.php' ); } /** * Render the presets & roles page. * * @return void */ public function render_presets_page() { $this->render_command_center_page( 'admin/partials/edminboost-presets-page.php' ); } /** * Render the Menu Studio page. * * @return void */ public function render_menu_studio_page() { $this->render_command_center_page( 'admin/partials/edminboost-menu-studio-page.php' ); } /** * Redirect legacy behavior URL to Appearance. * * @return void */ public function render_behavior_page() { if ( ! current_user_can( EDMINBOOST_Settings::CAPABILITY ) ) { return; } wp_safe_redirect( admin_url( 'admin.php?page=' . self::PAGE_SLUG . EDMINBOOST_Command_Center::PAGE_APPEARANCE ) ); exit; } /** * Render the Appearance settings page. * * @return void */ public function render_appearance_page() { $this->render_command_center_page( 'admin/partials/edminboost-appearance-page.php' ); } /** * Render the Productivity settings page. * * @return void */ public function render_productivity_page() { $this->render_command_center_page( 'admin/partials/edminboost-productivity-page.php' ); } /** * Render the Security settings page. * * @return void */ public function render_security_page() { $this->render_command_center_page( 'admin/partials/edminboost-security-page.php' ); } /** * Render the Performance settings page. * * @return void */ public function render_performance_page() { $this->render_command_center_page( 'admin/partials/edminboost-performance-page.php' ); } /** * Render a Command Center partial. * * @param string $partial Relative path under the plugin directory. * @return void */ private function render_command_center_page( $partial ) { if ( ! current_user_can( EDMINBOOST_Settings::CAPABILITY ) ) { return; } $cc_settings = EDMINBOOST_Command_Center::get_settings(); $current_page = $this->get_current_cc_page_slug(); include EDMINBOOST_PLUGIN_DIR . $partial; } /** * Render a Command Center partial (used by premium licensed screens and CC tab AJAX). * * @param string $partial Relative path under the plugin directory. * @return void */ public function render_command_center_partial( $partial ) { $this->render_command_center_page( $partial ); } /** * Include optional admin partial paths supplied by filters (empty on WordPress.org). * * When the first argument is an associative array, its keys are extracted into the * partial include scope (partials included from a static method do not inherit the caller scope). * * @param string $slot Markup slot key (edminboost_*); premium package maps slots to partials. * @param mixed ...$args Optional variable context array, then extra filter arguments for the slot filter. * @return void */ public static function include_filtered_partials( $slot, ...$args ) { $include_context = array(); $filter_args = $args; if ( ! empty( $args ) && is_array( $args[0] ) && self::is_partial_include_context( $args[0] ) ) { $include_context = $args[0]; $filter_args = array_slice( $args, 1 ); } /** * Filter optional admin partial paths to include at a Command Center markup slot. * * @param string[] $partial_paths Absolute paths; default empty array. * @param string $slot Markup slot key (same value passed to include_filtered_partials()). */ $partial_paths = apply_filters( 'edminboost_command_center_partial_paths', array(), $slot, ...$filter_args ); if ( ! is_array( $partial_paths ) ) { return; } foreach ( $partial_paths as $partial_path ) { if ( ! is_string( $partial_path ) || ! is_readable( $partial_path ) ) { continue; } if ( ! empty( $include_context ) ) { // phpcs:ignore WordPress.PHP.DontExtract.extract_extract -- scoped partial variables from caller. extract( $include_context, EXTR_SKIP ); } include $partial_path; } } /** * Whether an array is a variable context map for partial includes (not a numeric filter argument list). * * @param array $context Candidate context array. * @return bool */ private static function is_partial_include_context( $context ) { if ( ! is_array( $context ) || array() === $context ) { return false; } foreach ( array_keys( $context ) as $key ) { if ( is_string( $key ) ) { return true; } } return false; } /** * Resolve the active Command Center admin page slug for nav highlighting. * * Uses the global $plugin_page set by wp-admin (or primed during CC tab AJAX). * * @return string */ private function get_current_cc_page_slug() { global $plugin_page; if ( ! empty( $plugin_page ) ) { return sanitize_key( $plugin_page ); } if ( isset( $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return sanitize_key( wp_unslash( $_GET['page'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended } return ''; } /** * Enqueue admin styles. * * @param string $hook_suffix Current admin page hook. * @return void */ public function enqueue_styles( $hook_suffix ) { if ( ! $this->is_plugin_screen( $hook_suffix ) ) { return; } wp_enqueue_style( 'dashicons' ); wp_enqueue_style( 'edminboost-themes', EDMINBOOST_PLUGIN_URL . 'admin/css/edminboost-themes.css', array(), EDMINBOOST_VERSION ); wp_enqueue_style( 'edminboost-admin', EDMINBOOST_PLUGIN_URL . 'admin/css/edminboost-admin.css', array( 'dashicons', 'edminboost-themes' ), EDMINBOOST_VERSION ); } /** * Enqueue admin scripts. * * @param string $hook_suffix Current admin page hook. * @return void */ public function enqueue_scripts( $hook_suffix ) { if ( ! $this->is_plugin_screen( $hook_suffix ) ) { return; } $screen_page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended /** * Filter script dependencies for the plugin admin bundle. * * Premium package may enqueue drawer assets and append handles (e.g. edminboost-command-center-bar). * * @param string[] $deps Script handle dependencies. * @param string $hook_suffix Current admin page hook. * @param string $screen_page Current admin page slug query arg. */ $admin_script_deps = apply_filters( 'edminboost_admin_script_deps', array(), $hook_suffix, $screen_page ); wp_enqueue_script( 'edminboost-admin', EDMINBOOST_PLUGIN_URL . 'admin/js/edminboost-admin.js', $admin_script_deps, EDMINBOOST_VERSION, true ); $localize = array( 'version' => EDMINBOOST_VERSION, 'isPremiumBuild' => EDMINBOOST_Plan::is_direct_build(), 'currentPage' => $screen_page, 'optionName' => EDMINBOOST_Settings::OPTION_NAME, 'strings' => array( 'ready' => __( 'EdminBoost is ready.', 'edminboost-admin-customization' ), 'configureItem' => __( 'Configure', 'edminboost-admin-customization' ), 'removeFromTopBar' => __( 'Remove from top bar', 'edminboost-admin-customization' ), 'emptyCanvas' => __( 'Toggle items from the left panel or drag them here to build your top bar.', 'edminboost-admin-customization' ), 'customLinkPathRequired' => __( 'Enter an admin path.', 'edminboost-admin-customization' ), 'customLinkLabelRequired' => __( 'Enter a label.', 'edminboost-admin-customization' ), 'customLinkPathInvalid' => __( 'Use a relative admin path such as edit.php?post_type=page.', 'edminboost-admin-customization' ), 'customLinkAnchorInvalid' => __( 'Use letters, numbers, hyphens, underscores, or dots in the anchor.', 'edminboost-admin-customization' ), 'customLinkDuplicate' => __( 'That link is already on your top bar.', 'edminboost-admin-customization' ), 'topBarOverviewEmpty' => __( 'Admin link shortcuts can appear in your WordPress top bar. Add links in the Top Bar editor.', 'edminboost-admin-customization' ), 'topBarOverviewHasLinks' => __( 'Admin link shortcuts appear in your WordPress top bar.', 'edminboost-admin-customization' ), 'settingsSaved' => __( 'Settings saved.', 'edminboost-admin-customization' ), 'settingsSaveFailed' => __( 'Could not save settings. Please try again.', 'edminboost-admin-customization' ), 'sessionExpired' => __( 'Your session expired. Refresh the page and log in again.', 'edminboost-admin-customization' ), 'presetApplied' => __( 'Preset applied.', 'edminboost-admin-customization' ), 'presetNameRequired' => __( 'Enter a name for your preset.', 'edminboost-admin-customization' ), 'presetSaved' => __( 'Preset saved.', 'edminboost-admin-customization' ), 'presetRenamed' => __( 'Preset renamed.', 'edminboost-admin-customization' ), 'presetDuplicated' => __( 'Preset duplicated.', 'edminboost-admin-customization' ), 'emptyMenuCanvas' => __( 'Drag menu items here to reorder your admin sidebar.', 'edminboost-admin-customization' ), 'removeFromSidebar' => __( 'Remove from sidebar', 'edminboost-admin-customization' ), 'selectLayoutPreset' => __( 'Select a layout preset to continue.', 'edminboost-admin-customization' ), 'saveAndLaunch' => __( 'Save and launch', 'edminboost-admin-customization' ), 'presetBadgeBuiltIn' => __( 'Built-in', 'edminboost-admin-customization' ), 'presetBadgeSaved' => __( 'Saved', 'edminboost-admin-customization' ), 'presetBadgeVirtual' => __( 'Layout', 'edminboost-admin-customization' ), 'emptyLayoutPreview' => __( 'No links in this preview yet.', 'edminboost-admin-customization' ), 'emptySidebarPreview' => __( 'No sidebar items in this preview yet.', 'edminboost-admin-customization' ), 'previewWordPressLogo' => __( 'WordPress', 'edminboost-admin-customization' ), 'previewProfile' => __( 'My account', 'edminboost-admin-customization' ), 'pageLoading' => __( 'Loading…', 'edminboost-admin-customization' ), 'pageLoadFailed' => __( 'Could not load that page. Please try again.', 'edminboost-admin-customization' ), 'formResetConfirm' => __( 'Reset all fields on this page to their default values? Your saved settings are not changed until you click Save.', 'edminboost-admin-customization' ), 'formResetConfirmYes' => __( 'Yes, reset to defaults', 'edminboost-admin-customization' ), 'formResetCancel' => __( 'Cancel', 'edminboost-admin-customization' ), ), 'presets' => self::get_presets_for_js(), 'presetCategories' => EDMINBOOST_Command_Center::get_preset_categories(), 'themePresets' => EDMINBOOST_Theme::get_presets_for_js(), 'themeColorLabels' => EDMINBOOST_Theme::get_color_labels(), 'themeSettings' => EDMINBOOST_Theme::get_settings(), 'settingsSave' => array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'action' => 'edminboost_save_settings', ), 'ccNav' => array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'action' => 'edminboost_load_cc_page', 'nonce' => wp_create_nonce( 'edminboost_cc_nav' ), ), ); /** * Filter localized data for plugin admin JavaScript. * * @param array $localize Localized script data. * @param string $screen_page Current admin page slug query arg. */ $localize = apply_filters( 'edminboost_admin_script_data', $localize, $screen_page ); wp_localize_script( 'edminboost-admin', 'edminboostData', $localize ); if ( apply_filters( 'edminboost_should_enqueue_licensed_admin_assets', false ) ) { /** * Enqueue licensed admin scripts after the base plugin script. * * @param string $hook_suffix Current admin page hook. * @param string $screen_page Current admin page slug query arg. */ do_action( 'edminboost_enqueue_licensed_admin_scripts', $hook_suffix, $screen_page ); } } /** * Build preset catalog for admin JavaScript (includes resolved layouts). * * @return array */ private function get_presets_for_js() { $presets = array(); foreach ( EDMINBOOST_Command_Center::get_picker_presets( true ) as $preset_id => $preset ) { if ( ! EDMINBOOST_Plan::include_preset_in_ui( $preset_id, 'layout' ) ) { continue; } $presets[ $preset_id ] = array( 'name' => isset( $preset['name'] ) ? $preset['name'] : $preset_id, 'description' => isset( $preset['description'] ) ? $preset['description'] : '', 'system' => ! empty( $preset['system'] ), 'virtual' => ! empty( $preset['virtual'] ), 'category' => ! empty( $preset['virtual'] ) ? 'source' : ( ! empty( $preset['system'] ) ? ( isset( $preset['category'] ) ? $preset['category'] : 'workflow' ) : 'saved' ), 'top_bar_items' => EDMINBOOST_Command_Center::resolve_preset_top_bar_items( $preset_id ), 'sidebar_items' => EDMINBOOST_Command_Center::resolve_preset_sidebar_preview_items( $preset_id ), 'menu_studio' => EDMINBOOST_Command_Center::resolve_preset_menu_studio( $preset_id ), 'visible_menu_slugs' => EDMINBOOST_Command_Center::get_preset_visible_menu_slugs( $preset_id ), 'visible_top_level_menu_slugs' => EDMINBOOST_Command_Center::get_preset_visible_top_level_menu_slugs( $preset_id ), ); } return $presets; } /** * Redirect to the Dashboard setup wizard after activation. * * @return void */ public function maybe_activation_redirect() { if ( ! is_user_logged_in() || ! current_user_can( EDMINBOOST_Settings::CAPABILITY ) ) { return; } if ( wp_doing_ajax() ) { return; } if ( ! get_transient( 'edminboost_activation_redirect' ) ) { return; } delete_transient( 'edminboost_activation_redirect' ); if ( EDMINBOOST_Command_Center::is_setup_complete() ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- core activation flow. if ( isset( $_GET['activate-multi'] ) ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- admin page routing. $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; if ( self::PAGE_SLUG === $page ) { return; } wp_safe_redirect( admin_url( 'admin.php?page=' . self::PAGE_SLUG ) ); exit; } /** * Add Go Pro and Docs links on the Plugins screen (under the plugin name). * * Runs after the licensed Settings shortcut so order is Go Pro, Docs, Settings, … * * @param array $links Plugin action links. * @return array */ public function add_plugin_action_links( $links ) { $docs_link = sprintf( '%2$s', esc_url( EDMINBOOST_PLUGIN_DOCS_URL ), esc_html__( 'Docs', 'edminboost-admin-customization' ) ); array_unshift( $links, $docs_link ); /** * Filter whether the Plugins screen shows the external Go Pro link. * * @param bool $show Default true (WordPress.org and unlicensed premium). */ if ( apply_filters( 'edminboost_show_plugin_go_pro_link', true ) ) { $go_pro_link = sprintf( '%2$s', esc_url( EDMINBOOST_PLUGIN_GO_PRO_URL ), esc_html__( 'Go Pro', 'edminboost-admin-customization' ) ); array_unshift( $links, $go_pro_link ); } return $links; } /** * Whether the given admin page slug belongs to this plugin. * * @param string|null $page Optional page slug; reads $_GET['page'] when null. * @return bool */ public static function is_plugin_admin_page( $page = null ) { if ( null === $page ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- routing helper. $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; } if ( '' === $page ) { return false; } return 0 === strpos( $page, self::PAGE_SLUG ); } /** * Output buffer depth before an AJAX handler starts its capture buffer. * * @var int */ private static $ajax_json_buffer_depth = 0; /** * Whether the early EdminBoost admin-ajax output buffer was started. * * @var bool */ private static $ajax_prime_buffer_started = false; /** * Start output buffering as early as possible for EdminBoost admin-ajax actions. * * @return void */ public function prime_edminboost_ajax_output_buffer() { if ( ! wp_doing_ajax() ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- routing only. $action = isset( $_REQUEST['action'] ) ? sanitize_key( wp_unslash( $_REQUEST['action'] ) ) : ''; if ( '' === $action || 0 !== strpos( $action, 'edminboost_' ) ) { return; } if ( self::$ajax_prime_buffer_started ) { return; } self::$ajax_prime_buffer_started = true; self::$ajax_json_buffer_depth = ob_get_level(); ob_start(); } /** * Begin capturing stray output for an EdminBoost admin-ajax handler. * * @return void */ public static function begin_ajax_json_response() { self::begin_ajax_json_buffer(); } /** * @return void */ private static function begin_ajax_json_buffer() { if ( ! self::$ajax_prime_buffer_started ) { self::$ajax_json_buffer_depth = ob_get_level(); ob_start(); return; } ob_start(); } /** * Discard captured AJAX output so the response body is valid JSON only. * * @return void */ private static function discard_ajax_json_buffer() { while ( ob_get_level() > self::$ajax_json_buffer_depth ) { ob_end_clean(); } } /** * @param mixed $data Success payload. * @return void */ public static function send_ajax_json_success( $data ) { self::discard_ajax_json_buffer(); wp_send_json_success( $data ); } /** * @param mixed $data Error payload. * @param int|null $status_code Optional HTTP status code. * @return void */ public static function send_ajax_json_error( $data, $status_code = null ) { self::discard_ajax_json_buffer(); if ( null !== $status_code ) { wp_send_json_error( $data, $status_code ); } wp_send_json_error( $data ); } /** * AJAX: load a Command Center tab without a full page reload. * * @return void */ public function ajax_load_cc_page() { self::begin_ajax_json_buffer(); if ( ! current_user_can( EDMINBOOST_Settings::CAPABILITY ) ) { self::send_ajax_json_error( array( 'message' => __( 'You do not have permission to view this page.', 'edminboost-admin-customization' ), ), 403 ); } if ( ! check_ajax_referer( 'edminboost_cc_nav', 'nonce', false ) ) { self::send_ajax_json_error( array( 'message' => __( 'Security check failed. Refresh the page and try again.', 'edminboost-admin-customization' ), ), 403 ); } $page = isset( $_POST['page'] ) ? sanitize_key( wp_unslash( $_POST['page'] ) ) : ''; $use_form_defaults = ! empty( $_POST['form_defaults'] ); if ( ! $this->is_valid_cc_nav_page( $page ) ) { self::send_ajax_json_error( array( 'message' => __( 'Unknown Command Center page.', 'edminboost-admin-customization' ), ), 400 ); } $html = $this->capture_cc_page_html( $page, $use_form_defaults ); if ( '' === $html ) { self::send_ajax_json_error( array( 'message' => __( 'Could not load that page.', 'edminboost-admin-customization' ), ), 500 ); } $page_title = $this->get_cc_page_title( $page ); $response = array( 'html' => $html, 'page' => $page, 'title' => $page_title, 'documentTitle' => sprintf( /* translators: 1: page title, 2: site name */ __( '%1$s ‹ %2$s — WordPress', 'edminboost-admin-customization' ), $page_title, get_bloginfo( 'name' ) ), 'ccNavNonce' => wp_create_nonce( 'edminboost_cc_nav' ), ); /** * Filter CC tab navigation AJAX payload (premium adds drawer preview nonces when licensed). * * @param array $response AJAX success payload. * @param string $page Loaded admin page slug. */ $response = apply_filters( 'edminboost_cc_nav_ajax_response', $response, $page ); self::send_ajax_json_success( $response ); } /** * Whether a page slug is a Command Center navigation target. * * @param string $page Admin page slug. * @return bool */ private function is_valid_cc_nav_page( $page ) { foreach ( EDMINBOOST_Command_Center::get_nav_items() as $item ) { if ( isset( $item['slug'] ) && $page === $item['slug'] ) { return true; } } return false; } /** * Render a Command Center page and return its HTML. * * @param string $page Admin page slug. * @param bool $use_form_defaults When true, render forms with plugin defaults (not saved values). * @return string */ private function capture_cc_page_html( $page, $use_form_defaults = false ) { $form_defaults_filter = null; if ( $use_form_defaults ) { $form_defaults_filter = static function () { return EDMINBOOST_Settings::get_form_defaults(); }; add_filter( 'edminboost_settings', $form_defaults_filter, 9999 ); } $this->prime_cc_page_context( $page ); ob_start(); $rendered = apply_filters( 'edminboost_render_cc_page', false, $page, $this ); if ( $rendered ) { return (string) ob_get_clean(); } switch ( $page ) { case self::PAGE_SLUG: $this->render_admin_page(); break; case self::PAGE_SLUG . EDMINBOOST_Command_Center::PAGE_APPEARANCE: $this->render_appearance_page(); break; case self::PAGE_SLUG . EDMINBOOST_Command_Center::PAGE_MAPPER: $this->render_mapper_page(); break; case self::PAGE_SLUG . EDMINBOOST_Command_Center::PAGE_PRESETS: $this->render_presets_page(); break; case self::PAGE_SLUG . EDMINBOOST_Command_Center::PAGE_MENU_STUDIO: $this->render_menu_studio_page(); break; case self::PAGE_SLUG . EDMINBOOST_Command_Center::PAGE_PRODUCTIVITY: $this->render_productivity_page(); break; case self::PAGE_SLUG . EDMINBOOST_Command_Center::PAGE_SECURITY: $this->render_security_page(); break; case self::PAGE_SLUG . EDMINBOOST_Command_Center::PAGE_PERFORMANCE: $this->render_performance_page(); break; default: ob_end_clean(); if ( $form_defaults_filter ) { remove_filter( 'edminboost_settings', $form_defaults_filter, 9999 ); } return ''; } $html = (string) ob_get_clean(); if ( $form_defaults_filter ) { remove_filter( 'edminboost_settings', $form_defaults_filter, 9999 ); } return $html; } /** * Prime globals used by admin page partials during AJAX renders. * * @param string $page Admin page slug. * @return void */ private function prime_cc_page_context( $page ) { global $plugin_page, $title; $plugin_page = $page; $title = $this->get_cc_page_title( $page ); EDMINBOOST_Command_Center::ensure_discovery_menu_snapshot(); } /** * Human-readable title for a Command Center page slug. * * @param string $page Admin page slug. * @return string */ private function get_cc_page_title( $page ) { /** * Filter the Command Center page title for extension screens. * * @param string $title Page title. * @param string $page Admin page slug. */ $filtered = apply_filters( 'edminboost_cc_page_title', '', $page ); if ( '' !== $filtered ) { return $filtered; } $titles = array( self::PAGE_SLUG => __( 'Dashboard', 'edminboost-admin-customization' ), self::PAGE_SLUG . EDMINBOOST_Command_Center::PAGE_APPEARANCE => __( 'Theme', 'edminboost-admin-customization' ), self::PAGE_SLUG . EDMINBOOST_Command_Center::PAGE_MAPPER => __( 'Top Bar', 'edminboost-admin-customization' ), self::PAGE_SLUG . EDMINBOOST_Command_Center::PAGE_PRESETS => __( 'Layouts', 'edminboost-admin-customization' ), self::PAGE_SLUG . EDMINBOOST_Command_Center::PAGE_MENU_STUDIO => __( 'Menu Studio', 'edminboost-admin-customization' ), self::PAGE_SLUG . EDMINBOOST_Command_Center::PAGE_PRODUCTIVITY => __( 'Productivity', 'edminboost-admin-customization' ), self::PAGE_SLUG . EDMINBOOST_Command_Center::PAGE_SECURITY => __( 'Security', 'edminboost-admin-customization' ), self::PAGE_SLUG . EDMINBOOST_Command_Center::PAGE_PERFORMANCE => __( 'Performance', 'edminboost-admin-customization' ), self::PAGE_SLUG . EDMINBOOST_Command_Center::PAGE_BILLING => __( 'Billing', 'edminboost-admin-customization' ), ); return isset( $titles[ $page ] ) ? $titles[ $page ] : ''; } /** * AJAX: save Command Center settings without a full page reload. * * @return void */ public function ajax_save_settings() { self::begin_ajax_json_buffer(); if ( ! current_user_can( EDMINBOOST_Settings::CAPABILITY ) ) { self::send_ajax_json_error( array( 'message' => __( 'You do not have permission to save these settings.', 'edminboost-admin-customization' ), ), 403 ); } $nonce = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : ''; if ( ! wp_verify_nonce( $nonce, EDMINBOOST_Settings::SETTINGS_GROUP . '-options' ) ) { self::send_ajax_json_error( array( 'message' => __( 'Security check failed. Refresh the page and try again.', 'edminboost-admin-customization' ), ), 403 ); } $raw = isset( $_POST[ EDMINBOOST_Settings::OPTION_NAME ] ) ? wp_unslash( $_POST[ EDMINBOOST_Settings::OPTION_NAME ] ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized via EDMINBOOST_Settings::sanitize(). : array(); if ( ! is_array( $raw ) ) { self::send_ajax_json_error( array( 'message' => __( 'Invalid settings payload.', 'edminboost-admin-customization' ), ), 400 ); } $before_cc = EDMINBOOST_Command_Center::get_settings(); $before_custom_preset_ids = isset( $before_cc['presets'] ) && is_array( $before_cc['presets'] ) ? array_keys( $before_cc['presets'] ) : array(); $sanitized = EDMINBOOST_Settings::sanitize( $raw ); update_option( EDMINBOOST_Settings::OPTION_NAME, $sanitized, false ); $cc_raw = isset( $raw['command_center'] ) && is_array( $raw['command_center'] ) ? $raw['command_center'] : array(); EDMINBOOST_Command_Center::ensure_discovery_menu_snapshot(); self::send_ajax_json_success( $this->build_ajax_save_response( $sanitized, $cc_raw, $before_custom_preset_ids ) ); } /** * Build the AJAX save success payload for admin JavaScript. * * @param array $sanitized Sanitized settings after save. * @param array $cc_raw Raw command_center input. * @param array $before_custom_preset_ids Custom preset ids before save. * @return array */ private function build_ajax_save_response( $sanitized, $cc_raw, $before_custom_preset_ids ) { $cc = isset( $sanitized['command_center'] ) && is_array( $sanitized['command_center'] ) ? $sanitized['command_center'] : array(); $after_custom = isset( $cc['presets'] ) && is_array( $cc['presets'] ) ? array_keys( $cc['presets'] ) : array(); $new_custom_ids = array_values( array_diff( $after_custom, $before_custom_preset_ids ) ); $selected_preset = ''; $message = __( 'Settings saved.', 'edminboost-admin-customization' ); if ( ! empty( $cc_raw['_setup_wizard_save'] ) ) { $message = __( 'Command Center launched.', 'edminboost-admin-customization' ); } elseif ( ! empty( $cc_raw['_apply_preset'] ) ) { $selected_preset = sanitize_key( $cc_raw['_apply_preset'] ); $message = __( 'Preset applied.', 'edminboost-admin-customization' ); } elseif ( ! empty( $cc_raw['_duplicate_preset'] ) ) { $selected_preset = ! empty( $new_custom_ids ) ? (string) reset( $new_custom_ids ) : ''; $message = __( 'Preset duplicated.', 'edminboost-admin-customization' ); } elseif ( ! empty( $cc_raw['_save_custom_preset'] ) && is_array( $cc_raw['_save_custom_preset'] ) && ! empty( $cc_raw['_save_custom_preset']['name'] ) ) { $selected_preset = ! empty( $new_custom_ids ) ? (string) reset( $new_custom_ids ) : ''; $message = __( 'Preset saved.', 'edminboost-admin-customization' ); } elseif ( ! empty( $cc_raw['_rename_custom_preset'] ) && is_array( $cc_raw['_rename_custom_preset'] ) && ! empty( $cc_raw['_rename_custom_preset']['id'] ) && ! empty( $cc_raw['_rename_custom_preset']['name'] ) ) { $selected_preset = sanitize_key( $cc_raw['_rename_custom_preset']['id'] ); $message = __( 'Preset renamed.', 'edminboost-admin-customization' ); } $response = array( 'message' => $message, 'presets' => $this->get_presets_for_js(), 'selected_preset' => $selected_preset, 'active_layout_preset' => EDMINBOOST_Command_Center::detect_active_layout_preset( $cc ), 'default_preset' => isset( $cc['default_preset'] ) ? (string) $cc['default_preset'] : '', 'theme' => EDMINBOOST_Theme::get_settings( $cc ), 'setup_complete' => EDMINBOOST_Command_Center::is_setup_complete( $cc ), ); /** * Filter the AJAX settings save success payload for admin JavaScript. * * @param array $response Success payload. * @param array $cc_raw Raw command_center input from the save request. */ return apply_filters( 'edminboost_ajax_save_response', $response, $cc_raw ); } /** * Determine whether the current screen belongs to this plugin. * * @param string $hook_suffix Current admin page hook. * @return bool */ private function is_plugin_screen( $hook_suffix ) { return false !== strpos( $hook_suffix, self::PAGE_SLUG ); } /** * AJAX: export plugin settings as JSON. * * @return void */ public function ajax_export_settings() { self::begin_ajax_json_buffer(); if ( ! current_user_can( EDMINBOOST_Settings::CAPABILITY ) ) { self::send_ajax_json_error( array( 'message' => __( 'Permission denied.', 'edminboost-admin-customization' ) ), 403 ); } if ( ! check_ajax_referer( 'edminboost_export_settings', 'nonce', false ) ) { self::send_ajax_json_error( array( 'message' => __( 'Security check failed. Refresh the page and try again.', 'edminboost-admin-customization' ), ), 403 ); } self::send_ajax_json_success( array( 'json' => wp_json_encode( EDMINBOOST_Settings::get(), JSON_PRETTY_PRINT ), ) ); } /** * AJAX: import plugin settings from JSON. * * @return void */ public function ajax_import_settings() { self::begin_ajax_json_buffer(); if ( ! current_user_can( EDMINBOOST_Settings::CAPABILITY ) ) { self::send_ajax_json_error( array( 'message' => __( 'Permission denied.', 'edminboost-admin-customization' ) ), 403 ); } if ( ! check_ajax_referer( 'edminboost_import_settings', 'nonce', false ) ) { self::send_ajax_json_error( array( 'message' => __( 'Security check failed. Refresh the page and try again.', 'edminboost-admin-customization' ), ), 403 ); } $json = isset( $_POST['json'] ) ? sanitize_textarea_field( wp_unslash( $_POST['json'] ) ) : ''; $data = json_decode( $json, true ); if ( ! is_array( $data ) ) { self::send_ajax_json_error( array( 'message' => __( 'Invalid JSON payload.', 'edminboost-admin-customization' ) ), 400 ); } $sanitized = EDMINBOOST_Settings::sanitize( $data ); update_option( EDMINBOOST_Settings::OPTION_NAME, $sanitized, false ); self::send_ajax_json_success( array( 'message' => __( 'Settings imported.', 'edminboost-admin-customization' ) ) ); } /** * Add a body class on plugin admin screens for layout scoping. * * @param string $classes Space-separated admin body classes. * @return string */ public function filter_admin_body_class( $classes ) { $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; if ( ! $screen || empty( $screen->id ) || false === strpos( $screen->id, self::PAGE_SLUG ) ) { return $classes; } return trim( $classes . ' edminboost-plugin-screen' ); } }