settings = $settings ?? new Settings(); $this->database = $database ?? new Database(); $this->metabox_manager = new Metabox_Manager($this->settings); } /** * Initialize admin interface * * @return void */ public function init(): void { add_action('admin_menu', [$this, 'add_admin_menu']); add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_scripts']); add_action('admin_init', [$this, 'handle_admin_init']); add_action('admin_notices', [$this, 'show_admin_notices']); // AJAX handlers add_action('wp_ajax_thinkrank_dismiss_notice', [$this, 'dismiss_notice']); // Initialize metabox manager $this->metabox_manager->init(); } /** * Add admin menu pages * * @return void */ public function add_admin_menu(): void { // Main menu page $this->pages['dashboard'] = add_menu_page( __('ThinkRank', 'thinkrank'), __('ThinkRank', 'thinkrank'), 'manage_options', 'thinkrank', [$this, 'render_dashboard_page'], $this->get_menu_icon(), 30 ); // Dashboard submenu (same as main) $this->pages['dashboard_sub'] = add_submenu_page( 'thinkrank', __('Dashboard', 'thinkrank'), __('Dashboard', 'thinkrank'), 'manage_options', 'thinkrank', [$this, 'render_dashboard_page'] ); // Essential SEO page (React tabbed interface) $this->pages['essential_seo'] = add_submenu_page( 'thinkrank', __('Essential SEO', 'thinkrank'), __('Essential SEO', 'thinkrank'), 'manage_options', 'thinkrank-essential-seo', [$this, 'render_essential_seo_page'] ); // AI Tools page $this->pages['ai_tools'] = add_submenu_page( 'thinkrank', __('AI Tools', 'thinkrank'), __('AI Tools', 'thinkrank'), 'edit_posts', 'thinkrank-ai-tools', [$this, 'render_ai_tools_page'] ); // Settings page $this->pages['settings'] = add_submenu_page( 'thinkrank', __('Settings', 'thinkrank'), __('Settings', 'thinkrank'), 'manage_options', 'thinkrank-settings', [$this, 'render_settings_page'] ); // Usage Analytics page $this->pages['analytics'] = add_submenu_page( 'thinkrank', __('Usage Analytics', 'thinkrank'), __('Usage Analytics', 'thinkrank'), 'edit_posts', 'thinkrank-analytics', [$this, 'render_analytics_page'] ); // Hook for page-specific initialization foreach ($this->pages as $page_hook) { add_action("load-{$page_hook}", [$this, 'load_admin_page']); } } /** * Enqueue admin scripts and styles * * APPROACH: Manual enqueuing with disabled webpack code splitting * - All dependencies bundled into main admin.js (677KB) * - Chart.js separated into charts.js (138KB) for performance * - No dynamic chunks - predictable loading order * * @param string $hook_suffix Current admin page hook * @return void */ public function enqueue_admin_scripts(string $hook_suffix): void { // Only load on our admin pages if (!in_array($hook_suffix, $this->pages, true)) { return; } // Get asset files for cache busting $admin_asset_file = THINKRANK_PLUGIN_DIR . 'assets/admin.asset.php'; $admin_asset_data = file_exists($admin_asset_file) ? include $admin_asset_file : [ 'dependencies' => [], 'version' => THINKRANK_VERSION, ]; // Enqueue Chart.js bundle only on pages that render charts (Analytics and Essential SEO Performance) $charts_asset_file = THINKRANK_PLUGIN_DIR . 'assets/charts.asset.php'; $should_enqueue_charts = in_array($hook_suffix, [ $this->pages['analytics'] ?? '', $this->pages['essential_seo'] ?? '', ], true); if ($should_enqueue_charts && file_exists($charts_asset_file)) { $charts_asset_data = include $charts_asset_file; wp_enqueue_script( 'thinkrank-charts', THINKRANK_PLUGIN_URL . 'assets/charts.js', $charts_asset_data['dependencies'], $charts_asset_data['version'], true ); // Add charts as dependency for admin script to ensure registration before use $admin_dependencies = array_merge($admin_asset_data['dependencies'], ['thinkrank-charts']); } else { // Do not load charts on pages that don't need it $admin_dependencies = $admin_asset_data['dependencies']; } wp_enqueue_script( 'thinkrank-admin', THINKRANK_PLUGIN_URL . 'assets/admin.js', $admin_dependencies, $admin_asset_data['version'], true ); // Add defer attribute for better performance wp_script_add_data('thinkrank-admin', 'defer', true); // Enqueue admin styles wp_enqueue_style( 'thinkrank-admin', THINKRANK_PLUGIN_URL . 'assets/admin.css', ['wp-components'], $admin_asset_data['version'] ); // Enqueue WordPress media library for MediaPicker component wp_enqueue_media(); // Localize script with data wp_localize_script('thinkrank-admin', 'thinkrankAdmin', [ 'apiUrl' => rest_url('thinkrank/v1/'), 'nonce' => wp_create_nonce('wp_rest'), 'restNonce' => wp_create_nonce('wp_rest'), 'adminNonce' => wp_create_nonce('thinkrank_admin'), 'currentUser' => wp_get_current_user()->ID, 'capabilities' => $this->get_user_capabilities(), 'settings' => $this->get_admin_settings(), 'i18n' => $this->get_i18n_strings(), 'isAdmin' => current_user_can('manage_options'), // Site information for default values 'siteName' => get_bloginfo('name'), 'siteDescription' => get_bloginfo('description'), 'siteUrl' => home_url(), 'adminEmail' => get_option('admin_email'), ]); // Add welcome notice dismissal script if (get_option('thinkrank_show_welcome') && !$this->has_api_key_configured()) { wp_add_inline_script('thinkrank-admin', ' jQuery(document).ready(function($) { $(".thinkrank-dismiss-welcome").on("click", function(e) { e.preventDefault(); $.post(ajaxurl, { action: "thinkrank_dismiss_notice", notice_type: "welcome", nonce: thinkrankAdmin.adminNonce }, function(response) { $(".thinkrank-welcome-notice").fadeOut(); }); }); }); '); } } /** * Handle admin initialization * * @return void */ public function handle_admin_init(): void { // Show welcome screen for new installations (only if no API key configured) if (get_option('thinkrank_show_welcome') && !$this->has_api_key_configured()) { add_action('admin_notices', [$this, 'show_welcome_notice']); } // Check for plugin updates $this->check_plugin_updates(); } /** * Load admin page * * @return void */ public function load_admin_page(): void { // Add help tabs $this->add_help_tabs(); // Add screen options $this->add_screen_options(); } /** * Render dashboard page * * @return void */ public function render_dashboard_page(): void { $this->render_admin_page('dashboard', [ 'title' => __('ThinkRank Dashboard', 'thinkrank'), 'description' => __('AI-powered SEO optimization for WordPress', 'thinkrank'), ]); } /** * Render Essential SEO page * * @return void */ public function render_essential_seo_page(): void { $this->render_admin_page('essential-seo', [ 'title' => __('Essential SEO', 'thinkrank'), 'description' => __('Configure your site-wide SEO settings with AI-powered optimization', 'thinkrank'), ]); } /** * Render AI Tools page * * @return void */ public function render_ai_tools_page(): void { $this->render_admin_page('ai-tools', [ 'title' => __('AI Tools', 'thinkrank'), 'description' => __('AI-powered content tools including Content Planner and Metadata Generator', 'thinkrank'), ]); } /** * Render settings page * * @return void */ public function render_settings_page(): void { $this->render_admin_page('settings', [ 'title' => __('ThinkRank Settings', 'thinkrank'), 'description' => __('Configure your AI SEO settings', 'thinkrank'), ]); } /** * Render usage analytics page * * @return void */ public function render_analytics_page(): void { $this->render_admin_page('analytics', [ 'title' => __('Usage Analytics', 'thinkrank'), 'description' => __('Track your AI usage, costs, and plugin performance analytics', 'thinkrank'), ]); } /** * Render admin page template * * @param string $page Page identifier * @param array $data Page data * @return void */ private function render_admin_page(string $page, array $data): void { ?>

