| @@ -60,9 +60,9 @@ | ||
| 60 | 60 | |
| 61 | 61 | /** |
| 62 | 62 | * The resolved config every consumer reads. |
| 63 | 63 | * |
| 64 | - * @return array{enabled: bool, frequency_days: int, recipients: string[], sections_enabled: string[], next_scheduled_at: ?string, last_sent_at: ?string} | |
| 64 | + * @return array{enabled: bool, frequency_days: int, recipients: string[], sections_enabled: string[], next_scheduled_at: ?string, last_sent_at: ?string, last_skip: ?array} | |
| 65 | 65 | */ |
| 66 | 66 | public function get(): array { |
| 67 | 67 | $this->load_defaults_file(); |
| 68 | 68 | |
| @@ -70,8 +70,11 @@ | ||
| 70 | 70 | $state = [ |
| 71 | 71 | 'enabled' => !empty($stored['enabled']), |
| 72 | 72 | 'next_scheduled_at' => $stored['next_scheduled_at'] ?? null, |
| 73 | 73 | 'last_sent_at' => $stored['last_sent_at'] ?? null, |
| 74 | + // Why the last scheduled run sent nothing, or null. Read by the | |
| 75 | + // panel so a paused report is never mistaken for a healthy one. | |
| 76 | + 'last_skip' => is_array($stored['last_skip'] ?? null) ? $stored['last_skip'] : null, | |
| 74 | 77 | ]; |
| 75 | 78 | |
| 76 | 79 | $report = [ |
| 77 | 80 | 'frequency_days' => self::FREQUENCY_DAYS, |
| @@ -182,8 +185,39 @@ | ||
| 182 | 185 | return $this->update_schedule( |
| 183 | 186 | $config['last_sent_at'], |
| 184 | 187 | wp_date('Y-m-d H:i:s', max($next ?: time(), time())) |
| 185 | 188 | ); |
| 189 | + } | |
| 190 | + | |
| 191 | + /** | |
| 192 | + * Note why a scheduled run sent nothing (#742). | |
| 193 | + * | |
| 194 | + * `search_console_not_connected` leaves the schedule alone so the next | |
| 195 | + * hourly tick tries again; `no_data` is recorded by the generator after | |
| 196 | + * it has already pushed the schedule out a period. Either way the panel | |
| 197 | + * shows the reason and when it was last seen. | |
| 198 | + * | |
| 199 | + * @param string $reason Machine-readable reason. | |
| 200 | + */ | |
| 201 | + public function record_skip(string $reason): void { | |
| 202 | + $stored = $this->stored(); | |
| 203 | + $stored['last_skip'] = [ | |
| 204 | + 'reason' => sanitize_key($reason), | |
| 205 | + 'at' => current_time('mysql'), | |
| 206 | + ]; | |
| 207 | + update_option(self::OPTION_KEY, $stored, false); | |
| 208 | + } | |
| 209 | + | |
| 210 | + /** | |
| 211 | + * A report went out — whatever paused it earlier no longer applies. | |
| 212 | + */ | |
| 213 | + public function clear_skip(): void { | |
| 214 | + $stored = $this->stored(); | |
| 215 | + if (!array_key_exists('last_skip', $stored)) { | |
| 216 | + return; | |
| 217 | + } | |
| 218 | + unset($stored['last_skip']); | |
| 219 | + update_option(self::OPTION_KEY, $stored, false); | |
| 186 | 220 | } |
| 187 | 221 | |
| 188 | 222 | /** |
| 189 | 223 | * Update only the schedule timestamps. Called from the scheduler after |