Compare commits

..

37 Commits

Author SHA1 Message Date
gitea-actions[bot] 4339c223a1 chore(release): build 02.61.00 [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
2026-07-13 00:58:56 +00:00
jmiller cb56430e37 Merge pull request 'Release: dev → main (runbackup CSS fixes)' (#258) from dev into main
Cascade Main -> Dev / Cascade main -> dev (push) Successful in 2s
Sync Workflows to Repos / sync (push) Has been skipped
2026-07-13 00:58:38 +00:00
mokogitea-actions[bot] 67611604e2 chore(version): pre-release bump to 02.60.06-dev [skip ci]
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 23s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Has been skipped
2026-07-13 00:58:35 +00:00
gitea-actions[bot] 00c1add0cd chore(version): auto-bump patch 02.60.05-dev [skip ci] 2026-07-13 00:58:23 +00:00
jmiller 1e3f963beb Merge remote-tracking branch 'origin/dev' into dev
Universal: Auto Version Bump / Version Bump (push) Successful in 9s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 20s
2026-07-12 19:58:07 -05:00
jmiller c280229b90 @
chore: back-merge main into dev (reconcile release version stamps)

Absorbs main (previous release) into dev ahead of the next release.
All conflicts were VERSION-stamp comments/tags in manifests, SECURITY.md
and issue-branch.yml — kept dev's current line. No functional changes.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
@
2026-07-12 19:57:54 -05:00
mokogitea-actions[bot] fbf0a2de76 chore(version): pre-release bump to 02.60.04-dev [skip ci] 2026-07-13 00:54:57 +00:00
jmiller 30b8e66eca Merge pull request 'style(runbackup): gray card via bg-body-secondary class' (#257) from style/runbackup-card-bg-class into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 48s
2026-07-13 00:54:14 +00:00
jmiller 83b261db02 @
Generic: Project CI / Lint & Validate (pull_request) Successful in 8s
Generic: Project CI / Tests (pull_request) Failing after 5s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Universal: PR Check / Secret Scan (pull_request) Successful in 6s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 7s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
style(runbackup): gray card via bg-body-secondary utility class

Move the card background off a custom CSS rule and onto a Bootstrap
utility class (bg-body-secondary) on the card element itself, so the gray
panel background is declared in the markup. Supersedes the .card
background-color rule added in the previous change.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
@
2026-07-12 19:54:01 -05:00
mokogitea-actions[bot] e0285f112f chore(version): pre-release bump to 02.60.03-dev [skip ci] 2026-07-13 00:52:32 +00:00
jmiller f5538ade88 Merge pull request 'style(runbackup): gray card panel' (#256) from style/runbackup-gray-card into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 20s
2026-07-13 00:52:15 +00:00
jmiller 2d27385ac9 @
Generic: Project CI / Lint & Validate (pull_request) Successful in 9s
Generic: Project CI / Tests (pull_request) Failing after 11s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Universal: PR Check / Secret Scan (pull_request) Successful in 5s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 7s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
style(runbackup): gray card panel on the black backdrop

Set the backup card (.msb-runbackup > .card) to a gray background so it
reads as a panel against the black full-screen backdrop instead of stark
white.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
@
2026-07-12 19:52:06 -05:00
mokogitea-actions[bot] a3eb713d81 chore(version): pre-release bump to 02.60.02-dev [skip ci] 2026-07-13 00:06:00 +00:00
jmiller 7106ece393 Merge pull request 'fix(css): reliably load component stylesheets (progress bar unstyled)' (#255) from fix/runbackup-css-load into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 17s
2026-07-13 00:05:45 +00:00
mokogitea-actions[bot] 394ad0d607 chore(version): pre-release bump to 02.60.01-dev [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
2026-07-13 00:05:33 +00:00
jmiller f5bcc7c457 @
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 17s
fix(css): reliably load component stylesheets (progress bar was unstyled)

The full-screen backup progress bar lost all styling after the CSS moved
to the Web Asset Manager: this component's WAM style assets do not emit a
<link> on the admin document. The asset resolves without error (Joomla
lazily registers media/com_mokosuitebackup/joomla.asset.json, so useStyle
does not throw) yet no stylesheet tag is rendered — and the same was
already silently true of com_mokosuitebackup.admin (it was just cosmetic,
so unnoticed). Confirmed live: neither runbackup.css nor admin.css loads,
body stays white, the bar has no track/fill.

Attach the external media stylesheets directly to the document
(addStyleSheet), which renders reliably via <jdoc:include type="styles">.
The joomla.asset.json + useStyle calls are kept so the WAM path resumes
automatically if/when it starts emitting. Fixes both runbackup and the
profile edit screen.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
@
2026-07-12 19:05:15 -05:00
gitea-actions[bot] 69c70d4480 chore(release): build 02.60.00 [skip ci] 2026-07-12 23:33:41 +00:00
jmiller 89f1869120 Merge pull request 'Release: dev → main (02.59.x)' (#253) from dev into main
Cascade Main -> Dev / Cascade main -> dev (push) Has been cancelled
Sync Workflows to Repos / sync (push) Has been cancelled
2026-07-12 23:33:28 +00:00
mokogitea-actions[bot] 0c845fe807 chore(version): pre-release bump to 02.59.09-dev [skip ci]
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 21s
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
RC Revert / Rename rc/ back to dev/ (pull_request) Has been cancelled
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Has been cancelled
2026-07-12 23:33:14 +00:00
jmiller a60f7ac032 Merge pull request 'docs(ajax): correct preupdateAck docblock' (#254) from docs/preupdateack-docblock into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 19s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Joomla: Extension CI / Lint & Validate (pull_request) Has been cancelled
Joomla: Extension CI / Release Readiness Check (pull_request) Has been cancelled
Universal: PR Check / Branch Policy (pull_request) Has been cancelled
Universal: PR Check / Require Docs Update (pull_request) Has been cancelled
Universal: PR Check / Wiki Update Reminder (pull_request) Has been cancelled
Universal: PR Check / Secret Scan (pull_request) Has been cancelled
Universal: PR Check / Validate PR (pull_request) Has been cancelled
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Has been cancelled
Generic: Repo Health / Access control (pull_request) Has been cancelled
Generic: Repo Health / Site Health (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Has been cancelled
Joomla: Extension CI / PHPStan Analysis (pull_request) Has been cancelled
Joomla: Extension CI / Build RC Pre-Release (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
2026-07-12 23:33:00 +00:00
jmiller f62b1ec72e @
Generic: Project CI / Lint & Validate (pull_request) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Branch Policy (pull_request) Has been cancelled
Universal: PR Check / Require Docs Update (pull_request) Has been cancelled
Universal: PR Check / Wiki Update Reminder (pull_request) Has been cancelled
Universal: PR Check / Secret Scan (pull_request) Has been cancelled
Universal: PR Check / Validate PR (pull_request) Has been cancelled
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Has been cancelled
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
RC Revert / Rename rc/ back to dev/ (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
docs(ajax): correct preupdateAck docblock to one-shot skip semantics

The docblock still described the removed 10-minute throttle ("arms BOTH
keys"). It now sets a one-shot, action-keyed skip flag consumed once by
the system plugin. Comment-only; no behaviour change.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
@
2026-07-12 18:28:54 -05:00
mokogitea-actions[bot] 0562d3effd chore(version): pre-release bump to 02.59.08-dev [skip ci] 2026-07-12 23:25:09 +00:00
gitea-actions[bot] 1c5c511c06 chore(version): auto-bump patch 02.59.07-dev [skip ci] 2026-07-12 23:24:59 +00:00
jmiller 444cfa3fdb Merge remote-tracking branch 'origin/dev' into dev
Universal: Auto Version Bump / Version Bump (push) Successful in 7s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 17s
2026-07-12 18:24:40 -05:00
jmiller 1e6d9ba078 @
chore: back-merge main into dev (release 02.59.00 version stamps)

Reconciles main (released 02.59.00) into dev ahead of the next release.
All conflicts were VERSION-stamp comments/tags in manifests, SECURITY.md
and issue-branch.yml — kept dev's current 02.59.05 line. No functional
changes. Clears the dev -> main merge conflict for the release PR.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
@
2026-07-12 18:24:12 -05:00
mokogitea-actions[bot] 9ee800b151 chore(version): pre-release bump to 02.59.06-dev [skip ci] 2026-07-12 23:18:54 +00:00
jmiller 25991c474b Merge pull request 'refactor(runbackup): move screen CSS into the Web Asset Manager' (#252) from fix/runbackup-css-wam into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 15s
2026-07-12 23:18:41 +00:00
mokogitea-actions[bot] 059cbfe058 chore(version): pre-release bump to 02.59.05-dev [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
RC Revert / Rename rc/ back to dev/ (pull_request) Has been cancelled
2026-07-12 23:18:27 +00:00
jmiller d658019007 @
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 16s
refactor(runbackup): move screen CSS into the Web Asset Manager

All full-screen backup CSS now loads through Joomla WAM instead of an
inline <style> block (and the redundant inline style="width:0"):

- New media/css/runbackup.css holds the whole stylesheet.
- Registered as asset com_mokosuitebackup.runbackup in joomla.asset.json.
- tmpl/runbackup/default.php loads it via
  getDocument()->getWebAssetManager()->useStyle(com_mokosuitebackup.runbackup),
  matching the existing com_mokosuitebackup.admin pattern.

The css folder already ships via the manifest <folder>css</folder>, so no
manifest change is needed.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
@
2026-07-12 18:18:08 -05:00
mokogitea-actions[bot] 8237f85056 chore(version): pre-release bump to 02.59.04-dev [skip ci] 2026-07-12 23:14:28 +00:00
jmiller 14cc00f93f Merge pull request 'style(runbackup): black backdrop on the full-screen backup screen' (#251) from fix/runbackup-black-bg into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 16s
2026-07-12 23:14:15 +00:00
mokogitea-actions[bot] d1ccb8d419 chore(version): pre-release bump to 02.59.03-dev [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
RC Revert / Rename rc/ back to dev/ (pull_request) Has been cancelled
2026-07-12 23:14:09 +00:00
jmiller 8a783aa25a @
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 15s
style(runbackup): black backdrop on the full-screen backup screen

Set html/body background to #000 so the full-screen pre-update / Backup
Now progress screen runs on a black backdrop (the card + Atum-coloured
progress bar sit on top).

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
@
2026-07-12 18:13:51 -05:00
mokogitea-actions[bot] d9d6ee9181 chore(version): pre-release bump to 02.59.02-dev [skip ci] 2026-07-12 22:48:45 +00:00
jmiller f02c4d4c9d Merge pull request 'fix: pre-action backup fires for every extension (not one per 10 min)' (#250) from fix/preaction-per-action-backup into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 17s
2026-07-12 22:48:33 +00:00
mokogitea-actions[bot] 2df4b280eb chore(version): pre-release bump to 02.59.01-dev [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
RC Revert / Rename rc/ back to dev/ (pull_request) Has been cancelled
2026-07-12 22:48:27 +00:00
jmiller 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
@
2026-07-12 17:48:09 -05:00
38 changed files with 226 additions and 106 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow # DEFGROUP: MokoGitea.Workflow
# INGROUP: mokocli.Automation # INGROUP: mokocli.Automation
# VERSION: 02.59.00 # VERSION: 02.61.00
# BRIEF: Auto-create feature branch when an issue is opened # BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch" name: "Universal: Issue Branch"
+6 -11
View File
@@ -1,16 +1,21 @@
# Changelog # Changelog
## [Unreleased] ## [Unreleased]
## [02.61.00] --- 2026-07-13
## [02.60.00] --- 2026-07-12
## [02.59.00] --- 2026-07-12 ## [02.59.00] --- 2026-07-12
### Fixed ### 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 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 **only `layout=update`**, no `view`/`task` — so the previous match on `view=update`/`update.install` never triggered. The plugin now intercepts `layout=update` (as well as `view=update` and the legacy `update.install`) and returns the browser there flagged `is_backed_up=1`. - 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 **only `layout=update`**, no `view`/`task` — so the previous match on `view=update`/`update.install` never triggered. The plugin now intercepts `layout=update` (as well as `view=update` and the legacy `update.install`) and returns the browser there flagged `is_backed_up=1`.
### Added ### Added
- **Snapshot transfer + master→slave injection (#237):** download a content snapshot as a portable **`.msbsnap`** file (zip of `manifest.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). - **Snapshot transfer + master→slave injection (#237):** download a content snapshot as a portable **`.msbsnap`** file (zip of `manifest.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`'s `update.update` / `manage.remove` are POST actions (CSRF token + a checked `cid[]` 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 by `backup_before_update` / `backup_before_uninstall`, super-user only, and skipped if a backup already ran this session. - Full-screen backup now also fronts **extension updates and uninstalls** (Extensions → Update / Manage), not just core Joomla updates. Because `com_installer`'s `update.update` / `manage.remove` are POST actions (CSRF token + a checked `cid[]` 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 by `backup_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. - 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 validated `returnurl` (flagged `is_backed_up=1` so 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) - **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 validated `returnurl` (flagged `is_backed_up=1` so 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 standalone `restore.php` is intentionally left in place (every backup overwrites it, so newer backups still depend on it). (#229) - 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 standalone `restore.php` is intentionally left in place (every backup overwrites it, so newer backups still depend on it). (#229)
@@ -56,13 +61,3 @@
### Fixed ### 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) - 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 `postflight` now removes the orphaned `com_component-mokosuitebackup` registration (stray extension record, admin menu items, schema rows, and `administrator/` folder) left behind by the old "Type - Name" component `<name>` — self-healing on update so affected sites reconcile to the real `com_mokosuitebackup`. (#213 follow-up)
## [02.56.08] --- 2026-07-05
### Fixed
- Component manifest `<name>` reverted to the element-safe `MokoSuiteBackup`. The "Type - Name" convention derived element `com_component-mokosuitebackup`, registering a duplicate component and leaving the real `com_mokosuitebackup` orphaned on the old version. (#213)
+1 -1
View File
@@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla
INGROUP: Template-Joomla.Documentation INGROUP: Template-Joomla.Documentation
REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
PATH: /SECURITY.md PATH: /SECURITY.md
VERSION: 02.59.00 VERSION: 02.61.00
BRIEF: Security vulnerability reporting and handling policy BRIEF: Security vulnerability reporting and handling policy
--> -->
@@ -0,0 +1,86 @@
/*
* @package MokoSuiteBackup
* @subpackage com_mokosuitebackup
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*
* Full-screen backup progress screen (view=runbackup). Loaded via the Joomla
* Web Asset Manager (asset com_mokosuitebackup.runbackup).
*/
/* Full-screen backup runs on a black backdrop. */
html,
body {
background-color: #000 !important;
}
/* Colours are pulled from the Atum admin template's own CSS custom properties
so the screen matches whatever theme the site runs, with Bootstrap and then
a literal as progressive fallbacks. */
.msb-runbackup {
--msb-accent: var(--template-link-color, var(--bs-link-color, #2a69b8));
--msb-accent2: var(--template-special-color, var(--msb-accent));
--msb-success: var(--template-success-color, var(--bs-success, #2f7d32));
--msb-danger: var(--template-danger-color, var(--bs-danger, #c52128));
--msb-track: var(--template-bg-light, var(--bs-gray-200, #edf0f5));
padding: 0 1rem;
}
/* overflow:hidden clips the full-bleed bar to the card's rounded corners.
The card's gray background comes from a Bootstrap utility class on the
element itself (see the template), not a rule here. */
.msb-runbackup .card {
overflow: hidden;
}
.msb-rb-progress {
height: 2.5rem;
background: var(--msb-track);
overflow: hidden;
}
.msb-rb-bar {
position: relative;
height: 100%;
width: 0;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-weight: 600;
font-size: .95rem;
white-space: nowrap;
background-color: var(--msb-accent);
background-image: linear-gradient(90deg, var(--msb-accent), var(--msb-accent2));
transition: width .35s ease;
}
.msb-rb-bar.is-animated::after {
content: "";
position: absolute;
inset: 0;
background-image: linear-gradient(45deg, rgba(255, 255, 255, .18) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .18) 50%, rgba(255, 255, 255, .18) 75%, transparent 75%, transparent);
background-size: 1rem 1rem;
animation: msb-rb-stripes 1s linear infinite;
}
.msb-rb-bar.is-success {
background-image: none;
background-color: var(--msb-success);
}
.msb-rb-bar.is-danger {
background-image: none;
background-color: var(--msb-danger);
}
@keyframes msb-rb-stripes {
from {
background-position: 1rem 0;
}
to {
background-position: 0 0;
}
}
@@ -9,6 +9,11 @@
"name": "com_mokosuitebackup.admin", "name": "com_mokosuitebackup.admin",
"type": "style", "type": "style",
"uri": "com_mokosuitebackup/css/admin.css" "uri": "com_mokosuitebackup/css/admin.css"
},
{
"name": "com_mokosuitebackup.runbackup",
"type": "style",
"uri": "com_mokosuitebackup/css/runbackup.css"
} }
] ]
} }
@@ -17,7 +17,7 @@
display label there. display label there.
--> -->
<name>MokoSuiteBackup</name> <name>MokoSuiteBackup</name>
<version>02.59.00</version> <version>02.61.00</version>
<creationDate>2026-06-02</creationDate> <creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -0,0 +1 @@
/* 02.59.01 — no schema changes */
@@ -0,0 +1 @@
/* 02.59.02 — no schema changes */
@@ -0,0 +1 @@
/* 02.59.03 — no schema changes */
@@ -0,0 +1 @@
/* 02.59.04 — no schema changes */
@@ -0,0 +1 @@
/* 02.59.05 — no schema changes */
@@ -0,0 +1 @@
/* 02.59.06 — no schema changes */
@@ -0,0 +1 @@
/* 02.59.07 — no schema changes */
@@ -0,0 +1 @@
/* 02.59.08 — no schema changes */
@@ -0,0 +1 @@
/* 02.59.09 — no schema changes */
@@ -0,0 +1 @@
/* 02.60.00 — no schema changes */
@@ -0,0 +1 @@
/* 02.60.01 — no schema changes */
@@ -0,0 +1 @@
/* 02.60.02 — no schema changes */
@@ -0,0 +1 @@
/* 02.60.03 — no schema changes */
@@ -0,0 +1 @@
/* 02.60.04 — no schema changes */
@@ -0,0 +1 @@
/* 02.60.05 — no schema changes */
@@ -0,0 +1 @@
/* 02.60.06 — no schema changes */
@@ -0,0 +1 @@
/* 02.61.00 — no schema changes */
@@ -89,11 +89,11 @@ class AjaxController extends BaseController
* *
* Called by the full-screen backup screen after a successful backup so the * Called by the full-screen backup screen after a successful backup so the
* imminent server-side onExtensionBeforeUpdate / onExtensionBeforeUninstall * imminent server-side onExtensionBeforeUpdate / onExtensionBeforeUninstall
* backup is skipped — it arms the same 10-minute throttle keys the system * backup is skipped once — it sets a ONE-SHOT skip flag (keyed by the
* plugin checks, preventing a duplicate backup when the update / uninstall * `action` the backup was fronting) that plg_system_mokosuitebackup
* then proceeds. Arms BOTH keys so a single ack covers whichever action the * consumes on the re-fired update/uninstall request, so exactly that one
* full-screen backup was fronting. * action is not double-backed-up while later, distinct actions still are.
* POST: task=ajax.preupdateAck * POST: task=ajax.preupdateAck&action=update|uninstall
*/ */
public function preupdateAck(): void public function preupdateAck(): void
{ {
@@ -109,11 +109,21 @@ class AjaxController extends BaseController
return; return;
} }
// Same keys + semantics as plg_system_mokosuitebackup::runPreActionBackup(). /* Set a ONE-SHOT skip flag for the specific action the full-screen backup
was fronting. runPreActionBackup() consumes it on the re-fired
update/uninstall request (once), so the synchronous backup is skipped
that one time but the NEXT distinct action still backs up. Keyed by
action so an update ack never suppresses a later, unrelated uninstall.
For a core Joomla update (no action) neither flag is set — a core
update doesn't fire onExtensionBefore(Update|Uninstall). */
$action = $this->app->getInput()->getCmd('action', '');
$session = Factory::getSession(); $session = Factory::getSession();
$now = time();
$session->set('mokosuitebackup.preaction_backup_before_update', $now); if ($action === 'update') {
$session->set('mokosuitebackup.preaction_backup_before_uninstall', $now); $session->set('mokosuitebackup.skip_preaction_backup_before_update', true);
} elseif ($action === 'uninstall') {
$session->set('mokosuitebackup.skip_preaction_backup_before_uninstall', true);
}
$this->sendJson(['error' => false, 'message' => 'Pre-action backup acknowledged']); $this->sendJson(['error' => false, 'message' => 'Pre-action backup acknowledged']);
} }
@@ -40,6 +40,9 @@ class HtmlView extends BaseHtmlView
public bool $autostart = true; public bool $autostart = true;
/** Which action the backup fronts ('update' | 'uninstall' | ''); drives the completion ack skip flag. */
public string $action = '';
public function display($tpl = null): void public function display($tpl = null): void
{ {
$input = Factory::getApplication()->getInput(); $input = Factory::getApplication()->getInput();
@@ -48,6 +51,7 @@ class HtmlView extends BaseHtmlView
$this->description = $input->getString('description', ''); $this->description = $input->getString('description', '');
$this->returnUrl = $input->getRaw('returnurl', ''); $this->returnUrl = $input->getRaw('returnurl', '');
$this->autostart = (bool) $input->getInt('autostart', 1); $this->autostart = (bool) $input->getInt('autostart', 1);
$this->action = $input->getCmd('msb_action', '');
if ($this->profileId <= 0) { if ($this->profileId <= 0) {
$this->profileId = 1; $this->profileId = 1;
@@ -14,13 +14,18 @@ use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text; use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route; use Joomla\CMS\Router\Route;
use Joomla\CMS\Session\Session; use Joomla\CMS\Session\Session;
use Joomla\CMS\Uri\Uri;
HTMLHelper::_('behavior.formvalidator'); HTMLHelper::_('behavior.formvalidator');
HTMLHelper::_('behavior.keepalive'); HTMLHelper::_('behavior.keepalive');
HTMLHelper::_('bootstrap.modal'); HTMLHelper::_('bootstrap.modal');
/* Component CSS via the Web Asset Manager (declared in media/joomla.asset.json) */ /* Component CSS. Registered in media/joomla.asset.json, but this component's WAM
styles don't emit on the admin document (asset resolves without error yet no
<link> renders), so attach the external stylesheet directly — reliably output
via <jdoc:include type="styles" />. */
$this->getDocument()->getWebAssetManager()->useStyle('com_mokosuitebackup.admin'); $this->getDocument()->getWebAssetManager()->useStyle('com_mokosuitebackup.admin');
$this->getDocument()->addStyleSheet(Uri::root(true) . '/media/com_mokosuitebackup/css/admin.css');
$profileId = (int) $this->item->id; $profileId = (int) $this->item->id;
$token = Session::getFormToken(); $token = Session::getFormToken();
@@ -71,6 +71,7 @@ $config = [
'profileId' => $this->profileId, 'profileId' => $this->profileId,
'description' => $this->description, 'description' => $this->description,
'returnUrl' => $safeReturnUrl, 'returnUrl' => $safeReturnUrl,
'action' => $this->action,
'dashboardUrl' => $dashboardUrl, 'dashboardUrl' => $dashboardUrl,
'recordUrl' => Route::_('index.php?option=com_mokosuitebackup&view=backup&id=__MSBID__', false), 'recordUrl' => Route::_('index.php?option=com_mokosuitebackup&view=backup&id=__MSBID__', false),
'autostart' => $this->autostart, 'autostart' => $this->autostart,
@@ -87,55 +88,19 @@ $config = [
'leaveWarn' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING'), 'leaveWarn' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING'),
], ],
]; ];
// Screen CSS lives in an external media stylesheet (media/com_mokosuitebackup/
// css/runbackup.css), not an inline <style> block. It is registered in
// media/joomla.asset.json, but this component's Web Asset Manager styles do not
// emit on the admin document (the asset resolves without error yet no <link> is
// produced — the same is true of com_mokosuitebackup.admin), so we attach the
// stylesheet directly to the document, which reliably renders via <jdoc:include
// type="styles" />.
$this->getDocument()->getWebAssetManager()->useStyle('com_mokosuitebackup.runbackup');
$this->getDocument()->addStyleSheet(Uri::root(true) . '/media/com_mokosuitebackup/css/runbackup.css');
?> ?>
<style>
/* Colours are pulled from the Atum admin template's own CSS custom
properties so the screen matches whatever theme the site runs, with
Bootstrap and then a literal as progressive fallbacks. */
.msb-runbackup {
--msb-accent: var(--template-link-color, var(--bs-link-color, #2a69b8));
--msb-accent2: var(--template-special-color, var(--msb-accent));
--msb-success: var(--template-success-color, var(--bs-success, #2f7d32));
--msb-danger: var(--template-danger-color, var(--bs-danger, #c52128));
--msb-track: var(--template-bg-light, var(--bs-gray-200, #edf0f5));
padding: 0 1rem;
}
/* overflow:hidden clips the full-bleed bar to the card's rounded corners. */
.msb-runbackup .card { overflow: hidden; }
.msb-rb-progress {
height: 2.5rem;
background: var(--msb-track);
overflow: hidden;
}
.msb-rb-bar {
height: 100%;
width: 0;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-weight: 600;
font-size: .95rem;
white-space: nowrap;
background-color: var(--msb-accent);
background-image: linear-gradient(90deg, var(--msb-accent), var(--msb-accent2));
transition: width .35s ease;
}
.msb-rb-bar.is-animated::after {
content: "";
position: absolute;
inset: 0;
background-image: linear-gradient(45deg, rgba(255,255,255,.18) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.18) 50%, rgba(255,255,255,.18) 75%, transparent 75%, transparent);
background-size: 1rem 1rem;
animation: msb-rb-stripes 1s linear infinite;
}
.msb-rb-bar { position: relative; }
.msb-rb-bar.is-success { background-image: none; background-color: var(--msb-success); }
.msb-rb-bar.is-danger { background-image: none; background-color: var(--msb-danger); }
@keyframes msb-rb-stripes { from { background-position: 1rem 0; } to { background-position: 0 0; } }
</style>
<div class="msb-runbackup"> <div class="msb-runbackup">
<div class="card shadow-sm"> <div class="card shadow-sm bg-body-secondary">
<div class="card-body p-4 pb-3"> <div class="card-body p-4 pb-3">
<h1 class="h4 mb-1" id="msb-rb-title"> <h1 class="h4 mb-1" id="msb-rb-title">
<span class="icon-archive" aria-hidden="true"></span> <span class="icon-archive" aria-hidden="true"></span>
@@ -164,7 +129,7 @@ $config = [
<!-- Full-bleed progress bar, edge-to-edge along the bottom of the card. --> <!-- Full-bleed progress bar, edge-to-edge along the bottom of the card. -->
<div class="msb-rb-progress" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"> <div class="msb-rb-progress" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
<div id="msb-rb-bar" class="msb-rb-bar is-animated" style="width:0;">0%</div> <div id="msb-rb-bar" class="msb-rb-bar is-animated">0%</div>
</div> </div>
</div> </div>
</div> </div>
@@ -227,10 +192,11 @@ $config = [
setBar(100, false); setBar(100, false);
el.title.textContent = L.complete || 'Backup complete'; el.title.textContent = L.complete || 'Backup complete';
/* Arm the pre-action throttles so a following server-side onExtension /* Set the one-shot skip flag for THIS action so the following
Before(Update|Uninstall) backup is skipped (no duplicate backup). server-side onExtensionBefore(Update|Uninstall) backup is skipped once
(no duplicate backup) without suppressing later, distinct actions.
Awaited so it lands before we hand back to the update/uninstall. */ Awaited so it lands before we hand back to the update/uninstall. */
try { await post({ task: 'ajax.preupdateAck' }); } catch (e) {} try { await post({ task: 'ajax.preupdateAck', action: CFG.action || '' }); } catch (e) {}
if (CFG.returnUrl) { if (CFG.returnUrl) {
/* Pre-update / pre-uninstall flow: continue the original action. /* Pre-update / pre-uninstall flow: continue the original action.
@@ -8,7 +8,7 @@
--> -->
<extension type="module" client="administrator" method="upgrade"> <extension type="module" client="administrator" method="upgrade">
<name>Module - MokoSuiteBackup - cPanel</name> <name>Module - MokoSuiteBackup - cPanel</name>
<version>02.59.00</version> <version>02.61.00</version>
<creationDate>2026-06-23</creationDate> <creationDate>2026-06-23</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
--> -->
<extension type="plugin" group="actionlog" method="upgrade"> <extension type="plugin" group="actionlog" method="upgrade">
<name>Action Log - MokoSuiteBackup</name> <name>Action Log - MokoSuiteBackup</name>
<version>02.59.00</version> <version>02.61.00</version>
<creationDate>2026-06-04</creationDate> <creationDate>2026-06-04</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
--> -->
<extension type="plugin" group="console" method="upgrade"> <extension type="plugin" group="console" method="upgrade">
<name>Console - MokoSuiteBackup</name> <name>Console - MokoSuiteBackup</name>
<version>02.59.00</version> <version>02.61.00</version>
<creationDate>2026-06-04</creationDate> <creationDate>2026-06-04</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
--> -->
<extension type="plugin" group="content" method="upgrade"> <extension type="plugin" group="content" method="upgrade">
<name>Content - MokoSuiteBackup</name> <name>Content - MokoSuiteBackup</name>
<version>02.59.00</version> <version>02.61.00</version>
<creationDate>2026-06-04</creationDate> <creationDate>2026-06-04</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="quickicon" method="upgrade"> <extension type="plugin" group="quickicon" method="upgrade">
<name>Quick Icon - MokoSuiteBackup</name> <name>Quick Icon - MokoSuiteBackup</name>
<version>02.59.00</version> <version>02.61.00</version>
<creationDate>2026-06-02</creationDate> <creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -73,6 +73,10 @@
var ret = window.location.href; var ret = window.location.href;
ret += (ret.indexOf('?') === -1 ? '?' : '&') + 'msb_resume=1'; ret += (ret.indexOf('?') === -1 ? '?' : '&') + 'msb_resume=1';
/* Tell the backup screen which action it is fronting so its completion
ack sets the matching one-shot skip flag (and not the other). */
var action = (task === 'update.update') ? 'update' : 'uninstall';
/* Build the redirect BEFORE we swallow the click. btoa() throws on /* Build the redirect BEFORE we swallow the click. btoa() throws on
non-Latin1 input (e.g. a UTF-8 filter value in the query string), so non-Latin1 input (e.g. a UTF-8 filter value in the query string), so
encode as UTF-8 first; if it still fails, bail WITHOUT preventing the encode as UTF-8 first; if it still fails, bail WITHOUT preventing the
@@ -81,6 +85,7 @@
try { try {
target = cfg.runbackupUrl target = cfg.runbackupUrl
+ '&profile_id=' + encodeURIComponent(cfg.profileId) + '&profile_id=' + encodeURIComponent(cfg.profileId)
+ '&msb_action=' + action
+ '&returnurl=' + encodeURIComponent(btoa(unescape(encodeURIComponent(ret)))); + '&returnurl=' + encodeURIComponent(btoa(unescape(encodeURIComponent(ret))));
} catch (x) { } catch (x) {
return; return;
@@ -7,7 +7,7 @@
--> -->
<extension type="plugin" group="system" method="upgrade"> <extension type="plugin" group="system" method="upgrade">
<name>System - MokoSuiteBackup</name> <name>System - MokoSuiteBackup</name>
<version>02.59.00</version> <version>02.61.00</version>
<creationDate>2026-06-02</creationDate> <creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -28,6 +28,12 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface
{ {
protected $autoloadLanguage = true; protected $autoloadLanguage = true;
/** @var array<string,bool> Per-request guard: at most one pre-action backup per request (dedupes a batch update). */
private array $preactionRan = [];
/** @var array<string,bool> Per-request cache of the one-shot "client already backed up" skip flag. */
private array $preactionSkip = [];
public static function getSubscribedEvents(): array public static function getSubscribedEvents(): array
{ {
return [ return [
@@ -223,23 +229,20 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface
return; return;
} }
// Already backed up this session (throttle window)? Then the JS should
// let the action proceed without a fresh backup.
$session = Factory::getSession();
$throttleK = $view === 'update'
? 'mokosuitebackup.preaction_backup_before_update'
: 'mokosuitebackup.preaction_backup_before_uninstall';
$lastRun = (int) $session->get($throttleK, 0);
$recent = $lastRun > 0 && (time() - $lastRun) < 600;
$doc->getWebAssetManager()->useScript('core'); $doc->getWebAssetManager()->useScript('core');
/* No time-based "recently backed up" suppression here: each distinct
Update/Uninstall click should get its own full-screen backup. The
re-fired click after a backup is guarded client-side by
window.__msbResuming, and the synchronous server-side fallback is
deduped per-request + by a one-shot skip flag (see runPreActionBackup),
so there is no double backup without blocking distinct actions. */
$doc->addScriptOptions('plg_system_mokosuitebackup.installer', [ $doc->addScriptOptions('plg_system_mokosuitebackup.installer', [
'runbackupUrl' => Route::_('index.php?option=com_mokosuitebackup&view=runbackup&tmpl=component&autostart=1', false), 'runbackupUrl' => Route::_('index.php?option=com_mokosuitebackup&view=runbackup&tmpl=component&autostart=1', false),
'profileId' => (int) $params->get('default_profile', 1), 'profileId' => (int) $params->get('default_profile', 1),
'backupBeforeUpdate' => $view === 'update' && (bool) $beforeUpdate, 'backupBeforeUpdate' => $view === 'update' && (bool) $beforeUpdate,
'backupBeforeUninstall' => $view === 'manage' && (bool) $beforeUninst, 'backupBeforeUninstall' => $view === 'manage' && (bool) $beforeUninst,
'recentBackup' => $recent, 'recentBackup' => false,
]); ]);
$doc->addScript(Uri::root(true) . '/media/plg_system_mokosuitebackup/js/installer-backup.js', [], ['defer' => true]); $doc->addScript(Uri::root(true) . '/media/plg_system_mokosuitebackup/js/installer-backup.js', [], ['defer' => true]);
@@ -313,16 +316,20 @@ JS;
$session = Factory::getSession(); $session = Factory::getSession();
// Returned from the backup screen: arm the throttle so the synchronous // Returned from the backup screen: mark this core update backed up so we
// onExtensionBeforeUpdate backup doesn't run again, then let it proceed. // don't redirect to the backup screen again on a page revisit, then let
// it proceed. Uses a DEDICATED core-update key — a core update does not
// fire onExtensionBefore(Update|Uninstall), so it must not touch the
// extension pre-action flags (which would suppress extension backups).
if ((int) $input->getInt('is_backed_up', 0) === 1) { if ((int) $input->getInt('is_backed_up', 0) === 1) {
$session->set('mokosuitebackup.preaction_backup_before_update', time()); $session->set('mokosuitebackup.core_update_backed_up', time());
return; return;
} }
// Backed up recently already — let the update proceed. // Backed up this core update recently already — let the update proceed
$lastRun = (int) $session->get('mokosuitebackup.preaction_backup_before_update', 0); // without redirecting to the backup screen again.
$lastRun = (int) $session->get('mokosuitebackup.core_update_backed_up', 0);
if ($lastRun > 0 && (time() - $lastRun) < 600) { if ($lastRun > 0 && (time() - $lastRun) < 600) {
return; return;
@@ -525,17 +532,37 @@ JS;
return; return;
} }
// Throttle: only run once per 10 minutes to prevent duplicate /* One backup per Update action — NOT a 10-minute time throttle. The old
// backups when multiple extensions are updated in a batch 600s window suppressed every extension's pre-action backup for 10
$session = Factory::getSession(); minutes after any single one ran (e.g. after updating this very
$sessionKey = 'mokosuitebackup.preaction_' . $paramName; extension), so distinct updates silently went un-backed-up. Instead:
$lastRun = $session->get($sessionKey, 0);
if (time() - $lastRun < 600) { (a) dedupe within a single request — a batch update fires this handler
once per extension, all in the same request, so back up only once;
(b) honour a one-shot session skip flag set by ajax.preupdateAck when
the client-side full-screen backup already ran and is re-firing the
real update/uninstall — consumed once, so the NEXT distinct action
backs up again. */
if (!empty($this->preactionRan[$paramName])) {
return; return;
} }
$session->set($sessionKey, time()); $session = Factory::getSession();
$skipKey = 'mokosuitebackup.skip_preaction_' . $paramName;
if (!array_key_exists($paramName, $this->preactionSkip)) {
$this->preactionSkip[$paramName] = (bool) $session->get($skipKey, false);
$session->set($skipKey, false);
}
if ($this->preactionSkip[$paramName]) {
// Client already backed up; skip this whole re-fired (possibly batch) request.
$this->preactionRan[$paramName] = true;
return;
}
$this->preactionRan[$paramName] = true;
$profileId = (int) $params->get('default_profile', 1); $profileId = (int) $params->get('default_profile', 1);
@@ -7,7 +7,7 @@
--> -->
<extension type="plugin" group="task" method="upgrade"> <extension type="plugin" group="task" method="upgrade">
<name>Task - MokoSuiteBackup</name> <name>Task - MokoSuiteBackup</name>
<version>02.59.00</version> <version>02.61.00</version>
<creationDate>2026-06-02</creationDate> <creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
--> -->
<extension type="plugin" group="webservices" method="upgrade"> <extension type="plugin" group="webservices" method="upgrade">
<name>Web Services - MokoSuiteBackup</name> <name>Web Services - MokoSuiteBackup</name>
<version>02.59.00</version> <version>02.61.00</version>
<creationDate>2026-06-02</creationDate> <creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
+1 -1
View File
@@ -8,7 +8,7 @@
<extension type="package" method="upgrade"> <extension type="package" method="upgrade">
<name>Package - MokoSuiteBackup</name> <name>Package - MokoSuiteBackup</name>
<packagename>mokosuitebackup</packagename> <packagename>mokosuitebackup</packagename>
<version>02.59.00</version> <version>02.61.00</version>
<creationDate>2026-06-02</creationDate> <creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>