PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / trunk
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO vtrunk
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 1.11.0 All 47 releases
thinkrank / includes / integrations / class-google-analytics-client.php

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

325 lines 9.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Google Analytics Client Class
5 *
6 * Handles communication with Google Analytics API for website analytics data
7 * retrieval and connection testing. Extends the base Google API client with
8 * Analytics-specific functionality and rate limiting.
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 Analytics Client Class
25 *
26 * Single Responsibility: Handle Google Analytics API communication
27 * Following ThinkRank HTTP client patterns from Claude_Client and OpenAI_Client
28 *
29 * @since 1.0.0
30 */
31 class Google_Analytics_Client extends Google_API_Base_Client {
32
33 /**
34 * Google Analytics Data API base URL (GA4)
35 */
36 private const API_BASE_URL = 'https://analyticsdata.googleapis.com/v1beta';
37
38 /**
39 * Rate limit transient key prefix
40 * Following ThinkRank option naming patterns
41 */
42 private const RATE_LIMIT_KEY = 'thinkrank_analytics_rate_limit';
43
44 /**
45 * Maximum requests per day (Google standard quota)
46 */
47 private const MAX_REQUESTS_PER_DAY = 50000;
48
49 /**
50 * Google Analytics Property ID (GA4)
51 *
52 * @var string
53 */
54 private string $property_id;
55
56 /**
57 * Constructor
58 *
59 * @param string $api_key Google Analytics API key
60 * @param string $property_id GA4 Property ID (properties/XXXXXXXXX)
61 * @param int $timeout Request timeout in seconds
62 * @param string|null $access_token OAuth Access Token (optional)
63 */
64 public function __construct(string $api_key, string $property_id, int $timeout = 30, ?string $access_token = null) {
65 parent::__construct($api_key, $timeout, $access_token);
66 $this->property_id = $property_id;
67 }
68
69 /**
70 * Test API connection
71 * Following ThinkRank test_connection patterns from AI clients
72 *
73 * @return array Connection test results
74 */
75 public function test_connection(): array {
76 try {
77 // Test with a simple metadata request
78 $result = $this->get_metadata();
79
80 return [
81 'success' => true,
82 'message' => 'Google Analytics Data API connection successful',
83 'property_id' => $this->property_id,
84 'dimensions_count' => count($result['dimensions'] ?? []),
85 'metrics_count' => count($result['metrics'] ?? [])
86 ];
87 } catch (\Exception $e) {
88 return [
89 'success' => false,
90 'error' => $e->getMessage()
91 ];
92 }
93 }
94
95 /**
96 * Get metadata for available dimensions and metrics
97 *
98 * @return array Metadata information
99 * @throws \Exception If API request fails
100 */
101 public function get_metadata(): array {
102 $endpoint = "/{$this->property_id}/metadata";
103 $params = [];
104
105 $full_url = self::API_BASE_URL . $endpoint;
106
107 // Only append API key if no access token is present
108 if (!empty($this->api_key) && empty($this->access_token)) {
109 $params['key'] = $this->api_key;
110 }
111
112 return $this->make_request($full_url, $params, 'GET');
113 }
114
115 /**
116 * Run analytics report for specified metrics and date range
117 *
118 * @param string $date_range Date range ('7d', '30d', '90d')
119 * @param array $metrics Metrics to retrieve (GA4 metric names)
120 * @param array $dimensions Dimensions to group by
121 * @return array Analytics report data
122 * @throws \Exception If API request fails
123 */
124 public function run_report(string $date_range = '30d', array $metrics = ['sessions'], array $dimensions = []): array {
125 $endpoint = "/{$this->property_id}:runReport";
126
127 // Convert date range to start/end dates
128 $end_date = gmdate('Y-m-d');
129 $days = (int) str_replace('d', '', $date_range);
130 $start_date = gmdate('Y-m-d', strtotime("-{$days} days"));
131
132 $request_body = [
133 'dateRanges' => [
134 [
135 'startDate' => $start_date,
136 'endDate' => $end_date
137 ]
138 ],
139 'metrics' => array_map(function ($metric) {
140 return ['name' => $metric];
141 }, $metrics)
142 ];
143
144 // Add dimensions if provided
145 if (!empty($dimensions)) {
146 $request_body['dimensions'] = array_map(function ($dimension) {
147 return ['name' => $dimension];
148 }, $dimensions);
149 }
150
151 $full_url = self::API_BASE_URL . $endpoint;
152
153 // The API key is sent by make_request() in the x-goog-api-key header — never
154 // in the query string, which is captured by proxy/access logs.
155 return $this->make_request($full_url, $request_body, 'POST');
156 }
157
158 /**
159 * Get website traffic data using GA4 metrics
160 *
161 * @param string $date_range Date range for data
162 * @return array Traffic data
163 * @throws \Exception If API request fails
164 */
165 public function get_traffic_data(string $date_range = '30d'): array {
166 $metrics = [
167 'sessions',
168 'screenPageViews',
169 'bounceRate',
170 'averageSessionDuration',
171 'activeUsers'
172 ];
173
174 $result = $this->run_report($date_range, $metrics);
175
176 // Parse the response and extract metric values
177 $rows = $result['rows'] ?? [];
178 $metric_values = [];
179
180 if (!empty($rows)) {
181 $metric_values = $rows[0]['metricValues'] ?? [];
182 }
183
184 return [
185 'sessions' => (int) ($metric_values[0]['value'] ?? 0),
186 'pageviews' => (int) ($metric_values[1]['value'] ?? 0),
187 'bounce_rate' => (float) ($metric_values[2]['value'] ?? 0),
188 'avg_session_duration' => (float) ($metric_values[3]['value'] ?? 0),
189 'active_users' => (int) ($metric_values[4]['value'] ?? 0),
190 'date_range' => $date_range,
191 'property_id' => $this->property_id
192 ];
193 }
194
195 /**
196 * Get top pages data using GA4 dimensions and metrics
197 *
198 * @param int $limit Number of pages to retrieve
199 * @param string $date_range Date range for data
200 * @return array Top pages data
201 * @throws \Exception If API request fails
202 */
203 public function get_top_pages(int $limit = 10, string $date_range = '30d'): array {
204 $metrics = ['screenPageViews', 'sessions'];
205 $dimensions = ['pagePath', 'pageTitle'];
206
207 $result = $this->run_report($date_range, $metrics, $dimensions);
208
209 $pages = [];
210 $rows = $result['rows'] ?? [];
211
212 foreach (array_slice($rows, 0, $limit) as $row) {
213 $dimension_values = $row['dimensionValues'] ?? [];
214 $metric_values = $row['metricValues'] ?? [];
215
216 $pages[] = [
217 'path' => $dimension_values[0]['value'] ?? '',
218 'title' => $dimension_values[1]['value'] ?? '',
219 'pageviews' => (int) ($metric_values[0]['value'] ?? 0),
220 'sessions' => (int) ($metric_values[1]['value'] ?? 0)
221 ];
222 }
223
224 return [
225 'pages' => $pages,
226 'limit' => $limit,
227 'date_range' => $date_range,
228 'total_pages' => count($rows)
229 ];
230 }
231
232 /**
233 * Get organic search traffic data for SEO analytics
234 *
235 * @param string $date_range Date range for data
236 * @return array Organic traffic data
237 * @throws \Exception If API request fails
238 */
239 public function get_organic_traffic(string $date_range = '30d'): array {
240 $metrics = ['sessions', 'screenPageViews', 'activeUsers'];
241 $dimensions = ['sessionDefaultChannelGrouping'];
242
243 $result = $this->run_report($date_range, $metrics, $dimensions);
244
245 $organic_data = [
246 'sessions' => 0,
247 'pageviews' => 0,
248 'users' => 0
249 ];
250
251 $rows = $result['rows'] ?? [];
252
253 foreach ($rows as $row) {
254 $dimension_values = $row['dimensionValues'] ?? [];
255 $metric_values = $row['metricValues'] ?? [];
256
257 $channel = $dimension_values[0]['value'] ?? '';
258
259 // Filter for organic search traffic
260 if (strtolower($channel) === 'organic search') {
261 $organic_data['sessions'] = (int) ($metric_values[0]['value'] ?? 0);
262 $organic_data['pageviews'] = (int) ($metric_values[1]['value'] ?? 0);
263 $organic_data['users'] = (int) ($metric_values[2]['value'] ?? 0);
264 break;
265 }
266 }
267
268 return [
269 'organic_traffic' => $organic_data,
270 'date_range' => $date_range,
271 'property_id' => $this->property_id
272 ];
273 }
274
275 /**
276 * Get rate limit configuration
277 * Following ThinkRank rate limiting patterns
278 *
279 * @return array Rate limit configuration
280 */
281 protected function get_rate_limits(): array {
282 return [
283 'max_requests_per_day' => self::MAX_REQUESTS_PER_DAY,
284 'reset_time' => get_transient(self::RATE_LIMIT_KEY . '_reset') ?: strtotime('tomorrow')
285 ];
286 }
287
288 /**
289 * Get rate limit transient key
290 * Following ThinkRank option naming patterns
291 *
292 * @return string Rate limit key
293 */
294 protected function get_rate_limit_key(): string {
295 return self::RATE_LIMIT_KEY;
296 }
297
298 /**
299 * Get rate limit error message
300 *
301 * @return string Error message
302 */
303 protected function get_rate_limit_error_message(): string {
304 return 'Google Analytics API rate limit exceeded. Try again tomorrow.';
305 }
306
307 /**
308 * Get measurement ID
309 *
310 * @return string Measurement ID
311 */
312 public function get_measurement_id(): string {
313 return $this->property_id;
314 }
315
316 /**
317 * Get property ID
318 *
319 * @return string Property ID
320 */
321 public function get_property_id(): string {
322 return $this->property_id;
323 }
324 }
325