diff --git a/CHANGELOG.md b/CHANGELOG.md index fad6f4b0..7349eb83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## [Unreleased] ### Fixed +- Pre-update / pre-uninstall skip flag is now **time-bounded**. It records the moment it was armed and is honoured only within a short TTL (5 min), always consumed one-shot. Previously a bare boolean: if a user ran the full-screen pre-update backup then abandoned the flow (closed the tab before the update re-fired), the flag lingered for the whole session and could silently suppress a later, unrelated pre-action backup — i.e. a real update running with **no** backup. Now a stale flag is ignored and the server backs up as normal. - Pre-update / pre-uninstall backup can no longer run **twice**. The one-shot "skip the synchronous server-side backup" flag is now armed **server-side** the moment the full-screen backup completes (in `AjaxController::step`, keyed by the `msb_action` it is fronting), instead of relying solely on the best-effort client `ajax.preupdateAck` POST (whose failure was silently swallowed in a `catch()`). If that ack ever fails to land, the re-fired extension update/uninstall no longer triggers a second backup behind the "update is running" overlay. The flag is armed **only** on genuine completion (`done`, no error), so a failed or cancelled backup still lets the server back up before the action — the failure mode stays "one extra backup", never "no backup". - Component **Options** page showed the raw `COM_MOKOSUITEBACKUP_CONFIGURATION` constant instead of a translated title. Joomla's `com_config` derives the Options-page title (and the component name) from the extension *element* (`com_mokosuitebackup`), but the language files defined those keys only under the legacy `COM_MOKOJOOMBACKUP_` stem. Added the element-derived `COM_MOKOSUITEBACKUP` and `COM_MOKOSUITEBACKUP_CONFIGURATION` keys to all four language files (en-GB/en-US, `.ini` and `.sys.ini`), matching the already-migrated `COM_MOKOSUITEBACKUP_ACTION_*` ACL keys. The 400+ explicitly-referenced `COM_MOKOJOOMBACKUP_*` keys are unaffected and left as-is. (#234) diff --git a/source/packages/com_mokosuitebackup/src/Controller/AjaxController.php b/source/packages/com_mokosuitebackup/src/Controller/AjaxController.php index 9f741f32..76cc781f 100644 --- a/source/packages/com_mokosuitebackup/src/Controller/AjaxController.php +++ b/source/packages/com_mokosuitebackup/src/Controller/AjaxController.php @@ -144,15 +144,19 @@ class AjaxController extends BaseController * Called from two places: ajax.step on genuine completion (authoritative) and * ajax.preupdateAck (best-effort client ack) — either one arming the flag is * enough, and re-arming it is harmless. + * + * Stores the unix time it was armed (not a bare bool) so the plugin can honour + * the skip only within a short TTL and never let a stale flag from an abandoned + * backup suppress a genuine, unrelated pre-action backup later in the session. */ private function armPreactionSkip(string $action): void { $session = Factory::getSession(); if ($action === 'update') { - $session->set('mokosuitebackup.skip_preaction_backup_before_update', true); + $session->set('mokosuitebackup.skip_preaction_backup_before_update', time()); } elseif ($action === 'uninstall') { - $session->set('mokosuitebackup.skip_preaction_backup_before_uninstall', true); + $session->set('mokosuitebackup.skip_preaction_backup_before_uninstall', time()); } } diff --git a/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php b/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php index c6f869da..e466ff22 100644 --- a/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php +++ b/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php @@ -35,6 +35,17 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface /** @var array Per-request cache of the one-shot "client already backed up" skip flag. */ private array $preactionSkip = []; + /** + * Max age (seconds) of the client "already backed up" skip flag that this + * plugin will honour. The flag stores the unix time it was armed (by the + * full-screen backup's ajax.step completion / ajax.preupdateAck); the re-fired + * update/uninstall follows within a second or two, so a small window is ample. + * Bounding it means a backup that was run then ABANDONED (browser closed before + * the update re-fired) cannot silently suppress a genuine, unrelated pre-action + * backup minutes or hours later in the same session. + */ + private const PREACTION_SKIP_TTL = 300; + public static function getSubscribedEvents(): array { return [ @@ -648,12 +659,16 @@ JS; $skipKey = 'mokosuitebackup.skip_preaction_' . $paramName; if (!array_key_exists($paramName, $this->preactionSkip)) { - $this->preactionSkip[$paramName] = (bool) $session->get($skipKey, false); - $session->set($skipKey, false); + /* The flag stores the unix time it was armed. Consume it one-shot (reset + to 0) and honour the skip ONLY if it was armed within the TTL — a + stale flag from an abandoned backup must not suppress a real one. */ + $armedAt = (int) $session->get($skipKey, 0); + $session->set($skipKey, 0); + $this->preactionSkip[$paramName] = ($armedAt > 0 && (time() - $armedAt) <= self::PREACTION_SKIP_TTL); } if ($this->preactionSkip[$paramName]) { - // Client already backed up; skip this whole re-fired (possibly batch) request. + // Client already backed up (recently); skip this re-fired (possibly batch) request. $this->preactionRan[$paramName] = true; return;