| @@ -117,22 +117,8 @@ | ||
| 117 | 117 | 'is_test' => $is_test, |
| 118 | 118 | 'shared' => $shared, |
| 119 | 119 | ]; |
| 120 | 120 | |
| 121 | - // Search Console answered with an error rather than with data. | |
| 122 | - // Every section is about to come back empty, and the no-data gate | |
| 123 | - // further down would read that as a property with no traffic: it | |
| 124 | - // would skip the period and tell the user Search Console "returned | |
| 125 | - // no data". Record it as the failure it is and retry soon, the way | |
| 126 | - // a failed send already is. Checked before the hook below so Pro | |
| 127 | - // does not build an AI summary for a report that cannot go out. | |
| 128 | - // | |
| 129 | - // A test send still goes out: it exists to prove delivery. | |
| 130 | - $fetch_error = self::fetch_error($shared); | |
| 131 | - if (!$is_test && $fetch_error !== '') { | |
| 132 | - return $this->handle_fetch_failure($config, $context, $fetch_error); | |
| 133 | - } | |
| 134 | - | |
| 135 | 121 | /** |
| 136 | 122 | * Fires before the report is rendered/sent. Pro plugin uses |
| 137 | 123 | * this to fetch + attach the AI Highlights summary. |
| 138 | 124 | * |
| @@ -296,100 +282,8 @@ | ||
| 296 | 282 | 'result' => $result, |
| 297 | 283 | 'not_connected' => true, |
| 298 | 284 | 'reason' => $reason, |
| 299 | 285 | ]; |
| 300 | - } | |
| 301 | - | |
| 302 | - /** | |
| 303 | - * The error behind a failed data fetch, or '' when the fetch worked. | |
| 304 | - * | |
| 305 | - * A fetch fails in two shapes. Email_Report_Data_Provider::fetch() | |
| 306 | - * catches a thrown API error and returns `available: false` with the | |
| 307 | - * message in `error`. Analytics_Manager::get_dashboard_data() catches | |
| 308 | - * its own and returns normally, with the message in `current.error`. | |
| 309 | - * `available: false` without an `error` is not a failure, so it is not | |
| 310 | - * reported as one. | |
| 311 | - * | |
| 312 | - * @since 2.15.0 | |
| 313 | - * | |
| 314 | - * @param array $shared The data provider's fetch() result. | |
| 315 | - * @return string The error message, or '' when nothing failed. | |
| 316 | - */ | |
| 317 | - private static function fetch_error(array $shared): string { | |
| 318 | - if (empty($shared['available']) && !empty($shared['error']) && is_scalar($shared['error'])) { | |
| 319 | - return (string) $shared['error']; | |
| 320 | - } | |
| 321 | - | |
| 322 | - $current = is_array($shared['current'] ?? null) ? $shared['current'] : []; | |
| 323 | - if (!empty($current['error']) && is_scalar($current['error'])) { | |
| 324 | - return (string) $current['error']; | |
| 325 | - } | |
| 326 | - | |
| 327 | - return ''; | |
| 328 | - } | |
| 329 | - | |
| 330 | - /** | |
| 331 | - * A scheduled run whose Search Console fetch failed. | |
| 332 | - * | |
| 333 | - * Treated like a failed send, not like a period with no data: the | |
| 334 | - * period's log row is claimed and closed as `failed` with the API's | |
| 335 | - * message, the next attempt is booked RETRY_DELAY_HOURS out, and once | |
| 336 | - * MAX_SEND_ATTEMPTS are spent the schedule rejoins the normal cadence. | |
| 337 | - * The reason and the message are recorded for the panel. | |
| 338 | - * | |
| 339 | - * @since 2.15.0 | |
| 340 | - * | |
| 341 | - * @param array $config Per-site config. | |
| 342 | - * @param array $context Render context (period bounds). | |
| 343 | - * @param string $error The fetch error. | |
| 344 | - * @return array{success:bool,skipped:string,error?:string} | |
| 345 | - */ | |
| 346 | - private function handle_fetch_failure(array $config, array $context, string $error): array { | |
| 347 | - $frequency = (int) ($config['frequency_days'] ?? 30); | |
| 348 | - $message = self::short_error($error); | |
| 349 | - | |
| 350 | - $dedupe = $this->record_attempt($config, $context); | |
| 351 | - if (!$dedupe['inserted']) { | |
| 352 | - if (!empty($dedupe['exhausted'])) { | |
| 353 | - $this->config->update_schedule( | |
| 354 | - $this->config->get()['last_sent_at'] ?? null, | |
| 355 | - $this->compute_next_run($frequency) | |
| 356 | - ); | |
| 357 | - $this->config->record_skip('fetch_failed', $message); | |
| 358 | - return ['success' => false, 'skipped' => 'retry_limit', 'error' => $message]; | |
| 359 | - } | |
| 360 | - return [ | |
| 361 | - 'success' => false, | |
| 362 | - 'skipped' => empty($dedupe['write_failed']) ? 'duplicate' : 'log_write_failed', | |
| 363 | - ]; | |
| 364 | - } | |
| 365 | - | |
| 366 | - $this->finalize_log($config, $context, ['success' => false, 'error' => $message]); | |
| 367 | - | |
| 368 | - $attempts = (int) ($dedupe['attempts'] ?? 1); | |
| 369 | - $this->config->update_schedule( | |
| 370 | - $this->config->get()['last_sent_at'] ?? null, | |
| 371 | - $attempts >= self::MAX_SEND_ATTEMPTS | |
| 372 | - ? $this->compute_next_run($frequency) | |
| 373 | - : $this->compute_retry_run() | |
| 374 | - ); | |
| 375 | - $this->config->record_skip('fetch_failed', $message); | |
| 376 | - | |
| 377 | - return ['success' => false, 'skipped' => 'fetch_failed', 'error' => $message]; | |
| 378 | - } | |
| 379 | - | |
| 380 | - /** | |
| 381 | - * An error message fit for the log row and the panel: plain text, one | |
| 382 | - * line, at most 300 characters. | |
| 383 | - * | |
| 384 | - * @since 2.15.0 | |
| 385 | - * | |
| 386 | - * @param string $error Raw error message. | |
| 387 | - * @return string | |
| 388 | - */ | |
| 389 | - private static function short_error(string $error): string { | |
| 390 | - $error = trim((string) preg_replace('/\s+/', ' ', wp_strip_all_tags($error))); | |
| 391 | - return mb_strlen($error) > 300 ? rtrim(mb_substr($error, 0, 299)) . '…' : $error; | |
| 392 | 286 | } |
| 393 | 287 | |
| 394 | 288 | /** |
| 395 | 289 | * Claim this period's send by inserting a `pending` row in |