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

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