Compare commits

..

75 Commits

Author SHA1 Message Date
mokogitea-actions[bot] cbc8627fb7 chore(version): pre-release bump to 02.62.05-dev [skip ci] 2026-07-13 09:50:29 +00:00
gitea-actions[bot] f1dfc18f3d chore(version): auto-bump patch 02.62.04-dev [skip ci] 2026-07-13 09:50:15 +00:00
mokogitea-actions[bot] 20fcd1c39c Merge remote-tracking branch 'origin/main' into dev
Generic: Project CI / Tests (pull_request) Failing after 6s
Generic: Project CI / Lint & Validate (pull_request) Successful in 9s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 4s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 6s
Universal: PR Check / Wiki Update Reminder (pull_request) Successful in 2s
Universal: PR Check / Require Docs Update (pull_request) Failing after 5s
Universal: PR Check / Secret Scan (pull_request) Successful in 5s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 7s
Universal: PR Check / Validate PR (pull_request) Failing after 18s
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
Universal: Auto Version Bump / Version Bump (push) Successful in 9s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 22s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 25s
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
# Conflicts:
#	.mokogitea/workflows/issue-branch.yml
2026-07-13 04:39:03 -05:00
mokogitea-actions[bot] 3fb82bc03c chore(version): pre-release bump to 02.62.03-dev [skip ci] 2026-07-13 09:37:20 +00:00
mokogitea-actions[bot] 499436a8d0 chore(version): pre-release bump to 02.62.02-dev [skip ci] 2026-07-13 09:36:51 +00:00
jmiller a004b9c0d9 chore(sync): CI notify retry + JEXEC whole-file scan from Template-Generic
Universal: Auto Version Bump / Version Bump (push) Successful in 8s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 24s
2026-07-13 09:31:49 +00:00
jmiller 5b1cf9ea8e chore(sync): CI notify retry + JEXEC whole-file scan from Template-Generic
Universal: Auto Version Bump / Version Bump (push) Successful in 7s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 19s
2026-07-13 09:31:48 +00:00
jmiller 86fe470158 chore: sync pr-check.yml from Template-Generic [skip ci] 2026-07-13 09:28:49 +00:00
jmiller a6e196ffd0 chore: sync notify.yml from Template-Generic [skip ci] 2026-07-13 09:28:48 +00:00
jmiller d43b9405c0 chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-07-13 09:28:47 +00:00
mokogitea-actions[bot] 8506573898 chore(version): pre-release bump to 02.62.01-dev [skip ci] 2026-07-13 08:47:36 +00:00
jmiller 6ad5046f82 Merge pull request 'chore(sync): cascade main -> dev' (#264) from main 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 08:47:20 +00:00
gitea-actions[bot] 20a760097b chore(release): build 02.62.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 08:47:15 +00:00
jmiller b378794a05 Merge pull request 'Release: dev → main (auto-continue checkbox + PHP 8.3)' (#263) from dev into main
Sync Workflows to Repos / sync (push) Has been skipped
Cascade Main -> Dev / Cascade main -> dev (push) Successful in 2s
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
Generic: Project CI / Tests (pull_request) Failing after 6s
Universal: PR Check / Secret Scan (pull_request) Successful in 5s
Generic: Project CI / Lint & Validate (pull_request) Successful in 10s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 8s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
2026-07-13 08:46:59 +00:00
mokogitea-actions[bot] dc23f4dd5e chore(version): pre-release bump to 02.61.06-dev [skip ci]
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
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 23s
2026-07-13 01:52:14 +00:00
mokogitea-actions[bot] 9a5f4dbbb2 chore(version): pre-release bump to 02.61.05-dev [skip ci] 2026-07-13 01:51:29 +00:00
mokogitea-actions[bot] 45066237b0 chore(version): pre-release bump to 02.61.04-dev [skip ci] 2026-07-13 01:51:05 +00:00
jmiller 2296c2f754 Merge pull request 'fix: require PHP 8.3 (Joomla 6)' (#262) from fix/php83-v2 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
2026-07-13 01:47:12 +00:00
jmiller 7416c30925 @
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 4s
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
Generic: Project CI / Tests (pull_request) Failing after 5s
Universal: PR Check / Secret Scan (pull_request) Successful in 5s
Generic: Project CI / Lint & Validate (pull_request) Successful in 8s
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 / Validate PR (pull_request) Failing after 7s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 9s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
docs: update CLAUDE.md MVC/DI notes from Joomla 4/5 to Joomla 6

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
@
2026-07-12 20:46:35 -05:00
jmiller 186a75681a @
fix: require PHP 8.3 (Joomla 6) across code, composer, docs

Joomla 6 requires PHP 8.3+, and the MokoGitea repo metadata php_minimum
is 8.3, but the extension still enforced/declared 8.1:
- DashboardModel health check: PHP_VERSION >= 8.1.0 -> 8.3.0
- MokoRestore preflight check: 8.1 -> 8.3 (hint now "Joomla 6 requires PHP 8.3+")
- composer.json require php: >=8.1 -> >=8.3
- .mokogitea/CLAUDE.md: PHP 8.1+ -> 8.3+ (both places)

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
@
2026-07-12 20:46:35 -05:00
jmiller 0a60436f3d Merge pull request 'feat(runbackup): opt-in auto-continue + manual continue' (#261) from feat/runbackup-autocontinue 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
2026-07-13 01:44:55 +00:00
jmiller a2362d5400 @
Generic: Project CI / Tests (pull_request) Failing after 4s
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
Generic: Project CI / Lint & Validate (pull_request) Successful in 8s
Universal: PR Check / Secret Scan (pull_request) Successful in 5s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 7s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
feat(runbackup): opt-in auto-continue + manual continue on pre-update screen

The pre-update/uninstall full-screen backup now shows an "Automatically
continue the update when the backup finishes" checkbox (ticked by
default, so the seamless flow is unchanged). If unticked, the screen
stops on completion and offers a link to the new backup record plus a
"Continue the update" button, so the user can hand back to Joomla
manually — a reliable fallback when auto-continue does not fire.

Also drop the hard-to-read `small` text class from the progress screen.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
@
2026-07-12 20:44:10 -05:00
mokogitea-actions[bot] 6f1d270818 chore(version): pre-release bump to 02.61.01-dev [skip ci] 2026-07-13 01:02:13 +00:00
jmiller 7aa1f2eb43 Merge pull request 'chore(sync): cascade main -> dev' (#259) from main 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 23s
2026-07-13 01:01:57 +00:00
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
gitea-actions[bot] 9a02b7d903 chore(release): build 02.59.00 [skip ci] 2026-07-12 22:19:05 +00:00
jmiller d4fca16a13 Merge pull request 'Release: dev → main (02.58.x)' (#248) 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 22:18:48 +00:00
jmiller 90f08207ea @
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 22s
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
chore: back-merge main into dev (absorb workflow template syncs)

Reconciles the 5 [skip ci] Template-Generic workflow syncs that landed on
main with dev ahead of the release. Only conflict was the VERSION comment
stamp in issue-branch.yml (kept dev's 02.58.37 over the template
placeholder 01.00.00); no functional workflow changes. Clears the
dev -> main merge conflict for the release PR.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
@
2026-07-12 17:18:28 -05:00
mokogitea-actions[bot] 3ef9bdaa48 chore(version): pre-release bump to 02.58.37-dev [skip ci] 2026-07-12 22:14:58 +00:00
jmiller 7a7e804647 Merge pull request 'fix(review): open-redirect + btoa + honest duplicate-mode txn' (#249) from fix/prerelease-review 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
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
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
Generic: Project CI / Tests (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 22:14:43 +00:00
mokogitea-actions[bot] 498f62e975 chore(version): pre-release bump to 02.58.36-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:14:30 +00:00
jmiller f9c1c6b186 @
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 16s
Generic: Project CI / Lint & Validate (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: Project CI / Tests (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
fix(review): open-redirect, btoa UTF-8 safety, honest duplicate-mode txn

Pre-release review findings on dev -> main:

- Return-URL open-redirect (MED): a base64 returnurl like "/\evil.com"
  passed the root-relative allow-list (raw[1] is "\", not "/"), and a
  browser normalises leading "/\" to "//", yielding a protocol-relative
  redirect off-site. Reject any backslash or control char in the decoded
  return URL outright — legitimate Joomla admin URLs never contain them.

- installer-backup.js btoa() (LOW): btoa() throws on non-Latin1 input
  (e.g. a UTF-8 filter value in the list query string), and the throw was
  uncaught after preventDefault(), leaving the toolbar button dead. Build
  the redirect (UTF-8-safe base64) BEFORE swallowing the click; if it
  still fails, bail without preventing so Joomla proceeds normally.

- Snapshot duplicate-mode transaction (HIGH): restore() wrapped duplicate
  mode in transactionStart()/Rollback(), but duplicate mode writes via
  Joomla Table API whose Nested tables (categories/tags) issue LOCK
  TABLES, which implicitly COMMITS the open transaction in MySQL — so the
  rollback promise was illusory and a mid-inject failure left a slave
  half-updated. Run only the raw-DB modes (replace/create/merge) inside
  the transaction; duplicate mode runs outside it, matching its existing
  per-item defensive design (bad item skipped + logged, never fatal).

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
@
2026-07-12 17:14:10 -05:00
mokogitea-actions[bot] d84ccfca31 chore(version): pre-release bump to 02.58.35-dev [skip ci] 2026-07-12 21:43:00 +00:00
jmiller 8a428c6cbb Merge pull request 'style(runbackup): full-width backup card' (#247) from fix/runbackup-fw2 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 18s
2026-07-12 21:42:36 +00:00
jmiller b894c95f6b chore: sync pre-release.yml from Template-Generic [skip ci] 2026-07-06 17:04:53 +00:00
jmiller e3bd4a36ff chore: sync pr-check.yml from Template-Generic [skip ci] 2026-07-06 03:50:41 +00:00
jmiller da9265e329 chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-07-06 03:50:41 +00:00
jmiller b9c0ddf2b9 chore: sync ci-generic.yml from Template-Generic [skip ci] 2026-07-06 03:50:40 +00:00
jmiller 41e8bffd26 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-07-06 03:50:39 +00:00
66 changed files with 376 additions and 183 deletions
+4 -4
View File
@@ -7,7 +7,7 @@ Full-site backup and restore for Joomla — database, files, and configuration.
| Field | Value | | Field | Value |
|---|---| |---|---|
| **Package** | `pkg_mokosuitebackup` | | **Package** | `pkg_mokosuitebackup` |
| **Language** | PHP 8.1+ | | **Language** | PHP 8.3+ |
| **Branch** | develop on `dev`, merge to `main` (protected) | | **Branch** | develop on `dev`, merge to `main` (protected) |
| **Wiki** | [MokoSuiteBackup Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteBackup/wiki) | | **Wiki** | [MokoSuiteBackup Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteBackup/wiki) |
@@ -29,7 +29,7 @@ Joomla **package** with four sub-extensions:
### com_mokosuitebackup (Component) ### com_mokosuitebackup (Component)
- Admin backend for managing backup profiles and records - Admin backend for managing backup profiles and records
- Backup engine: `Engine/BackupEngine`, `Engine/DatabaseDumper`, `Engine/FileScanner`, `Engine/Archiver` - Backup engine: `Engine/BackupEngine`, `Engine/DatabaseDumper`, `Engine/FileScanner`, `Engine/Archiver`
- Joomla 4/5 MVC: Controllers, Models, Views, Tables - Joomla 6 MVC: Controllers, Models, Views, Tables
- Namespace: `Joomla\Component\MokoSuiteBackup\Administrator` - Namespace: `Joomla\Component\MokoSuiteBackup\Administrator`
- DB tables: `#__mokosuitebackup_profiles`, `#__mokosuitebackup_records` - DB tables: `#__mokosuitebackup_profiles`, `#__mokosuitebackup_records`
- CLI: `cli/mokosuitebackup.php` for cron-based backups - CLI: `cli/mokosuitebackup.php` for cron-based backups
@@ -64,8 +64,8 @@ Joomla **package** with four sub-extensions:
## Coding Standards ## Coding Standards
- PHP 8.1+ minimum - PHP 8.3+ minimum (Joomla 6)
- Joomla 4/5 DI container pattern: `services/provider.php` → Extension class - Joomla 6 DI container pattern: `services/provider.php` → Extension class
- Legacy stub `.php` file required for plugin loader but empty - Legacy stub `.php` file required for plugin loader but empty
- `SubscriberInterface` for event subscription (not `on*` method naming) - `SubscriberInterface` for event subscription (not `on*` method naming)
- `bind() → check() → store()` for Table operations (not `save()`) - `bind() → check() → store()` for Table operations (not `save()`)
+3 -6
View File
@@ -380,12 +380,9 @@ jobs:
import sys import sys
version, date = sys.argv[1], sys.argv[2] version, date = sys.argv[1], sys.argv[2]
content = open('CHANGELOG.md').read() content = open('CHANGELOG.md').read()
marker = f'## [{version}]' old = '## [Unreleased]'
# Idempotent: only promote when this version header isn't already present, new = f'## [Unreleased]\n\n## [{version}] --- {date}'
# otherwise re-runs (or same-version builds) create empty duplicate headers. content = content if ('## [' + version + ']') in content else content.replace(old, new, 1)
if marker not in content:
new = f'## [Unreleased]\n\n{marker} --- {date}'
content = content.replace('## [Unreleased]', new, 1)
open('CHANGELOG.md', 'w').write(content) open('CHANGELOG.md', 'w').write(content)
" "$VERSION" "$DATE" " "$VERSION" "$DATE"
git add CHANGELOG.md git add CHANGELOG.md
+5 -4
View File
@@ -131,10 +131,11 @@ jobs:
test: test:
name: Tests name: Tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: lint # Independent job (no `needs: lint`): the Gitea Actions scheduler does not
# Run only when lint succeeded; always() forces evaluation so a skipped # offer the dependent 2nd job of a needs-chain to runners, so it stalls in
# lint (e.g. template repos) skips this job cleanly instead of hanging. # "waiting" and is reaped by ABANDONED_JOB_TIMEOUT. Guard template repos
if: ${{ always() && needs.lint.result == 'success' }} # directly (same condition lint uses) instead of gating on lint's result.
if: ${{ !startsWith(github.event.repository.name, 'Template-') }}
steps: steps:
- name: Checkout - name: Checkout
+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.58.34 # VERSION: 02.62.05
# 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"
+4 -4
View File
@@ -46,13 +46,13 @@ jobs:
WORKFLOW="${{ github.event.workflow_run.name }}" WORKFLOW="${{ github.event.workflow_run.name }}"
URL="${{ github.event.workflow_run.html_url }}" URL="${{ github.event.workflow_run.html_url }}"
curl -sS \ curl -sS --retry 3 --retry-connrefused --retry-delay 2 --max-time 20 \
-H "Title: ${REPO} released" \ -H "Title: ${REPO} released" \
-H "Tags: white_check_mark,package" \ -H "Tags: white_check_mark,package" \
-H "Priority: default" \ -H "Priority: default" \
-H "Click: ${URL}" \ -H "Click: ${URL}" \
-d "${WORKFLOW} completed successfully." \ -d "${WORKFLOW} completed successfully." \
"${NTFY_URL}/${NTFY_TOPIC}" "${NTFY_URL}/${NTFY_TOPIC}" || echo "::warning::ntfy notification could not be delivered (non-fatal)"
- name: Notify on failure - name: Notify on failure
if: github.event.workflow_run.conclusion == 'failure' if: github.event.workflow_run.conclusion == 'failure'
@@ -61,10 +61,10 @@ jobs:
WORKFLOW="${{ github.event.workflow_run.name }}" WORKFLOW="${{ github.event.workflow_run.name }}"
URL="${{ github.event.workflow_run.html_url }}" URL="${{ github.event.workflow_run.html_url }}"
curl -sS \ curl -sS --retry 3 --retry-connrefused --retry-delay 2 --max-time 20 \
-H "Title: ${REPO} workflow failed" \ -H "Title: ${REPO} workflow failed" \
-H "Tags: x,warning" \ -H "Tags: x,warning" \
-H "Priority: high" \ -H "Priority: high" \
-H "Click: ${URL}" \ -H "Click: ${URL}" \
-d "${WORKFLOW} failed. Check the run for details." \ -d "${WORKFLOW} failed. Check the run for details." \
"${NTFY_URL}/${NTFY_TOPIC}" "${NTFY_URL}/${NTFY_TOPIC}" || echo "::warning::ntfy notification could not be delivered (non-fatal)"
+4 -3
View File
@@ -210,7 +210,7 @@ jobs:
- name: Check for merge conflict markers - name: Check for merge conflict markers
run: | run: |
CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true) CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --exclude-dir='.git' --exclude-dir='.mokogitea' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true)
if [ -n "$CONFLICTS" ]; then if [ -n "$CONFLICTS" ]; then
echo "::error::Merge conflict markers found in source files" echo "::error::Merge conflict markers found in source files"
echo "## Conflict Markers Found" >> $GITHUB_STEP_SUMMARY echo "## Conflict Markers Found" >> $GITHUB_STEP_SUMMARY
@@ -258,8 +258,9 @@ jobs:
while IFS= read -r -d '' file; do while IFS= read -r -d '' file; do
# Skip vendor, node_modules, and index.html stub files # Skip vendor, node_modules, and index.html stub files
case "$file" in ./vendor/*|./node_modules/*) continue ;; esac case "$file" in ./vendor/*|./node_modules/*) continue ;; esac
# Check first 10 lines for JEXEC or JPATH guard # Scan the whole file for the JEXEC/JPATH guard: it is placed after
if ! head -20 "$file" | grep -qE "defined\s*\(\s*['\"](_JEXEC|JPATH_BASE|\\\\JPATH_PLATFORM)['\"]"; then # the SPDX/file-header docblock, which commonly runs past 20 lines.
if ! grep -qE "defined\s*\(\s*['\"](_JEXEC|JPATH_BASE|\\\\JPATH_PLATFORM)['\"]" "$file"; then
echo "::error file=${file}::Missing JEXEC guard: ${file}" echo "::error file=${file}::Missing JEXEC guard: ${file}"
ERRORS=$((ERRORS + 1)) ERRORS=$((ERRORS + 1))
fi fi
+8 -2
View File
@@ -7,7 +7,7 @@
# INGROUP: mokocli.Release # INGROUP: mokocli.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic # REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogitea/workflows/pre-release.yml # PATH: /.mokogitea/workflows/pre-release.yml
# VERSION: 05.02.00 # VERSION: 05.02.01
# BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches # BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches
name: "Universal: Pre-Release" name: "Universal: Pre-Release"
@@ -162,7 +162,13 @@ jobs:
git add -A git add -A
git diff --cached --quiet || { git diff --cached --quiet || {
git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]" git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]"
git push origin HEAD 2>&1 # Push the bump commit, but do NOT fail the release if the target branch
# is protected and the release identity is not on the push allowlist.
# The build proceeds from the in-tree bumped version regardless; if the
# push is rejected, the next run simply re-bumps from the same base.
if ! git push origin HEAD 2>&1; then
echo "::warning::Version-bump commit could not be pushed (protected branch?). Building from in-tree version ${VERSION} anyway."
fi
} }
# Auto-detect element via manifest_element.php # Auto-detect element via manifest_element.php
+11 -35
View File
@@ -1,14 +1,23 @@
# Changelog # Changelog
## [Unreleased] ## [Unreleased]
## [02.62.00] --- 2026-07-13
## [02.61.00] --- 2026-07-13
## [02.60.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)
@@ -46,36 +55,3 @@
### Fixed ### Fixed
- Admin menu / duplicate-component fallout: (a) the orphaned `com_component-mokosuitebackup` is 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 constant `COM_MOKOJOOMBACKUP_SHORT`. (#213 fallout) - Admin menu / duplicate-component fallout: (a) the orphaned `com_component-mokosuitebackup` is 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 constant `COM_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`) used `PREPARE`/`EXECUTE`/`DEALLOCATE`, which Joomla's installer rejects; the drop now runs in the package installer script via an INFORMATION_SCHEMA-gated plain `ALTER` (portable across MariaDB and MySQL 8). (#213 update path) - 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`) used `PREPARE`/`EXECUTE`/`DEALLOCATE`, which Joomla's installer rejects; the drop now runs in the package installer script via an INFORMATION_SCHEMA-gated plain `ALTER` (portable across MariaDB and MySQL 8). (#213 update path)
## [02.57.00] --- 2026-07-05
### Added
- `BackupRunner` service — 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 to `BackupEngine`; 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 `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)
## [02.56.07] --- 2026-07-05
### Security
- Mask the FTP password in `AjaxController::maskSecrets()`/`mergeExistingSecrets()` — stored FTP remotes no longer leak their password via `listRemotes`. (#169)
- Stop the API single-item view (`Backups/JsonapiView`) leaking the server `absolute_path`. (#187)
- SFTP uploader uses `StrictHostKeyChecking=accept-new` instead of `no`. (#182)
### Fixed
- `DatabaseDumper` writes a real newline after `DROP TABLE` (was a literal `\n`). (#179)
- Profile-picker forms order by `id` instead of the dropped `ordering` column. (#180)
- `uninstall.mysql.sql` now drops the snapshots table. (#181)
- CLI snapshot delete uses the `data_file` column (was non-existent `file_path`). (#184)
- Remove the duplicate pre-update backup (content plugin no longer subscribes to `onExtensionBeforeUpdate`). (#186)
- `DatabaseImporter` collects non-fatal statement errors (`getErrors()`/`hasErrors()`). (#188)
+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.58.34 VERSION: 02.62.05
BRIEF: Security vulnerability reporting and handling policy BRIEF: Security vulnerability reporting and handling policy
--> -->
+1 -1
View File
@@ -12,7 +12,7 @@
} }
], ],
"require": { "require": {
"php": ">=8.1", "php": ">=8.3",
"ext-zip": "*", "ext-zip": "*",
"ext-pdo": "*", "ext-pdo": "*",
"ext-pdo_mysql": "*", "ext-pdo_mysql": "*",
@@ -349,6 +349,8 @@ COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_ANYWAY="Continue without backup"
COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD="Back to dashboard" COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD="Back to dashboard"
COM_MOKOJOOMBACKUP_RUNBACKUP_VIEW_RECORD="View backup record" COM_MOKOJOOMBACKUP_RUNBACKUP_VIEW_RECORD="View backup record"
COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING="A backup is in progress. Leaving now will cancel it." COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING="A backup is in progress. Leaving now will cancel it."
COM_MOKOJOOMBACKUP_RUNBACKUP_AUTOCONTINUE="Automatically continue the update when the backup finishes"
COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_UPDATE="Continue the update"
COM_MOKOJOOMBACKUP_CONFIG_CLEANUP="Cleanup Defaults" COM_MOKOJOOMBACKUP_CONFIG_CLEANUP="Cleanup Defaults"
COM_MOKOJOOMBACKUP_CONFIG_MAX_AGE="Max Backup Age (days)" COM_MOKOJOOMBACKUP_CONFIG_MAX_AGE="Max Backup Age (days)"
@@ -71,6 +71,8 @@ COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_ANYWAY="Continue without backup"
COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD="Back to dashboard" COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD="Back to dashboard"
COM_MOKOJOOMBACKUP_RUNBACKUP_VIEW_RECORD="View backup record" COM_MOKOJOOMBACKUP_RUNBACKUP_VIEW_RECORD="View backup record"
COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING="A backup is in progress. Leaving now will cancel it." COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING="A backup is in progress. Leaving now will cancel it."
COM_MOKOJOOMBACKUP_RUNBACKUP_AUTOCONTINUE="Automatically continue the update when the backup finishes"
COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_UPDATE="Continue the update"
COM_MOKOJOOMBACKUP_CONFIG_CLEANUP="Cleanup Defaults" COM_MOKOJOOMBACKUP_CONFIG_CLEANUP="Cleanup Defaults"
COM_MOKOJOOMBACKUP_CONFIG_MAX_AGE="Max Backup Age (days)" COM_MOKOJOOMBACKUP_CONFIG_MAX_AGE="Max Backup Age (days)"
@@ -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.58.34</version> <version>02.62.05</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 +0,0 @@
/* 02.58.34 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.35 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.36 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.37 — no schema changes */
@@ -0,0 +1 @@
/* 02.59.00 — no schema changes */
@@ -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 */
@@ -0,0 +1 @@
/* 02.61.01 — no schema changes */
@@ -0,0 +1 @@
/* 02.61.04 — no schema changes */
@@ -0,0 +1 @@
/* 02.61.05 — no schema changes */
@@ -0,0 +1 @@
/* 02.61.06 — no schema changes */
@@ -0,0 +1 @@
/* 02.62.00 — no schema changes */
@@ -0,0 +1 @@
/* 02.62.01 — no schema changes */
@@ -0,0 +1 @@
/* 02.62.02 — no schema changes */
@@ -0,0 +1 @@
/* 02.62.03 — no schema changes */
@@ -0,0 +1 @@
/* 02.62.04 — no schema changes */
@@ -0,0 +1 @@
/* 02.62.05 — 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']);
} }
@@ -480,8 +480,8 @@ function actionPreflight(): array
$checks[] = [ $checks[] = [
'label' => 'PHP Version', 'label' => 'PHP Version',
'value' => PHP_VERSION, 'value' => PHP_VERSION,
'ok' => version_compare(PHP_VERSION, '8.1', '>='), 'ok' => version_compare(PHP_VERSION, '8.3', '>='),
'hint' => 'Joomla 4/5 requires PHP 8.1+', 'hint' => 'Joomla 6 requires PHP 8.3+',
]; ];
$checks[] = [ $checks[] = [
@@ -125,12 +125,24 @@ class SnapshotRestoreEngine
$prefix = $db->getPrefix(); $prefix = $db->getPrefix();
$totalRows = 0; $totalRows = 0;
try {
$db->transactionStart();
// Build list of tables to restore based on selected types // Build list of tables to restore based on selected types
$tablesToRestore = $this->getTablesToRestore($restoreTypes); $tablesToRestore = $this->getTablesToRestore($restoreTypes);
/* Duplicate mode inserts brand-new records through Joomla's Table API,
whose Nested tables (categories, tags) issue LOCK TABLES — which
implicitly COMMITS any open transaction in MySQL/InnoDB. Wrapping it
in transactionStart()/Rollback() would be a false atomicity promise:
the first category/tag store would commit and defeat the rollback.
Duplicate mode is therefore best-effort and per-item defensive (a bad
item is skipped and logged, never fatal) rather than all-or-nothing.
The raw-DB modes (replace/create/merge) remain fully transactional. */
$useTransaction = ($mode !== 'duplicate');
try {
if ($useTransaction) {
$db->transactionStart();
}
if ($mode === 'duplicate') { if ($mode === 'duplicate') {
$totalRows = $this->restoreDuplicate($data, $restoreTypes); $totalRows = $this->restoreDuplicate($data, $restoreTypes);
} else { } else {
@@ -156,7 +168,9 @@ class SnapshotRestoreEngine
} }
} }
if ($useTransaction) {
$db->transactionCommit(); $db->transactionCommit();
}
$this->log('Restore complete: ' . $totalRows . ' total rows'); $this->log('Restore complete: ' . $totalRows . ' total rows');
@@ -188,12 +202,16 @@ class SnapshotRestoreEngine
'log' => implode("\n", $this->log), 'log' => implode("\n", $this->log),
]; ];
} catch (\Throwable $e) { } catch (\Throwable $e) {
if ($useTransaction) {
try { try {
$db->transactionRollback(); $db->transactionRollback();
$this->log('Transaction rolled back'); $this->log('Transaction rolled back');
} catch (\Exception $rollbackEx) { } catch (\Exception $rollbackEx) {
$this->log('Rollback failed: ' . $rollbackEx->getMessage()); $this->log('Rollback failed: ' . $rollbackEx->getMessage());
} }
} else {
$this->log('Duplicate mode is not transactional; records already inserted are left in place.');
}
$this->log('FATAL: ' . $e->getMessage()); $this->log('FATAL: ' . $e->getMessage());
@@ -104,7 +104,7 @@ class DashboardModel extends BaseDatabaseModel
// PHP version // PHP version
$checks[] = (object) [ $checks[] = (object) [
'label' => 'PHP Version', 'label' => 'PHP Version',
'status' => version_compare(PHP_VERSION, '8.1.0', '>='), 'status' => version_compare(PHP_VERSION, '8.3.0', '>='),
'detail' => PHP_VERSION, 'detail' => PHP_VERSION,
]; ];
@@ -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();
@@ -34,6 +34,14 @@ if ($this->returnUrl !== '') {
$decoded = base64_decode($this->returnUrl, true); $decoded = base64_decode($this->returnUrl, true);
$raw = ($decoded !== false && $decoded !== '') ? $decoded : $this->returnUrl; $raw = ($decoded !== false && $decoded !== '') ? $decoded : $this->returnUrl;
/* Reject any backslash or control char outright: legitimate Joomla admin
return URLs never contain them, and a browser normalises a leading "/\"
to "//", turning a would-be root-relative path into a protocol-relative
open redirect (e.g. "/\evil.com" → "//evil.com"). */
if (preg_match('/[\x00-\x1f\x7f\\\\]/', $raw)) {
$raw = '';
}
$isAbsoluteHttp = (bool) preg_match('#^https?://#i', $raw); $isAbsoluteHttp = (bool) preg_match('#^https?://#i', $raw);
$isRootRelative = isset($raw[0]) && $raw[0] === '/' && (!isset($raw[1]) || $raw[1] !== '/'); $isRootRelative = isset($raw[0]) && $raw[0] === '/' && (!isset($raw[1]) || $raw[1] !== '/');
@@ -63,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,
@@ -77,57 +86,22 @@ $config = [
'dashboard' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD'), 'dashboard' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD'),
'viewRecord' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_VIEW_RECORD'), 'viewRecord' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_VIEW_RECORD'),
'leaveWarn' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING'), 'leaveWarn' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING'),
'continueUpdate' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_UPDATE'),
], ],
]; ];
// 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>
@@ -144,7 +118,17 @@ $config = [
</p> </p>
<div id="msb-rb-phase" class="fw-bold mb-1"></div> <div id="msb-rb-phase" class="fw-bold mb-1"></div>
<div id="msb-rb-status" class="text-muted small mb-0"><?php echo htmlspecialchars(Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_STARTING'), ENT_QUOTES, 'UTF-8'); ?></div> <div id="msb-rb-status" class="mb-0"><?php echo htmlspecialchars(Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_STARTING'), ENT_QUOTES, 'UTF-8'); ?></div>
<?php if ($safeReturnUrl !== '') : ?>
<!-- Pre-update/uninstall flow: opt in to auto-continue, or get a manual "Continue" button on completion. -->
<div class="form-check mt-3" id="msb-rb-autocontinue-wrap">
<input class="form-check-input" type="checkbox" id="msb-rb-autocontinue" checked>
<label class="form-check-label" for="msb-rb-autocontinue">
<?php echo htmlspecialchars(Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_AUTOCONTINUE'), ENT_QUOTES, 'UTF-8'); ?>
</label>
</div>
<?php endif; ?>
<div id="msb-rb-actions" class="d-none mt-3"> <div id="msb-rb-actions" class="d-none mt-3">
<button type="button" id="msb-rb-retry" class="btn btn-primary d-none"></button> <button type="button" id="msb-rb-retry" class="btn btn-primary d-none"></button>
@@ -156,7 +140,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>
@@ -180,7 +164,9 @@ $config = [
retry: document.getElementById('msb-rb-retry'), retry: document.getElementById('msb-rb-retry'),
record: document.getElementById('msb-rb-record'), record: document.getElementById('msb-rb-record'),
continue: document.getElementById('msb-rb-continue'), continue: document.getElementById('msb-rb-continue'),
dashboard: document.getElementById('msb-rb-dashboard') dashboard: document.getElementById('msb-rb-dashboard'),
autocontinue: document.getElementById('msb-rb-autocontinue'),
autocontinueWrap: document.getElementById('msb-rb-autocontinue-wrap')
}; };
function setBar(pct, striped) { function setBar(pct, striped) {
@@ -219,16 +205,37 @@ $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. If "auto-continue" is ticked
No "view record" button here — we're handing back to Joomla. */ (default) hand back to Joomla automatically; otherwise stop here
and offer a link to the new backup record plus a manual
"Continue the update" button. */
if (el.autocontinueWrap) { el.autocontinueWrap.classList.add('d-none'); }
var autoContinue = !el.autocontinue || el.autocontinue.checked;
if (autoContinue) {
setStatus(L.continuing || 'Continuing…'); setStatus(L.continuing || 'Continuing…');
window.setTimeout(function () { window.location.href = CFG.returnUrl; }, 1200); window.setTimeout(function () { window.location.href = CFG.returnUrl; }, 1200);
} else {
setStatus(L.complete || 'Backup complete');
showActions();
if (lastRecordId > 0 && CFG.recordUrl) {
showBtn(el.record, L.viewRecord || 'View backup record',
CFG.recordUrl.replace('__MSBID__', String(lastRecordId)));
}
el.continue.classList.remove('btn-warning');
el.continue.classList.add('btn-success');
showBtn(el.continue, L.continueUpdate || 'Continue the update', CFG.returnUrl);
}
} else { } else {
/* Manual "Backup Now": offer to view the record that was just made. */ /* Manual "Backup Now": offer to view the record that was just made. */
setStatus(L.complete || 'Backup complete'); setStatus(L.complete || 'Backup complete');
@@ -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.58.34</version> <version>02.62.05</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.58.34</version> <version>02.62.05</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.58.34</version> <version>02.62.05</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.58.34</version> <version>02.62.05</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.58.34</version> <version>02.62.05</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>
@@ -70,6 +70,27 @@
return; return;
} }
var ret = window.location.href;
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
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
click so Joomla's own update/uninstall proceeds normally. */
var target;
try {
target = cfg.runbackupUrl
+ '&profile_id=' + encodeURIComponent(cfg.profileId)
+ '&msb_action=' + action
+ '&returnurl=' + encodeURIComponent(btoa(unescape(encodeURIComponent(ret))));
} catch (x) {
return;
}
e.preventDefault(); e.preventDefault();
e.stopImmediatePropagation(); e.stopImmediatePropagation();
@@ -77,12 +98,7 @@
sessionStorage.setItem(STORAGE_KEY, JSON.stringify({ task: task, cids: cids })); sessionStorage.setItem(STORAGE_KEY, JSON.stringify({ task: task, cids: cids }));
} catch (x) {} } catch (x) {}
var ret = window.location.href; window.location.href = target;
ret += (ret.indexOf('?') === -1 ? '?' : '&') + 'msb_resume=1';
window.location.href = cfg.runbackupUrl
+ '&profile_id=' + encodeURIComponent(cfg.profileId)
+ '&returnurl=' + encodeURIComponent(btoa(ret));
}, true); }, true);
/* On return from the backup screen: restore the selection and re-fire. */ /* On return from the backup screen: restore the selection and re-fire. */
@@ -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.58.34</version> <version>02.62.05</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.58.34</version> <version>02.62.05</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.58.34</version> <version>02.62.05</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.58.34</version> <version>02.62.05</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>