# thinkrank/1.10.0/includes/api/class-manager.php

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

- Page: https://pluginprobe.com/plugins/thinkrank/1.10.0/code/includes/api/class-manager.php
- Raw: https://pluginprobe.com/plugins/thinkrank/1.10.0/raw/includes/api/class-manager.php
- Modified: 2026-02-15T11:55:26+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.10.0/code/includes/api/class-manager.php#L10-L20`.

```php
<?php

/**
 * API Manager Class
 *
 * Handles REST API endpoints registration and management
 *
 * @package ThinkRank\API
 * @since 1.0.0
 */

declare(strict_types=1);

namespace ThinkRank\API;

// Import endpoint classes
use ThinkRank\API\Site_Identity_Endpoint;
use ThinkRank\API\Performance_Endpoint;
use ThinkRank\API\Schema_Endpoint;
use ThinkRank\API\Settings_Management_Endpoint;
use ThinkRank\API\Content_Brief_Endpoint;
use ThinkRank\API\Social_Media_Endpoint;
use ThinkRank\API\Sitemap_Endpoint;
use ThinkRank\API\SEO_Analytics_Endpoint;
use ThinkRank\API\Usage_Analytics_Endpoint;
use ThinkRank\API\Integrations_Endpoint;
use ThinkRank\API\Social_Platforms_Endpoint;
use ThinkRank\API\LLMs_Txt_Endpoint;
use ThinkRank\API\Global_SEO_Endpoint;
use ThinkRank\API\Image_SEO_Endpoint;
use ThinkRank\API\Instant_Indexing_Endpoint;
use ThinkRank\API\Pillar_Content_Endpoint;
use ThinkRank\API\Global_Robot_Meta_Endpoint;
use ThinkRank\API\Author_Archives_Endpoint;


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

/**
 * API Manager Class
 *
 * Single Responsibility: Manage REST API endpoints
 *
 * @since 1.0.0
 */
class Manager {

    /**
     * API namespace
     *
     * @var string
     */
    private const NAMESPACE = 'thinkrank/v1';

    /**
     * Initialize API manager
     *
     * @return void
     */
    public function init(): void {
        add_action('rest_api_init', [$this, 'register_routes']);
        add_action('rest_api_init', [$this, 'register_endpoint_classes']);
    }

    /**
     * Register REST API routes
     *
     * @return void
     */
    public function register_routes(): void {
        // Core endpoints
        register_rest_route(self::NAMESPACE, '/capabilities', [
            'methods' => 'GET',
            'callback' => [$this, 'get_capabilities'],
            'permission_callback' => [$this, 'check_basic_permissions'],
        ]);

        register_rest_route(self::NAMESPACE, '/plugin-info', [
            'methods' => 'GET',
            'callback' => [$this, 'get_plugin_info'],
            'permission_callback' => [$this, 'check_basic_permissions'],
        ]);

        register_rest_route(self::NAMESPACE, '/system-status', [
            'methods' => 'GET',
            'callback' => [$this, 'get_system_status'],
            'permission_callback' => [$this, 'check_basic_permissions'],
        ]);



        // Settings endpoints
        register_rest_route(self::NAMESPACE, '/settings', [
            'methods' => 'GET',
            'callback' => [$this, 'get_settings'],
            'permission_callback' => [$this, 'check_admin_permissions'],
        ]);

        register_rest_route(self::NAMESPACE, '/settings', [
            'methods' => 'POST',
            'callback' => [$this, 'save_settings'],
            'permission_callback' => [$this, 'check_admin_permissions'],
            'args' => [
                'ai_provider' => [
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_key',
                ],
                'openai_api_key' => [
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_text_field',
                ],
                'openai_model' => [
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_text_field',
                ],
                'claude_api_key' => [
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_text_field',
                ],
                'claude_model' => [
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_text_field',
                ],
                'gemini_api_key' => [
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_text_field',
                ],
                'gemini_model' => [
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_text_field',
                ],
                'keep_data_on_uninstall' => [
                    'type' => 'boolean',
                    'sanitize_callback' => 'rest_sanitize_boolean',
                ],
            ],
        ]);



        // Metadata endpoints
        register_rest_route(self::NAMESPACE, '/metadata/(?P<post_id>\d+)', [
            'methods' => 'GET',
            'callback' => [$this, 'get_metadata'],
            'permission_callback' => [$this, 'check_basic_permissions'],
            'args' => [
                'post_id' => [
                    'type' => 'integer',
                    'required' => true,
                ],
            ],
        ]);

        // AI endpoints
        register_rest_route(self::NAMESPACE, '/ai/generate-metadata', [
            'methods' => 'POST',
            'callback' => [$this, 'generate_ai_metadata'],
            'permission_callback' => [$this, 'check_basic_permissions'],
            'args' => [
                'content' => [
                    'type' => 'string',
                    'required' => true,
                    'sanitize_callback' => 'sanitize_textarea_field',
                ],
                'target_keyword' => [
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_text_field',
                ],
                'content_type' => [
                    'type' => 'string',
                    'default' => 'blog_post',
                    'sanitize_callback' => 'sanitize_text_field',
                ],
                'tone' => [
                    'type' => 'string',
                    'default' => 'professional',
                    'sanitize_callback' => 'sanitize_text_field',
                ],
            ],
        ]);

        register_rest_route(self::NAMESPACE, '/ai/test-connection', [
            'methods' => 'POST',
            'callback' => [$this, 'test_ai_connection'],
            'permission_callback' => [$this, 'check_admin_permissions'],
            'args' => [
                'api_key' => [
                    'type' => 'string',
                    'required' => false,
                    'sanitize_callback' => 'sanitize_text_field',
                ],
                'provider' => [
                    'type' => 'string',
                    'required' => false,
                    'default' => 'openai',
                    'sanitize_callback' => 'sanitize_key',
                ],
            ],
        ]);

        register_rest_route(self::NAMESPACE, '/ai/providers', [
            'methods' => 'GET',
            'callback' => [$this, 'get_ai_providers'],
            'permission_callback' => [$this, 'check_basic_permissions'],
        ]);

        // Register content brief endpoints
        $content_brief_endpoint = new \ThinkRank\API\Content_Brief_Endpoint();
        $content_brief_endpoint->register_routes();

        // Register SEO score endpoints
        try {
            $database = new \ThinkRank\Core\Database();
            $seo_calculator = new \ThinkRank\AI\SEOScoreCalculator($database);
            $seo_score_endpoint = new \ThinkRank\API\SEOScoreEndpoint($seo_calculator);
            $seo_score_endpoint->register_routes();
        } catch (\Exception $e) {
            // SEO Score endpoint registration failed
        }

        // Register Usage Analytics endpoints
        try {
            $usage_analytics_endpoint = new \ThinkRank\API\Usage_Analytics_Endpoint();
            $usage_analytics_endpoint->register_routes();
        } catch (\Exception $e) {
            // Usage Analytics endpoint registration failed
        }

        // Register SEO Analytics endpoints
        try {
            $seo_analytics_endpoint = new \ThinkRank\API\SEO_Analytics_Endpoint();
            $seo_analytics_endpoint->register_routes();
        } catch (\Exception $e) {
            // Failed to register SEO Analytics endpoint
        }

        // Register Instant Indexing endpoints
        try {
            $instant_indexing_endpoint = new \ThinkRank\API\Instant_Indexing_Endpoint();
            $instant_indexing_endpoint->register_routes();
        } catch (\Exception $e) {
            // Failed to register Instant Indexing endpoint
        }

        // Register Pillar Content endpoints
        try {
            $pillar_content_endpoint = new \ThinkRank\API\Pillar_Content_Endpoint();
            $pillar_content_endpoint->register_routes();
        } catch (\Exception $e) {
            // Failed to register Pillar Content endpoint
        }
        // Register Global Robot Meta endpoints
        try {
            $global_robot_meta_endpoint = new \ThinkRank\API\Global_Robot_Meta_Endpoint();
            $global_robot_meta_endpoint->register_routes();
        } catch (\Exception $e) {
            // Failed to register Global Robot Meta endpoint
        }

        // Register Author Archives endpoints
        try {
            $author_archives_endpoint = new \ThinkRank\API\Author_Archives_Endpoint();
            $author_archives_endpoint->register_routes();
        } catch (\Exception $e) {
            // Failed to register Author Archives endpoint
        }


        // Add a simple test endpoint to verify API is working
        register_rest_route(self::NAMESPACE, '/seo-score/test', [
            'methods' => 'GET',
            'callback' => function () {
                return ['message' => 'SEO Score API is working!', 'timestamp' => current_time('mysql')];
            },
            'permission_callback' => [$this, 'check_basic_permissions'],
        ]);

        register_rest_route(self::NAMESPACE, '/ai/status', [
            'methods' => 'GET',
            'callback' => [$this, 'get_ai_status'],
            'permission_callback' => [$this, 'check_basic_permissions'],
        ]);

        register_rest_route(self::NAMESPACE, '/ai/analyze-content', [
            'methods' => 'POST',
            'callback' => [$this, 'analyze_content'],
            'permission_callback' => [$this, 'check_basic_permissions'],
            'args' => [
                'content' => [
                    'type' => 'string',
                    'required' => true,
                    'sanitize_callback' => 'sanitize_textarea_field',
                ],
                'metadata' => [
                    'type' => 'object',
                    'required' => false,
                    'sanitize_callback' => [$this, 'sanitize_metadata_object'],
                ],
                'post_id' => [
                    'type' => 'integer',
                    'required' => false,
                    'sanitize_callback' => 'absint',
                ],
            ],
        ]);
    }

    /**
     * Check basic permissions (for logged-in users)
     *
     * @param \WP_REST_Request $request Request object
     * @return bool|WP_Error Permission status
     */
    public function check_basic_permissions(\WP_REST_Request $request) {
        // Allow access for logged-in users who can edit posts
        if (!is_user_logged_in()) {
            return new \WP_Error(
                'rest_forbidden',
                __('You must be logged in to access this endpoint.', 'thinkrank'),
                ['status' => 401]
            );
        }

        if (!current_user_can('edit_posts')) {
            return new \WP_Error(
                'rest_forbidden',
                __('You do not have permission to access this endpoint.', 'thinkrank'),
                ['status' => 403]
            );
        }

        return true;
    }


    /**
     * Simple transient-based rate limiter
     *
     * @param string $bucket_id Unique bucket per user/IP and route
     * @param int $limit Max requests per minute
     * @return bool|\WP_Error True if allowed, or WP_Error when rate limited
     */
    private function enforce_rate_limit(string $bucket_id, int $limit) {
        $settings = new \ThinkRank\Core\Settings();
        $enabled = (bool) $settings->get('enable_rate_limiting', true);
        if (!$enabled) {
            return true;
        }
        $now = time();
        $window = 60;
        $key = 'thinkrank_rl_' . md5($bucket_id);
        $bucket = get_transient($key);
        if (!is_array($bucket)) {
            $bucket = ['start' => $now, 'count' => 0];
        }
        if ($now - ($bucket['start'] ?? 0) >= $window) {
            $bucket = ['start' => $now, 'count' => 0];
        }
        if (($bucket['count'] ?? 0) >= max(1, $limit)) {
            return new \WP_Error('rate_limited', __('Rate limit exceeded. Please wait a moment and try again.', 'thinkrank'), ['status' => 429]);
        }
        $bucket['count']++;
        set_transient($key, $bucket, $window);
        return true;
    }

    /**
     * Check admin permissions (for settings)
     *
     * @param \WP_REST_Request $request Request object
     * @return bool|WP_Error Permission status
     */
    public function check_admin_permissions(\WP_REST_Request $request) {
        // Allow access for administrators only
        if (!is_user_logged_in()) {
            return new \WP_Error(
                'rest_forbidden',
                __('You must be logged in to access this endpoint.', 'thinkrank'),
                ['status' => 401]
            );
        }

        if (!current_user_can('manage_options')) {
            return new \WP_Error(
                'rest_forbidden',
                __('You do not have permission to manage settings.', 'thinkrank'),
                ['status' => 403]
            );
        }

        return true;
    }

    /**
     * Get user capabilities
     *
     * @param \WP_REST_Request $request Request object
     * @return \WP_REST_Response Response object
     */
    public function get_capabilities(\WP_REST_Request $request): \WP_REST_Response {
        return new \WP_REST_Response([
            'manage_settings' => current_user_can('manage_options'),
            'view_analytics' => current_user_can('edit_posts'),

            'use_ai_features' => current_user_can('edit_posts'),
        ]);
    }

    /**
     * Get plugin information
     *
     * @param \WP_REST_Request $request Request object
     * @return \WP_REST_Response Response object
     */
    public function get_plugin_info(\WP_REST_Request $request): \WP_REST_Response {
        return new \WP_REST_Response([
            'version' => THINKRANK_VERSION,
            'name' => 'ThinkRank',
            'description' => 'AI-native SEO plugin for WordPress',
        ]);
    }

    /**
     * Get system status
     *
     * @param \WP_REST_Request $request Request object
     * @return \WP_REST_Response Response object
     */
    public function get_system_status(\WP_REST_Request $request): \WP_REST_Response {
        return new \WP_REST_Response([
            'status' => 'healthy',
            'issues' => [],
            'php_version' => PHP_VERSION,
            'wp_version' => get_bloginfo('version'),
        ]);
    }



    /**
     * Get settings
     *
     * @param \WP_REST_Request $request Request object
     * @return \WP_REST_Response Response object
     */
    public function get_settings(\WP_REST_Request $request): \WP_REST_Response {
        // Use Settings class for consistent access (handles decryption automatically)
        $settings_instance = new \ThinkRank\Core\Settings();

        $settings = [
            'ai_provider' => $settings_instance->get('ai_provider', 'openai'),
            'openai_api_key' => $settings_instance->get('openai_api_key', ''),
            'openai_model' => $settings_instance->get('openai_model', 'gpt-5-nano'),
            'claude_api_key' => $settings_instance->get('claude_api_key', ''),
            'claude_model' => $settings_instance->get('claude_model', 'claude-3-7-sonnet-latest'),
            'gemini_api_key' => $settings_instance->get('gemini_api_key', ''),
            'gemini_model' => $settings_instance->get('gemini_model', 'gemini-2.5-flash'),
            'max_tokens' => $settings_instance->get('max_tokens', 1000),
            'temperature' => $settings_instance->get('temperature', 0.7),
            'cache_duration' => $settings_instance->get('cache_duration', 3600),
            'keep_data_on_uninstall' => $settings_instance->get('keep_data_on_uninstall', true),
        ];



        // Don't send full API keys to frontend for security - mask them
        if (!empty($settings['openai_api_key'])) {
            $settings['openai_api_key'] = '••••••••' . substr($settings['openai_api_key'], -4);
        }
        if (!empty($settings['claude_api_key'])) {
            $settings['claude_api_key'] = '••••••••' . substr($settings['claude_api_key'], -4);
        }
        if (!empty($settings['gemini_api_key'])) {
            $settings['gemini_api_key'] = '••••••••' . substr($settings['gemini_api_key'], -4);
        }

        return new \WP_REST_Response($settings);
    }

    /**
     * Save settings
     *
     * @param \WP_REST_Request $request Request object
     * @return \WP_REST_Response Response object
     */
    public function save_settings(\WP_REST_Request $request): \WP_REST_Response {
        $params = $request->get_params();

        // Get Settings instance for proper encryption handling
        $settings = new \ThinkRank\Core\Settings();

        // Map frontend parameter names to setting keys
        $settings_map = [
            'ai_provider' => 'ai_provider',
            'openai_api_key' => 'openai_api_key',
            'openai_model' => 'openai_model',
            'claude_api_key' => 'claude_api_key',
            'claude_model' => 'claude_model',
            'gemini_api_key' => 'gemini_api_key',
            'gemini_model' => 'gemini_model',
            'max_tokens' => 'max_tokens',
            'temperature' => 'temperature',
            'cache_duration' => 'cache_duration',
            'keep_data_on_uninstall' => 'keep_data_on_uninstall',
        ];

        // Processing settings save request

        foreach ($settings_map as $param_key => $setting_key) {
            if (isset($params[$param_key])) {
                $value = $params[$param_key];

                // Handle API keys specially - check for masked values
                if (in_array($param_key, ['openai_api_key', 'claude_api_key', 'gemini_api_key'])) {
                    // Don't update if masked (but allow empty to clear)
                    if (strpos($value, '••••••••') === 0) {
                        continue;
                    }
                }

                // Use Settings class for all operations (handles encryption automatically)
                if (!$settings->set($setting_key, $value)) {
                    // Settings save failed, continue with other settings
                }
            }
        }

        // Auto-dismiss welcome notice if API key was saved
        $this->maybe_dismiss_welcome_notice($params);

        // Force AI Manager to re-initialize client with new settings
        if (isset($params['ai_provider']) || isset($params['openai_api_key']) || isset($params['claude_api_key']) || isset($params['gemini_api_key'])) {
            // Clear any cached AI Manager instances to force re-initialization
            wp_cache_delete('thinkrank_ai_manager', 'thinkrank');

            // If we have an AI Manager instance, force it to re-initialize
            try {
                $ai_manager = new \ThinkRank\AI\Manager($settings);
                $ai_manager->reinitialize_client();
            } catch (\Exception $e) {
                // Ignore initialization errors at this point
            }
        }

        return new \WP_REST_Response([
            'success' => true,
            'message' => __('Settings saved successfully', 'thinkrank'),
            'settings' => $this->get_settings($request)->get_data(),
        ]);
    }

    /**
     * Maybe dismiss welcome notice if API key was saved
     *
     * @param array $params Request parameters
     * @return void
     */
    private function maybe_dismiss_welcome_notice(array $params): void {
        // Check if an API key was saved (not cleared)
        $api_key_saved = false;

        if (!empty($params['openai_api_key']) && $params['openai_api_key'] !== '') {
            $api_key_saved = true;
        }

        if (!empty($params['claude_api_key']) && $params['claude_api_key'] !== '') {
            $api_key_saved = true;
        }

        // Auto-dismiss welcome notice if API key was configured
        if ($api_key_saved && get_option('thinkrank_show_welcome')) {
            delete_option('thinkrank_show_welcome');
        }
    }

    /**
     * Get metadata for post
     *
     * @param \WP_REST_Request $request Request object
     * @return \WP_REST_Response Response object
     */
    public function get_metadata(\WP_REST_Request $request): \WP_REST_Response {
        $post_id = $request->get_param('post_id');

        // Get existing metadata
        $metadata = [
            'title' => get_post_meta($post_id, '_thinkrank_title', true),
            'description' => get_post_meta($post_id, '_thinkrank_description', true),
            'keywords' => get_post_meta($post_id, '_thinkrank_keywords', true),
            'seo_score' => get_post_meta($post_id, '_thinkrank_seo_score', true) ?: 0,
            'last_generated' => get_post_meta($post_id, '_thinkrank_last_generated', true),
        ];

        return new \WP_REST_Response($metadata);
    }

    /**
     * Generate AI-powered SEO metadata
     *
     * @param \WP_REST_Request $request Request object containing content and generation options
     * @return \WP_REST_Response Response object with generated metadata or error message
     * @throws \Exception When AI metadata generation fails or AI client is unavailable
     */
    public function generate_ai_metadata(\WP_REST_Request $request): \WP_REST_Response {
        $content = $request->get_param('content');
        $options = [
            'target_keyword' => $request->get_param('target_keyword'),
            'content_type' => $request->get_param('content_type'),
            'tone' => $request->get_param('tone'),
        ];

        // Rate limiting: per user/IP per route
        $user_id = get_current_user_id();
        $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
        $bucket_id = 'ai_generate|' . ($user_id ?: $ip);
        $limit = (int) (new \ThinkRank\Core\Settings())->get('max_requests_per_minute', 10);
        $allowed = $this->enforce_rate_limit($bucket_id, $limit);
        if (is_wp_error($allowed)) {
            return new \WP_REST_Response([
                'success' => false,
                'message' => $allowed->get_error_message(),
            ], $allowed->get_error_data()['status'] ?? 429);
        }

        try {
            // Get AI manager instance
            $ai_manager = new \ThinkRank\AI\Manager();
            $ai_manager->initialize_client();

            $metadata = $ai_manager->generate_seo_metadata($content, $options);

            return new \WP_REST_Response([
                'success' => true,
                'data' => $metadata,
                'message' => __('SEO metadata generated successfully', 'thinkrank'),
            ]);
        } catch (\Exception $e) {
            return new \WP_REST_Response([
                'success' => false,
                'message' => $e->getMessage(),
            ], 400);
        }
    }

    /**
     * Test AI connection for specified provider
     *
     * @param \WP_REST_Request $request Request object containing api_key and provider parameters
     * @return \WP_REST_Response Response object with connection test results
     * @throws \Exception When API connection test encounters unexpected errors
     */
    public function test_ai_connection(\WP_REST_Request $request): \WP_REST_Response {
        // Rate limiting: per user/IP per route
        $user_id = get_current_user_id();
        $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
        $bucket_id = 'ai_test|' . ($user_id ?: $ip);
        $limit = (int) (new \ThinkRank\Core\Settings())->get('max_requests_per_minute', 10);
        $allowed = $this->enforce_rate_limit($bucket_id, $limit);
        if (is_wp_error($allowed)) {
            return new \WP_REST_Response([
                'success' => false,
                'message' => $allowed->get_error_message(),
            ], $allowed->get_error_data()['status'] ?? 429);
        }

        try {
            $api_key = $request->get_param('api_key');
            $provider = $request->get_param('provider') ?: 'openai';

            // If no API key provided in request, try to get from saved settings
            if (empty($api_key)) {
                $settings = new \ThinkRank\Core\Settings();
                if ($provider === 'openai') {
                    $api_key = (string) $settings->get('openai_api_key', '');
                } elseif ($provider === 'claude') {
                    $api_key = (string) $settings->get('claude_api_key', '');
                } else {
                    $api_key = (string) $settings->get('gemini_api_key', '');
                }

                if (empty($api_key)) {
                    return new \WP_REST_Response([
                        'success' => false,
                        'message' => __('No API key provided or saved for the selected provider.', 'thinkrank'),
                    ], 400);
                }
            }

            // Test the connection with a simple API call
            if ($provider === 'openai') {
                $result = $this->test_openai_connection($api_key);
            } elseif ($provider === 'claude') {
                $result = $this->test_claude_connection($api_key);
            } else {
                $result = $this->test_gemini_connection($api_key);
            }

            return new \WP_REST_Response($result, $result['success'] ? 200 : 400);
        } catch (\Exception $e) {
            return new \WP_REST_Response([
                'success' => false,
                'message' => $e->getMessage(),
            ], 500);
        }
    }

    /**
     * Test OpenAI API connection
     *
     * @param string $api_key API key to test
     * @return array Test result
     */
    private function test_openai_connection(string $api_key): array {
        $url = 'https://api.openai.com/v1/models';

        $response = wp_remote_get($url, [
            'headers' => [
                'Authorization' => 'Bearer ' . $api_key,
                'Content-Type' => 'application/json',
            ],
            'timeout' => 10,
        ]);

        if (is_wp_error($response)) {
            return [
                'success' => false,
                'message' => __('Failed to connect to OpenAI API: ', 'thinkrank') . $response->get_error_message(),
            ];
        }

        $status_code = wp_remote_retrieve_response_code($response);
        $body = wp_remote_retrieve_body($response);

        if ($status_code === 200) {
            $data = json_decode($body, true);
            if (isset($data['data']) && is_array($data['data'])) {
                return [
                    'success' => true,
                    'message' => __('OpenAI API connection successful!', 'thinkrank'),
                    'models_count' => count($data['data']),
                ];
            }
        }

        // Handle error response
        $error_data = json_decode($body, true);
        $error_message = $error_data['error']['message'] ?? __('Unknown API error', 'thinkrank');

        return [
            'success' => false,
            'message' => __('OpenAI API Error: ', 'thinkrank') . $error_message,
        ];
    }

    /**
     * Test Claude API connection
     *
     * @param string $api_key API key to test
     * @return array Test result
     */
    private function test_claude_connection(string $api_key): array {
        // First validate the key format
        if (!str_starts_with($api_key, 'sk-ant-')) {
            return [
                'success' => false,
                'message' => __('Invalid Claude API key format. Should start with "sk-ant-"', 'thinkrank'),
            ];
        }

        // Test with a simple API call
        $url = 'https://api.anthropic.com/v1/messages';

        // Get the configured Claude model, with fallback to a current model
        $claude_model = (new \ThinkRank\Core\Settings())->get('claude_model', 'claude-3-7-sonnet-latest');

        $body = [
            'model' => $claude_model,
            'max_tokens' => 10,
            'messages' => [
                [
                    'role' => 'user',
                    'content' => 'Hello'
                ]
            ]
        ];

        $response = wp_remote_post($url, [
            'headers' => [
                'x-api-key' => $api_key,
                'Content-Type' => 'application/json',
                'anthropic-version' => '2023-06-01',
            ],
            'body' => wp_json_encode($body),
            'timeout' => 10,
        ]);

        if (is_wp_error($response)) {
            return [
                'success' => false,
                'message' => __('Failed to connect to Claude API: ', 'thinkrank') . $response->get_error_message(),
            ];
        }

        $status_code = wp_remote_retrieve_response_code($response);
        $response_body = wp_remote_retrieve_body($response);

        if ($status_code === 200) {
            return [
                'success' => true,
                'message' => __('Claude API connection successful!', 'thinkrank'),
            ];
        } else {
            $error_data = json_decode($response_body, true);
            $error_message = $error_data['error']['message'] ?? __('Unknown API error', 'thinkrank');

            return [
                'success' => false,
                /* translators: %1$d: HTTP status code, %2$s: error message from Claude API */
                'message' => sprintf(__('Claude API error (%1$d): %2$s', 'thinkrank'), $status_code, $error_message),
            ];
        }
    }

    /**
     * Test Gemini API connection
     *
     * @param string $api_key API key to test
     * @return array Test result
     */
    private function test_gemini_connection(string $api_key): array {
        // Test with a simple API call
        $gemini_model = (new \ThinkRank\Core\Settings())->get('gemini_model', 'gemini-2.5-flash');
        $url = "https://generativelanguage.googleapis.com/v1beta/models/{$gemini_model}:generateContent?key={$api_key}";

        $body = [
            'contents' => [
                [
                    'parts' => [
                        ['text' => 'Hello']
                    ]
                ]
            ],
            'generationConfig' => [
                'maxOutputTokens' => 10,
                'temperature' => 0.1,
            ]
        ];

        $response = wp_remote_post($url, [
            'headers' => [
                'Content-Type' => 'application/json',
            ],
            'body' => wp_json_encode($body),
            'timeout' => 10,
        ]);

        if (is_wp_error($response)) {
            return [
                'success' => false,
                'message' => __('Failed to connect to Gemini API: ', 'thinkrank') . $response->get_error_message(),
            ];
        }

        $status_code = wp_remote_retrieve_response_code($response);
        $response_body = wp_remote_retrieve_body($response);

        if ($status_code === 200) {
            return [
                'success' => true,
                'message' => __('Gemini API connection successful!', 'thinkrank'),
            ];
        } else {
            $error_data = json_decode($response_body, true);
            $error_message = $error_data['error']['message'] ?? __('Unknown API error', 'thinkrank');

            return [
                'success' => false,
                /* translators: %1$d: HTTP status code, %2$s: error message from Gemini API */
                'message' => sprintf(__('Gemini API error (%1$d): %2$s', 'thinkrank'), $status_code, $error_message),
            ];
        }
    }

    /**
     * Get AI providers
     *
     * @param \WP_REST_Request $request Request object
     * @return \WP_REST_Response Response object
     */
    public function get_ai_providers(\WP_REST_Request $request): \WP_REST_Response {
        $ai_manager = new \ThinkRank\AI\Manager();
        $providers = $ai_manager->get_available_providers();

        return new \WP_REST_Response($providers);
    }

    /**
     * Get AI status
     *
     * @param \WP_REST_Request $request Request object
     * @return \WP_REST_Response Response object
     */
    public function get_ai_status(\WP_REST_Request $request): \WP_REST_Response {
        $ai_manager = new \ThinkRank\AI\Manager();
        $status = $ai_manager->get_provider_status();

        return new \WP_REST_Response($status);
    }

    /**
     * Analyze content for SEO optimization
     *
     * @param \WP_REST_Request $request Request object containing content, metadata, and optional post_id
     * @return \WP_REST_Response Response object with analysis results or error message
     * @throws \Exception When AI analysis fails or AI client initialization fails
     */
    public function analyze_content(\WP_REST_Request $request): \WP_REST_Response {
        $content = $request->get_param('content');
        $metadata = $request->get_param('metadata') ?: [];
        $post_id = $request->get_param('post_id');

        // Rate limiting: per user/IP per route
        $user_id = get_current_user_id();
        $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
        $bucket_id = 'ai_analyze|' . ($user_id ?: $ip);
        $limit = (int) (new \ThinkRank\Core\Settings())->get('max_requests_per_minute', 10);
        $allowed = $this->enforce_rate_limit($bucket_id, $limit);
        if (is_wp_error($allowed)) {
            return new \WP_REST_Response([
                'success' => false,
                'message' => $allowed->get_error_message(),
            ], $allowed->get_error_data()['status'] ?? 429);
        }

        try {
            // Get AI manager instance
            $ai_manager = new \ThinkRank\AI\Manager();
            $ai_manager->initialize_client();

            // Perform content analysis
            $analysis = $ai_manager->analyze_content($content, $metadata);

            return new \WP_REST_Response([
                'success' => true,
                'data' => $analysis,
                'message' => __('Content analyzed successfully', 'thinkrank'),
            ]);
        } catch (\Exception $e) {
            return new \WP_REST_Response([
                'success' => false,
                'message' => $e->getMessage(),
            ], 400);
        }
    }

    /**
     * Sanitize metadata object for API endpoints
     *
     * @param mixed $metadata Metadata to sanitize
     * @return array Sanitized metadata array
     */
    public function sanitize_metadata_object($metadata): array {
        if (!is_array($metadata)) {
            return [];
        }

        $sanitized = [];
        foreach ($metadata as $key => $value) {
            $sanitized_key = sanitize_key($key);

            if (is_string($value)) {
                $sanitized[$sanitized_key] = sanitize_text_field($value);
            } elseif (is_array($value)) {
                // Recursively sanitize nested arrays
                $sanitized[$sanitized_key] = array_map('sanitize_text_field', $value);
            } elseif (is_numeric($value)) {
                $sanitized[$sanitized_key] = (float) $value;
            } elseif (is_bool($value)) {
                $sanitized[$sanitized_key] = (bool) $value;
            }
            // Skip other data types for security
        }

        return $sanitized;
    }

    /**
     * Register endpoint classes
     *
     * @return void
     */
    public function register_endpoint_classes(): void {
        // Register endpoint classes that exist
        try {
            $site_identity_endpoint = new Site_Identity_Endpoint();
            $site_identity_endpoint->register_routes();
        } catch (\Exception $e) {
            // Failed to register Site Identity endpoint
        }

        try {
            $performance_endpoint = new Performance_Endpoint();
            $performance_endpoint->register_routes();
        } catch (\Exception $e) {
            // Failed to register Performance endpoint
        }

        try {
            $schema_endpoint = new Schema_Endpoint();
            $schema_endpoint->register_routes();
        } catch (\Exception $e) {
            // Failed to register Schema endpoint
        }

        try {
            $settings_endpoint = new Settings_Management_Endpoint();
            $settings_endpoint->register_routes();
        } catch (\Exception $e) {
            // Failed to register Settings Management endpoint
        }

        try {
            $integrations_endpoint = new Integrations_Endpoint();
            $integrations_endpoint->register_routes();
        } catch (\Exception $e) {
            // Failed to register Integrations endpoint
        }

        try {
            $social_platforms_endpoint = new Social_Platforms_Endpoint();
            $social_platforms_endpoint->register_routes();
        } catch (\Exception $e) {
            // Failed to register Social Platforms endpoint
        }

        try {
            $content_brief_endpoint = new Content_Brief_Endpoint();
            $content_brief_endpoint->register_routes();
        } catch (\Exception $e) {
            // Failed to register Content Brief endpoint
        }

        try {
            $social_media_endpoint = new Social_Media_Endpoint();
            $social_media_endpoint->register_routes();
        } catch (\Exception $e) {
            // Failed to register Social Media endpoint
        }

        try {
            $sitemap_endpoint = new Sitemap_Endpoint();
            $sitemap_endpoint->register_routes();
        } catch (\Exception $e) {
            // Failed to register Sitemap endpoint
        }

        try {
            $llms_txt_endpoint = new LLMs_Txt_Endpoint();
            $llms_txt_endpoint->register_routes();
        } catch (\Exception $e) {
            // Failed to register LLMs.txt endpoint
        }

        try {
            $global_seo_endpoint = new Global_SEO_Endpoint();
            $global_seo_endpoint->register_routes();
        } catch (\Exception $e) {
            // Failed to register Global SEO endpoint
        }

        try {
            $image_seo_endpoint = new Image_SEO_Endpoint();
            $image_seo_endpoint->register_routes();
        } catch (\Exception $e) {
            // Failed to register Image SEO endpoint
        }
    }
}

```
