clock = $clock ?? 'time'; $this->event = $event ?? static fn (): int|false => wp_next_scheduled(Schedule::HOOK); } /** How long a backup may be absent before it counts as missing. */ public function grace(): int { return self::INTERVALS * $this->config->interval; } /** When a backup last worked, or null when none ever has. */ public function lastSuccess(): ?int { return $this->status->success(); } /** When the next run is due, or null when no backup event is on the cron list. */ public function due(): ?int { $due = ($this->event)(); return is_int($due) && $due > 0 ? $due : null; } /** * How long the scheduled run has been sitting past its time, in seconds; 0 * when it is not. WP-Cron is always a little late — it waits for a page * load — so only a whole interval of lateness means anything. */ public function late(): int { $due = $this->due(); return $due === null ? 0 : max(0, ($this->clock)() - $due); } /** * True when no backup has been made for too long. Measured from the last * success where there is one; from the overdue event where there is not, * because a site that has never backed up has nothing else to measure from. * * Never from before the schedule was last set: shorten weekly to hourly the * day after a good run and that run is already "two intervals" old, though * the new schedule has not had its first turn yet. */ public function overdue(): bool { if (!$this->config->enabled) { return false; } $success = $this->lastSuccess(); if ($success === null) { return $this->late() > $this->grace(); } $from = max($success, (int) get_option(Schedule::SET_OPTION, 0)); return ($this->clock)() - $from > $this->grace(); } /** True when a whole cycle has been missed, so a catch-up run is owed. */ public function owed(): bool { return $this->config->enabled && $this->late() > $this->config->interval; } /** The sentence the screen and the notice carry, or null when nothing is missing. */ public function problem(): ?string { if (!$this->overdue()) { return null; } $now = ($this->clock)(); $success = $this->lastSuccess(); // The event is still moving, so something is running the schedule and it // is the runs that fail. Status already carries the reason why. (Only a // site that has succeeded before can reach this: with no success at all, // overdue is itself measured from a stuck event.) if ($this->late() <= $this->config->interval) { return sprintf( /* translators: %s: a length of time, e.g. "3 days" */ __('No backup has worked in %s. The schedule is still running, so the runs themselves are failing. The last error says why.', 'freshet-backup'), human_time_diff((int) $success, $now) ); } $stopped = __('WP-Cron looks stopped. It only runs when someone visits the site, and a page cache can keep those visits from reaching it. Press Run backup now, then see the readme for how to keep backups on schedule.', 'freshet-backup'); return $success === null ? sprintf( /* translators: %s: a length of time, e.g. "3 days" */ __('No backup has been made yet, and the first one was due %s ago.', 'freshet-backup'), human_time_diff($now - $this->late(), $now) ) . ' ' . $stopped : sprintf( /* translators: 1: a length of time, e.g. "3 days"; 2: e.g. "every day" */ __('No backup has worked in %1$s, and backups are set to run %2$s.', 'freshet-backup'), human_time_diff($success, $now), Schedule::describe($this->config->interval) ) . ' ' . $stopped; } /** * Take the catch-up slot, or refuse: one per interval, whether the run that * spends it works or not. Written before that run rather than after, so a * dump that dies does not bring the next wp-admin load straight back to it. */ public function claim(): bool { if (!$this->owed()) { return false; } $now = ($this->clock)(); if ($now - (int) get_option(self::ATTEMPT_OPTION, 0) < $this->config->interval) { return false; } update_option(self::ATTEMPT_OPTION, $now, false); return true; } }