# thinkrank/1.0.1/includes/integrations/class-google-search-console-client.php

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

- Page: https://pluginprobe.com/plugins/thinkrank/1.0.1/code/includes/integrations/class-google-search-console-client.php
- Raw: https://pluginprobe.com/plugins/thinkrank/1.0.1/raw/includes/integrations/class-google-search-console-client.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/integrations/class-google-search-console-client.php#L10-L20`.

```php
<?php
/**
 * Google Search Console Client Class
 *
 * Handles communication with Google Search Console API for search performance
 * data retrieval and site verification. Extends the base Google API client with
 * Search Console-specific functionality and rate limiting.
 *
 * @package ThinkRank\Integrations
 * @since 1.0.0
 */

declare(strict_types=1);

namespace ThinkRank\Integrations;

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

/**
 * Google Search Console Client Class
 *
 * Single Responsibility: Handle Google Search Console API communication
 * Following ThinkRank HTTP client patterns from Claude_Client and OpenAI_Client
 *
 * @since 1.0.0
 */
class Google_Search_Console_Client extends Google_API_Base_Client {

    /**
     * Google Search Console API base URL
     */
    private const API_BASE_URL = 'https://www.googleapis.com/webmasters/v3';

    /**
     * Rate limit transient key prefix
     * Following ThinkRank option naming patterns
     */
    private const RATE_LIMIT_KEY = 'thinkrank_gsc_rate_limit';

    /**
     * Maximum requests per day (Google standard quota)
     */
    private const MAX_REQUESTS_PER_DAY = 2000;

    /**
     * Test API connection
     * Following ThinkRank test_connection patterns from AI clients
     *
     * @return array Connection test results
     */
    public function test_connection(): array {
        try {
            $result = $this->list_sites();

            return [
                'success' => true,
                'message' => 'Google Search Console API connection successful',
                'sites_count' => count($result['siteEntry'] ?? [])
            ];
        } catch (\Exception $e) {
            return [
                'success' => false,
                'error' => $e->getMessage()
            ];
        }
    }

    /**
     * List verified sites in Search Console
     *
     * @return array List of verified sites
     * @throws \Exception If API request fails
     */
    public function list_sites(): array {
        $endpoint = '/sites';
        $params = ['key' => $this->api_key];

        $full_url = self::API_BASE_URL . $endpoint;
        return $this->make_request($full_url, $params, 'GET');
    }

    /**
     * Verify site ownership in Search Console
     *
     * @param string $site_url Site URL to verify
     * @param string $verification_method Verification method used
     * @return array Verification results
     */
    public function verify_site(string $site_url, string $verification_method = 'meta'): array {
        try {
            // Check if the site is already verified by listing sites
            $sites = $this->list_sites();
            $site_verified = false;

            foreach ($sites['siteEntry'] ?? [] as $site) {
                if ($site['siteUrl'] === $site_url) {
                    $site_verified = true;
                    break;
                }
            }

            return [
                'success' => $site_verified,
                'message' => $site_verified ? 'Site is verified in Search Console' : 'Site not found in Search Console',
                'site_url' => $site_url,
                'verification_method' => $verification_method
            ];
        } catch (\Exception $e) {
            return [
                'success' => false,
                'error' => $e->getMessage()
            ];
        }
    }

    /**
     * Get search performance data with flexible dimensions
     *
     * @param string $site_url Site URL to get data for
     * @param string $date_range Date range ('7d', '30d', '90d')
     * @param array $dimensions Dimensions to group by (query, page, country, device, searchAppearance)
     * @param int $row_limit Maximum number of rows to return
     * @return array Search performance data
     * @throws \Exception If API request fails
     */
    public function get_search_performance(string $site_url, string $date_range = '30d', array $dimensions = ['query'], int $row_limit = 100): array {
        // Calculate date range
        $end_date = gmdate('Y-m-d');
        $days = (int) str_replace('d', '', $date_range);
        $start_date = gmdate('Y-m-d', strtotime("-{$days} days"));

        $endpoint = '/sites/' . urlencode($site_url) . '/searchAnalytics/query';

        $request_body = [
            'startDate' => $start_date,
            'endDate' => $end_date,
            'dimensions' => $dimensions,
            'rowLimit' => $row_limit
        ];

        $full_url = self::API_BASE_URL . $endpoint . '?key=' . $this->api_key;
        return $this->make_request($full_url, $request_body, 'POST');
    }

    /**
     * Get top search queries
     *
     * @param string $site_url Site URL to get data for
     * @param int $limit Number of queries to retrieve
     * @return array Top search queries
     * @throws \Exception If API request fails
     */
    public function get_top_queries(string $site_url, int $limit = 10): array {
        try {
            $performance_data = $this->get_search_performance($site_url, '30d');
            $queries = [];

            foreach ($performance_data['rows'] ?? [] as $row) {
                if (count($queries) >= $limit) {
                    break;
                }

                $queries[] = [
                    'query' => $row['keys'][0] ?? '',
                    'clicks' => $row['clicks'] ?? 0,
                    'impressions' => $row['impressions'] ?? 0,
                    'ctr' => $row['ctr'] ?? 0,
                    'position' => $row['position'] ?? 0
                ];
            }

            return [
                'queries' => $queries,
                'site_url' => $site_url,
                'limit' => $limit
            ];
        } catch (\Exception $e) {
            return [
                'queries' => [],
                'site_url' => $site_url,
                'limit' => $limit,
                'error' => $e->getMessage()
            ];
        }
    }

    /**
     * Get page performance data for SEO analytics
     *
     * @param string $site_url Site URL to get data for
     * @param string $date_range Date range for data
     * @param int $limit Number of pages to retrieve
     * @return array Page performance data
     * @throws \Exception If API request fails
     */
    public function get_page_performance(string $site_url, string $date_range = '30d', int $limit = 25): array {
        $result = $this->get_search_performance($site_url, $date_range, ['page'], $limit);

        $pages = [];
        $rows = $result['rows'] ?? [];

        foreach ($rows as $row) {
            $pages[] = [
                'page' => $row['keys'][0] ?? '',
                'clicks' => $row['clicks'] ?? 0,
                'impressions' => $row['impressions'] ?? 0,
                'ctr' => round(($row['ctr'] ?? 0) * 100, 2), // Convert to percentage
                'position' => round($row['position'] ?? 0, 1)
            ];
        }

        return [
            'pages' => $pages,
            'site_url' => $site_url,
            'date_range' => $date_range,
            'total_pages' => count($pages)
        ];
    }

    /**
     * Get device performance breakdown for mobile SEO insights
     *
     * @param string $site_url Site URL to get data for
     * @param string $date_range Date range for data
     * @return array Device performance data
     * @throws \Exception If API request fails
     */
    public function get_device_performance(string $site_url, string $date_range = '30d'): array {
        $result = $this->get_search_performance($site_url, $date_range, ['device'], 10);

        $devices = [];
        $rows = $result['rows'] ?? [];

        foreach ($rows as $row) {
            $device = $row['keys'][0] ?? '';
            $devices[$device] = [
                'clicks' => $row['clicks'] ?? 0,
                'impressions' => $row['impressions'] ?? 0,
                'ctr' => round(($row['ctr'] ?? 0) * 100, 2),
                'position' => round($row['position'] ?? 0, 1)
            ];
        }

        return [
            'devices' => $devices,
            'site_url' => $site_url,
            'date_range' => $date_range
        ];
    }

    /**
     * Get search appearance data for rich results tracking
     *
     * @param string $site_url Site URL to get data for
     * @param string $date_range Date range for data
     * @return array Search appearance data
     * @throws \Exception If API request fails
     */
    public function get_search_appearance(string $site_url, string $date_range = '30d'): array {
        $result = $this->get_search_performance($site_url, $date_range, ['searchAppearance'], 20);

        $appearances = [];
        $rows = $result['rows'] ?? [];

        foreach ($rows as $row) {
            $appearance = $row['keys'][0] ?? '';
            $appearances[$appearance] = [
                'clicks' => $row['clicks'] ?? 0,
                'impressions' => $row['impressions'] ?? 0,
                'ctr' => round(($row['ctr'] ?? 0) * 100, 2),
                'position' => round($row['position'] ?? 0, 1)
            ];
        }

        return [
            'appearances' => $appearances,
            'site_url' => $site_url,
            'date_range' => $date_range
        ];
    }

    /**
     * Get site indexing status and coverage data
     *
     * @param string $site_url Site URL to check
     * @return array Indexing status and coverage data
     * @throws \Exception If API request fails
     */
    public function get_indexing_status(string $site_url): array {
        try {
            // Get overall search performance to estimate indexed pages
            $performance = $this->get_search_performance($site_url, '30d', ['page'], 1000);
            $indexed_pages = count($performance['rows'] ?? []);

            // Get basic site info
            $sites = $this->list_sites();
            $site_info = null;

            foreach ($sites['siteEntry'] ?? [] as $site) {
                if ($site['siteUrl'] === $site_url) {
                    $site_info = $site;
                    break;
                }
            }

            return [
                'site_url' => $site_url,
                'is_verified' => !is_null($site_info),
                'indexed_pages_estimate' => $indexed_pages,
                'permission_level' => $site_info['permissionLevel'] ?? 'none',
                'last_updated' => gmdate('Y-m-d H:i:s')
            ];
        } catch (\Exception $e) {
            return [
                'site_url' => $site_url,
                'is_verified' => false,
                'indexed_pages_estimate' => 0,
                'permission_level' => 'none',
                'error' => $e->getMessage(),
                'last_updated' => gmdate('Y-m-d H:i:s')
            ];
        }
    }

    /**
     * Get keyword opportunities for SEO insights
     * Identifies queries with high impressions but low CTR or position
     *
     * @param string $site_url Site URL to analyze
     * @param string $date_range Date range for analysis
     * @param int $min_impressions Minimum impressions threshold
     * @return array Keyword opportunities
     * @throws \Exception If API request fails
     */
    public function get_keyword_opportunities(string $site_url, string $date_range = '30d', int $min_impressions = 100): array {
        $result = $this->get_search_performance($site_url, $date_range, ['query'], 500);

        $opportunities = [];
        $rows = $result['rows'] ?? [];

        foreach ($rows as $row) {
            $impressions = $row['impressions'] ?? 0;
            $ctr = $row['ctr'] ?? 0;
            $position = $row['position'] ?? 0;
            $clicks = $row['clicks'] ?? 0;

            // Identify opportunities: high impressions, low CTR, or position 4-10
            if ($impressions >= $min_impressions) {
                $opportunity_score = 0;
                $opportunity_reasons = [];

                // Low CTR opportunity
                if ($ctr < 0.05 && $position <= 10) { // Less than 5% CTR in top 10
                    $opportunity_score += 30;
                    $opportunity_reasons[] = 'Low CTR for top 10 position';
                }

                // Position 4-10 opportunity (could reach top 3)
                if ($position >= 4 && $position <= 10) {
                    $opportunity_score += 40;
                    $opportunity_reasons[] = 'Ranking 4-10, potential for top 3';
                }

                // High impressions, low clicks
                if ($impressions > 500 && $clicks < 25) {
                    $opportunity_score += 20;
                    $opportunity_reasons[] = 'High impressions but low clicks';
                }

                if ($opportunity_score > 0) {
                    $opportunities[] = [
                        'query' => $row['keys'][0] ?? '',
                        'clicks' => $clicks,
                        'impressions' => $impressions,
                        'ctr' => round($ctr * 100, 2),
                        'position' => round($position, 1),
                        'opportunity_score' => $opportunity_score,
                        'reasons' => $opportunity_reasons
                    ];
                }
            }
        }

        // Sort by opportunity score (highest first)
        usort($opportunities, function($a, $b) {
            return $b['opportunity_score'] <=> $a['opportunity_score'];
        });

        return [
            'opportunities' => array_slice($opportunities, 0, 50), // Top 50 opportunities
            'site_url' => $site_url,
            'date_range' => $date_range,
            'total_opportunities' => count($opportunities)
        ];
    }

    /**
     * Get rate limit configuration
     * Following ThinkRank rate limiting patterns
     *
     * @return array Rate limit configuration
     */
    protected function get_rate_limits(): array {
        return [
            'max_requests_per_day' => self::MAX_REQUESTS_PER_DAY,
            'reset_time' => get_transient(self::RATE_LIMIT_KEY . '_reset') ?: strtotime('tomorrow')
        ];
    }

    /**
     * Get rate limit transient key
     * Following ThinkRank option naming patterns
     *
     * @return string Rate limit key
     */
    protected function get_rate_limit_key(): string {
        return self::RATE_LIMIT_KEY;
    }

    /**
     * Get rate limit error message
     *
     * @return string Error message
     */
    protected function get_rate_limit_error_message(): string {
        return 'Google Search Console API rate limit exceeded. Try again tomorrow.';
    }
}

```
