| 1 |
<?php |
| 2 |
|
| 3 |
/** |
| 4 |
* Google Search Analytics Client |
| 5 |
* |
| 6 |
* Client library for interacting with Google Search Console Search Analytics API. |
| 7 |
* Extends the specific Google API Base Client for standardized request handling. |
| 8 |
* |
| 9 |
* @package ThinkRank |
| 10 |
* @subpackage Integrations |
| 11 |
* @since 1.0.0 |
| 12 |
*/ |
| 13 |
|
| 14 |
declare(strict_types=1); |
| 15 |
|
| 16 |
namespace ThinkRank\Integrations; |
| 17 |
|
| 18 |
// Prevent direct access |
| 19 |
if (!defined('ABSPATH')) { |
| 20 |
exit; |
| 21 |
} |
| 22 |
|
| 23 |
/** |
| 24 |
* Google Search Analytics Client Class |
| 25 |
* |
| 26 |
* specific client for fetching search analytics data from Google Search Console. |
| 27 |
* |
| 28 |
* @since 1.0.0 |
| 29 |
*/ |
| 30 |
class Google_Search_Analytics_Client extends Google_API_Base_Client { |
| 31 |
|
| 32 |
/** |
| 33 |
* API Base URL |
| 34 |
* |
| 35 |
* @var string |
| 36 |
*/ |
| 37 |
protected const API_BASE_URL = 'https://www.googleapis.com/webmasters/v3'; |
| 38 |
|
| 39 |
/** |
| 40 |
* Get search analytics data |
| 41 |
* |
| 42 |
* @param string $site_url The URL of the property in search console. |
| 43 |
* @param string $start_date Start date in YYYY-MM-DD format. |
| 44 |
* @param string $end_date End date in YYYY-MM-DD format. |
| 45 |
* @param array $dimensions Dimensions to group by (e.g., 'query', 'page', 'country', 'device'). |
| 46 |
* @param int $row_limit Maximum number of rows to return. |
| 47 |
* @return array Analytical data including rows and totals. |
| 48 |
* @throws \Exception If the API request fails. |
| 49 |
*/ |
| 50 |
public function get_search_analytics_data(string $site_url, string $start_date, string $end_date, array $dimensions = [], int $row_limit = 1000): array { |
| 51 |
$endpoint = '/sites/' . rawurlencode($site_url) . '/searchAnalytics/query'; |
| 52 |
|
| 53 |
$request_body = [ |
| 54 |
'startDate' => $start_date, |
| 55 |
'endDate' => $end_date, |
| 56 |
'dimensions' => $dimensions, |
| 57 |
'rowLimit' => $row_limit, |
| 58 |
// Include fresh (still-processing) data so the most recent days |
| 59 |
// aren't missing — matches the GSC web UI default. |
| 60 |
'dataState' => 'all', |
| 61 |
]; |
| 62 |
|
| 63 |
// Construct full URL. The API key is sent by make_request() in the |
| 64 |
// x-goog-api-key header, not the query string (which proxies/logs capture). |
| 65 |
$full_url = self::API_BASE_URL . $endpoint; |
| 66 |
|
| 67 |
$response = $this->make_request($full_url, $request_body, 'POST'); |
| 68 |
|
| 69 |
|
| 70 |
return [ |
| 71 |
'rows' => $response['rows'] ?? [], |
| 72 |
'responseAggregationType' => $response['responseAggregationType'] ?? null, |
| 73 |
]; |
| 74 |
} |
| 75 |
|
| 76 |
/** |
| 77 |
* Test the connection to the API |
| 78 |
* |
| 79 |
* @return array Connection test result |
| 80 |
* |
| 81 |
* @throws \Exception On failure. |
| 82 |
*/ |
| 83 |
public function test_connection(): array { |
| 84 |
try { |
| 85 |
// We can't easily test without a site URL, but we can check if credentials are present |
| 86 |
if (empty($this->api_key) && empty($this->access_token)) { |
| 87 |
throw new \Exception('No API key or Access Token provided.'); |
| 88 |
} |
| 89 |
|
| 90 |
return [ |
| 91 |
'success' => true, |
| 92 |
'message' => 'Client initialized with credentials.', |
| 93 |
]; |
| 94 |
} catch (\Exception $e) { |
| 95 |
return [ |
| 96 |
'success' => false, |
| 97 |
'message' => $e->getMessage(), |
| 98 |
]; |
| 99 |
} |
| 100 |
} |
| 101 |
|
| 102 |
/** |
| 103 |
* Get rate limits for this API |
| 104 |
* |
| 105 |
* @return array Rate limit configuration |
| 106 |
*/ |
| 107 |
protected function get_rate_limits(): array { |
| 108 |
return [ |
| 109 |
// Shares Google Search Console's 2000 requests/day quota — this client |
| 110 |
// hits the same GSC endpoint as Google_Search_Console_Client, so it must |
| 111 |
// count against the same budget instead of running effectively unlimited. |
| 112 |
'max_requests_per_day' => 2000, |
| 113 |
'reset_time' => get_transient($this->get_rate_limit_key() . '_reset') ?: strtotime('tomorrow'), |
| 114 |
]; |
| 115 |
} |
| 116 |
|
| 117 |
/** |
| 118 |
* Get the rate limit key for creating unique transients |
| 119 |
* |
| 120 |
* Uses the same plugin-prefixed key as Google_Search_Console_Client so both |
| 121 |
* clients share (and jointly respect) the single GSC daily quota. |
| 122 |
* |
| 123 |
* @return string Rate limit key |
| 124 |
*/ |
| 125 |
protected function get_rate_limit_key(): string { |
| 126 |
return 'thinkrank_gsc_rate_limit'; |
| 127 |
} |
| 128 |
|
| 129 |
/** |
| 130 |
* Get the error message to display when rate limit is exceeded |
| 131 |
* |
| 132 |
* @return string Error message |
| 133 |
*/ |
| 134 |
protected function get_rate_limit_error_message(): string { |
| 135 |
return 'Google Search Console API rate limit exceeded. Please try again later.'; |
| 136 |
} |
| 137 |
} |
| 138 |
|