# thinkrank/1.0.1/includes/admin/class-manager.php

ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console &amp; Local SEO, version 1.0.1. 734 lines.

- Page: https://pluginprobe.com/plugins/thinkrank/1.0.1/code/includes/admin/class-manager.php
- Raw: https://pluginprobe.com/plugins/thinkrank/1.0.1/raw/includes/admin/class-manager.php
- Modified: 2025-08-10T07:35:04+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/thinkrank/1.0.1/code/includes/admin/class-manager.php#L10-L20`.

```php
<?php
/**
 * Admin Manager Class
 * 
 * Handles WordPress admin interface integration
 * 
 * @package ThinkRank\Admin
 * @since 1.0.0
 */

declare(strict_types=1);

namespace ThinkRank\Admin;

use ThinkRank\Core\Settings;
use ThinkRank\Core\Database;
use ThinkRank\Admin\Metabox_Manager;

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

/**
 * Admin Manager Class
 * 
 * Single Responsibility: Manage WordPress admin interface
 * 
 * @since 1.0.0
 */
class Manager {
    
    /**
     * Settings instance
     * 
     * @var Settings
     */
    private Settings $settings;
    
    /**
     * Database instance
     *
     * @var Database
     */
    private Database $database;

    /**
     * Metabox manager instance
     *
     * @var Metabox_Manager
     */
    private Metabox_Manager $metabox_manager;


    
    /**
     * Admin pages
     * 
     * @var array
     */
    private array $pages = [];
    
    /**
     * Constructor
     * 
     * @param Settings $settings Settings instance
     * @param Database $database Database instance
     */
    public function __construct(Settings $settings = null, Database $database = null) {
        $this->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 {
        ?>
        <div class="wrap">
            <div id="thinkrank-<?php echo esc_attr($page); ?>" class="thinkrank-admin-page">
                <div class="thinkrank-loading">
                    <div class="spinner is-active"></div>
                    <p><?php esc_html_e('Loading ThinkRank...', 'thinkrank'); ?></p>
                </div>
            </div>
        </div>
        <?php
    }
    
    /**
     * Add meta boxes to post edit screens
     * 
     * @return void
     */
    public function add_meta_boxes(): void {
        $post_types = get_post_types(['public' => 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 '<div id="thinkrank-meta-box" data-post-id="' . esc_attr($post->ID) . '">';
        echo '<div class="thinkrank-loading"><div class="spinner is-active"></div></div>';
        echo '</div>';
    }
    
    /**
     * 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 {
        ?>
        <div class="notice notice-success is-dismissible thinkrank-welcome-notice">
            <h3><?php esc_html_e('Welcome to ThinkRank!', 'thinkrank'); ?></h3>
            <p><?php esc_html_e('Thank you for installing ThinkRank. Get started by configuring your AI settings.', 'thinkrank'); ?></p>
            <p>
                <a href="<?php echo esc_url(admin_url('admin.php?page=thinkrank-settings')); ?>" class="button button-primary">
                    <?php esc_html_e('Configure Settings', 'thinkrank'); ?>
                </a>
                <a href="#" class="button thinkrank-dismiss-welcome">
                    <?php esc_html_e('Dismiss', 'thinkrank'); ?>
                </a>
            </p>
        </div>
        <?php
    }
    
    /**
     * Dismiss notice via AJAX
     * 
     * @return void
     */
    public function dismiss_notice(): void {
        check_ajax_referer('thinkrank_admin', 'nonce');
        
        $notice_type = sanitize_key($_POST['notice_type'] ?? '');
        
        if ($notice_type === 'welcome') {
            delete_option('thinkrank_show_welcome');
        }
        
        wp_die();
    }

    /**
     * Check if API key is configured
     *
     * @return bool True if at least one API key is configured
     */
    private function has_api_key_configured(): bool {
        $settings = new \ThinkRank\Core\Settings();
        $openai_key = $settings->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(
            '<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
                <g clipPath="url(#thinkrank-clip)">
                    <g filter="url(#thinkrank-shadow)">
                        <circle cx="14" cy="9.2" r="1.3" fill="#a7aaad"/>
                    </g>
                    <path d="M19.5 7v5.3c0 2.4 0 3.6-.5 4.5-.4.8-1 1.4-1.8 1.8-.9.5-2.1.5-4.5.5H7.3c-2.4 0-3.6 0-4.5-.5-.8-.4-1.4-1-1.8-1.8-.2-.3-.3-.7-.4-1.1.5-.4.9-.6 1.3-.7.5-.2.9-.2 1.4-.2.7.1 1.3.2 2 .3.7.1 1.4.2 2.1.1 1.5-.3 2.5-1 3.4-2 .5-.5.9-1 1.4-1.5.3-.3.6-.7.9-1 .3.1.6.2.9.2.9 0 1.7-.8 1.7-1.7 0-.2 0-.4-.1-.6.7-.4 1.4-.8 2.1-1.1.6-.3 1.2-.6 1.6-.8v.4zM12.5 0c2.4 0 3.6 0 4.5.5.8.4 1.4 1 1.8 1.8.4.7.5 1.6.5 3.1-.1 0-.2.1-.3.1-.5.2-1.1.5-1.8.8-.7.3-1.5.7-2.2 1.1-.3-.3-.7-.4-1.1-.4-.9 0-1.7.8-1.7 1.7 0 .3.1.6.3.9-.3.4-.6.7-.9 1-.5.6-.9 1.1-1.3 1.5-.9.9-1.8 1.5-3 1.7-.6.1-1.2.1-1.8 0-.6-.1-1.3-.3-2-.4-.6-.1-1.2-.1-1.8.1-.3.1-.7.3-1 .5 0-.6 0-1.4 0-2.3V7c0-2.4 0-3.6.5-4.5.4-.8 1-1.4 1.8-1.8C3.9 0 5.1 0 7.5 0h5zm-5.8 8.2c0-.1-.1-.1-.2 0l-.2.9c0 0 0 .1-.1.1l-.9.2c-.1 0-.1.1 0 .1l.9.2c0 0 .1 0 .1.1l.2.9c0 .1.1.1.2 0l.2-.9c0 0 0-.1.1-.1l.9-.2c.1 0 .1-.1 0-.1l-.9-.2c0 0-.1 0-.1-.1l-.2-.9zm8.8-.4c0 .1 0 .2 0 .3 0 .7-.6 1.3-1.3 1.3-.2 0-.4 0-.5-.1.1-.1.2-.2.3-.3.4-.4.9-.8 1.5-1.2zm-1.3-1c.3 0 .5.1.7.2-.6.4-1.1.8-1.5 1.2l-.1.1c-.1.1-.2.2-.3.3-.1-.2-.1-.4-.1-.6 0-.7.6-1.2 1.3-1.2zM8.6 2.5c-.1-.2-.4-.2-.4 0l-.4 1.4c0 .1-.1.1-.1.1L6.2 4.4c-.2.1-.2.4 0 .4l1.4.4c.1 0 .1.1.1.1l.4 1.4c.1.2.4.2.4 0l.4-1.4c0-.1.1-.1.1-.1l1.4-.4c.2-.1.2-.4 0-.4L8.9 4c-.1 0-.1-.1-.1-.1L8.6 2.5z" fill="#a7aaad"/>
                </g>
                <defs>
                    <filter id="thinkrank-shadow" x="11.2" y="7.2" width="5.6" height="5.6" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
                        <feFlood floodOpacity="0" result="BackgroundImageFix"/>
                        <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
                        <feOffset dy="0.7"/>
                        <feGaussianBlur stdDeviation="0.7"/>
                        <feComposite in2="hardAlpha" operator="out"/>
                        <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
                        <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
                        <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
                    </filter>
                    <clipPath id="thinkrank-clip">
                        <rect width="20" height="20" rx="5.5" fill="white"/>
                    </clipPath>
                </defs>
            </svg>'
        );
    }
    
    /**
     * 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' => '<p>' . __('ThinkRank.ai helps you optimize your content with AI-powered SEO suggestions.', 'thinkrank') . '</p>',
        ]);
        
        $screen->set_help_sidebar(
            '<p><strong>' . __('For more information:', 'thinkrank') . '</strong></p>' .
            '<p><a href="https://thinkrank.ai/docs" target="_blank">' . __('Documentation', 'thinkrank') . '</a></p>' .
            '<p><a href="https://thinkrank.ai/support" target="_blank">' . __('Support', 'thinkrank') . '</a></p>'
        );
    }
    
    /**
     * 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;
            }
        ';
    }


}

```
