Silent-failure-hunter:
- ensureUpdateSite(): wrap the two inserts in a transaction so a failed link
insert cannot orphan a #__update_sites row (the idempotency guard checks only
the link table, so an orphan would be re-created on every retry). Surface
failures via Log::add(jerror) + enqueueMessage instead of a silent error_log(),
matching restoreDownloadKey().
Code-reviewer:
- markStalledAsFailed(): raise threshold to a generous 6h (well above
PreflightCheck's 30-min per-profile start guard) so a long browser/CLI backup
is not reaped mid-flight; delete partial archives for reaped rows to match
PreflightCheck; add a WARNING breadcrumb in the catch. Real fix (heartbeat
column) tracked as a follow-up in the stalled-backup issue.
Authored-by: Moko Consulting
Claude-Session: https://claude.ai/code/session_019V1WPYojhKyyPHewfUsPEz
- BackupsController::cancelStalled()/purge(): use Factory::getContainer()
instead of the protected CMSApplication::getContainer() (fatal on Cancel Stalled)
- BackupStatusHelper::markStalledAsFailed(): flip orphaned "running" records
older than a 1h threshold to "fail"; swept when the records list loads
- backup detail view: render "Not finished" for a null/zero backupend
instead of the bogus "Monday, November 29, -0001"
- runbackup: full-screen black overlay (fixed, z-indexed) instead of an
in-admin-chrome card, matching the lock overlay
- script.php ensureUpdateSite(): recreate a missing package update site in
postflight so the "update site not found" banner self-heals
Authored-by: Moko Consulting
Claude-Session: https://claude.ai/code/session_019V1WPYojhKyyPHewfUsPEz
The one-shot "client already backed up" skip flag was a bare boolean with no
expiry. If a user ran the full-screen pre-update backup then abandoned the flow
(closed the tab before the re-fired update fired), the flag lingered for the
whole session and could suppress a LATER, unrelated pre-action backup — a real
update running with no backup.
Store the unix time the flag was armed (AjaxController::armPreactionSkip) and
honour the skip only within PREACTION_SKIP_TTL (300s), consuming it one-shot.
A stale flag is now ignored and the server backs up as normal.
Completes the pre-update double-backup hardening: server-side arming stops the
"ack fails -> double backup" direction; the TTL stops the "abandoned flow ->
skipped backup" direction. Fail-safe both ways.
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
The one-shot flag that tells plg_system_mokosuitebackup to skip its synchronous
pre-update/pre-uninstall backup was set only by the client-side ajax.preupdateAck
POST, whose failure the runbackup screen swallows in a catch(). If that POST
failed, the re-fired extension update ran a SECOND backup behind the black
"update is running" overlay — the "backup runs twice" symptom.
Arm the skip flag server-side in AjaxController::step the moment a backup that
was launched as a pre-update/pre-uninstall front completes (keyed by the
msb_action the runbackup screen now passes on each step). This is authoritative
and independent of the fragile ack POST, which is kept as a redundant fallback.
Failure-safe by design: the flag is armed ONLY on genuine completion (done, no
error), so a failed or cancelled backup never arms it — a real update is never
left un-backed-up. Worst case is one redundant backup, never a missing one.
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
Joomla's com_config derives the Options-page title and component name from
the extension element (com_mokosuitebackup), but the language files defined
those keys only under the legacy COM_MOKOJOOMBACKUP_ stem, so the Options
screen rendered the raw constant.
Add the element-derived COM_MOKOSUITEBACKUP and COM_MOKOSUITEBACKUP_CONFIGURATION
keys to all four language files (en-GB/en-US, .ini and .sys.ini), mirroring the
already-migrated COM_MOKOSUITEBACKUP_ACTION_* ACL keys. The 400+ explicitly
referenced COM_MOKOJOOMBACKUP_* keys are unaffected.
Refs #234
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
All sub-extensions already use the "Type - Name" convention; the
component was the exception because its <name> derives the element
(com_ + sanitized name), so "Component - MokoSuiteBackup" would produce
com_component-mokosuitebackup and orphan the real component.
Fix: pin the element with an explicit <element>com_mokosuitebackup</element>
tag. Joomla's InstallerAdapter::getElement() reads <element> before
falling back to <name>, so the element stays com_mokosuitebackup (matches
the existing record on upgrade — no orphan) while the Extensions manager
now shows "Component - MokoSuiteBackup". Manifest filename stays
mokosuitebackup.xml. Verified against Joomla 6.1 InstallerAdapter/
ComponentAdapter and manifest XML is well-formed.
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
Package updates could fatal with a compile-time "Cannot redeclare class
Pkg_MokoSuiteBackupInstallerScript" when Joomla extracts/includes the
package script.php twice in one request (two tmp/install_* dirs). Because
the class is a top-level unconditional declaration it is early-bound at
compile time, so a runtime return can't prevent the redeclare — wrapping
it in `if (!class_exists(...)) { ... }` makes it a conditional (runtime)
declaration, so the second include is a no-op instead of a fatal.
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
The mod_quickicon helper array_merge()s a defaults array into each icon a
plugin returns; if a plugin's DB query throws or it returns a malformed
(unwrapped) icon, the whole admin dashboard can 500 with an array_merge
TypeError. The current handler is structurally correct (icons are wrapped
in a list, matching core plg_quickicon_joomlaupdate), so this hardens it:
- Extract the query/state logic into buildStatusIcon().
- Wrap it in try/catch — on any Throwable, log and contribute no icon
instead of bubbling into the dashboard render.
- Document why the [$icon] list-wrapping is load-bearing.
Closes#297.
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
RestoreEngine ignored DatabaseImporter::hasErrors(), so the non-stepped
restore always reported success even when SQL statements failed. It now
logs the failures and returns a warning ("database may be incomplete").
DatabaseImporter's splitter is also quote/backtick/escape-aware so a ; or
newline inside a value no longer splits a statement, and comments are only
skipped between statements — matching the stepped-engine fix (#291).
Closes#188.
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
typeLabels[TYPE] referenced an undefined uppercase variable; the forEach
parameter is lowercase `type`, so every type label rendered as the raw
type string instead of its translated label.
Closes#170.
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
Replaces the old public ntfy.sh default in the runtime defaults: profile
form default/hint, NotificationSender fallbacks, the ntfy_server column
default in install.mysql.sql, and the en-GB field description. ntfy.sh is
still documented as the public alternative.
Closes#166.
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
- 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)
- **Cancel Stalled no longer throws a fatal error.** `BackupsController::cancelStalled()` (and `purge()`) fetched the database via `$this->app->getContainer()`, but `CMSApplication::getContainer()` is `protected` — clicking **Cancel Stalled** died with *"Call to protected method Joomla\CMS\Application\CMSApplication::getContainer() from scope ...\BackupsController"*, leaving no UI path to clear a stuck backup. Both call sites now use `Factory::getContainer()`, matching the rest of the component.
- **Interrupted backups no longer stay stuck in "running" forever.** When the driving window or CLI process dies mid-run (closed tab, PHP timeout, killed cron), the record was orphaned in `running` with no end time, showed as a phantom in-progress backup on the dashboard, and — until the fix above — could not be cleared. The records list now self-heals on load via `BackupStatusHelper::markStalledAsFailed()`, which flips `running` records older than a generous 6-hour threshold to `fail` and deletes their partial archives (the threshold sits well above `PreflightCheck`'s 30-minute per-profile start guard so a genuinely long-running browser/CLI backup is never reaped mid-flight; a per-step heartbeat column is the real fix, tracked as a follow-up).
- **Backup detail no longer shows a bogus "Monday, November 29, -0001" end time.** An unfinished backup stores the null date `0000-00-00 00:00:00`, which `HTMLHelper::_('date', …)` rendered as a fake far-past date. The detail view now shows **Not finished** for a null/zero `backupend`.
- **Update site now self-heals.** When the package's `#__update_sites` row is missing, the records view warned *"MokoSuiteBackup update site not found. Reinstall the package to register the update server."* The install script's `postflight` now recreates the update site (and re-attaches a preserved download key) via `ensureUpdateSite()` on the next install/update — no manual reinstall required.
### Changed
- **Run-backup progress screen is now a true full-screen black overlay.** `view=runbackup` rendered as a small Bootstrap card inside the admin chrome (sidebar/toolbar still visible); it now covers the whole viewport on black — matching the "Backup in progress" lock overlay other admin tabs show — with a centered dark panel and light-on-dark text.
## [02.66.00] --- 2026-07-16
## [02.65.00] --- 2026-07-16
## [02.65.00] --- 2026-07-16
### Added
- Backup- and restore-progress modals (backups view) now have the same controls as the full-screen runbackup screen: an "automatically return to the list when finished" checkbox, a Cancel button (double-confirmed) while running, and terminal action buttons. Backup: View backup record / Back to backups on success, Retry / Back on failure. Restore matches, with a stronger cancel warning (a restore cannot be rolled back mid-flight) and no record link.
### Fixed
- ntfy default server URL now points to `ntfy.mokoconsulting.tech` (was the public `ntfy.sh`) in the profile form default/hint, `NotificationSender` fallbacks, the `ntfy_server` column default, and the field description. (#166)
- Non-stepped restore (`RestoreEngine`) now surfaces database-import failures instead of always reporting a clean success: it checks `DatabaseImporter::hasErrors()`, logs the failed statements, and returns a `warning` with a "database may be incomplete" message. `DatabaseImporter`'s statement splitter is also now quote-aware (a `;`/newline inside a value no longer splits a statement) with comments only skipped between statements. (#188)
- Restore Cancel no longer orphans server-side state. The stepped-restore modal's Cancel button now calls a new `ajax.restoreCancel` action that deletes the restore staging directory and session/state files, and `SteppedSession::cleanupOldSessions()` gained a backstop that reaps orphaned `mokosuitebackup-restore-*` staging dirs (for restores abandoned by closing the browser). Previously a cancelled/abandoned restore left the extracted staging dir behind indefinitely.
- Stepped restore no longer reports a partial database import as a clean "Restore complete". `SteppedRestoreEngine::stepDatabase()` now counts failed statements and surfaces the total (the completion message warns and the modal keeps the result on-screen instead of auto-returning). The statement splitter is also quote-aware — a `;` or newline inside a string value no longer splits a statement mid-value — and comment lines are only skipped between statements. (Sibling of #188 for the stepped/standalone paths; see #291.)
- Database view "One Problem" on MokoSuiteBackup: reconciled the `#__mokosuitebackup_profiles` schema-checker output with the update-file history. Corrected the stale `TINYINT` definition in `01.01.01.sql` to the final `VARCHAR(20)` type, fixed the `MODIFY COLUMN` parser trip-up in `01.39.00.sql`, and rewrote the retired `sftp_*``ADD COLUMN` statements in `01.35.00.sql`/`01.36.00.sql` as bare `ADD` so Joomla's checker no longer demands columns that `02.52.25` and the postflight purge deliberately remove. Joomla's schema checker now reports no problems.
### Changed
- Component now shows the "Type - Name" pretty name `Component - MokoSuiteBackup` in the Extensions manager, consistent with the other sub-extensions. Achieved safely with an `<element>com_mokosuitebackup</element>` tag in the manifest (Joomla reads `<element>` before `<name>` for the extension element), so the element stays `com_mokosuitebackup` and no duplicate/orphaned component is created.
- Backup/restore progress modals in the backups view now use the same full-screen black, big-text, animated Atum-coloured progress-bar theme as the `runbackup` screen, so all backup progress surfaces match. New `media/css/backup-progress.css` scopes the black to the modal (the list page is unaffected until a modal opens).
- Standalone MokoRestore wizard (generated `restore.php`) re-branded to the full-black MokoSuite theme — black background, big header, dark panels/forms, and MokoSuite accent colours — so the restore experience matches the rest of the system instead of the old light blue "Joomla-styled" look.
COM_MOKOJOOMBACKUP_FIELD_NTFY_SERVER_DESC="URL of the ntfy server. Default is the public ntfy.sh service. Use your own server URL for self-hosted instances."
COM_MOKOJOOMBACKUP_FIELD_NTFY_SERVER_DESC="URL of the ntfy server. Default is the Moko Consulting ntfy server (ntfy.mokoconsulting.tech). Use your own server URL for self-hosted instances, or https://ntfy.sh for the public service."
@@ -1091,3 +1187,4 @@ class Pkg_MokoSuiteBackupInstallerScript
}catch(\Exception$e){}
}
}
}// end class_exists guard
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.