The pre-update return URL is assigned to window.location.href, so a
javascript:/data: URI (empty host) would have passed the host-only check
and executed. Require an absolute http(s) same-host URL or a single-slash
root-relative path; reject javascript:/data:/vbscript: and protocol-relative
//host.
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
Replace the popup-modal pre-update approach with a dedicated full-page
backup screen, modelled on Akeeba Backup-on-Update, and fix the white
screen caused by running a backup synchronously inside the update request.
- New view=runbackup (View/Runbackup + tmpl/runbackup): full-screen
progress page that auto-starts the stepped backup (ajax.init -> loop
ajax.step), then redirects to a validated returnurl on completion or
shows a completion/retry panel. Auto-resolved by MVCFactory.
- System plugin: on com_joomlaupdate task=update.install (when
backup_before_update is on), redirect to the full-screen backup screen
with returnurl back to update.install&is_backed_up=1; on return, arm the
throttle so onExtensionBeforeUpdate does not run a duplicate backup. No
synchronous backup in the update request.
- Dashboard 'Backup Now' navigates to the full-screen screen instead of
the inline modal; the inline modal + stepped JS are removed.
- Retire the popup modal: delete media/js/update-backup.js, remove the
plugin <media> element and the preupdate_auto_intercept param.
- Dashboard: warn when Global Config live_site is empty (CLI/scheduled
backup naming + URL generation depend on it).
Refs #196
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
The pre-update/uninstall backup runs synchronously inside the extension
update request. Without raising limits, a large site exceeds
max_execution_time/memory_limit mid-backup and the update page
white-screens. Raise set_time_limit(0)/memory_limit/ignore_user_abort
like the web-cron path.
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
Joomla's console fills $_SERVER['HTTP_HOST'] with the reserved sentinel
'joomla.invalid', which the [HOST] placeholder resolver used verbatim
because its unusable-host check only knew about empty/localhost. Treat
'joomla.invalid' as unusable too, falling back to live_site then the
system hostname, so CLI/console/scheduled backups get a real name.
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
Opt-in param preupdate_auto_intercept (off by default). When enabled the
injected update-page script wraps window.Joomla.submitbutton so clicking
the toolbar Update button first pops the progress modal, runs the
pre-update backup, and re-issues the original update task on success.
- Matches only update.update / update.install (not Find Updates /
Clear Cache, which also start with 'update').
- Reuses the phase-1 overlay + ajax.preupdateAck throttle so the
subsequent server-side onExtensionBeforeUpdate backup is skipped.
- The manual 'Back up now' notice remains a fallback for flows that
don't route through submitbutton (e.g. some com_joomlaupdate paths).
Refs #196
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
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