PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.7.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.7.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
← All changes | includes/integrations/class-google-pagespeed-client.php +46 -1 2.0.22.7.0 View file →
@@ -111,8 +111,34 @@
111 111 return new self('', $timeout ?? self::DEFAULT_TIMEOUT, $access_token !== '' ? $access_token : null);
112 112 }
113 113
114 114 /**
115 + * Whether the site holds a credential the PageSpeed API will accept.
116 + *
117 + * The counterpart to for_site(): either of the first two rungs of its auth
118 + * order is enough, and a caller that wants to refuse the keyless third rung
119 + * asks this rather than testing one credential itself. Callers that did the
120 + * latter locked out every site configured with only an API key — the
121 + * credential for_site() actually *prefers*, since a dedicated key bills its
122 + * own project quota (#519).
123 + *
124 + * @since 2.1.1
125 + *
126 + * @return bool True when an API key or an OAuth token is configured.
127 + */
128 + public static function site_has_credentials(): bool {
129 + if (!class_exists('\\ThinkRank\\Core\\Settings')) {
130 + return false;
131 + }
132 +
133 + $settings = new \ThinkRank\Core\Settings();
134 +
135 + // OAuth tokens are encrypted at rest; Settings::get() decrypts them.
136 + return '' !== trim((string) $settings->get('google_pagespeed_api_key', ''))
137 + || '' !== trim((string) $settings->get('google_access_token', ''));
138 + }
139 +
140 + /**
115 141 * Run PageSpeed test for a URL
116 142 *
117 143 * @param string $url URL to test
118 144 * @param string $strategy Device strategy ('mobile' or 'desktop')
@@ -185,13 +211,32 @@
185 211 set_transient('thinkrank_psi_failure_' . $hash, $e->getMessage(), self::FAILURE_TTL);
186 212 throw $e;
187 213 }
188 214
215 + // Lighthouse answers 200 with a populated lighthouseResult even when the
216 + // audit itself failed (NO_FCP, ERRORED_DOCUMENT_REQUEST, …); the score
217 + // then comes back null. Coercing that to 0 stored a failed run as a
218 + // genuine "this site scores 0" measurement, which every consumer —
219 + // the SEO score's mobile factor most visibly — has no way to tell from
220 + // a real result. Treat it as the failure it is so the caller's existing
221 + // failure handling applies.
222 + $runtime_error = $result['lighthouseResult']['runtimeError']['code'] ?? '';
223 + $raw_score = $result['lighthouseResult']['categories']['performance']['score'] ?? null;
224 +
225 + if (('' !== $runtime_error && 'NO_ERROR' !== $runtime_error) || null === $raw_score) {
226 + $message = $result['lighthouseResult']['runtimeError']['message']
227 + ?? __('PageSpeed Insights returned no performance score for this URL.', 'thinkrank');
228 +
229 + set_transient('thinkrank_psi_failure_' . $hash, $message, self::FAILURE_TTL);
230 +
231 + throw new \Exception($message); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
232 + }
233 +
189 234 $snapshot = [
190 235 'core_web_vitals' => $this->parse_core_web_vitals($result),
191 236 'opportunities' => $this->parse_opportunities($result),
192 237 'diagnostics' => $this->parse_diagnostics($result),
193 - 'performance_score' => (float) (($result['lighthouseResult']['categories']['performance']['score'] ?? 0) * 100),
238 + 'performance_score' => (float) ($raw_score * 100),
194 239 'loading_experience' => $result['loadingExperience'] ?? [],
195 240 'fetched_at' => time(),
196 241 ];
197 242