PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 1.10.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v1.10.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-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 1.10.0, at includes/integrations/class-google-analytics-client.php

328 lines 9.9 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 // Only append API key if no access token is present
154 if (!empty($this->api_key) && empty($this->access_token)) {
155 $full_url .= '?key=' . $this->api_key;
156 }
157
158 return $this->make_request($full_url, $request_body, 'POST');
159 }
160
161 /**
162 * Get website traffic data using GA4 metrics
163 *
164 * @param string $date_range Date range for data
165 * @return array Traffic data
166 * @throws \Exception If API request fails
167 */
168 public function get_traffic_data(string $date_range = '30d'): array {
169 $metrics = [
170 'sessions',
171 'screenPageViews',
172 'bounceRate',
173 'averageSessionDuration',
174 'activeUsers'
175 ];
176
177 $result = $this->run_report($date_range, $metrics);
178
179 // Parse the response and extract metric values
180 $rows = $result['rows'] ?? [];
181 $metric_values = [];
182
183 if (!empty($rows)) {
184 $metric_values = $rows[0]['metricValues'] ?? [];
185 }
186
187 return [
188 'sessions' => (int) ($metric_values[0]['value'] ?? 0),
189 'pageviews' => (int) ($metric_values[1]['value'] ?? 0),
190 'bounce_rate' => (float) ($metric_values[2]['value'] ?? 0),
191 'avg_session_duration' => (float) ($metric_values[3]['value'] ?? 0),
192 'active_users' => (int) ($metric_values[4]['value'] ?? 0),
193 'date_range' => $date_range,
194 'property_id' => $this->property_id
195 ];
196 }
197
198 /**
199 * Get top pages data using GA4 dimensions and metrics
200 *
201 * @param int $limit Number of pages to retrieve
202 * @param string $date_range Date range for data
203 * @return array Top pages data
204 * @throws \Exception If API request fails
205 */
206 public function get_top_pages(int $limit = 10, string $date_range = '30d'): array {
207 $metrics = ['screenPageViews', 'sessions'];
208 $dimensions = ['pagePath', 'pageTitle'];
209
210 $result = $this->run_report($date_range, $metrics, $dimensions);
211
212 $pages = [];
213 $rows = $result['rows'] ?? [];
214
215 foreach (array_slice($rows, 0, $limit) as $row) {
216 $dimension_values = $row['dimensionValues'] ?? [];
217 $metric_values = $row['metricValues'] ?? [];
218
219 $pages[] = [
220 'path' => $dimension_values[0]['value'] ?? '',
221 'title' => $dimension_values[1]['value'] ?? '',
222 'pageviews' => (int) ($metric_values[0]['value'] ?? 0),
223 'sessions' => (int) ($metric_values[1]['value'] ?? 0)
224 ];
225 }
226
227 return [
228 'pages' => $pages,
229 'limit' => $limit,
230 'date_range' => $date_range,
231 'total_pages' => count($rows)
232 ];
233 }
234
235 /**
236 * Get organic search traffic data for SEO analytics
237 *
238 * @param string $date_range Date range for data
239 * @return array Organic traffic data
240 * @throws \Exception If API request fails
241 */
242 public function get_organic_traffic(string $date_range = '30d'): array {
243 $metrics = ['sessions', 'screenPageViews', 'activeUsers'];
244 $dimensions = ['sessionDefaultChannelGrouping'];
245
246 $result = $this->run_report($date_range, $metrics, $dimensions);
247
248 $organic_data = [
249 'sessions' => 0,
250 'pageviews' => 0,
251 'users' => 0
252 ];
253
254 $rows = $result['rows'] ?? [];
255
256 foreach ($rows as $row) {
257 $dimension_values = $row['dimensionValues'] ?? [];
258 $metric_values = $row['metricValues'] ?? [];
259
260 $channel = $dimension_values[0]['value'] ?? '';
261
262 // Filter for organic search traffic
263 if (strtolower($channel) === 'organic search') {
264 $organic_data['sessions'] = (int) ($metric_values[0]['value'] ?? 0);
265 $organic_data['pageviews'] = (int) ($metric_values[1]['value'] ?? 0);
266 $organic_data['users'] = (int) ($metric_values[2]['value'] ?? 0);
267 break;
268 }
269 }
270
271 return [
272 'organic_traffic' => $organic_data,
273 'date_range' => $date_range,
274 'property_id' => $this->property_id
275 ];
276 }
277
278 /**
279 * Get rate limit configuration
280 * Following ThinkRank rate limiting patterns
281 *
282 * @return array Rate limit configuration
283 */
284 protected function get_rate_limits(): array {
285 return [
286 'max_requests_per_day' => self::MAX_REQUESTS_PER_DAY,
287 'reset_time' => get_transient(self::RATE_LIMIT_KEY . '_reset') ?: strtotime('tomorrow')
288 ];
289 }
290
291 /**
292 * Get rate limit transient key
293 * Following ThinkRank option naming patterns
294 *
295 * @return string Rate limit key
296 */
297 protected function get_rate_limit_key(): string {
298 return self::RATE_LIMIT_KEY;
299 }
300
301 /**
302 * Get rate limit error message
303 *
304 * @return string Error message
305 */
306 protected function get_rate_limit_error_message(): string {
307 return 'Google Analytics API rate limit exceeded. Try again tomorrow.';
308 }
309
310 /**
311 * Get measurement ID
312 *
313 * @return string Measurement ID
314 */
315 public function get_measurement_id(): string {
316 return $this->property_id;
317 }
318
319 /**
320 * Get property ID
321 *
322 * @return string Property ID
323 */
324 public function get_property_id(): string {
325 return $this->property_id;
326 }
327 }
328