PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.12.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.12.0
2.12.0 2.11.0 2.10.0 2.9.0 2.8.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 All 53 releases
← All changes | includes/integrations/class-google-pagespeed-client.php +52 -1 2.0.0 → 2.12.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
@@ -223,10 +268,16 @@
223 268 'test_url' => $test_url,
224 269 'performance_score' => $performance_score
225 270 ];
226 271 } catch (\Exception $e) {
272 + // `message` as well as `error`: the success branch above reports
273 + // its outcome as `message`, and every consumer reads that key, so
274 + // a failure that only set `error` was reported to the admin as an
275 + // error with no reason at all (#852). `error` is kept for any
276 + // caller that already reads it.
227 277 return [
228 278 'success' => false,
279 + 'message' => $e->getMessage(),
229 280 'error' => $e->getMessage()
230 281 ];
231 282 }
232 283 }