PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 1.1.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v1.1.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 / seo / class-performance-data-collector.php

class-performance-data-collector.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 1.1.0, at includes/seo/class-performance-data-collector.php

355 lines 11.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Performance Data Collector Class
4 *
5 * Automated system for collecting and storing performance data from Google PageSpeed Insights.
6 * Runs on WordPress cron to build historical performance tracking over time.
7 *
8 * @package ThinkRank\SEO
9 * @since 1.0.0
10 */
11
12 declare(strict_types=1);
13
14 namespace ThinkRank\SEO;
15
16 use ThinkRank\Integrations\Google_PageSpeed_Client;
17
18 /**
19 * Performance Data Collector Class
20 *
21 * Handles automated collection and storage of performance data for historical tracking.
22 * Integrates with WordPress cron system for scheduled data collection.
23 *
24 * @since 1.0.0
25 */
26 class Performance_Data_Collector {
27
28 /**
29 * Performance Monitoring Manager instance
30 *
31 * @var Performance_Monitoring_Manager
32 */
33 private Performance_Monitoring_Manager $performance_manager;
34
35 /**
36 * Google PageSpeed Client instance
37 *
38 * @var Google_PageSpeed_Client|null
39 */
40 private ?Google_PageSpeed_Client $pagespeed_client;
41
42 /**
43 * Cron hook name for data collection
44 */
45 private const CRON_HOOK = 'thinkrank_collect_performance_data';
46
47 /**
48 * Constructor
49 */
50 public function __construct() {
51 $this->performance_manager = new Performance_Monitoring_Manager();
52
53 // Initialize PageSpeed client with proper class loading
54 $this->initialize_pagespeed_client();
55
56 // Register cron hooks
57 add_action(self::CRON_HOOK, [$this, 'collect_performance_data']);
58
59 // Schedule cron if not already scheduled
60 if (!wp_next_scheduled(self::CRON_HOOK)) {
61 wp_schedule_event(time(), 'daily', self::CRON_HOOK);
62 }
63 }
64
65 /**
66 * Initialize PageSpeed client with proper error handling
67 *
68 * @return void
69 */
70 private function initialize_pagespeed_client(): void {
71 // Ensure the Google PageSpeed Client class is loaded
72 if (!class_exists('ThinkRank\\Integrations\\Google_PageSpeed_Client')) {
73 $pagespeed_file = THINKRANK_PLUGIN_DIR . 'includes/integrations/class-google-pagespeed-client.php';
74 if (file_exists($pagespeed_file)) {
75 require_once $pagespeed_file;
76 }
77 }
78
79 // Ensure the base client is loaded
80 if (!class_exists('ThinkRank\\Integrations\\Google_API_Base_Client')) {
81 $base_client_file = THINKRANK_PLUGIN_DIR . 'includes/integrations/class-google-api-base-client.php';
82 if (file_exists($base_client_file)) {
83 require_once $base_client_file;
84 }
85 }
86
87 try {
88 // Get Google PageSpeed API key from settings
89 $api_key = $this->get_pagespeed_api_key();
90
91 if (empty($api_key)) {
92 $this->pagespeed_client = null;
93 return;
94 }
95
96 $this->pagespeed_client = new Google_PageSpeed_Client($api_key);
97 } catch (\Exception $e) {
98 $this->pagespeed_client = null;
99 }
100 }
101
102 /**
103 * Get Google PageSpeed API key from settings
104 *
105 * @return string API key or empty string if not configured
106 */
107 private function get_pagespeed_api_key(): string {
108 // Get the API key from integrations settings
109 $integrations_settings = get_option('thinkrank_integrations_settings', []);
110 return $integrations_settings['google_pagespeed_api_key'] ?? '';
111 }
112
113 /**
114 * Collect performance data for the site
115 *
116 * @return bool Success status
117 */
118 public function collect_performance_data(): bool {
119 try {
120 $home_url = home_url();
121
122 // Test both mobile and desktop
123 $devices = ['mobile', 'desktop'];
124 $success = true;
125
126 foreach ($devices as $device) {
127 $device_success = $this->collect_device_performance_data($home_url, $device);
128 if (!$device_success) {
129 $success = false;
130 }
131 }
132
133 // Run data cleanup (keep 1 year of data)
134 $this->cleanup_old_data(365);
135
136 return $success;
137
138 } catch (\Exception $e) {
139 return false;
140 }
141 }
142
143 /**
144 * Collect performance data for specific device type
145 *
146 * @param string $url URL to test
147 * @param string $device_type Device type (mobile/desktop)
148 * @return bool Success status
149 */
150 private function collect_device_performance_data(string $url, string $device_type): bool {
151 try {
152 // Check if PageSpeed client is available
153 if (!$this->pagespeed_client) {
154 return false;
155 }
156
157 // Get Core Web Vitals data
158 $core_web_vitals = $this->pagespeed_client->get_core_web_vitals($url, $device_type);
159
160 // Run full PageSpeed test to get performance score
161 $pagespeed_result = $this->pagespeed_client->run_pagespeed_test($url, $device_type, ['performance']);
162
163 // Extract performance score
164 $performance_score = 0;
165 if (isset($pagespeed_result['lighthouseResult']['categories']['performance']['score'])) {
166 $performance_score = $pagespeed_result['lighthouseResult']['categories']['performance']['score'] * 100;
167 }
168
169 // Prepare data for storage
170 $performance_data = $core_web_vitals;
171 $performance_data['performance_score'] = $performance_score;
172
173 // Store in database
174 $stored = $this->performance_manager->store_historical_performance_data(
175 $performance_data,
176 'site',
177 null,
178 $device_type
179 );
180
181
182
183 return $stored;
184
185 } catch (\Exception $e) {
186 return false;
187 }
188 }
189
190 /**
191 * Manually trigger data collection (for testing or immediate collection)
192 *
193 * @return array Collection results
194 */
195 public function manual_collect(): array {
196 $results = [
197 'success' => false,
198 'message' => '',
199 'data_collected' => false,
200 'errors' => []
201 ];
202
203 try {
204 $success = $this->collect_performance_data();
205
206 if ($success) {
207 $results['success'] = true;
208 $results['data_collected'] = true;
209 $results['message'] = __('Performance data collected successfully', 'thinkrank');
210 } else {
211 $results['message'] = __('Failed to collect performance data', 'thinkrank');
212 $results['errors'][] = 'Data collection failed';
213 }
214
215 } catch (\Exception $e) {
216 $results['message'] = __('Error during data collection', 'thinkrank');
217 $results['errors'][] = $e->getMessage();
218 }
219
220 return $results;
221 }
222
223 /**
224 * Get collection schedule information
225 *
226 * @return array Schedule information
227 */
228 public function get_schedule_info(): array {
229 $next_scheduled = wp_next_scheduled(self::CRON_HOOK);
230
231 return [
232 'is_scheduled' => $next_scheduled !== false,
233 'next_run' => $next_scheduled ? gmdate('Y-m-d H:i:s', $next_scheduled) : null,
234 'next_run_human' => $next_scheduled ? human_time_diff($next_scheduled) : null,
235 'cron_hook' => self::CRON_HOOK,
236 'frequency' => 'daily'
237 ];
238 }
239
240 /**
241 * Clean up old performance data (data retention policy)
242 *
243 * @param int $days Number of days to keep (default: 365 days = 1 year)
244 * @return int Number of records deleted
245 */
246 public function cleanup_old_data(int $days = 365): int {
247 global $wpdb;
248
249 $table_name = $wpdb->prefix . 'thinkrank_seo_performance';
250 $cutoff_date = gmdate('Y-m-d H:i:s', strtotime("-{$days} days"));
251
252 $sql = sprintf(
253 "DELETE FROM %s WHERE measured_at < %%s",
254 $table_name
255 );
256
257 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Performance data cleanup requires direct database access
258 $deleted = $wpdb->query(
259 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- SQL is properly prepared with placeholders
260 $wpdb->prepare($sql, $cutoff_date)
261 );
262
263 return $deleted !== false ? (int) $deleted : 0;
264 }
265
266 /**
267 * Reschedule data collection
268 *
269 * @param string $frequency Cron frequency (hourly, daily, weekly)
270 * @return bool Success status
271 */
272 public function reschedule_collection(string $frequency = 'daily'): bool {
273 try {
274 // Clear existing schedule
275 wp_clear_scheduled_hook(self::CRON_HOOK);
276
277 // Schedule new collection
278 $scheduled = wp_schedule_event(time(), $frequency, self::CRON_HOOK);
279
280 return $scheduled !== false;
281
282 } catch (\Exception $e) {
283 return false;
284 }
285 }
286
287 /**
288 * Clear scheduled data collection
289 *
290 * @return bool Success status
291 */
292 public function clear_schedule(): bool {
293 try {
294 wp_clear_scheduled_hook(self::CRON_HOOK);
295 return true;
296 } catch (\Exception $e) {
297 return false;
298 }
299 }
300
301 /**
302 * Get recent collection statistics
303 *
304 * @param int $days Number of days to check
305 * @return array Collection statistics
306 */
307 public function get_collection_stats(int $days = 7): array {
308 global $wpdb;
309
310 $table_name = $wpdb->prefix . 'thinkrank_seo_performance';
311 $start_date = gmdate('Y-m-d H:i:s', strtotime("-{$days} days"));
312
313 try {
314 $sql = sprintf("
315 SELECT
316 COUNT(*) as total_records,
317 COUNT(DISTINCT DATE(measured_at)) as days_with_data,
318 COUNT(DISTINCT device_type) as device_types,
319 MIN(measured_at) as first_measurement,
320 MAX(measured_at) as last_measurement
321 FROM %s
322 WHERE measured_at >= %%s
323 AND context_type = 'site'
324 AND measured_by = 'google_pagespeed'
325 ", $table_name);
326
327 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Performance statistics retrieval requires direct database access
328 $stats = $wpdb->get_row(
329 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- SQL is properly prepared with placeholders
330 $wpdb->prepare($sql, $start_date),
331 ARRAY_A
332 );
333
334 return [
335 'total_records' => (int) ($stats['total_records'] ?? 0),
336 'days_with_data' => (int) ($stats['days_with_data'] ?? 0),
337 'device_types' => (int) ($stats['device_types'] ?? 0),
338 'first_measurement' => $stats['first_measurement'] ?? null,
339 'last_measurement' => $stats['last_measurement'] ?? null,
340 'collection_rate' => $stats['days_with_data'] ? round(($stats['days_with_data'] / $days) * 100, 1) : 0
341 ];
342
343 } catch (\Exception $e) {
344 return [
345 'total_records' => 0,
346 'days_with_data' => 0,
347 'device_types' => 0,
348 'first_measurement' => null,
349 'last_measurement' => null,
350 'collection_rate' => 0
351 ];
352 }
353 }
354 }
355