The server-side onExtensionBeforeUpdate backup runs synchronously and
cannot drive a browser modal, so nothing ever 'popped'. Add a client
notice + on-demand live-progress modal on com_joomlaupdate and
com_installer(view=update):
- plg_system_mokosuitebackup gains onBeforeCompileHead, which injects a
'back up before you update' notice + Back up now button on the update
pages (gated by show_update_notice + backup_before_update), plus a new
media JS asset that runs the same stepped backup as the dashboard
(ajax.init then a loop of ajax.step) with a dependency-free progress
overlay.
- New AjaxController::preupdateAck() arms the same 10-min throttle key
runPreActionBackup() checks, so proceeding with Joomla's update after a
modal backup does not run a duplicate server-side backup.
- Plugin manifest registers the new media/js folder; language keys added.
Phase-1 baseline (notice + on-demand). Auto-intercepting the Update
button is the planned phase-2 follow-up (#196).
Refs #196
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
Retention (RetentionManager and the plugin's hourly cleanup) only ever
deleted local files; remote archives grew unbounded. Add an idempotent
delete() to RemoteUploaderInterface + all four uploaders and have
RetentionManager remove each pruned archive from the profile's enabled
remotes (best-effort; failures logged). The shared restore.php is left
in place since every backup overwrites it. (#229)
Consolidate duplicated plumbing (#230):
- New RemoteUploaderFactory replaces the createUploaderFromParams copy
duplicated in BackupEngine and SteppedBackupEngine.
- RetentionManager becomes the single retention authority (global-default
fallback + pruneOrphans()); the system plugin delegates to it and its
duplicate doCleanup()/deleteBackupRecord() logic is removed.
- Backend controller, API controller and legacy cli/mokosuitebackup.php
now run through the shared BackupRunner instead of BackupEngine directly.
Refs #229#230
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
MokoRestore::generateStandaloneScript() rewrites the 'Backup Archive'
pre-check via str_replace(ORIG, REPL) but was missing the third
$php subject argument, so every standalone-mode backup fatally errored
with 'str_replace() expects at least 3 arguments, 2 given' at the
'Generating standalone restore.php...' step. The archive still finalized
and uploaded, but no restore.php was produced -- the underlying cause of
#226 (restore script missing on the remote).
Refs #226
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
The standalone restore script was uploaded to each remote with the result
discarded and nothing logged, so a failed second upload left the restore script
missing from the remote with no warning while the archive still recorded
'complete'. Capture the result: log success, and on failure set uploadFailed
(-> warning status) and log the reason.
Fixes#226
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
Per feedback, wrap the destination modal content in a Bootstrap .container-fluid
and give that a 1rem frame in the WAM stylesheet, instead of a custom
.msbackup class on .modal-content. Inner header/body/footer drop their own
horizontal padding so the frame isn't doubled.
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
- Add/Edit Destination modal: SFTP key can now be UPLOADED as a file (read into
the key textarea client-side via FileReader) instead of copy/paste; pasting
still works.
- Give .modal-content a "msbackup" class with a 1rem padding frame — Bootstrap's
.modal-content has no padding of its own, which made the modal feel cramped.
Styling lives in the WAM stylesheet (media/.../admin.css), scoped to .msbackup.
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
runPreActionBackup() only enqueued a message when the backup failed, so a
successful pre-update backup produced no admin notification at all. Now it
surfaces every outcome via the BackupRunner status: complete -> success,
warning (archive made but remote upload failed) -> warning, fail -> error.
Refs #192
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
- Move component admin CSS to the Joomla Web Asset Manager: new
media/com_mokosuitebackup/css/admin.css + media/joomla.asset.json, a <media>
entry in the manifest, and useStyle('com_mokosuitebackup.admin') in the
profile edit view. Removes the inline <style> block (no more inline CSS).
- Remove the profile-level "Keep local copy" field/"Remote" fieldset from
forms/profile.xml — it is now per remote destination (modal remoteKeepLocal).
- Clean up the Add/Edit Destination modal padding/margins (in admin.css).
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
Fallout of the duplicate-component element bug:
- Preflight now runs removeOrphanedComponent() BEFORE the component install
recreates its menu, so the orphan's "Backup" menu (alias "backup") no longer
collides ("The alias backup is already being used by the Backup menu item").
- ensureSubmenuItems() recreates the top-level "Backup" menu when it is missing
(via new createTopMenu()) instead of bailing out — so a failed install that
deleted the parent menu no longer leaves Backup gone from the admin menu (which
also broke the cPanel module and MokoSuiteClient component detection).
- The top-level menu label now uses the short constant COM_MOKOJOOMBACKUP_SHORT
(manifest <menu> + enforced/normalized in the script).
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
Non-workflow docs/config only (CLAUDE.md, README, branch-protection, ISSUE_TEMPLATE/config.yml,
ci-issue-reporter). Workflow files, manifest schema, and Makefile install paths left for the
forge-rebrand gate. Authored-by: Moko Consulting
02.56.01.sql used PREPARE/EXECUTE/DEALLOCATE to conditionally drop the 26 legacy
remote_storage/ftp_*/sftp_*/gdrive_*/s3_* columns portably. Joomla's installer
rejects those statements on MySQL 8 (error 1295: "This command is not supported
in the prepared statement protocol yet"), aborting the whole component install
when updating from an old schema.
Neutralize 02.56.01.sql (now a no-op comment) and move the purge into
Pkg_MokoSuiteBackupInstallerScript::dropLegacyRemoteColumns(): gate on
INFORMATION_SCHEMA, then issue one plain ALTER dropping only the columns that
still exist — portable on MariaDB and MySQL 8, idempotent, non-fatal.
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
Every non-interactive backup trigger used a slightly different path, so
final status and notification gating behaved inconsistently depending on
what started the backup (this is why pre-update backups did not gate
notifications the way "Backup Now" does).
Add src/Service/BackupRunner.php as the single synchronous entry point.
It delegates to BackupEngine (which already sends notifications gated by
the profile's notify_on_success/notify_on_failure and applies retention —
so there is exactly ONE notification layer), and normalises the result,
including the real complete/warning/fail status the engine records but did
not return. BackupRunner self-loads BackupEngine so callers require one file.
Routed through BackupRunner:
- plg_system runPreActionBackup() (preaction) + web-cron handler (webcron)
- plg_content triggerAutoBackup() (pre-install)
- plg_task scheduled task run
- plg_console RunCommand (cli)
The dashboard AJAX/stepped "Backup Now" flow is unchanged (it is the
interactive equivalent). The installer progress popup is deferred — the
installer request cannot drive the AJAX modal (TODO(#196) noted in code).
php -l clean on all changed files.
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
Guard the promote so it only inserts a version header when absent (stops the
empty duplicate headers each build was appending), and remove the duplicate
02.56.11 header the non-idempotent promote just created.
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i