true]); foreach ($post_types as $post_type) { add_meta_box( 'thinkrank-seo', __('ThinkRank SEO', 'thinkrank'), [$this, 'render_seo_meta_box'], $post_type, 'normal', 'high' ); } } /** * Render SEO meta box * * @param \WP_Post $post Current post object * @return void */ public function render_seo_meta_box(\WP_Post $post): void { wp_nonce_field('thinkrank_meta_box', 'thinkrank_meta_box_nonce'); echo '
'; echo '
'; echo '
'; } /** * Save meta box data * * @param int $post_id Post ID * @return void */ public function save_meta_boxes(int $post_id): void { // Verify nonce if (!isset($_POST['thinkrank_meta_box_nonce'])) { return; } $nonce = sanitize_text_field(wp_unslash($_POST['thinkrank_meta_box_nonce'])); if (!wp_verify_nonce($nonce, 'thinkrank_meta_box')) { return; } // Check permissions if (!current_user_can('edit_post', $post_id)) { return; } // Save meta data (handled by AJAX in React components) do_action('thinkrank_save_post_meta', $post_id, $_POST); } /** * Show admin notices * * @return void */ public function show_admin_notices(): void { // Implementation will be added in next iteration } /** * Show welcome notice * * @return void */ public function show_welcome_notice(): void { ?>

get('openai_api_key'); $claude_key = $settings->get('claude_api_key'); return !empty($openai_key) || !empty($claude_key); } /** * Get menu icon * * @return string Menu icon */ private function get_menu_icon(): string { return 'data:image/svg+xml;base64,' . base64_encode( ' ' ); } /** * Get user capabilities for current user * * @return array User capabilities */ private function get_user_capabilities(): array { return [ 'manage_settings' => current_user_can('manage_options'), 'view_analytics' => current_user_can('edit_posts'), 'use_ai_features' => current_user_can('edit_posts'), ]; } /** * Get admin settings for JavaScript * * @return array Admin settings */ private function get_admin_settings(): array { return [ 'ai_provider' => $this->settings->get('ai_provider', 'openai'), 'cache_duration' => $this->settings->get('cache_duration', 3600), ]; } /** * Get internationalization strings * * @return array I18n strings */ private function get_i18n_strings(): array { return [ 'loading' => __('Loading...', 'thinkrank'), 'error' => __('An error occurred', 'thinkrank'), 'success' => __('Success!', 'thinkrank'), 'confirm' => __('Are you sure?', 'thinkrank'), 'cancel' => __('Cancel', 'thinkrank'), 'save' => __('Save', 'thinkrank'), ]; } /** * Add help tabs * * @return void */ private function add_help_tabs(): void { $screen = get_current_screen(); $screen->add_help_tab([ 'id' => 'thinkrank-overview', 'title' => __('Overview', 'thinkrank'), 'content' => '

' . __('ThinkRank.ai helps you optimize your content with AI-powered SEO suggestions.', 'thinkrank') . '

', ]); $screen->set_help_sidebar( '

' . __('For more information:', 'thinkrank') . '

' . '

' . __('Documentation', 'thinkrank') . '

' . '

' . __('Support', 'thinkrank') . '

' ); } /** * Add screen options * * @return void */ private function add_screen_options(): void { // Screen options will be added as needed } /** * Check for plugin updates * * @return void */ private function check_plugin_updates(): void { $current_version = get_option('thinkrank_version'); if (version_compare($current_version, THINKRANK_VERSION, '<')) { // Handle plugin update do_action('thinkrank_plugin_updated', $current_version, THINKRANK_VERSION); update_option('thinkrank_version', THINKRANK_VERSION); } } /** * Get admin styles * * @return string CSS styles */ private function get_admin_styles(): string { return ' .thinkrank-loading { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 40px 20px; text-align: center; } .thinkrank-loading .spinner { margin-bottom: 16px; } .thinkrank-loading p { margin: 0; color: #666; font-size: 14px; } .thinkrank-app { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; } .thinkrank-header { display: flex; justify-content: space-between; align-items: center; padding: 20px 0; border-bottom: 1px solid #ddd; margin-bottom: 20px; } .thinkrank-header h1 { margin: 0; font-size: 24px; font-weight: 600; } .thinkrank-header .version { font-size: 12px; color: #666; font-weight: normal; margin-left: 8px; } .thinkrank-header .tagline { margin: 4px 0 0 0; color: #666; font-size: 14px; } .thinkrank-dashboard-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-top: 20px; } .stat-card { background: #f9f9f9; padding: 20px; border-radius: 8px; text-align: center; border: 1px solid #ddd; } .stat-card h3 { margin: 0 0 10px 0; font-size: 14px; color: #666; text-transform: uppercase; letter-spacing: 0.5px; } .stat-number { font-size: 32px; font-weight: 700; color: #0073aa; margin: 0; line-height: 1; } .stat-label { margin: 4px 0 0 0; font-size: 12px; color: #666; } .thinkrank-error { padding: 20px; } .thinkrank-error .notice { margin: 0; } '; } }