$url, 'strategy' => $strategy, 'category' => $categories, 'key' => $this->api_key ]; $full_url = self::API_BASE_URL . $endpoint; return $this->make_request($full_url, $params, 'GET'); } /** * Test API connection * Following ThinkRank test_connection patterns from AI clients * * @return array Connection test results */ public function test_connection(): array { try { $test_url = home_url(); $result = $this->run_pagespeed_test($test_url, 'mobile', ['performance']); $performance_score = 0; if (isset($result['lighthouseResult']['categories']['performance']['score'])) { $performance_score = $result['lighthouseResult']['categories']['performance']['score'] * 100; } return [ 'success' => true, 'message' => 'PageSpeed Insights API connection successful', 'test_url' => $test_url, 'performance_score' => $performance_score ]; } catch (\Exception $e) { return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * Get Core Web Vitals data for a URL * * @param string $url URL to analyze * @param string $strategy Device strategy ('mobile' or 'desktop') * @return array Core Web Vitals data * @throws \Exception If API request fails */ public function get_core_web_vitals(string $url, string $strategy = 'mobile'): array { $result = $this->run_pagespeed_test($url, $strategy, ['performance']); return $this->parse_core_web_vitals($result); } /** * Get performance opportunities for a URL * * @param string $url URL to test * @param string $strategy Testing strategy (mobile/desktop) * @return array Performance opportunities * @throws \Exception If API request fails */ public function get_opportunities(string $url, string $strategy = 'mobile'): array { $result = $this->run_pagespeed_test($url, $strategy, ['performance']); return $this->parse_opportunities($result); } /** * Get diagnostic information for a URL * * @param string $url URL to test * @param string $strategy Testing strategy (mobile/desktop) * @return array Diagnostic information * @throws \Exception If API request fails */ public function get_diagnostics(string $url, string $strategy = 'mobile'): array { $result = $this->run_pagespeed_test($url, $strategy, ['performance']); return $this->parse_diagnostics($result); } /** * Parse Core Web Vitals from PageSpeed response * * @param array $pagespeed_data Raw PageSpeed API response * @return array Parsed Core Web Vitals data */ private function parse_core_web_vitals(array $pagespeed_data): array { $audits = $pagespeed_data['lighthouseResult']['audits'] ?? []; return [ 'lcp' => [ 'name' => 'Largest Contentful Paint', 'value' => round((($audits['largest-contentful-paint']['numericValue'] ?? 0) / 1000), 4), 'score' => ($audits['largest-contentful-paint']['score'] ?? 0) * 100, 'unit' => 's', 'good_threshold' => 2.5, 'needs_improvement_threshold' => 4.0, 'description' => 'Time until the largest content element is rendered' ], 'fid' => [ 'name' => 'First Input Delay', 'value' => round(($audits['max-potential-fid']['numericValue'] ?? 0), 4), 'score' => ($audits['max-potential-fid']['score'] ?? 0) * 100, 'unit' => 'ms', 'good_threshold' => 100, 'needs_improvement_threshold' => 300, 'description' => 'Time from first user interaction to browser response' ], 'cls' => [ 'name' => 'Cumulative Layout Shift', 'value' => round(($audits['cumulative-layout-shift']['numericValue'] ?? 0), 4), 'score' => ($audits['cumulative-layout-shift']['score'] ?? 0) * 100, 'unit' => '', 'good_threshold' => 0.1, 'needs_improvement_threshold' => 0.25, 'description' => 'Measure of visual stability during page load' ], 'fcp' => [ 'name' => 'First Contentful Paint', 'value' => round((($audits['first-contentful-paint']['numericValue'] ?? 0) / 1000), 4), 'score' => ($audits['first-contentful-paint']['score'] ?? 0) * 100, 'unit' => 's', 'good_threshold' => 1.8, 'needs_improvement_threshold' => 3.0, 'description' => 'Time until the first content is painted on screen' ] ]; } /** * Parse performance opportunities from PageSpeed data * * @param array $pagespeed_data Raw PageSpeed API response * @return array Parsed opportunities data */ private function parse_opportunities(array $pagespeed_data): array { $audits = $pagespeed_data['lighthouseResult']['audits'] ?? []; $opportunities = []; // Define opportunity audits that provide savings estimates $opportunity_audits = [ 'render-blocking-resources' => 'Eliminate render-blocking resources', 'unused-css-rules' => 'Remove unused CSS', 'unused-javascript' => 'Remove unused JavaScript', 'modern-image-formats' => 'Serve images in next-gen formats', 'offscreen-images' => 'Defer offscreen images', 'unminified-css' => 'Minify CSS', 'unminified-javascript' => 'Minify JavaScript', 'efficient-animated-content' => 'Use video formats for animated content', 'duplicated-javascript' => 'Remove duplicate modules in JavaScript bundles', 'legacy-javascript' => 'Avoid serving legacy JavaScript to modern browsers' ]; foreach ($opportunity_audits as $audit_id => $title) { if (isset($audits[$audit_id]) && isset($audits[$audit_id]['details'])) { $audit = $audits[$audit_id]; $savings = $audit['details']['overallSavingsMs'] ?? 0; if ($savings > 0) { $opportunities[] = [ 'id' => $audit_id, 'title' => $title, 'description' => $audit['description'] ?? '', 'estimated_savings' => $savings, 'score' => ($audit['score'] ?? 0) * 100, 'details' => $audit['details'] ?? [], 'difficulty' => $this->get_difficulty_level($audit_id) ]; } } } // Sort by estimated savings (highest first) usort($opportunities, function($a, $b) { return $b['estimated_savings'] - $a['estimated_savings']; }); return $opportunities; } /** * Parse diagnostic information from PageSpeed data * * @param array $pagespeed_data Raw PageSpeed API response * @return array Parsed diagnostics data */ private function parse_diagnostics(array $pagespeed_data): array { $audits = $pagespeed_data['lighthouseResult']['audits'] ?? []; $diagnostics = []; // Define diagnostic audits $diagnostic_audits = [ 'first-contentful-paint' => ['title' => 'First Contentful Paint', 'impact' => 'Performance'], 'largest-contentful-paint' => ['title' => 'Largest Contentful Paint', 'impact' => 'LCP'], 'first-meaningful-paint' => ['title' => 'First Meaningful Paint', 'impact' => 'Performance'], 'speed-index' => ['title' => 'Speed Index', 'impact' => 'Performance'], 'total-blocking-time' => ['title' => 'Total Blocking Time', 'impact' => 'Performance'], 'max-potential-fid' => ['title' => 'Max Potential First Input Delay', 'impact' => 'FID'], 'cumulative-layout-shift' => ['title' => 'Cumulative Layout Shift', 'impact' => 'CLS'], 'server-response-time' => ['title' => 'Initial server response time was short', 'impact' => 'Performance'], 'interactive' => ['title' => 'Time to Interactive', 'impact' => 'Performance'], 'user-timings' => ['title' => 'User Timing marks and measures', 'impact' => 'Performance'], 'critical-request-chains' => ['title' => 'Avoid chaining critical requests', 'impact' => 'Performance'], 'redirects' => ['title' => 'Avoid multiple page redirects', 'impact' => 'Performance'], 'installable-manifest' => ['title' => 'Web app manifest meets the installability requirements', 'impact' => 'PWA'], 'apple-touch-icon' => ['title' => 'Provides a valid apple-touch-icon', 'impact' => 'PWA'], 'splash-screen' => ['title' => 'Configured for a custom splash screen', 'impact' => 'PWA'], 'themed-omnibox' => ['title' => 'Sets a theme color for the address bar', 'impact' => 'PWA'], 'content-width' => ['title' => 'Content is sized correctly for the viewport', 'impact' => 'Mobile'], 'image-aspect-ratio' => ['title' => 'Displays images with correct aspect ratio', 'impact' => 'Layout'], 'image-size-responsive' => ['title' => 'Serves images with appropriate resolution', 'impact' => 'Performance'], 'preload-fonts' => ['title' => 'Fonts with font-display: optional are preloaded', 'impact' => 'Performance'], 'font-display' => ['title' => 'All text remains visible during webfont loads', 'impact' => 'Performance'] ]; foreach ($diagnostic_audits as $audit_id => $config) { if (isset($audits[$audit_id])) { $audit = $audits[$audit_id]; $score = $audit['score'] ?? null; $status = 'info'; if ($score !== null) { if ($score >= 0.9) { $status = 'passed'; } elseif ($score >= 0.5) { $status = 'warning'; } else { $status = 'failed'; } } $diagnostics[] = [ 'id' => $audit_id, 'title' => $config['title'], 'description' => $audit['description'] ?? '', 'status' => $status, 'score' => $score ? ($score * 100) : null, 'impact' => $config['impact'], 'details' => $audit['details'] ?? [], 'display_value' => $audit['displayValue'] ?? null ]; } } return $diagnostics; } /** * Get difficulty level for optimization opportunities * * @param string $audit_id Audit identifier * @return string Difficulty level */ private function get_difficulty_level(string $audit_id): string { $difficulty_map = [ 'unminified-css' => 'Easy', 'unminified-javascript' => 'Easy', 'modern-image-formats' => 'Easy', 'offscreen-images' => 'Medium', 'unused-css-rules' => 'Hard', 'unused-javascript' => 'Hard', 'render-blocking-resources' => 'Medium', 'efficient-animated-content' => 'Medium', 'duplicated-javascript' => 'Hard', 'legacy-javascript' => 'Medium' ]; return $difficulty_map[$audit_id] ?? 'Medium'; } /** * Get rate limit configuration * Following ThinkRank rate limiting patterns * * @return array Rate limit configuration */ protected function get_rate_limits(): array { return [ 'max_requests_per_day' => self::MAX_REQUESTS_PER_DAY, 'reset_time' => get_transient(self::RATE_LIMIT_KEY . '_reset') ?: strtotime('tomorrow') ]; } /** * Get rate limit transient key * Following ThinkRank option naming patterns * * @return string Rate limit key */ protected function get_rate_limit_key(): string { return self::RATE_LIMIT_KEY; } /** * Get rate limit error message * * @return string Error message */ protected function get_rate_limit_error_message(): string { return 'PageSpeed Insights API rate limit exceeded. Try again tomorrow.'; } }