3eb2562abd
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 18s
fix: pre-action backup fires for every extension, not one per 10 min Root cause of "pre-update backup only fires on the backup extension": a coarse 600s session throttle. Whenever ANY pre-action backup ran (including updating MokoSuiteBackup itself, or a core Joomla update), the key mokosuitebackup.preaction_backup_before_update was armed for 10 minutes. onBeforeCompileHead read it as `recentBackup` and DISABLED the client-side interceptor for all extensions, and runPreActionBackup was throttled by the same window. So after updating one extension, every other extension update silently skipped its backup for 10 minutes. Replace the time window with "one backup per Update action": - runPreActionBackup dedupes per-request (a batch update fires the event once per extension in a single request -> back up once) via instance flags, and consumes a ONE-SHOT session skip flag for the re-fired client update/uninstall (skip once, next distinct action backs up). - ajax.preupdateAck sets the one-shot flag for the SPECIFIC action (update|uninstall), passed from installer-backup.js via &msb_action and surfaced by Runbackup HtmlView -> CFG.action. A core update passes no action, so it never suppresses an extension backup. - onBeforeCompileHead drops the recentBackup time-suppression (the re-fire loop is already guarded client-side by window.__msbResuming). - The core Joomla update path uses its own key (mokosuitebackup.core_update_backed_up), decoupled from extensions. Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i @
13 KiB
13 KiB
Changelog
[Unreleased]
Fixed
- Pre-update backup now fires for every extension update/uninstall, not just the first one in a 10-minute window. A coarse 600-second session throttle (armed after any pre-action backup — even after updating MokoSuiteBackup itself or a core Joomla update) was disabling the client-side interceptor and the server-side fallback for all extensions for 10 minutes, so distinct updates silently went un-backed-up (looked like "only the backup extension triggers it"). Replaced with one backup per Update action: a single batch/click still backs up once (deduped per-request), the client re-fire is skipped via a one-shot action-keyed flag (consumed once), and the core-update path uses its own dedicated key so it no longer suppresses extension backups.
- Pre-update backup on the Joomla Update page is now seamless (Akeeba-style): after the full-screen backup runs, the plugin auto-ticks Joomla's "I have taken a backup" checkbox and clicks Install, so the update continues automatically instead of stopping for a second manual click.
- Pre-update full-screen backup now actually fires on the Joomla Update page. The "Install the update" button posts to
option=com_joomlaupdate&layout=update(Joomla's confirm/updating page) — carrying onlylayout=update, noview/task— so the previous match onview=update/update.installnever triggered. The plugin now interceptslayout=update(as well asview=updateand the legacyupdate.install) and returns the browser there flaggedis_backed_up=1.
Added
- Snapshot transfer + master→slave injection (#237): download a content snapshot as a portable
.msbsnapfile (zip ofmanifest.json+snapshot.json+ a sha256 integrity check); import one on another site via a new Import button on the Snapshots page (materialises a local snapshot record you can then restore); and a direct injection API —POST /api/index.php/v1/mokosuitebackup/snapshot/inject— so a master site can push a snapshot straight into a slave's Web Services API. Two conflict modes ship now: overwrite (replace matching items by ID — master wins) and create (skip existing, add only new), selectable per inject call or defaulted in Options → Snapshot Transfer. A receiving site must opt in via the new Allow Snapshot Injection setting. A third duplicate mode inserts everything as brand-new records with remapped IDs (articles, categories, modules — plus their tags, custom-field values and featured flags) using Joomla's Table API so assets/UCM/nested-sets stay valid; each item is inserted defensively (a bad item is skipped and logged, never fatal). - Full-screen backup now also fronts extension updates and uninstalls (Extensions → Update / Manage), not just core Joomla updates. Because
com_installer'supdate.update/manage.removeare POST actions (CSRF token + a checkedcid[]list) that a server-side redirect can't cleanly resume, this is done client-side: the toolbar Update/Uninstall click is intercepted, the selection is captured, the full-screen backup runs, and on return the original selection is restored and the real POST form is re-submitted. Gated bybackup_before_update/backup_before_uninstall, super-user only, and deduped to one backup per Update action. - Manual "Backup Now" completion now offers a View backup record button (links straight to the record that was just created). It appears only for manual backups — the pre-update / pre-uninstall flow hands control back to Joomla instead.
- Full-screen backup screen (
view=runbackup) for both pre-update and manual backups, modelled on Akeeba's Backup-on-Update — replaces the earlier popup-modal approach. Clicking Joomla's Install the update now redirects (server-side, Akeeba-style) to a dedicated full-page backup screen that runs the stepped backup with a real progress bar and then automatically continues the update via a validatedreturnurl(flaggedis_backed_up=1so the backup isn't repeated). Crucially, no backup runs synchronously inside the update request — which is what white-screened large sites. The dashboard Backup Now now opens the same full-screen screen instead of an inline modal. (#196) - Retention now prunes remote copies too: when a backup is pruned by age/count, its archive is deleted from every enabled remote destination (SFTP / FTP / S3 / Google Drive), not just the local copy. Each uploader gained an idempotent
delete()method (already-absent file = success), and removal is best-effort — a failing destination is logged but never blocks local pruning. The shared standalonerestore.phpis intentionally left in place (every backup overwrites it, so newer backups still depend on it). (#229)
Changed
- Consolidated backup plumbing into shared helpers (#230):
- New
RemoteUploaderFactoryreplaces thecreateUploaderFromParams()copy that was duplicated inBackupEngineandSteppedBackupEngine. RetentionManageris now the single retention authority — it takes the globalmax_age_days/max_backupsfallback and gainedpruneOrphans(); the system plugin's hourly cleanup delegates to it and its duplicatedeleteBackupRecord()logic is removed.- The backend controller, Web Services API controller, and legacy
cli/mokosuitebackup.phpnow run backups through the sharedBackupRunner(gaining the normalized complete/warning/fail status) instead of instantiatingBackupEnginedirectly.
- New
Fixed
- Package installer is now honest about success: the postflight no longer prints "installed successfully" / next-steps when the install actually failed or only partially completed. Joomla logs a failed child extension but still runs the package postflight, so the postflight now (a) verifies every bundled child declared in the manifest actually registered in
#__extensions(matched by element + type, and folder/group for plugins) and (b) verifies the component's schema tables — derived dynamically from the installedinstall.mysql.sql— actually exist. If anything is missing it enqueues an error listing what's missing and stops before the success message. Fail-open: any manifest/DB/IO glitch is treated as "nothing missing" so a transient error never turns a good install into a false failure. Unconditional housekeeping (e.g. download-key restore) still runs regardless. - Pre-update full-screen backup screen now actually triggers on Joomla 6 (and 4/5). The redirect matched only the legacy
update.installtask, which Joomla 4/5/6 don't use — they server-side-redirect to the updating pageview=update, which then extracts the downloaded package from JavaScript. The plugin now intercepts theview=updatepage load (the last point before any files change) and returns the browser there flaggedis_backed_up=1so the extraction proceeds after the backup. (update.finaliseis intentionally not intercepted — by then the files are already extracted.) - Pre-update/uninstall backup no longer white-screens the update on large sites. The synchronous backup that runs inside the extension update/uninstall request now raises
max_execution_time/memory_limit(andignore_user_abort) like the web-cron path, so it can't exhaust the request's default limits mid-backup. (Core Joomla updates additionally get a full-screen backup screen — see below.) - Archive names for CLI/console-triggered backups no longer come out as
joomla.invalid_…. The[HOST]placeholder took$_SERVER['HTTP_HOST']verbatim, but Joomla's console fills that with the reserved sentinel hostjoomla.invalid; the resolver now treats that (like empty/localhost) as unusable and falls back to the configuredlive_sitehost, then the system hostname. (Set the site's live_site to get the exact domain in CLI-built names.) - Standalone restore script generation no longer aborts backups with
str_replace() expects at least 3 arguments, 2 given.MokoRestore::generateStandaloneScript()had astr_replace()call (the "Backup Archive" pre-check rewrite) that was missing its$phpsubject argument, so every standalone-mode backup fatally errored while "Generating standalone restore.php…" — the archive still finalized and uploaded, but norestore.phpwas ever produced (the true root cause behind #226). (#226) - Remote upload: the standalone restore script upload is no longer silent — its result is now checked and logged, and a failed restore-script upload marks the backup as
warning(previously the result was discarded, so a missing restore script on the remote went unreported while the archive still showed success). (#226)
[02.58.00] --- 2026-07-06
Added
- SFTP destinations: upload an SSH private key file in the Add/Edit Destination modal instead of pasting it (reads the file into the key field client-side; pasting still works).
Changed
- Add/Edit Destination modal: wrap the content in a Bootstrap
.container-fluidand give it a 1rem padding frame via the WAM stylesheet (Bootstrap's.modal-contenthas no padding of its own).
Fixed
- Pre-update backup now shows an admin notification on every outcome (success / warning / failure) — previously a successful pre-update backup fired nothing, so it looked like the notification wasn't working. (#192)
Changed
- Component admin CSS now loads via the Joomla Web Asset Manager (
media/com_mokosuitebackup/+joomla.asset.json) instead of an inline<style>block. - "Keep local copy" is configured per remote destination (in the Add/Edit Destination modal); the redundant profile-level field and its "Remote" tab are removed.
- Add/Edit Destination modal: cleaned up the cramped padding/margins.
Fixed
- Admin menu / duplicate-component fallout: (a) the orphaned
com_component-mokosuitebackupis now removed in preflight too, so its "Backup" menu no longer collides on install ("The alias backup is already being used"); (b)ensureSubmenuItems()recreates the top-level "Backup" menu if it was deleted, instead of giving up — so Backup no longer disappears from the admin menu (and the cPanel module / MokoSuiteClient can find it again); (c) the component menu label now uses the short constantCOM_MOKOJOOMBACKUP_SHORT. (#213 fallout) - Installer no longer aborts on MySQL 8 with "This command is not supported in the prepared statement protocol yet" (error 1295). The legacy remote-column purge migration (
02.56.01.sql) usedPREPARE/EXECUTE/DEALLOCATE, which Joomla's installer rejects; the drop now runs in the package installer script via an INFORMATION_SCHEMA-gated plainALTER(portable across MariaDB and MySQL 8). (#213 update path)
[02.57.00] --- 2026-07-05
Added
BackupRunnerservice — a single synchronous entry point for backups that unifies final status and notification gating across the pre-update, web-cron, pre-install, scheduled-task and CLI triggers (delegates toBackupEngine; no double-notify). Returns a normalized result including the real complete/warning/fail status. Dashboard "Backup Now" (AJAX) unchanged; installer progress popup deferred to #196. (#214)
Fixed
- Release automation: the changelog-promote step is now idempotent — it only inserts a
## [version]header when that version isn't already present, preventing the empty, duplicated version headers that repeated/same-version builds were producing. (workflow)
[02.56.11] --- 2026-07-05
Fixed
- Package
postflightnow removes the orphanedcom_component-mokosuitebackupregistration (stray extension record, admin menu items, schema rows, andadministrator/folder) left behind by the old "Type - Name" component<name>— self-healing on update so affected sites reconcile to the realcom_mokosuitebackup. (#213 follow-up)
[02.56.08] --- 2026-07-05
Fixed
- Component manifest
<name>reverted to the element-safeMokoSuiteBackup. The "Type - Name" convention derived elementcom_component-mokosuitebackup, registering a duplicate component and leaving the realcom_mokosuitebackuporphaned on the old version. (#213)
[02.56.07] --- 2026-07-05
Security
- Mask the FTP password in
AjaxController::maskSecrets()/mergeExistingSecrets()— stored FTP remotes no longer leak their password vialistRemotes. (#169) - Stop the API single-item view (
Backups/JsonapiView) leaking the serverabsolute_path. (#187) - SFTP uploader uses
StrictHostKeyChecking=accept-newinstead ofno. (#182)
Fixed
DatabaseDumperwrites a real newline afterDROP TABLE(was a literal\n). (#179)- Profile-picker forms order by
idinstead of the droppedorderingcolumn. (#180) uninstall.mysql.sqlnow drops the snapshots table. (#181)- CLI snapshot delete uses the
data_filecolumn (was non-existentfile_path). (#184) - Remove the duplicate pre-update backup (content plugin no longer subscribes to
onExtensionBeforeUpdate). (#186) DatabaseImportercollects non-fatal statement errors (getErrors()/hasErrors()). (#188)