PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 1.28.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v1.28.0
2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 1.25.0 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.10.0 All 48 releases
thinkrank / includes / integrations / class-google-api-base-client.php

class-google-api-base-client.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 1.28.0, at includes/integrations/class-google-api-base-client.php

232 lines 7.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Google API Base Client Class
5 *
6 * Abstract base class for Google API integrations providing common functionality
7 * for HTTP requests, rate limiting, error handling, and response processing.
8 * Follows ThinkRank patterns established by Claude_Client and OpenAI_Client.
9 *
10 * @package ThinkRank\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 API Base Client Class
25 *
26 * Single Responsibility: Provide common Google API functionality
27 * Following ThinkRank HTTP client patterns from Claude_Client and OpenAI_Client
28 *
29 * @since 1.0.0
30 */
31 abstract class Google_API_Base_Client {
32
33 /**
34 * API key
35 *
36 * @var string
37 */
38 protected string $api_key;
39
40 /**
41 * OAuth Access Token
42 *
43 * @var string|null
44 */
45 protected ?string $access_token = null;
46
47 /**
48 * Request timeout in seconds
49 *
50 * @var int
51 */
52 protected int $timeout;
53
54 /**
55 * Rate limit configuration
56 *
57 * @var array
58 */
59 protected array $rate_limits;
60
61 /**
62 * Constructor
63 *
64 * @param string $api_key Google API key
65 * @param int $timeout Request timeout in seconds
66 * @param string|null $access_token OAuth Access Token (optional)
67 */
68 public function __construct(string $api_key, int $timeout = 20, ?string $access_token = null) {
69 $this->api_key = $api_key;
70 $this->timeout = $timeout;
71 $this->access_token = $access_token;
72 $this->rate_limits = $this->get_rate_limits();
73 }
74
75 /**
76 * Make HTTP request to Google API
77 * Following Claude_Client and OpenAI_Client patterns
78 *
79 * @param string $url Full API URL
80 * @param array $params Request parameters
81 * @param string $method HTTP method
82 * @return array Response data
83 * @throws \Exception If request fails
84 */
85 protected function make_request(string $url, array $params = [], string $method = 'GET'): array {
86 // Check rate limiting before making request
87 $this->check_rate_limit();
88
89 $args = [
90 'timeout' => $this->timeout,
91 'headers' => [
92 'User-Agent' => 'ThinkRank/' . THINKRANK_VERSION,
93 ],
94 'method' => $method
95 ];
96
97 // Add OAuth Authorization header if token exists; otherwise fall back to
98 // the API key sent in the x-goog-api-key HEADER (never the query string,
99 // which is logged by servers, proxies and referrers).
100 if (!empty($this->access_token)) {
101 $args['headers']['Authorization'] = 'Bearer ' . $this->access_token;
102 } elseif (!empty($this->api_key)) {
103 $args['headers']['x-goog-api-key'] = $this->api_key;
104 }
105
106 // Defensive: never let a key travel in the query string.
107 unset($params['key']);
108
109 if ($method === 'GET' && !empty($params)) {
110 $url .= '?' . http_build_query($params);
111 } elseif ($method === 'POST') {
112 $args['body'] = wp_json_encode($params);
113 $args['headers']['Content-Type'] = 'application/json';
114 }
115
116 $response = wp_remote_request($url, $args);
117
118 if (is_wp_error($response)) {
119 throw new \Exception('API request failed: ' . esc_html($response->get_error_message()));
120 }
121
122 $status_code = wp_remote_retrieve_response_code($response);
123 $response_body = wp_remote_retrieve_body($response);
124
125 if ($status_code >= 400) {
126 $error_data = json_decode($response_body, true);
127 $error_message = $error_data['error']['message'] ?? 'Unknown API error';
128 throw new \Exception(sprintf('Google API error (%d): %s', (int) $status_code, esc_html($error_message)), (int) $status_code);
129 }
130
131 $data = json_decode($response_body, true);
132
133 if (json_last_error() !== JSON_ERROR_NONE) {
134 throw new \Exception('Invalid JSON response from Google API');
135 }
136
137 // A valid-but-scalar body (null/number/string from a proxy/WAF/CDN on a
138 // 2xx) would violate this method's : array return type; reject it here so
139 // it surfaces as a catchable \Exception, not an uncatchable TypeError.
140 if (!is_array($data)) {
141 throw new \Exception('Unexpected non-array response from Google API');
142 }
143
144 // Update rate limit tracking after successful request
145 $this->update_rate_limit();
146
147 return $data;
148 }
149
150 /**
151 * Test API connection
152 * Must be implemented by concrete classes
153 *
154 * @return array Connection test results
155 */
156 abstract public function test_connection(): array;
157
158 /**
159 * Get rate limit configuration
160 * Must be implemented by concrete classes
161 *
162 * @return array Rate limit configuration
163 */
164 abstract protected function get_rate_limits(): array;
165
166 /**
167 * Check if request is within rate limits
168 * Following ThinkRank rate limiting patterns from existing classes
169 *
170 * @throws \Exception If rate limit exceeded
171 */
172 protected function check_rate_limit(): void {
173 $rate_limit_key = $this->get_rate_limit_key();
174 $current_time = time();
175
176 // Reset counter if it's a new day
177 if ($current_time >= $this->rate_limits['reset_time']) {
178 $this->reset_rate_limit_counter();
179 }
180
181 $current_count = get_transient($rate_limit_key . '_count') ?: 0;
182
183 if ($current_count >= $this->rate_limits['max_requests_per_day']) {
184 throw new \Exception(esc_html($this->get_rate_limit_error_message()));
185 }
186 }
187
188 /**
189 * Update rate limit counter after successful request
190 * Following ThinkRank transient patterns
191 */
192 protected function update_rate_limit(): void {
193 $rate_limit_key = $this->get_rate_limit_key();
194 $current_count = get_transient($rate_limit_key . '_count') ?: 0;
195 $new_count = $current_count + 1;
196
197 // Set transient to expire at end of day
198 $seconds_until_tomorrow = strtotime('tomorrow') - time();
199 set_transient($rate_limit_key . '_count', $new_count, $seconds_until_tomorrow);
200 }
201
202 /**
203 * Reset rate limit counter for new day
204 */
205 private function reset_rate_limit_counter(): void {
206 $rate_limit_key = $this->get_rate_limit_key();
207 delete_transient($rate_limit_key . '_count');
208 delete_transient($rate_limit_key . '_reset');
209
210 // Set new reset time for tomorrow
211 $seconds_until_tomorrow = strtotime('tomorrow') - time();
212 set_transient($rate_limit_key . '_reset', strtotime('tomorrow'), $seconds_until_tomorrow);
213 $this->rate_limits['reset_time'] = strtotime('tomorrow');
214 }
215
216 /**
217 * Get rate limit transient key
218 * Following ThinkRank option naming patterns
219 *
220 * @return string Rate limit key
221 */
222 abstract protected function get_rate_limit_key(): string;
223
224 /**
225 * Get rate limit error message
226 * Must be implemented by concrete classes
227 *
228 * @return string Error message
229 */
230 abstract protected function get_rate_limit_error_message(): string;
231 }
232