Compare commits

...

160 Commits

Author SHA1 Message Date
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 4d476fde5d @
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 18s
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
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
RC Revert / Rename rc/ back to dev/ (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
style(runbackup): drop max-width/margin so the card spans full width

The backup screen was centred in a 720px column; unset max-width and the
auto margins so it fills the available width. Keeps the horizontal padding.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
@
2026-07-12 16:41:47 -05:00
mokogitea-actions[bot] 2c12fdc176 chore(version): pre-release bump to 02.58.32-dev [skip ci] 2026-07-12 21:14:11 +00:00
mokogitea-actions[bot] 39495e5b12 chore(version): pre-release bump to 02.58.31-dev [skip ci] 2026-07-12 21:08:47 +00:00
jmiller 44ed98d3c3 Merge pull request 'fix: reliably intercept extension update/uninstall + polish backup screen' (#245) from fix/extension-intercept-ui 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 21:08:34 +00:00
mokogitea-actions[bot] 9aab873750 chore(version): pre-release bump to 02.58.30-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 21:07:56 +00:00
jmiller 4bc8b47d05 @
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 15s
fix: reliably intercept extension update/uninstall + polish backup screen

The full-screen pre-action backup did not fire for extension
updates/uninstalls because Joomla 6 renders the com_installer toolbar
Update/Uninstall buttons as `<button task="update.update">` web
components, not inline onclick="Joomla.submitbutton(...)". Wrapping
Joomla.submitbutton never caught them.

installer-backup.js now uses a capture-phase document click listener on
any `[task]` element matching update.update / manage.remove(/uninstall),
so it runs before the toolbar web component. It captures the checked
cid[] selection, runs the full-screen backup, and on return restores the
selection, sets the boxchecked list-guard field, and re-fires the real
toolbar button with __msbResuming set so it is not re-intercepted.

Also polishes the runbackup screen: the progress bar is now a full-bleed
strip along the bottom edge of the card (edge-to-edge, clipped to the
rounded corners) instead of a boxed bar crowding the status text, and it
is coloured from the Atum template CSS custom properties
(--template-link-color / --template-special-color / --template-success-color
/ --template-danger-color) with Bootstrap and literal fallbacks.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
@
2026-07-12 16:07:36 -05:00
mokogitea-actions[bot] d6078ef528 chore(version): pre-release bump to 02.58.29-dev [skip ci] 2026-07-12 20:51:25 +00:00
jmiller 3e4bb03165 Merge pull request 'feat: seamless auto-continue on Joomla Update after pre-update backup' (#244) from fix/joomlaupdate-seamless 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 20:50:59 +00:00
jmiller 59607a11cb feat: seamless auto-continue on Joomla Update after pre-update backup
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 20s
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
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
RC Revert / Rename rc/ back to dev/ (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
After the full-screen pre-update backup returns to the Joomla Update
confirm page (layout=update&is_backed_up=1), inject a small script that
ticks Joomla's 'I have taken a backup' checkbox (#joomlaupdate-confirm-
backup) and clicks the Install button (.emptystate-btnadd) so the update
continues automatically — no second manual click. Injected via
onBeforeCompileHead, super-user only, gated on backup_before_update.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-12 15:50:23 -05:00
mokogitea-actions[bot] 1326ab3fc8 chore(version): pre-release bump to 02.58.26-dev [skip ci] 2026-07-12 20:27:45 +00:00
jmiller efc1e19b27 Merge pull request 'fix: pre-update backup fires on Joomla Update (match layout=update)' (#243) from fix/preupdate-layout-update 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 20:27:33 +00:00
jmiller 86381a5319 fix: pre-update backup fires on Joomla Update (match layout=update)
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 3s
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
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
RC Revert / Rename rc/ back to dev/ (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
The 'Install the update' button posts to
option=com_joomlaupdate&layout=update (Joomla's confirm/updating page),
carrying only layout=update -- no view, no task. The redirect matched
view=update / update.install, so it never fired on the Joomla Update page.

Match layout=update (plus view=update and legacy update.install), and
return the browser to the same layout flagged is_backed_up=1.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-12 15:27:08 -05:00
mokogitea-actions[bot] 165e9bb34a chore(version): pre-release bump to 02.58.24-dev [skip ci] 2026-07-10 21:08:16 +00:00
jmiller 3587a05630 Merge pull request 'feat(#237): snapshot transfer + master→slave injection (all conflict modes)' (#241) from feature/237-snapshot-transfer 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 49s
2026-07-10 21:07:31 +00:00
gitea-actions[bot] 50fbe683d9 chore(version): auto-bump patch 02.58.23-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-10 17:00:55 +00:00
jmiller 4e07eabc0f feat(#237): implement duplicate snapshot mode (insert as new, remapped IDs)
Universal: Auto Version Bump / Version Bump (push) Successful in 7s
Finishes the deferred 'duplicate' conflict mode. Inserts snapshot content
as brand-new records with remapped IDs, leaving existing content
untouched — for master->slave 'push as new copies'.

- Uses Joomla's Table API (bootComponent MVCFactory) for Article,
  Category and Module so assets, UCM entries, category/tag nested-sets
  and alias uniqueness are handled by core rather than hand-rolled.
- Remaps category parent_id (parents first), article catid, module menu
  assignments, and custom-field-value item_id; re-features articles.
- Tags are reused/created by title (resolveTagIds) and assigned via the
  article table's newTags so UCM/tag-map stay correct.
- uniqueAlias() avoids alias collisions per scope.
- DEFENSIVE: every item is wrapped in try/catch — a bad item is skipped
  and logged, never fatal — so an untested/partial import degrades
  gracefully instead of corrupting content.

NOTE: still needs a real test pass on a dev site before relying on it.

Refs #237

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-10 12:00:45 -05:00
gitea-actions[bot] 2408476908 chore(version): auto-bump patch 02.58.22-dev [skip ci] 2026-07-10 16:41:28 +00:00
jmiller 49fcd5e63c feat(#237): portable snapshot transfer + master->slave injection API
Universal: Auto Version Bump / Version Bump (push) Successful in 16s
Adds the first increment of snapshot transfer:

- SnapshotPortable engine: package a snapshot into a portable .msbsnap
  (zip of manifest.json + snapshot.json + sha256), and ingest one back
  (from a zip upload or an inline payload) by materialising a local
  snapshot record.
- Admin: per-row 'Download portable snapshot' + an Import modal
  (upload .msbsnap) on the Snapshots page.
- API: POST /snapshot/inject — master pushes a snapshot payload into a
  slave's Web Services API; gated by a new 'Allow Snapshot Injection'
  option. Route added to the webservices plugin.
- Restore engine: new 'create' mode (skip existing, insert only new) and
  mode-name normalisation (overwrite->replace, create, duplicate). The
  'duplicate' (insert-as-new-with-remapped-IDs) mode returns a clear
  'not yet available' — deferred to a dedicated, tested pass.
- Options: 'Snapshot Transfer' fieldset — default conflict mode +
  allow-inject toggle. Language keys added (en-GB fallback).

Reuses the existing mokosuitebackup.snapshot.manage ACL. NOTE: untested
end-to-end (no dev build this session) — verify download/import/inject
on a dev build.

Refs #237

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-10 11:40:51 -05:00
mokogitea-actions[bot] 92d680d333 chore(version): pre-release bump to 02.58.21-dev [skip ci] 2026-07-10 16:14:24 +00:00
jmiller 9e64ffe017 Merge pull request 'feat: full-screen backup for all updates + uninstalls (Joomla 6 core + extensions)' (#240) from fix/preupdate-j6-viewupdate 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-10 16:14:09 +00:00
mokogitea-actions[bot] 4e5bf99f61 chore(version): pre-release bump to 02.58.20-dev [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
2026-07-10 15:54:02 +00:00
jmiller 7a0636606e feat: full-screen backup for extension update/uninstall + view-record button
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 18s
Extend the full-screen backup screen beyond core Joomla updates:

- Extension updates/uninstalls (com_installer): update.update and
  manage.remove are POST + checkToken with a checked cid[] list, so a
  server-side GET redirect can't resume them. New client-side interceptor
  (installer-backup.js, injected via onBeforeCompileHead on the update and
  manage views) wraps Joomla.submitbutton, stores the selection, sends the
  browser to view=runbackup, and on return restores the selection and
  re-submits the real POST form. Gated by backup_before_update /
  backup_before_uninstall, super-user only, skipped within the throttle
  window.

- runbackup: on manual 'Backup Now' completion, show a 'View backup
  record' button (links to view=backup&id=<record_id>); not shown for the
  pre-update/uninstall flow (which hands back to Joomla). SteppedBackup
  Engine now returns record_id in its step result.

- runbackup arms the pre-action throttles on success (ajax.preupdateAck,
  now arming both update+uninstall keys) so the following server-side
  onExtensionBefore(Update|Uninstall) backup is skipped (no duplicate).

- Plugin manifest re-registers media/js.

NOTE: untested end-to-end (no dev build available this session) — verify
the com_installer resume flow on a dev build before relying on it.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-10 10:53:46 -05:00
mokogitea-actions[bot] a63b87d97c chore(version): pre-release bump to 02.58.19-dev [skip ci] 2026-07-09 23:23:12 +00:00
jmiller e56a1fd8f6 fix: pre-update full-screen backup fires on Joomla 4/5/6 (view=update)
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 16s
The redirect only matched task=update.install, which Joomla 4/5/6 do not
use. Their 'Install the update' flow server-side-redirects to the updating
page (view=update), which then extracts the downloaded package from
JavaScript (AJAX to com_joomlaupdate/extract.php). So the view=update page
LOAD is the last server-side moment before any files change.

Intercept that page load (in onAfterRoute, before com_joomlaupdate renders
it), redirect to the full-screen backup with a returnurl back to
view=update&is_backed_up=1, then the browser returns and the JS extraction
runs. update.finalise is deliberately NOT intercepted -- by then the files
are already extracted, too late for a pre-update backup. Legacy
task=update.install (Joomla 3) still handled.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-09 18:17:07 -05:00
mokogitea-actions[bot] 5574e8569a chore(version): pre-release bump to 02.58.17-dev [skip ci] 2026-07-07 14:10:28 +00:00
gitea-actions[bot] 482e876932 chore(version): auto-bump patch 02.58.16-dev [skip ci] 2026-07-07 14:10:17 +00:00
jmiller 132701035f chore(repo): refine .gitignore rules and update MokoSuiteClient submodule
Universal: Auto Version Bump / Version Bump (push) Successful in 8s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 18s
2026-07-07 08:40:33 -05:00
jmiller b894c95f6b chore: sync pre-release.yml from Template-Generic [skip ci] 2026-07-06 17:04:53 +00:00
jmiller 8a4c4efaad Merge pull request 'fix: installer postflight is honest about a partial/failed install' (#239) from fix/installer-honesty into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 12s
2026-07-06 15:50:35 +00:00
mokogitea-actions[bot] 38160e9ae9 chore(version): pre-release bump to 02.58.15-dev [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
2026-07-06 15:50:17 +00:00
jmiller 6b4f98869a fix: installer postflight honest about partial/failed install
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 / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Secret Scan (pull_request) Successful in 8s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 37s
Generic: Project CI / Lint & Validate (pull_request) Successful in 33s
Universal: PR Check / Validate PR (pull_request) Failing after 28s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 32s
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
Joomla logs a failed child extension but still runs the package
postflight, so the 'installed successfully' message could appear on a
broken install. In postflight, before the success/next-steps messages:

- verify every bundled child declared in the package manifest actually
  registered in #__extensions (match element+type, and folder=group for
  plugins), and
- verify the component's schema tables (parsed dynamically from the
  installed install.mysql.sql) exist in the DB.

If anything is missing, enqueue an error listing it and RETURN before the
success message. Fail-open (try/catch -> nothing missing) so a transient
manifest/DB/IO glitch can't fail a good install. Unconditional
housekeeping (download-key restore, menu fixes) still runs regardless.
Names escaped with htmlspecialchars; table/child lists derived
dynamically, never hard-coded.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-06 10:49:49 -05:00
mokogitea-actions[bot] 0d832e60e9 chore(version): pre-release bump to 02.58.14-dev [skip ci] 2026-07-06 03:58:23 +00:00
jmiller 746629bf02 Merge pull request 'feat: full-screen backup screen for pre-update + Backup Now (Akeeba-style), fixes white screen' (#238) from feat/fullscreen-backup-screen 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-06 03:58:05 +00:00
jmiller 4311ec8f99 fix(security): enforce scheme allow-list on runbackup return URL
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
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 9s
Generic: Project CI / Lint & Validate (pull_request) Successful in 14s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 14s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 1m8s
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
The pre-update return URL is assigned to window.location.href, so a
javascript:/data: URI (empty host) would have passed the host-only check
and executed. Require an absolute http(s) same-host URL or a single-slash
root-relative path; reject javascript:/data:/vbscript: and protocol-relative
//host.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-05 22:57:40 -05:00
jmiller 8cfa46d985 feat: full-screen backup screen for pre-update + Backup Now (Akeeba-style)
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Secret Scan (pull_request) Successful in 7s
Generic: Project CI / Lint & Validate (pull_request) Successful in 12s
Universal: PR Check / Validate PR (pull_request) Failing after 13s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 49s
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
Replace the popup-modal pre-update approach with a dedicated full-page
backup screen, modelled on Akeeba Backup-on-Update, and fix the white
screen caused by running a backup synchronously inside the update request.

- New view=runbackup (View/Runbackup + tmpl/runbackup): full-screen
  progress page that auto-starts the stepped backup (ajax.init -> loop
  ajax.step), then redirects to a validated returnurl on completion or
  shows a completion/retry panel. Auto-resolved by MVCFactory.
- System plugin: on com_joomlaupdate task=update.install (when
  backup_before_update is on), redirect to the full-screen backup screen
  with returnurl back to update.install&is_backed_up=1; on return, arm the
  throttle so onExtensionBeforeUpdate does not run a duplicate backup. No
  synchronous backup in the update request.
- Dashboard 'Backup Now' navigates to the full-screen screen instead of
  the inline modal; the inline modal + stepped JS are removed.
- Retire the popup modal: delete media/js/update-backup.js, remove the
  plugin <media> element and the preupdate_auto_intercept param.
- Dashboard: warn when Global Config live_site is empty (CLI/scheduled
  backup naming + URL generation depend on it).

Refs #196

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-05 22:55:28 -05: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
mokogitea-actions[bot] 85c16b4af4 chore(version): pre-release bump to 02.58.13-dev [skip ci] 2026-07-06 03:34:43 +00:00
jmiller 094787d26d Merge pull request 'fix: raise resource limits for synchronous pre-update backup (white screen)' (#236) from fix/preupdate-wsod-limits 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 28s
2026-07-06 03:34:19 +00:00
mokogitea-actions[bot] 064963da9c chore(version): pre-release bump to 02.58.12-dev [skip ci]
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
2026-07-06 03:34:09 +00:00
jmiller 49463eb747 fix: raise resource limits for synchronous pre-update backup (WSOD)
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 / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Secret Scan (pull_request) Successful in 8s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 14s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 36s
Generic: Project CI / Lint & Validate (pull_request) Successful in 54s
Universal: PR Check / Validate PR (pull_request) Failing after 52s
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
The pre-update/uninstall backup runs synchronously inside the extension
update request. Without raising limits, a large site exceeds
max_execution_time/memory_limit mid-backup and the update page
white-screens. Raise set_time_limit(0)/memory_limit/ignore_user_abort
like the web-cron path.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-05 22:33:43 -05:00
mokogitea-actions[bot] 07cbe5de52 chore(version): pre-release bump to 02.58.11-dev [skip ci] 2026-07-06 03:04:04 +00:00
jmiller fbf97b2cdb Merge pull request 'fix: CLI backups no longer named joomla.invalid (host placeholder)' (#235) from fix/cli-host-placeholder 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 36s
2026-07-06 03:03:33 +00:00
mokogitea-actions[bot] 7cb0b08cfe chore(version): pre-release bump to 02.58.10-dev [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Successful in 3s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
2026-07-06 03:03:24 +00:00
jmiller 5ffcbcc2f1 fix: CLI backups no longer named joomla.invalid (host placeholder)
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 / Branch Policy (pull_request) Successful in 2s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 40s
Universal: PR Check / Secret Scan (pull_request) Successful in 9s
Generic: Project CI / Lint & Validate (pull_request) Successful in 17s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 16s
Universal: PR Check / Validate PR (pull_request) Failing after 50s
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
Joomla's console fills $_SERVER['HTTP_HOST'] with the reserved sentinel
'joomla.invalid', which the [HOST] placeholder resolver used verbatim
because its unusable-host check only knew about empty/localhost. Treat
'joomla.invalid' as unusable too, falling back to live_site then the
system hostname, so CLI/console/scheduled backups get a real name.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-05 22:02:46 -05:00
mokogitea-actions[bot] a47a9e5deb chore(version): pre-release bump to 02.58.09-dev [skip ci] 2026-07-06 02:14:34 +00:00
jmiller 7165da4999 Merge pull request 'feat: pre-update modal phase 2 — auto-run backup on Update click (#196)' (#233) from feat/preupdate-auto-intercept into dev
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 24s
2026-07-06 02:14:16 +00:00
jmiller c0ecf6ce73 feat: pre-update modal phase 2 — auto-run backup on Update click (#196)
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 4s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 9s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 31s
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
Opt-in param preupdate_auto_intercept (off by default). When enabled the
injected update-page script wraps window.Joomla.submitbutton so clicking
the toolbar Update button first pops the progress modal, runs the
pre-update backup, and re-issues the original update task on success.

- Matches only update.update / update.install (not Find Updates /
  Clear Cache, which also start with 'update').
- Reuses the phase-1 overlay + ajax.preupdateAck throttle so the
  subsequent server-side onExtensionBeforeUpdate backup is skipped.
- The manual 'Back up now' notice remains a fallback for flows that
  don't route through submitbutton (e.g. some com_joomlaupdate paths).

Refs #196

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-05 21:13:35 -05:00
mokogitea-actions[bot] 6060979ce8 chore(version): pre-release bump to 02.58.08-dev [skip ci] 2026-07-06 01:58:00 +00:00
jmiller da71057079 Merge pull request 'feat: pre-update backup notice + live-progress modal on update pages (#196)' (#232) from feat/preupdate-modal-notice 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 31s
2026-07-06 01:57:41 +00:00
jmiller 8dc4eaa384 feat: pre-update backup notice + live-progress modal on update pages
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Secret Scan (pull_request) Successful in 10s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Generic: Project CI / Lint & Validate (pull_request) Successful in 15s
Universal: PR Check / Validate PR (pull_request) Failing after 12s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 48s
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
The server-side onExtensionBeforeUpdate backup runs synchronously and
cannot drive a browser modal, so nothing ever 'popped'. Add a client
notice + on-demand live-progress modal on com_joomlaupdate and
com_installer(view=update):

- plg_system_mokosuitebackup gains onBeforeCompileHead, which injects a
  'back up before you update' notice + Back up now button on the update
  pages (gated by show_update_notice + backup_before_update), plus a new
  media JS asset that runs the same stepped backup as the dashboard
  (ajax.init then a loop of ajax.step) with a dependency-free progress
  overlay.
- New AjaxController::preupdateAck() arms the same 10-min throttle key
  runPreActionBackup() checks, so proceeding with Joomla's update after a
  modal backup does not run a duplicate server-side backup.
- Plugin manifest registers the new media/js folder; language keys added.

Phase-1 baseline (notice + on-demand). Auto-intercepting the Update
button is the planned phase-2 follow-up (#196).

Refs #196

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-05 20:56:50 -05:00
mokogitea-actions[bot] 4027c727d9 chore(version): pre-release bump to 02.58.07-dev [skip ci] 2026-07-06 01:36:07 +00:00
jmiller b729aa5052 Merge pull request 'feat: prune remote backups on retention + consolidate backup helpers (#229, #230)' (#231) from fix/retention-remote-shared-helper 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 32s
2026-07-06 01:35:45 +00:00
mokogitea-actions[bot] 7a829b6eca chore(version): pre-release bump to 02.58.06-dev [skip ci]
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
2026-07-06 01:34:49 +00:00
jmiller 447f7b572e feat: prune remote backups on retention + consolidate backup helpers
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 / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Secret Scan (pull_request) Successful in 10s
Universal: PR Check / Validate PR (pull_request) Failing after 14s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 40s
Generic: Project CI / Lint & Validate (pull_request) Successful in 52s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 1m8s
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
Retention (RetentionManager and the plugin's hourly cleanup) only ever
deleted local files; remote archives grew unbounded. Add an idempotent
delete() to RemoteUploaderInterface + all four uploaders and have
RetentionManager remove each pruned archive from the profile's enabled
remotes (best-effort; failures logged). The shared restore.php is left
in place since every backup overwrites it. (#229)

Consolidate duplicated plumbing (#230):
- New RemoteUploaderFactory replaces the createUploaderFromParams copy
  duplicated in BackupEngine and SteppedBackupEngine.
- RetentionManager becomes the single retention authority (global-default
  fallback + pruneOrphans()); the system plugin delegates to it and its
  duplicate doCleanup()/deleteBackupRecord() logic is removed.
- Backend controller, API controller and legacy cli/mokosuitebackup.php
  now run through the shared BackupRunner instead of BackupEngine directly.

Refs #229 #230

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-05 20:34:04 -05:00
mokogitea-actions[bot] f56c1bfbd1 chore(version): pre-release bump to 02.58.05-dev [skip ci] 2026-07-06 01:17:22 +00:00
jmiller aeb9d18ccb Merge pull request 'fix: restore missing str_replace subject arg in MokoRestore standalone gen (#226)' (#228) from fix/mokorestore-str-replace-args 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 29s
2026-07-06 01:17:01 +00:00
mokogitea-actions[bot] 0a51ed75da chore(version): pre-release bump to 02.58.04-dev [skip ci]
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
2026-07-06 01:16:02 +00:00
jmiller 0ae9bfd93a fix: restore missing subject arg in MokoRestore str_replace (standalone gen)
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 30s
MokoRestore::generateStandaloneScript() rewrites the 'Backup Archive'
pre-check via str_replace(ORIG, REPL) but was missing the third
$php subject argument, so every standalone-mode backup fatally errored
with 'str_replace() expects at least 3 arguments, 2 given' at the
'Generating standalone restore.php...' step. The archive still finalized
and uploaded, but no restore.php was produced -- the underlying cause of
#226 (restore script missing on the remote).

Refs #226

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-05 20:15:30 -05:00
mokogitea-actions[bot] 1c0a87ca4a chore(version): pre-release bump to 02.58.03-dev [skip ci] 2026-07-06 00:40:33 +00:00
jmiller 2cb8527c25 Merge pull request 'fix(engine): check + log restore-script remote upload (fixes #226)' (#227) from fix/restore-script-upload-result 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 24s
2026-07-06 00:40:18 +00:00
mokogitea-actions[bot] 35febb8854 chore(version): pre-release bump to 02.58.02-dev [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
2026-07-06 00:39:57 +00:00
jmiller bf91611b97 fix(engine): check + log restore-script remote upload (no longer silent)
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 21s
The standalone restore script was uploaded to each remote with the result
discarded and nothing logged, so a failed second upload left the restore script
missing from the remote with no warning while the archive still recorded
'complete'. Capture the result: log success, and on failure set uploadFailed
(-> warning status) and log the reason.

Fixes #226

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-05 19:39:37 -05:00
mokogitea-actions[bot] 9bc0fc8ad8 chore(version): pre-release bump to 02.58.01-dev [skip ci] 2026-07-06 00:33:03 +00:00
jmiller e75a58709b Merge pull request 'chore(sync): cascade main -> dev' (#225) 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 43s
2026-07-06 00:32:12 +00:00
gitea-actions[bot] 8be1e332c6 chore(release): build 02.58.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-06 00:32:07 +00:00
jmiller ba91f74edc Merge pull request 'release: promote dev → main (installer + admin-menu + UI fixes)' (#224) from dev into main
Sync Workflows to Repos / sync (push) Has been skipped
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Cascade Main -> Dev / Cascade main -> dev (push) Successful in 9s
Universal: PR Check / Branch Policy (pull_request) Successful in 9s
Universal: PR Check / Secret Scan (pull_request) Successful in 29s
Universal: PR Check / Validate PR (pull_request) Failing after 54s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 1m31s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
2026-07-06 00:30:29 +00:00
mokogitea-actions[bot] 387bfbdeda chore(version): pre-release bump to 02.57.13-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 1m53s
2026-07-06 00:17:00 +00:00
jmiller f7d8d82a4e Merge pull request 'refactor(ui): modal padding via Bootstrap container, drop msbackup class' (#223) from fix/modal-container-padding 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 27s
2026-07-06 00:16:33 +00:00
mokogitea-actions[bot] 9e5a942800 chore(version): pre-release bump to 02.57.12-dev [skip ci]
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
2026-07-06 00:16:10 +00:00
jmiller dad3ce2db3 refactor(ui): modal padding via Bootstrap container, drop msbackup class
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 20s
Per feedback, wrap the destination modal content in a Bootstrap .container-fluid
and give that a 1rem frame in the WAM stylesheet, instead of a custom
.msbackup class on .modal-content. Inner header/body/footer drop their own
horizontal padding so the frame isn't doubled.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-05 19:15:49 -05:00
mokogitea-actions[bot] 8325afb03a chore(version): pre-release bump to 02.57.11-dev [skip ci] 2026-07-06 00:06:23 +00:00
jmiller 662918db38 Merge pull request 'feat(ui): SFTP key file upload + msbackup modal padding frame' (#222) from fix/modal-msbackup-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 27s
2026-07-06 00:04:47 +00:00
jmiller 1ab99daaba feat(ui): SFTP key file upload; msbackup modal padding frame
Generic: Project CI / Lint & Validate (pull_request) Successful in 18s
Universal: PR Check / Branch Policy (pull_request) Successful in 3s
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 8s
Universal: PR Check / Validate PR (pull_request) Failing after 14s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 12s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 5s
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
- Add/Edit Destination modal: SFTP key can now be UPLOADED as a file (read into
  the key textarea client-side via FileReader) instead of copy/paste; pasting
  still works.
- Give .modal-content a "msbackup" class with a 1rem padding frame — Bootstrap's
  .modal-content has no padding of its own, which made the modal feel cramped.
  Styling lives in the WAM stylesheet (media/.../admin.css), scoped to .msbackup.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-05 19:04:05 -05:00
mokogitea-actions[bot] c84dcf2864 chore(version): pre-release bump to 02.57.10-dev [skip ci] 2026-07-05 23:59:02 +00:00
jmiller e1f5afb301 Merge pull request 'fix(plugin): pre-update backup notifies on success, not only failure' (#221) from fix/preupdate-notification 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 42s
2026-07-05 23:58:21 +00:00
mokogitea-actions[bot] dfd4e77ade chore(version): pre-release bump to 02.57.09-dev [skip ci]
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 3s
2026-07-05 23:58:13 +00:00
jmiller 1a1c81550c fix(plugin): pre-update backup notifies on success too, not only failure
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 / Branch Policy (pull_request) Successful in 3s
Generic: Project CI / Lint & Validate (pull_request) Successful in 17s
Universal: PR Check / Secret Scan (pull_request) Successful in 12s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 44s
Universal: PR Check / Validate PR (pull_request) Failing after 1m5s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 1m13s
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
runPreActionBackup() only enqueued a message when the backup failed, so a
successful pre-update backup produced no admin notification at all. Now it
surfaces every outcome via the BackupRunner status: complete -> success,
warning (archive made but remote upload failed) -> warning, fail -> error.

Refs #192

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-05 18:57:42 -05:00
mokogitea-actions[bot] 9571b3759a chore(version): pre-release bump to 02.57.08-dev [skip ci] 2026-07-05 23:49:26 +00:00
jmiller 115ccfb069 Merge pull request 'fix(ui): WAM-loaded CSS, per-destination keep-local, tidy destination modal' (#220) from fix/remote-modal-cleanup 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 28s
2026-07-05 23:49:04 +00:00
mokogitea-actions[bot] 2d538d3372 chore(version): pre-release bump to 02.57.07-dev [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
2026-07-05 23:48:51 +00:00
jmiller 97e8d0cbe9 fix(ui): WAM-loaded CSS, per-destination keep-local, tidy destination modal
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 / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Secret Scan (pull_request) Successful in 9s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 25s
Generic: Project CI / Lint & Validate (pull_request) Successful in 12s
Universal: PR Check / Validate PR (pull_request) Failing after 47s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 54s
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
- Move component admin CSS to the Joomla Web Asset Manager: new
  media/com_mokosuitebackup/css/admin.css + media/joomla.asset.json, a <media>
  entry in the manifest, and useStyle('com_mokosuitebackup.admin') in the
  profile edit view. Removes the inline <style> block (no more inline CSS).
- Remove the profile-level "Keep local copy" field/"Remote" fieldset from
  forms/profile.xml — it is now per remote destination (modal remoteKeepLocal).
- Clean up the Add/Edit Destination modal padding/margins (in admin.css).

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-05 18:48:33 -05:00
mokogitea-actions[bot] 8d5b33a74d chore(version): pre-release bump to 02.57.06-dev [skip ci] 2026-07-05 23:32:04 +00:00
jmiller 77bfaff8cd Merge pull request 'fix(installer): self-heal admin menu + alias collision + short-constant label' (#219) from fix/admin-menu-selfheal 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 26s
2026-07-05 23:31:41 +00:00
mokogitea-actions[bot] 1c420b2277 chore(version): pre-release bump to 02.57.05-dev [skip ci]
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
2026-07-05 23:31:24 +00:00
jmiller 2af2641712 fix(installer): self-heal admin menu; fix alias collision; short-constant label
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 22s
Fallout of the duplicate-component element bug:

- Preflight now runs removeOrphanedComponent() BEFORE the component install
  recreates its menu, so the orphan's "Backup" menu (alias "backup") no longer
  collides ("The alias backup is already being used by the Backup menu item").
- ensureSubmenuItems() recreates the top-level "Backup" menu when it is missing
  (via new createTopMenu()) instead of bailing out — so a failed install that
  deleted the parent menu no longer leaves Backup gone from the admin menu (which
  also broke the cPanel module and MokoSuiteClient component detection).
- The top-level menu label now uses the short constant COM_MOKOJOOMBACKUP_SHORT
  (manifest <menu> + enforced/normalized in the script).

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-05 18:31:06 -05:00
mokogitea-actions[bot] a3c7183374 chore(version): pre-release bump to 02.57.04-dev [skip ci] 2026-07-05 23:15:31 +00:00
jmiller 4f91d1e432 Merge pull request 'chore(sync): cascade main -> dev' (#212) 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 39s
2026-07-05 23:03:57 +00:00
jmiller 604412f82f Merge chore/normalize-docs: normalize doc/config refs
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Cascade Main -> Dev / Cascade main -> dev (push) Successful in 3s
Generic: Project CI / Lint & Validate (pull_request) Successful in 15s
Universal: PR Check / Secret Scan (pull_request) Successful in 11s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 15s
Universal: PR Check / Validate PR (pull_request) Failing after 50s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
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
2026-07-05 22:50:52 +00:00
jmiller 94ff15c128 chore(docs): normalize refs — MokoStandards->org wiki, moko-platform->mokocli, residual MokoGitea/MokoSuite
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 34s
Non-workflow docs/config only (CLAUDE.md, README, branch-protection, ISSUE_TEMPLATE/config.yml,
ci-issue-reporter). Workflow files, manifest schema, and Makefile install paths left for the
forge-rebrand gate. Authored-by: Moko Consulting
2026-07-05 22:50:50 +00:00
mokogitea-actions[bot] a92f423767 chore(version): pre-release bump to 02.57.02-dev [skip ci] 2026-07-05 22:44:13 +00:00
jmiller ba2cda1385 Merge pull request 'fix(installer): drop legacy remote columns in PHP, not PREPARE-based SQL' (#218) from fix/sql-prepared-statement-protocol 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 21s
2026-07-05 22:43:53 +00:00
mokogitea-actions[bot] 72aeea6955 chore(version): pre-release bump to 02.57.01-dev [skip ci]
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 3s
2026-07-05 22:41:55 +00:00
jmiller 8a85370e32 fix(installer): drop legacy remote columns in PHP, not PREPARE-based SQL
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 34s
02.56.01.sql used PREPARE/EXECUTE/DEALLOCATE to conditionally drop the 26 legacy
remote_storage/ftp_*/sftp_*/gdrive_*/s3_* columns portably. Joomla's installer
rejects those statements on MySQL 8 (error 1295: "This command is not supported
in the prepared statement protocol yet"), aborting the whole component install
when updating from an old schema.

Neutralize 02.56.01.sql (now a no-op comment) and move the purge into
Pkg_MokoSuiteBackupInstallerScript::dropLegacyRemoteColumns(): gate on
INFORMATION_SCHEMA, then issue one plain ALTER dropping only the columns that
still exist — portable on MariaDB and MySQL 8, idempotent, non-fatal.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-05 17:41:26 -05:00
gitea-actions[bot] ea91b49e50 chore(release): build 02.57.00 [skip ci] 2026-07-05 22:30:31 +00:00
jmiller 99d8e954bd Merge pull request 'feat: BackupRunner helper — unify backup run/status/notification' (#214) from feat/backup-runner-helper into main
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 / Branch Policy (pull_request) Successful in 3s
Universal: PR Check / Secret Scan (pull_request) Successful in 10s
Cascade Main -> Dev / Cascade main -> dev (push) Successful in 9s
Universal: PR Check / Validate PR (pull_request) Failing after 15s
Generic: Project CI / Lint & Validate (pull_request) Successful in 55s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 59s
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
2026-07-05 22:29:38 +00:00
jmiller 28ca110d7b docs(changelog): add BackupRunner entry (#214)
Universal: PR Check / Branch Policy (pull_request) Failing after 4s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 7s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 11s
Universal: PR Check / Wiki Update Reminder (pull_request) Successful in 2s
Universal: PR Check / Require Docs Update (pull_request) Failing after 8s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 3s
Universal: PR Check / Secret Scan (pull_request) Successful in 11s
Branch Cleanup / Delete merged branch (pull_request) Successful in 4s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 15s
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
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 15s
Generic: Project CI / Lint & Validate (pull_request) Successful in 1m3s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 51s
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
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-05 17:29:28 -05:00
jmiller 307067580c feat: BackupRunner helper — unify backup run/status/notification
Every non-interactive backup trigger used a slightly different path, so
final status and notification gating behaved inconsistently depending on
what started the backup (this is why pre-update backups did not gate
notifications the way "Backup Now" does).

Add src/Service/BackupRunner.php as the single synchronous entry point.
It delegates to BackupEngine (which already sends notifications gated by
the profile's notify_on_success/notify_on_failure and applies retention —
so there is exactly ONE notification layer), and normalises the result,
including the real complete/warning/fail status the engine records but did
not return. BackupRunner self-loads BackupEngine so callers require one file.

Routed through BackupRunner:
- plg_system  runPreActionBackup() (preaction) + web-cron handler (webcron)
- plg_content triggerAutoBackup() (pre-install)
- plg_task    scheduled task run
- plg_console RunCommand (cli)

The dashboard AJAX/stepped "Backup Now" flow is unchanged (it is the
interactive equivalent). The installer progress popup is deferred — the
installer request cannot drive the AJAX modal (TODO(#196) noted in code).

php -l clean on all changed files.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-05 17:28:59 -05:00
jmiller 7f5f041588 Merge pull request 'fix(release): idempotent changelog promote (no duplicate version headers)' (#217) from fix/changelog-promote-idempotent into main
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 / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Secret Scan (pull_request) Successful in 8s
Cascade Main -> Dev / Cascade main -> dev (push) Successful in 6s
Universal: PR Check / Validate PR (pull_request) Failing after 14s
Sync Workflows to Repos / sync (push) Has been skipped
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 14s
Generic: Project CI / Lint & Validate (pull_request) Successful in 1m6s
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
2026-07-05 22:28:19 +00:00
jmiller 129be7ef9d fix(release): idempotent changelog promote + dedupe 02.56.11 header
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Wiki Update Reminder (pull_request) Successful in 2s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 4s
Universal: PR Check / Require Docs Update (pull_request) Failing after 5s
Generic: Project CI / Lint & Validate (pull_request) Successful in 11s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: PR Check / Secret Scan (pull_request) Successful in 9s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 35s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 1m13s
Universal: PR Check / Validate PR (pull_request) Failing after 1m11s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 1m22s
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
Guard the promote so it only inserts a version header when absent (stops the
empty duplicate headers each build was appending), and remove the duplicate
02.56.11 header the non-idempotent promote just created.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-05 17:28:08 -05:00
jmiller 14392de078 Merge pull request 'docs(changelog): backfill 02.56.07 + 02.56.08 (shipped with empty changelogs)' (#215) from fix/changelog-backfill into main
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
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 10s
Generic: Project CI / Lint & Validate (pull_request) Successful in 15s
Universal: PR Check / Validate PR (pull_request) Failing after 13s
Cascade Main -> Dev / Cascade main -> dev (push) Successful in 5s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 14s
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
2026-07-05 22:26:17 +00:00
jmiller 8bdadb68c2 docs(changelog): backfill 02.56.07/08 + dedupe blank duplicate headers
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Wiki Update Reminder (pull_request) Successful in 1s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 5s
Universal: PR Check / Require Docs Update (pull_request) Failing after 5s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Secret Scan (pull_request) Successful in 6s
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
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 12s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 31s
Generic: Project CI / Lint & Validate (pull_request) Successful in 52s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 56s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 58s
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
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-05 17:26:05 -05:00
gitea-actions[bot] bcc4054ced chore: promote changelog [Unreleased] → [02.56.11]
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
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 9s
Generic: Project CI / Lint & Validate (pull_request) Successful in 13s
Cascade Main -> Dev / Cascade main -> dev (push) Successful in 3s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 10s
Universal: PR Check / Validate PR (pull_request) Failing after 33s
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
2026-07-05 22:23:35 +00:00
gitea-actions[bot] 0cba7f81e8 chore(release): build 02.56.11 [skip ci] 2026-07-05 22:23:26 +00:00
jmiller 2688ea352b Merge pull request 'fix(installer): self-heal orphaned com_component-mokosuitebackup on update' (#216) from fix/orphan-component-cleanup into main
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 / Branch Policy (pull_request) Successful in 2s
Cascade Main -> Dev / Cascade main -> dev (push) Successful in 4s
Sync Workflows to Repos / sync (push) Has been skipped
Universal: PR Check / Secret Scan (pull_request) Successful in 7s
Generic: Project CI / Lint & Validate (pull_request) Successful in 49s
Universal: PR Check / Validate PR (pull_request) Failing after 46s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Failing after 58s
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
2026-07-05 22:23:08 +00:00
mokogitea-actions[bot] 39d94a31d6 chore(version): pre-release bump to 02.56.11-dev [skip ci]
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 31s
2026-07-05 22:14:22 +00:00
jmiller 1029e40908 fix(installer): self-heal orphaned com_component-mokosuitebackup on update
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 7s
Generic: Project CI / Lint & Validate (pull_request) Successful in 16s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Wiki Update Reminder (pull_request) Successful in 3s
Universal: PR Check / Require Docs Update (pull_request) Failing after 7s
Universal: PR Check / Secret Scan (pull_request) Successful in 9s
Universal: PR Check / Validate PR (pull_request) Failing after 13s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 52s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 50s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 29s
Universal: Build & Release / Promote to RC (pull_request) Failing after 15s
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
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
The old "Type - Name" component <name> made Joomla register a duplicate
component under element com_component-mokosuitebackup (its own extension row,
admin menu, schema rows and administrator/ folder), orphaning the real
com_mokosuitebackup on the old version. The manifest fix (#213) prevents new
occurrences; this reconciles already-affected sites automatically.

Package postflight now removes any component whose element matches
%component%mokosuitebackup% (excluding com_mokosuitebackup): deletes its
#__menu, #__schemas and #__update_sites_extensions rows, the #__extensions
record, and the stray admin folder. Idempotent and non-fatal.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-05 17:11:51 -05:00
gitea-actions[bot] ec3094385f chore: promote changelog [Unreleased] → [02.56.08]
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 / Branch Policy (pull_request) Successful in 3s
Universal: PR Check / Secret Scan (pull_request) Successful in 8s
Cascade Main -> Dev / Cascade main -> dev (push) Successful in 4s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 17s
Generic: Project CI / Lint & Validate (pull_request) Successful in 1m17s
Universal: PR Check / Validate PR (pull_request) Failing after 1m13s
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
2026-07-05 22:02:08 +00:00
gitea-actions[bot] 4a2bb1b19d chore(release): build 02.56.08 [skip ci] 2026-07-05 22:01:48 +00:00
jmiller 3c5ad43e97 Merge pull request 'fix(manifest): component &lt;name&gt; must be element-safe (com_mokosuitebackup)' (#213) from fix/component-element-name into main
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
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 8s
Generic: Project CI / Lint & Validate (pull_request) Successful in 15s
Cascade Main -> Dev / Cascade main -> dev (push) Successful in 5s
Universal: PR Check / Validate PR (pull_request) Failing after 14s
Sync Workflows to Repos / sync (push) Has been skipped
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 14s
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
2026-07-05 22:01:17 +00:00
mokogitea-actions[bot] f92e5227dc chore(version): pre-release bump to 02.56.08-dev [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Successful in 3s
Universal: Build & Release / Promote to RC (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 / Build & Release Pipeline (pull_request) Successful in 53s
2026-07-05 21:58:57 +00:00
jmiller 9db54ac60c fix(manifest): component <name> must be element-safe (com_mokosuitebackup)
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 34s
The "Type - Name" convention (commit d155958) set the component <name> to
"Component - MokoSuiteBackup". Joomla derives a component's element from its
<name> (com_ + sanitized name), so this produced element
`com_component-mokosuitebackup` instead of `com_mokosuitebackup`. On update,
Joomla registered a NEW component (new element + folder) at the current
version and orphaned the real `com_mokosuitebackup` at its old version — which
is what `?option=com_mokosuitebackup` and the admin menu still load. Net
effect: the component appears "stuck" on the old version with the old UI.

Revert the component <name> to "MokoSuiteBackup" (-> com_mokosuitebackup) and
document why the display convention must not be applied to a component's
<name>. Packages are exempt (they use <packagename>); plugins/modules are
exempt (<name> is only a display label there).

Refs #171-area remote tab reports; fixes the duplicate-component registration.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-05 16:58:37 -05:00
jmiller 0c7c3552ed chore: sync workflow-sync-trigger.yml from Template-Generic [skip ci] 2026-07-05 21:49:42 +00:00
jmiller 351321161a chore: sync version-set.yml from Template-Generic [skip ci] 2026-07-05 21:49:40 +00:00
jmiller d2cc234fce chore: sync repo-health.yml from Template-Generic [skip ci] 2026-07-05 21:49:37 +00:00
jmiller 2389df8670 chore: sync rc-revert.yml from Template-Generic [skip ci] 2026-07-05 21:49:34 +00:00
jmiller 55f29498b0 chore: sync pre-release.yml from Template-Generic [skip ci] 2026-07-05 21:49:31 +00:00
jmiller a5397c0f16 chore: sync pr-check.yml from Template-Generic [skip ci] 2026-07-05 21:49:28 +00:00
jmiller c0792a6ba0 chore: sync notify.yml from Template-Generic [skip ci] 2026-07-05 21:49:26 +00:00
jmiller 7a3f0f914e chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-07-05 21:49:24 +00:00
jmiller cc7f18ae45 chore: sync gitleaks.yml from Template-Generic [skip ci] 2026-07-05 21:49:22 +00:00
jmiller 4cf8f8243d chore: sync cleanup.yml from Template-Generic [skip ci] 2026-07-05 21:49:20 +00:00
jmiller efb28fa386 chore: sync ci-issue-reporter.yml from Template-Generic [skip ci] 2026-07-05 21:49:17 +00:00
jmiller 68043c9a37 chore: sync ci-generic.yml from Template-Generic [skip ci] 2026-07-05 21:49:15 +00:00
jmiller e5558fad26 chore: sync cascade-dev.yml from Template-Generic [skip ci] 2026-07-05 21:49:12 +00:00
jmiller 60c5c00333 chore: sync branch-cleanup.yml from Template-Generic [skip ci] 2026-07-05 21:49:10 +00:00
jmiller f9169fd9f7 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-07-05 21:49:07 +00:00
jmiller d69db6fac3 chore: sync auto-bump.yml from Template-Generic [skip ci] 2026-07-05 21:49:05 +00:00
132 changed files with 3267 additions and 455 deletions
+12 -2
View File
@@ -29,6 +29,7 @@ secrets/
*.pid *.pid
*.seed *.seed
# ============================================================ # ============================================================
# OS / Editor / IDE cruft # OS / Editor / IDE cruft
# ============================================================ # ============================================================
@@ -45,6 +46,7 @@ Icon?
.idea/ .idea/
.settings/ .settings/
.claude/ .claude/
.gemini/
.vscode/* .vscode/*
!.vscode/tasks.json !.vscode/tasks.json
!.vscode/settings.json.example !.vscode/settings.json.example
@@ -116,6 +118,8 @@ out/
*.map *.map
*.css.map *.css.map
*.js.map *.js.map
*.min.css
*.min.js
*.tsbuildinfo *.tsbuildinfo
# ============================================================ # ============================================================
@@ -151,7 +155,7 @@ package-lock.json
# PHP / Composer tooling # PHP / Composer tooling
# ============================================================ # ============================================================
vendor/ vendor/
!source/media/vendor/ !src/media/vendor/
composer.lock composer.lock
*.phar *.phar
codeception.phar codeception.phar
@@ -177,7 +181,7 @@ __pycache__/
*.egg *.egg
*.egg-info/ *.egg-info/
.installed.cfg .installed.cfg
MANIFEST /MANIFEST
develop-eggs/ develop-eggs/
downloads/ downloads/
eggs/ eggs/
@@ -201,3 +205,9 @@ hypothesis/
profile.ps1 profile.ps1
.mcp.json .mcp.json
# ============================================================
# Local wiki clone (not version controlled)
# ============================================================
wiki/
docs/
+2 -2
View File
@@ -59,8 +59,8 @@ Joomla **package** with four sub-extensions:
- **Attribution**: `Authored-by: Moko Consulting` - **Attribution**: `Authored-by: Moko Consulting`
- **Workflow directory**: `.mokogitea/` (not `.gitea/` or `.github/`) - **Workflow directory**: `.mokogitea/` (not `.gitea/` or `.github/`)
- **Minification**: handled at build time (CI) - **Minification**: handled at build time (CI)
- **Wiki**: documentation lives in the Gitea wiki, not `docs/` files - **Wiki**: documentation lives in the MokoGitea wiki, not `docs/` files
- **Standards**: [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home) - **Standards**: [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/.mokogitea/wiki)
## Coding Standards ## Coding Standards
+1 -1
View File
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: MokoGitea.Workflow
# 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/auto-bump.yml # PATH: /.mokogitea/workflows/auto-bump.yml
+6 -6
View File
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: MokoGitea.Workflow
# 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/auto-release.yml # PATH: /.mokogitea/workflows/auto-release.yml
@@ -142,8 +142,8 @@ jobs:
run: | run: |
git fetch origin rc git fetch origin rc
git checkout rc git checkout rc
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" git config --local user.email "mokogitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]" git config --local user.name "mokogitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git" git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
- name: Publish RC release - name: Publish RC release
@@ -214,8 +214,8 @@ jobs:
- name: Configure git for bot pushes - name: Configure git for bot pushes
run: | run: |
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" git config --local user.email "mokogitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]" git config --local user.name "mokogitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git" git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
- name: Check for merge conflict markers - name: Check for merge conflict markers
@@ -382,7 +382,7 @@ jobs:
content = open('CHANGELOG.md').read() content = open('CHANGELOG.md').read()
old = '## [Unreleased]' old = '## [Unreleased]'
new = f'## [Unreleased]\n\n## [{version}] --- {date}' new = f'## [Unreleased]\n\n## [{version}] --- {date}'
content = content.replace(old, new, 1) content = content if ('## [' + version + ']') in content else content.replace(old, 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
+1 -1
View File
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoStandards.Universal # INGROUP: MokoStandards.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic # REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogitea/workflows/branch-cleanup.yml # PATH: /.mokogitea/workflows/branch-cleanup.yml
+2 -2
View File
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoStandards.Cascade # INGROUP: MokoStandards.Cascade
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic # REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogitea/workflows/cascade-dev.yml # PATH: /.mokogitea/workflows/cascade-dev.yml
@@ -71,7 +71,7 @@ jobs:
echo "Reusing open cascade PR #${PR}" echo "Reusing open cascade PR #${PR}"
fi fi
# 3. wait for Gitea to compute mergeability (conflict detection) # 3. wait for MokoGitea to compute mergeability (conflict detection)
MERGEABLE="" MERGEABLE=""
for _ in 1 2 3 4 5 6; do for _ in 1 2 3 4 5 6; do
MERGEABLE=$(curl -sf -H "$AUTH" "${API}/pulls/${PR}" | jqnum mergeable) MERGEABLE=$(curl -sf -H "$AUTH" "${API}/pulls/${PR}" | jqnum mergeable)
+6 -5
View File
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoStandards.CI # INGROUP: MokoStandards.CI
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic # REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogitea/workflows/ci-generic.yml # PATH: /.mokogitea/workflows/ci-generic.yml
@@ -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
+2 -2
View File
@@ -3,12 +3,12 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: MokoGitea.Workflow
# INGROUP: mokocli.Universal # INGROUP: mokocli.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic # REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogitea/workflows/ci-issue-reporter.yml # PATH: /.mokogitea/workflows/ci-issue-reporter.yml
# VERSION: 01.00.00 # VERSION: 01.00.00
# BRIEF: Reusable workflow — creates/updates a Gitea issue when a CI gate fails. # BRIEF: Reusable workflow — creates/updates a MokoGitea issue when a CI gate fails.
# Clones MokoCLI and runs cli/ci_issue_reporter.sh. # Clones MokoCLI and runs cli/ci_issue_reporter.sh.
name: "Universal: CI Issue Reporter" name: "Universal: CI Issue Reporter"
+1 -1
View File
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoStandards.Maintenance # INGROUP: MokoStandards.Maintenance
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards # REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
# PATH: /.mokogitea/workflows/cleanup.yml # PATH: /.mokogitea/workflows/cleanup.yml
+1 -1
View File
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoStandards.Security # INGROUP: MokoStandards.Security
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API # REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
# PATH: /.mokogitea/workflows/gitleaks.yml # PATH: /.mokogitea/workflows/gitleaks.yml
+2 -2
View File
@@ -3,9 +3,9 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: MokoGitea.Workflow
# INGROUP: mokocli.Automation # INGROUP: mokocli.Automation
# VERSION: 02.56.07 # VERSION: 02.60.00
# BRIEF: Auto-create feature branch when an issue is opened # BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch" name: "Universal: Issue Branch"
+1 -1
View File
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoStandards.Notifications # INGROUP: MokoStandards.Notifications
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards # REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
# PATH: /.mokogitea/workflows/notify.yml # PATH: /.mokogitea/workflows/notify.yml
+4 -4
View File
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: MokoGitea.Workflow
# INGROUP: mokocli.CI # INGROUP: mokocli.CI
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic # REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogitea/workflows/pr-check.yml # PATH: /.mokogitea/workflows/pr-check.yml
@@ -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
@@ -350,7 +350,7 @@ jobs:
joomla) joomla)
MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1) MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
if [ -z "$MANIFEST" ]; then if [ -z "$MANIFEST" ]; then
echo "::warning::No Joomla manifest found (WaaS site)" echo "::warning::No Joomla manifest found (MokoSuite site)"
exit 0 exit 0
fi fi
echo "Manifest: ${MANIFEST}" echo "Manifest: ${MANIFEST}"
@@ -363,7 +363,7 @@ jobs:
# Block legacy raw/branch update server URLs on MokoGitea # Block legacy raw/branch update server URLs on MokoGitea
RAW_URLS=$(grep -n 'raw/branch' "$MANIFEST" | grep -i 'mokoconsulting\|mokogitea\|git\.mokoconsulting\.tech' || true) RAW_URLS=$(grep -n 'raw/branch' "$MANIFEST" | grep -i 'mokoconsulting\|mokogitea\|git\.mokoconsulting\.tech' || true)
if [ -n "$RAW_URLS" ]; then if [ -n "$RAW_URLS" ]; then
echo "::error::Manifest contains legacy raw/branch update server URL on MokoGitea. Use the Gitea Pages URL instead (e.g. /{REPO}/updates.xml not /{REPO}/raw/branch/main/updates.xml)" echo "::error::Manifest contains legacy raw/branch update server URL on MokoGitea. Use the MokoGitea Pages URL instead (e.g. /{REPO}/updates.xml not /{REPO}/raw/branch/main/updates.xml)"
echo "$RAW_URLS" echo "$RAW_URLS"
exit 1 exit 1
fi fi
+11 -5
View File
@@ -3,11 +3,11 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: MokoGitea.Workflow
# 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"
@@ -156,13 +156,19 @@ jobs:
fi fi
# Commit version bump # Commit version bump
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" git config --local user.email "mokogitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]" git config --local user.name "mokogitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git" git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
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
+1 -1
View File
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: MokoGitea.Workflow
# INGROUP: mokocli.Universal # INGROUP: mokocli.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic # REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogitea/workflows/rc-revert.yml # PATH: /.mokogitea/workflows/rc-revert.yml
+2 -2
View File
@@ -6,7 +6,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: MokoGitea.Workflow
# INGROUP: mokocli.Validation # INGROUP: mokocli.Validation
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic # REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogitea/workflows/repo-health.yml # PATH: /.mokogitea/workflows/repo-health.yml
@@ -88,7 +88,7 @@ jobs:
# Hardcoded authorized users — always allowed # Hardcoded authorized users — always allowed
case "$ACTOR" in case "$ACTOR" in
jmiller|gitea-actions[bot]) jmiller|mokogitea-actions[bot])
ALLOWED=true ALLOWED=true
PERMISSION=admin PERMISSION=admin
METHOD="hardcoded allowlist" METHOD="hardcoded allowlist"
+1 -1
View File
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow.Template # DEFGROUP: MokoGitea.Workflow.Template
# INGROUP: MokoStandards.CI # INGROUP: MokoStandards.CI
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla # REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
# PATH: /.mokogitea/workflows/version-set.yml # PATH: /.mokogitea/workflows/version-set.yml
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: MokoGitea.Workflow
# INGROUP: mokocli.Universal # INGROUP: mokocli.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic # REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogitea/workflows/workflow-sync-trigger.yml # PATH: /.mokogitea/workflows/workflow-sync-trigger.yml
+61 -10
View File
@@ -1,15 +1,66 @@
# Changelog # Changelog
## [Unreleased] ## [Unreleased]
## [02.56.07] --- 2026-07-05 ## [02.60.00] --- 2026-07-12
## [02.56.07] --- 2026-07-05
## [02.56.05] --- 2026-07-05
## [02.56.05] --- 2026-07-05
## [02.56.00] --- 2026-07-04
## [02.56.00] --- 2026-07-04 ## [02.59.00] --- 2026-07-12
### Fixed
- Pre-update backup now fires for **every** extension update/uninstall, not just the first one in a 10-minute window. A coarse 600-second session throttle (armed after any pre-action backup — even after updating MokoSuiteBackup itself or a core Joomla update) was disabling the client-side interceptor **and** the server-side fallback for *all* extensions for 10 minutes, so distinct updates silently went un-backed-up (looked like "only the backup extension triggers it"). Replaced with **one backup per Update action**: a single batch/click still backs up once (deduped per-request), the client re-fire is skipped via a **one-shot** action-keyed flag (consumed once), and the core-update path uses its own dedicated key so it no longer suppresses extension backups.
- Pre-update backup on the **Joomla Update** page is now **seamless** (Akeeba-style): after the full-screen backup runs, the plugin auto-ticks Joomla's "I have taken a backup" checkbox and clicks Install, so the update continues automatically instead of stopping for a second manual click.
- Pre-update full-screen backup now actually fires on the **Joomla Update** page. The "Install the update" button posts to `option=com_joomlaupdate&layout=update` (Joomla's confirm/updating page) — carrying **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
- **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 deduped to one backup per Update action.
- Manual **"Backup Now"** completion now offers a **View backup record** button (links straight to the record that was just created). It appears only for manual backups — the pre-update / pre-uninstall flow hands control back to Joomla instead.
- **Full-screen backup screen** (`view=runbackup`) for both pre-update and manual backups, modelled on Akeeba's Backup-on-Update — replaces the earlier popup-modal approach. Clicking Joomla's **Install the update** now redirects (server-side, Akeeba-style) to a dedicated full-page backup screen that runs the stepped backup with a real progress bar and then **automatically continues the update** via a 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)
### Changed
- Consolidated backup plumbing into shared helpers (#230):
- New `RemoteUploaderFactory` replaces the `createUploaderFromParams()` copy that was duplicated in `BackupEngine` and `SteppedBackupEngine`.
- `RetentionManager` is now the single retention authority — it takes the global `max_age_days`/`max_backups` fallback and gained `pruneOrphans()`; the system plugin's hourly cleanup delegates to it and its duplicate `deleteBackupRecord()` logic is removed.
- The backend controller, Web Services API controller, and legacy `cli/mokosuitebackup.php` now run backups through the shared `BackupRunner` (gaining the normalized complete/warning/fail status) instead of instantiating `BackupEngine` directly.
### Fixed
- Package installer is now **honest about success**: the postflight no longer prints "installed successfully" / next-steps when the install actually failed or only partially completed. Joomla logs a failed child extension but still runs the package postflight, so the postflight now (a) verifies every bundled child declared in the manifest actually registered in `#__extensions` (matched by element + type, and folder/group for plugins) and (b) verifies the component's schema tables — derived dynamically from the installed `install.mysql.sql` — actually exist. If anything is missing it enqueues an error listing what's missing and stops before the success message. Fail-open: any manifest/DB/IO glitch is treated as "nothing missing" so a transient error never turns a good install into a false failure. Unconditional housekeeping (e.g. download-key restore) still runs regardless.
- Pre-update full-screen backup screen now actually triggers on **Joomla 6** (and 4/5). The redirect matched only the legacy `update.install` task, which Joomla 4/5/6 don't use — they server-side-redirect to the **updating page `view=update`**, which then extracts the downloaded package from JavaScript. The plugin now intercepts the `view=update` page **load** (the last point before any files change) and returns the browser there flagged `is_backed_up=1` so the extraction proceeds after the backup. (`update.finalise` is intentionally not intercepted — by then the files are already extracted.)
- Pre-update/uninstall backup no longer **white-screens** the update on large sites. The synchronous backup that runs inside the extension update/uninstall request now raises `max_execution_time`/`memory_limit` (and `ignore_user_abort`) like the web-cron path, so it can't exhaust the request's default limits mid-backup. (Core Joomla updates additionally get a full-screen backup screen — see below.)
- Archive names for **CLI/console-triggered backups** no longer come out as `joomla.invalid_…`. The `[HOST]` placeholder took `$_SERVER['HTTP_HOST']` verbatim, but Joomla's console fills that with the reserved sentinel host `joomla.invalid`; the resolver now treats that (like empty/`localhost`) as unusable and falls back to the configured `live_site` host, then the system hostname. (Set the site's *live_site* to get the exact domain in CLI-built names.)
- Standalone restore script generation no longer aborts backups with `str_replace() expects at least 3 arguments, 2 given`. `MokoRestore::generateStandaloneScript()` had a `str_replace()` call (the "Backup Archive" pre-check rewrite) that was missing its `$php` subject argument, so **every** standalone-mode backup fatally errored while "Generating standalone restore.php…" — the archive still finalized and uploaded, but no `restore.php` was ever produced (the true root cause behind #226). (#226)
- Remote upload: the standalone restore script upload is no longer silent — its result is now checked and logged, and a failed restore-script upload marks the backup as `warning` (previously the result was discarded, so a missing restore script on the remote went unreported while the archive still showed success). (#226)
## [02.58.00] --- 2026-07-06
### Added
- SFTP destinations: **upload an SSH private key file** in the Add/Edit Destination modal instead of pasting it (reads the file into the key field client-side; pasting still works).
### Changed
- Add/Edit Destination modal: wrap the content in a Bootstrap `.container-fluid` and give it a 1rem padding frame via the WAM stylesheet (Bootstrap's `.modal-content` has no padding of its own).
### Fixed
- Pre-update backup now shows an admin notification on **every** outcome (success / warning / failure) — previously a *successful* pre-update backup fired nothing, so it looked like the notification wasn't working. (#192)
### Changed
- Component admin CSS now loads via the Joomla **Web Asset Manager** (`media/com_mokosuitebackup/` + `joomla.asset.json`) instead of an inline `<style>` block.
- "Keep local copy" is configured **per remote destination** (in the Add/Edit Destination modal); the redundant profile-level field and its "Remote" tab are removed.
- Add/Edit Destination modal: cleaned up the cramped padding/margins.
### Fixed
- Admin menu / duplicate-component fallout: (a) the orphaned `com_component-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)
## [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)
+1 -1
View File
@@ -4,7 +4,7 @@ Thank you for your interest in contributing to MokoSuiteBackup.
## Getting Started ## Getting Started
1. Fork the repository on Gitea 1. Fork the repository on MokoGitea
2. Create a feature branch from `dev` (`feature/your-feature`) 2. Create a feature branch from `dev` (`feature/your-feature`)
3. Make your changes following the coding standards below 3. Make your changes following the coding standards below
4. Submit a pull request targeting `dev` 4. Submit a pull request targeting `dev`
+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.56.07 VERSION: 02.60.00
BRIEF: Security vulnerability reporting and handling policy BRIEF: Security vulnerability reporting and handling policy
--> -->
@@ -13,7 +13,7 @@ namespace Joomla\Component\MokoSuiteBackup\Api\Controller;
defined('_JEXEC') or die; defined('_JEXEC') or die;
use Joomla\CMS\MVC\Controller\ApiController; use Joomla\CMS\MVC\Controller\ApiController;
use Joomla\Component\MokoSuiteBackup\Administrator\Engine\BackupEngine; use Joomla\Component\MokoSuiteBackup\Administrator\Service\BackupRunner;
class BackupsController extends ApiController class BackupsController extends ApiController
{ {
@@ -38,8 +38,7 @@ class BackupsController extends ApiController
$profileId = (int) ($data['profile'] ?? 1); $profileId = (int) ($data['profile'] ?? 1);
$description = $data['description'] ?? 'API backup ' . date('Y-m-d H:i:s'); $description = $data['description'] ?? 'API backup ' . date('Y-m-d H:i:s');
$engine = new BackupEngine(); $result = (new BackupRunner())->run($profileId, $description, 'api');
$result = $engine->run($profileId, $description, 'api');
if ($result['success']) { if ($result['success']) {
$this->app->setHeader('status', 200); $this->app->setHeader('status', 200);
@@ -21,9 +21,11 @@ namespace Joomla\Component\MokoSuiteBackup\Api\Controller;
defined('_JEXEC') or die; defined('_JEXEC') or die;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory; use Joomla\CMS\Factory;
use Joomla\CMS\MVC\Controller\ApiController; use Joomla\CMS\MVC\Controller\ApiController;
use Joomla\Component\MokoSuiteBackup\Administrator\Engine\SnapshotEngine; use Joomla\Component\MokoSuiteBackup\Administrator\Engine\SnapshotEngine;
use Joomla\Component\MokoSuiteBackup\Administrator\Engine\SnapshotPortable;
use Joomla\Component\MokoSuiteBackup\Administrator\Engine\SnapshotRestoreEngine; use Joomla\Component\MokoSuiteBackup\Administrator\Engine\SnapshotRestoreEngine;
class SnapshotsController extends ApiController class SnapshotsController extends ApiController
@@ -159,11 +161,11 @@ class SnapshotsController extends ApiController
$data = json_decode($this->input->json->getRaw(), true) ?: []; $data = json_decode($this->input->json->getRaw(), true) ?: [];
$mode = $data['mode'] ?? 'replace'; $mode = (string) ($data['mode'] ?? 'replace');
$contentTypes = $data['content_types'] ?? []; $contentTypes = $data['content_types'] ?? [];
// Enforce valid restore mode // Accept the external mode names too; the engine normalises + validates.
if (!in_array($mode, ['replace', 'merge'], true)) { if (!in_array($mode, ['replace', 'merge', 'overwrite', 'create', 'duplicate'], true)) {
$mode = 'replace'; $mode = 'replace';
} }
@@ -183,6 +185,86 @@ class SnapshotsController extends ApiController
return $this; return $this;
} }
/**
* Inject a snapshot payload directly into this site (master → slave).
* POST /api/index.php/v1/mokosuitebackup/snapshot/inject
*
* Body: {
* "snapshot": { version, content_types, tables }, // the payload (required)
* "mode": "overwrite" | "create" | "duplicate", // optional; defaults to the site's snapshot_inject_mode
* "content_types": [ ... ], // optional filter
* "description": "..." // optional
* }
*/
public function inject(): static
{
if (!$this->app->getIdentity()->authorise('mokosuitebackup.snapshot.manage', 'com_mokosuitebackup')) {
$this->app->setHeader('status', 403);
echo json_encode(['errors' => [['title' => 'Access denied']]]);
$this->app->close();
return $this;
}
$params = ComponentHelper::getParams('com_mokosuitebackup');
// The receiving (slave) site must explicitly allow injection.
if (!(int) $params->get('snapshot_allow_inject', 0)) {
$this->app->setHeader('status', 403);
echo json_encode(['errors' => [['title' => 'Snapshot injection is disabled on this site']]]);
$this->app->close();
return $this;
}
$body = json_decode($this->input->json->getRaw(), true) ?: [];
$snapshot = $body['snapshot'] ?? null;
if (!is_array($snapshot)) {
$this->app->setHeader('status', 400);
echo json_encode(['errors' => [['title' => 'Missing "snapshot" payload']]]);
$this->app->close();
return $this;
}
// Explicit mode wins; otherwise the site default from Options.
$mode = (string) ($body['mode'] ?? $params->get('snapshot_inject_mode', 'overwrite'));
$contentTypes = $body['content_types'] ?? [];
$description = (string) ($body['description'] ?? 'Injected snapshot');
// Materialise a local record from the payload, then restore it.
$ingest = SnapshotPortable::ingestData($snapshot, $description);
if (empty($ingest['success'])) {
$this->app->setHeader('status', 422);
echo json_encode(['errors' => [['title' => $ingest['message']]]]);
$this->app->close();
return $this;
}
$engine = new SnapshotRestoreEngine();
$result = $engine->restore((int) $ingest['id'], $mode, $contentTypes);
$result['snapshot_id'] = (int) $ingest['id'];
if (!empty($ingest['warnings'])) {
$result['warnings'] = array_merge($result['warnings'] ?? [], $ingest['warnings']);
}
if ($result['success']) {
$this->app->setHeader('status', 200);
echo json_encode(['data' => $result]);
} else {
$this->app->setHeader('status', 500);
echo json_encode(['errors' => [['title' => $result['message']]], 'data' => $result]);
}
$this->app->close();
return $this;
}
/** /**
* Delete a snapshot record and its data file (DELETE /api/index.php/v1/mokosuitebackup/snapshot/:id) * Delete a snapshot record and its data file (DELETE /api/index.php/v1/mokosuitebackup/snapshot/:id)
*/ */
@@ -30,7 +30,7 @@ if (!defined('JPATH_BASE')) {
require_once JPATH_BASE . '/includes/framework.php'; require_once JPATH_BASE . '/includes/framework.php';
use Joomla\CMS\Factory; use Joomla\CMS\Factory;
use Joomla\Component\MokoSuiteBackup\Administrator\Engine\BackupEngine; use Joomla\Component\MokoSuiteBackup\Administrator\Service\BackupRunner;
// Parse CLI arguments // Parse CLI arguments
$profileId = 1; $profileId = 1;
@@ -56,8 +56,7 @@ echo "Profile: {$profileId}\n";
echo "Description: {$description}\n"; echo "Description: {$description}\n";
echo "Starting backup...\n\n"; echo "Starting backup...\n\n";
$engine = new BackupEngine(); $result = (new BackupRunner())->run($profileId, $description, 'cli');
$result = $engine->run($profileId, $description, 'cli');
if ($result['success']) { if ($result['success']) {
echo "SUCCESS: " . $result['message'] . "\n"; echo "SUCCESS: " . $result['message'] . "\n";
@@ -206,6 +206,31 @@
/> />
</fieldset> </fieldset>
<fieldset name="snapshot_transfer" label="COM_MOKOJOOMBACKUP_CONFIG_SNAPSHOT_TRANSFER">
<field
name="snapshot_inject_mode"
type="list"
label="COM_MOKOJOOMBACKUP_CONFIG_SNAPSHOT_INJECT_MODE"
description="COM_MOKOJOOMBACKUP_CONFIG_SNAPSHOT_INJECT_MODE_DESC"
default="overwrite"
>
<option value="overwrite">COM_MOKOJOOMBACKUP_SNAPSHOT_MODE_OVERWRITE</option>
<option value="create">COM_MOKOJOOMBACKUP_SNAPSHOT_MODE_CREATE</option>
<option value="duplicate">COM_MOKOJOOMBACKUP_SNAPSHOT_MODE_DUPLICATE</option>
</field>
<field
name="snapshot_allow_inject"
type="radio"
label="COM_MOKOJOOMBACKUP_CONFIG_SNAPSHOT_ALLOW_INJECT"
description="COM_MOKOJOOMBACKUP_CONFIG_SNAPSHOT_ALLOW_INJECT_DESC"
default="0"
class="btn-group"
>
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
</fieldset>
<fieldset name="notifications" label="COM_MOKOJOOMBACKUP_CONFIG_NOTIFICATIONS"> <fieldset name="notifications" label="COM_MOKOJOOMBACKUP_CONFIG_NOTIFICATIONS">
<field <field
name="notify_email" name="notify_email"
@@ -205,19 +205,9 @@
/> />
</fieldset> </fieldset>
<fieldset name="remote" label="COM_MOKOJOOMBACKUP_FIELDSET_REMOTE"> <!-- The "Keep local copy" setting is now configured per remote destination
<field (in the Add/Edit Destination modal), so the profile-level field and its
name="remote_keep_local" "Remote" tab have been removed. -->
type="radio"
label="COM_MOKOJOOMBACKUP_FIELD_KEEP_LOCAL"
description="COM_MOKOJOOMBACKUP_FIELD_KEEP_LOCAL_DESC"
default="1"
class="btn-group"
>
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
</fieldset>
<fieldset name="retention" label="COM_MOKOJOOMBACKUP_FIELDSET_RETENTION"> <fieldset name="retention" label="COM_MOKOJOOMBACKUP_FIELDSET_RETENTION">
<field <field
@@ -331,9 +331,25 @@ COM_MOKOJOOMBACKUP_CONFIG_SHOW_UPDATE_NOTICE_DESC="Display the update site confi
COM_MOKOJOOMBACKUP_CONFIG_PREACTION="Pre-action Backups" COM_MOKOJOOMBACKUP_CONFIG_PREACTION="Pre-action Backups"
COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UPDATE="Backup Before Extension Update" COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UPDATE="Backup Before Extension Update"
COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UPDATE_DESC="Automatically run a full backup before any extension is updated. Uses the default profile. Throttled to once per 10 minutes to prevent duplicate backups during batch updates." COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UPDATE_DESC="Automatically run a full backup before any extension is updated. Uses the default profile. Throttled to once per 10 minutes to prevent duplicate backups during batch updates."
COM_MOKOJOOMBACKUP_CONFIG_PREUPDATE_AUTO_INTERCEPT="Auto-run on Update click"
COM_MOKOJOOMBACKUP_CONFIG_PREUPDATE_AUTO_INTERCEPT_DESC="When enabled, clicking the Update button on the Joomla Update or Extensions update page first pops the backup progress modal, runs the pre-update backup, then automatically continues the update. When disabled, a notice with a manual 'Back up now' button is shown instead."
COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UNINSTALL="Backup Before Extension Uninstall" COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UNINSTALL="Backup Before Extension Uninstall"
COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UNINSTALL_DESC="Automatically run a full backup before any extension is uninstalled. Uses the default profile. Throttled to once per 10 minutes." COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UNINSTALL_DESC="Automatically run a full backup before any extension is uninstalled. Uses the default profile. Throttled to once per 10 minutes."
; Full-screen backup progress view (runbackup)
COM_MOKOJOOMBACKUP_RUNBACKUP_TITLE="Backing Up"
COM_MOKOJOOMBACKUP_RUNBACKUP_PROFILE="Profile: %s"
COM_MOKOJOOMBACKUP_RUNBACKUP_STARTING="Starting backup…"
COM_MOKOJOOMBACKUP_RUNBACKUP_RUNNING="Backing up…"
COM_MOKOJOOMBACKUP_RUNBACKUP_COMPLETE="Backup complete"
COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUING="Backup complete — continuing…"
COM_MOKOJOOMBACKUP_RUNBACKUP_FAILED="Backup failed"
COM_MOKOJOOMBACKUP_RUNBACKUP_RETRY="Retry backup"
COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_ANYWAY="Continue without backup"
COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD="Back to dashboard"
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_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)"
COM_MOKOJOOMBACKUP_CONFIG_MAX_AGE_DESC="Default maximum age for backup records. Used by the system plugin and CLI cleanup command." COM_MOKOJOOMBACKUP_CONFIG_MAX_AGE_DESC="Default maximum age for backup records. Used by the system plugin and CLI cleanup command."
@@ -382,6 +398,8 @@ COM_MOKOJOOMBACKUP_FIELD_NOTIFY_USER_GROUPS_DESC="Select Joomla user groups whos
; Dashboard warnings ; Dashboard warnings
COM_MOKOJOOMBACKUP_DASHBOARD_DEFAULT_DIR_WARNING_TITLE="Backup directory is inside the web root" COM_MOKOJOOMBACKUP_DASHBOARD_DEFAULT_DIR_WARNING_TITLE="Backup directory is inside the web root"
COM_MOKOJOOMBACKUP_DASHBOARD_DEFAULT_DIR_WARNING="One or more profiles store backups in the default directory inside the web root. This may expose backup archives if .htaccess is not supported. Move backups to a directory outside the web root for better security." COM_MOKOJOOMBACKUP_DASHBOARD_DEFAULT_DIR_WARNING="One or more profiles store backups in the default directory inside the web root. This may expose backup archives if .htaccess is not supported. Move backups to a directory outside the web root for better security."
COM_MOKOJOOMBACKUP_DASHBOARD_LIVESITE_WARNING_TITLE="Site URL (live_site) is not set"
COM_MOKOJOOMBACKUP_DASHBOARD_LIVESITE_WARNING="Your Global Configuration has no Site URL (live_site). CLI and scheduled backups can't reliably determine the site's domain (archive names fall back to the server hostname), and some URLs may be generated incorrectly. Set Site URL in System → Global Configuration to your full site address including https://."
COM_MOKOJOOMBACKUP_WEB_ACCESSIBLE_WARNING="This backup is stored inside the web root and may be directly downloadable if .htaccess is not supported." COM_MOKOJOOMBACKUP_WEB_ACCESSIBLE_WARNING="This backup is stored inside the web root and may be directly downloadable if .htaccess is not supported."
@@ -477,6 +495,22 @@ COM_MOKOJOOMBACKUP_WEBCRON_IP_NONE="No IP restrictions — any IP can trigger we
COM_MOKOJOOMBACKUP_WEBCRON_IP_PLACEHOLDER="Enter IP address" COM_MOKOJOOMBACKUP_WEBCRON_IP_PLACEHOLDER="Enter IP address"
COM_MOKOJOOMBACKUP_WEBCRON_IP_ADD="Add" COM_MOKOJOOMBACKUP_WEBCRON_IP_ADD="Add"
; Snapshot transfer / injection (#237)
COM_MOKOJOOMBACKUP_SNAPSHOT_DOWNLOAD="Download portable snapshot (.msbsnap)"
COM_MOKOJOOMBACKUP_SNAPSHOT_NOT_FOUND="Snapshot not found."
COM_MOKOJOOMBACKUP_SNAPSHOT_IMPORT="Import Snapshot"
COM_MOKOJOOMBACKUP_SNAPSHOT_IMPORT_DESC="Upload a .msbsnap file exported from another site. It is added as a snapshot here, which you can then restore."
COM_MOKOJOOMBACKUP_SNAPSHOT_IMPORT_FILE="Snapshot file (.msbsnap)"
COM_MOKOJOOMBACKUP_SNAPSHOT_IMPORT_NO_FILE="No snapshot file was uploaded, or the upload failed."
COM_MOKOJOOMBACKUP_SNAPSHOT_MODE_OVERWRITE="Overwrite — replace matching items (master wins)"
COM_MOKOJOOMBACKUP_SNAPSHOT_MODE_CREATE="Create — skip existing, add only new items"
COM_MOKOJOOMBACKUP_SNAPSHOT_MODE_DUPLICATE="Duplicate — add as new copies (remapped IDs)"
COM_MOKOJOOMBACKUP_CONFIG_SNAPSHOT_TRANSFER="Snapshot Transfer / Injection"
COM_MOKOJOOMBACKUP_CONFIG_SNAPSHOT_INJECT_MODE="Default Conflict Mode"
COM_MOKOJOOMBACKUP_CONFIG_SNAPSHOT_INJECT_MODE_DESC="Default mode used when a snapshot is injected via the API without an explicit mode."
COM_MOKOJOOMBACKUP_CONFIG_SNAPSHOT_ALLOW_INJECT="Allow Snapshot Injection (API)"
COM_MOKOJOOMBACKUP_CONFIG_SNAPSHOT_ALLOW_INJECT_DESC="Allow a remote master site to inject snapshots directly into this site via the Web Services API (POST /snapshot/inject). Off by default; requires an API token with snapshot-manage permission."
; Snapshot browse / detail view ; Snapshot browse / detail view
COM_MOKOJOOMBACKUP_SNAPSHOT_BROWSE="Browse Snapshot" COM_MOKOJOOMBACKUP_SNAPSHOT_BROWSE="Browse Snapshot"
COM_MOKOJOOMBACKUP_SNAPSHOT_TAB_ARTICLES="Articles" COM_MOKOJOOMBACKUP_SNAPSHOT_TAB_ARTICLES="Articles"
@@ -53,9 +53,25 @@ COM_MOKOJOOMBACKUP_CONFIG_SHOW_UPDATE_NOTICE_DESC="Display the update site confi
COM_MOKOJOOMBACKUP_CONFIG_PREACTION="Pre-action Backups" COM_MOKOJOOMBACKUP_CONFIG_PREACTION="Pre-action Backups"
COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UPDATE="Backup Before Extension Update" COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UPDATE="Backup Before Extension Update"
COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UPDATE_DESC="Automatically run a full backup before any extension is updated. Uses the default profile. Throttled to once per 10 minutes to prevent duplicate backups during batch updates." COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UPDATE_DESC="Automatically run a full backup before any extension is updated. Uses the default profile. Throttled to once per 10 minutes to prevent duplicate backups during batch updates."
COM_MOKOJOOMBACKUP_CONFIG_PREUPDATE_AUTO_INTERCEPT="Auto-run on Update click"
COM_MOKOJOOMBACKUP_CONFIG_PREUPDATE_AUTO_INTERCEPT_DESC="When enabled, clicking the Update button on the Joomla Update or Extensions update page first pops the backup progress modal, runs the pre-update backup, then automatically continues the update. When disabled, a notice with a manual 'Back up now' button is shown instead."
COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UNINSTALL="Backup Before Extension Uninstall" COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UNINSTALL="Backup Before Extension Uninstall"
COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UNINSTALL_DESC="Automatically run a full backup before any extension is uninstalled. Uses the default profile. Throttled to once per 10 minutes." COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UNINSTALL_DESC="Automatically run a full backup before any extension is uninstalled. Uses the default profile. Throttled to once per 10 minutes."
; Full-screen backup progress view (runbackup)
COM_MOKOJOOMBACKUP_RUNBACKUP_TITLE="Backing Up"
COM_MOKOJOOMBACKUP_RUNBACKUP_PROFILE="Profile: %s"
COM_MOKOJOOMBACKUP_RUNBACKUP_STARTING="Starting backup…"
COM_MOKOJOOMBACKUP_RUNBACKUP_RUNNING="Backing up…"
COM_MOKOJOOMBACKUP_RUNBACKUP_COMPLETE="Backup complete"
COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUING="Backup complete — continuing…"
COM_MOKOJOOMBACKUP_RUNBACKUP_FAILED="Backup failed"
COM_MOKOJOOMBACKUP_RUNBACKUP_RETRY="Retry backup"
COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_ANYWAY="Continue without backup"
COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD="Back to dashboard"
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_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)"
COM_MOKOJOOMBACKUP_CONFIG_MAX_AGE_DESC="Default maximum age for backup records. Used by the system plugin and CLI cleanup command." COM_MOKOJOOMBACKUP_CONFIG_MAX_AGE_DESC="Default maximum age for backup records. Used by the system plugin and CLI cleanup command."
@@ -73,6 +89,8 @@ COM_MOKOJOOMBACKUP_FOLDER_NOT_FOUND="Directory not found"
COM_MOKOJOOMBACKUP_BACKUP_DIR_DEFAULT="Default (inside web root)" COM_MOKOJOOMBACKUP_BACKUP_DIR_DEFAULT="Default (inside web root)"
COM_MOKOJOOMBACKUP_DASHBOARD_DEFAULT_DIR_WARNING_TITLE="Backup directory is inside the web root" COM_MOKOJOOMBACKUP_DASHBOARD_DEFAULT_DIR_WARNING_TITLE="Backup directory is inside the web root"
COM_MOKOJOOMBACKUP_DASHBOARD_DEFAULT_DIR_WARNING="One or more profiles store backups in the default directory inside the web root. This may expose backup archives if .htaccess is not supported. Move backups to a directory outside the web root for better security." COM_MOKOJOOMBACKUP_DASHBOARD_DEFAULT_DIR_WARNING="One or more profiles store backups in the default directory inside the web root. This may expose backup archives if .htaccess is not supported. Move backups to a directory outside the web root for better security."
COM_MOKOJOOMBACKUP_DASHBOARD_LIVESITE_WARNING_TITLE="Site URL (live_site) is not set"
COM_MOKOJOOMBACKUP_DASHBOARD_LIVESITE_WARNING="Your Global Configuration has no Site URL (live_site). CLI and scheduled backups can't reliably determine the site's domain (archive names fall back to the server hostname), and some URLs may be generated incorrectly. Set Site URL in System → Global Configuration to your full site address including https://."
COM_MOKOJOOMBACKUP_WEB_ACCESSIBLE_WARNING="This backup is stored inside the web root and may be directly downloadable if .htaccess is not supported." COM_MOKOJOOMBACKUP_WEB_ACCESSIBLE_WARNING="This backup is stored inside the web root and may be directly downloadable if .htaccess is not supported."
COM_MOKOJOOMBACKUP_FOLDER_EXISTS="Directory exists" COM_MOKOJOOMBACKUP_FOLDER_EXISTS="Directory exists"
COM_MOKOJOOMBACKUP_FOLDER_NOT_FOUND="Directory not found" COM_MOKOJOOMBACKUP_FOLDER_NOT_FOUND="Directory not found"
@@ -0,0 +1,33 @@
/**
* MokoSuiteBackup — admin styles (loaded via the Joomla Web Asset Manager).
*/
/* Add/Edit Destination modal.
* The modal content is wrapped in a Bootstrap .container-fluid because
* Bootstrap's .modal-content has no padding of its own. Give that container a
* single, comfortable 1rem frame and drop the inner sections' horizontal padding
* so it isn't doubled. */
#remoteModal .modal-content > .container-fluid {
padding: 1rem;
}
#remoteModal .modal-header,
#remoteModal .modal-body,
#remoteModal .modal-footer {
padding-left: 0;
padding-right: 0;
}
#remoteModal .modal-body .row + .row,
#remoteModal .remote-type-fields {
margin-top: 0.25rem;
}
#remoteModal hr {
margin: 1rem 0;
opacity: 0.12;
}
#remoteModal .form-label {
margin-bottom: 0.35rem;
}
@@ -0,0 +1,84 @@
/*
* @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. */
.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;
}
}
@@ -0,0 +1,19 @@
{
"$schema": "https://developer.joomla.org/schemas/json-schema/web_assets.json",
"name": "com_mokosuitebackup",
"version": "1.0.0",
"description": "MokoSuiteBackup web assets",
"license": "GPL-3.0-or-later",
"assets": [
{
"name": "com_mokosuitebackup.admin",
"type": "style",
"uri": "com_mokosuitebackup/css/admin.css"
},
{
"name": "com_mokosuitebackup.runbackup",
"type": "style",
"uri": "com_mokosuitebackup/css/runbackup.css"
}
]
}
@@ -6,8 +6,18 @@
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
--> -->
<extension type="component" method="upgrade"> <extension type="component" method="upgrade">
<name>Component - MokoSuiteBackup</name> <!--
<version>02.56.07</version> IMPORTANT: a component's <name> drives the Joomla-derived extension
element (com_ + sanitized name), so it MUST stay element-safe:
"MokoSuiteBackup" -> com_mokosuitebackup. Do NOT apply the "Type - Name"
display convention here — "Component - MokoSuiteBackup" derives
com_component-mokosuitebackup, which registers a duplicate component and
orphans the real one. Packages are exempt because they set the element
via <packagename>; plugins/modules are exempt because <name> is only a
display label there.
-->
<name>MokoSuiteBackup</name>
<version>02.60.00</version>
<creationDate>2026-06-02</creationDate> <creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -37,7 +47,7 @@
</update> </update>
<administration> <administration>
<menu img="class:archive">Backup</menu> <menu img="class:archive">COM_MOKOJOOMBACKUP_SHORT</menu>
<submenu> <submenu>
<menu link="option=com_mokosuitebackup&amp;view=dashboard" <menu link="option=com_mokosuitebackup&amp;view=dashboard"
img="class:home" img="class:home"
@@ -68,6 +78,11 @@
</languages> </languages>
</administration> </administration>
<media destination="com_mokosuitebackup" folder="media">
<folder>css</folder>
<filename>joomla.asset.json</filename>
</media>
<api> <api>
<files folder="api"> <files folder="api">
<folder>src</folder> <folder>src</folder>
@@ -1,32 +1,13 @@
-- Purge legacy single-remote storage columns from installs where they are still present. -- Legacy single-remote storage columns are now purged by the package installer
-- script (Pkg_MokoSuiteBackupInstallerScript::dropLegacyRemoteColumns()), NOT here.
-- --
-- Background: 02.52.25.sql originally used `DROP COLUMN IF EXISTS`, which is a -- The 26 legacy remote_storage/ftp_*/sftp_*/gdrive_*/s3_* columns must be removed
-- MariaDB-only extension and errors on Oracle MySQL 8.x. On MySQL 8 installs the -- portably across MariaDB and MySQL 8. `DROP COLUMN IF EXISTS` is MariaDB-only
-- migration failed but Joomla still recorded the schema as applied, leaving all 26 -- (errors on MySQL 8); plain `DROP COLUMN` errors when a column is absent. The
-- legacy remote_storage/ftp_*/sftp_*/gdrive_*/s3_* columns stranded on the profiles -- previous version gated the ALTER on INFORMATION_SCHEMA and ran it via
-- table. This migration removes them portably. -- PREPARE/EXECUTE/DEALLOCATE — but those statements are rejected by Joomla's
-- installer SQL path (MySQL error 1295: "This command is not supported in the
-- prepared statement protocol yet"), which ABORTED the whole component install.
-- --
-- It must be safe on BOTH engines AND on installs where the columns are already gone -- The drop is now done in PHP (INFORMATION_SCHEMA gate + a plain ALTER for only the
-- (MariaDB, or anyone who ran the corrected 02.52.25). Plain `DROP COLUMN` errors when -- columns that still exist), so this migration is intentionally a no-op.
-- a column is absent, and `DROP COLUMN IF EXISTS` errors on MySQL 8 — so neither works
-- unconditionally. We gate the drop on INFORMATION_SCHEMA and build the ALTER via a
-- prepared statement, which runs on MySQL 8 and MariaDB alike. All 26 columns were
-- created and dropped together, so the presence of `remote_storage` gates the whole set.
-- When the columns are absent this is a no-op (`DO 0`).
SET @moko_has_legacy_remote := (
SELECT COUNT(*)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = '#__mokosuitebackup_profiles'
AND COLUMN_NAME = 'remote_storage'
);
SET @moko_drop_legacy_remote := IF(@moko_has_legacy_remote > 0,
'ALTER TABLE `#__mokosuitebackup_profiles` DROP COLUMN `remote_storage`, DROP COLUMN `ftp_host`, DROP COLUMN `ftp_port`, DROP COLUMN `ftp_username`, DROP COLUMN `ftp_password`, DROP COLUMN `ftp_path`, DROP COLUMN `ftp_passive`, DROP COLUMN `ftp_ssl`, DROP COLUMN `sftp_host`, DROP COLUMN `sftp_port`, DROP COLUMN `sftp_username`, DROP COLUMN `sftp_auth_type`, DROP COLUMN `sftp_password`, DROP COLUMN `sftp_key_data`, DROP COLUMN `sftp_passphrase`, DROP COLUMN `sftp_path`, DROP COLUMN `gdrive_client_id`, DROP COLUMN `gdrive_client_secret`, DROP COLUMN `gdrive_refresh_token`, DROP COLUMN `gdrive_folder_id`, DROP COLUMN `s3_endpoint`, DROP COLUMN `s3_region`, DROP COLUMN `s3_access_key`, DROP COLUMN `s3_secret_key`, DROP COLUMN `s3_bucket`, DROP COLUMN `s3_path`',
'DO 0'
);
PREPARE moko_stmt FROM @moko_drop_legacy_remote;
EXECUTE moko_stmt;
DEALLOCATE PREPARE moko_stmt;
@@ -0,0 +1 @@
/* 02.56.08 — no schema changes */
@@ -0,0 +1 @@
/* 02.56.11 — no schema changes */
@@ -0,0 +1 @@
/* 02.57.00 — no schema changes */
@@ -0,0 +1 @@
/* 02.57.01 — no schema changes */
@@ -0,0 +1 @@
/* 02.57.02 — no schema changes */
@@ -0,0 +1 @@
/* 02.57.04 — no schema changes */
@@ -0,0 +1 @@
/* 02.57.05 — no schema changes */
@@ -0,0 +1 @@
/* 02.57.06 — no schema changes */
@@ -0,0 +1 @@
/* 02.57.07 — no schema changes */
@@ -0,0 +1 @@
/* 02.57.08 — no schema changes */
@@ -0,0 +1 @@
/* 02.57.09 — no schema changes */
@@ -0,0 +1 @@
/* 02.57.10 — no schema changes */
@@ -0,0 +1 @@
/* 02.57.11 — no schema changes */
@@ -0,0 +1 @@
/* 02.57.12 — no schema changes */
@@ -0,0 +1 @@
/* 02.57.13 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.00 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.01 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.02 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.03 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.04 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.05 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.06 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.07 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.08 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.09 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.10 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.11 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.12 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.13 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.14 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.15 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.16 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.17 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.19 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.20 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.21 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.22 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.23 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.24 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.26 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.29 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.30 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.31 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.32 — 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 */
@@ -84,6 +84,50 @@ class AjaxController extends BaseController
$this->sendJson($result); $this->sendJson($result);
} }
/**
* Mark the pre-action backup as satisfied for this session.
*
* Called by the full-screen backup screen after a successful backup so the
* imminent server-side onExtensionBeforeUpdate / onExtensionBeforeUninstall
* backup is skipped once — it sets a ONE-SHOT skip flag (keyed by the
* `action` the backup was fronting) that plg_system_mokosuitebackup
* consumes on the re-fired update/uninstall request, so exactly that one
* action is not double-backed-up while later, distinct actions still are.
* POST: task=ajax.preupdateAck&action=update|uninstall
*/
public function preupdateAck(): void
{
if (!Session::checkToken('get') && !Session::checkToken('post')) {
$this->sendJson(['error' => true, 'message' => 'Invalid token'], 403);
return;
}
if (!$this->app->getIdentity()->authorise('mokosuitebackup.backup.run', 'com_mokosuitebackup')) {
$this->sendJson(['error' => true, 'message' => 'Access denied'], 403);
return;
}
/* 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();
if ($action === 'update') {
$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']);
}
/** /**
* Cancel a backup record stuck in "running" status. * Cancel a backup record stuck in "running" status.
* POST: task=ajax.cancelBackup&id=123 * POST: task=ajax.cancelBackup&id=123
@@ -16,8 +16,8 @@ use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\AdminController; use Joomla\CMS\MVC\Controller\AdminController;
use Joomla\CMS\Router\Route; use Joomla\CMS\Router\Route;
use Joomla\CMS\Session\Session; use Joomla\CMS\Session\Session;
use Joomla\Component\MokoSuiteBackup\Administrator\Engine\BackupEngine;
use Joomla\Component\MokoSuiteBackup\Administrator\Engine\RestoreEngine; use Joomla\Component\MokoSuiteBackup\Administrator\Engine\RestoreEngine;
use Joomla\Component\MokoSuiteBackup\Administrator\Service\BackupRunner;
class BackupsController extends AdminController class BackupsController extends AdminController
{ {
@@ -54,8 +54,7 @@ class BackupsController extends AdminController
$profileId = $this->input->getInt('profile_id', 1); $profileId = $this->input->getInt('profile_id', 1);
$description = $this->input->getString('description', ''); $description = $this->input->getString('description', '');
$engine = new BackupEngine(); $result = (new BackupRunner())->run($profileId, $description, 'backend');
$result = $engine->run($profileId, $description, 'backend');
// Surface preflight warnings as Joomla messages // Surface preflight warnings as Joomla messages
if (!empty($result['warnings'])) { if (!empty($result['warnings'])) {
@@ -18,6 +18,7 @@ use Joomla\CMS\MVC\Controller\AdminController;
use Joomla\CMS\Router\Route; use Joomla\CMS\Router\Route;
use Joomla\CMS\Session\Session; use Joomla\CMS\Session\Session;
use Joomla\Component\MokoSuiteBackup\Administrator\Engine\SnapshotEngine; use Joomla\Component\MokoSuiteBackup\Administrator\Engine\SnapshotEngine;
use Joomla\Component\MokoSuiteBackup\Administrator\Engine\SnapshotPortable;
use Joomla\Component\MokoSuiteBackup\Administrator\Engine\SnapshotRestoreEngine; use Joomla\Component\MokoSuiteBackup\Administrator\Engine\SnapshotRestoreEngine;
class SnapshotsController extends AdminController class SnapshotsController extends AdminController
@@ -29,6 +30,95 @@ class SnapshotsController extends AdminController
return parent::getModel($name, $prefix, $config); return parent::getModel($name, $prefix, $config);
} }
/**
* Download a snapshot as a portable .msbsnap file (manifest + payload + checksum).
*/
public function downloadPortable(): void
{
$this->checkToken('get');
if (!$this->app->getIdentity()->authorise('mokosuitebackup.snapshot.manage', 'com_mokosuitebackup')) {
$this->setMessage(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 'error');
$this->setRedirect(Route::_('index.php?option=com_mokosuitebackup&view=snapshots', false));
return;
}
$id = $this->input->getInt('id', 0);
$item = $id ? $this->getModel('Snapshot')->getItem($id) : null;
if (!$item || empty($item->id)) {
$this->setMessage(Text::_('COM_MOKOJOOMBACKUP_SNAPSHOT_NOT_FOUND'), 'error');
$this->setRedirect(Route::_('index.php?option=com_mokosuitebackup&view=snapshots', false));
return;
}
try {
$file = SnapshotPortable::package($item);
} catch (\Throwable $e) {
$this->setMessage($e->getMessage(), 'error');
$this->setRedirect(Route::_('index.php?option=com_mokosuitebackup&view=snapshots', false));
return;
}
$name = SnapshotPortable::filename($item);
while (@ob_end_clean()) {
// flush buffers
}
header('Content-Type: application/octet-stream');
header("Content-Disposition: attachment; filename*=UTF-8''" . rawurlencode($name));
header('Content-Length: ' . filesize($file));
header('Cache-Control: no-cache, must-revalidate');
readfile($file);
@unlink($file);
$this->app->close();
}
/**
* Import an uploaded .msbsnap file — materialise a local snapshot record.
*/
public function import(): void
{
$this->checkToken();
if (!$this->app->getIdentity()->authorise('mokosuitebackup.snapshot.manage', 'com_mokosuitebackup')) {
$this->setMessage(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 'error');
$this->setRedirect(Route::_('index.php?option=com_mokosuitebackup&view=snapshots', false));
return;
}
$file = $this->input->files->get('msbsnap_file', null, 'raw');
if (!is_array($file) || empty($file['tmp_name']) || (int) ($file['error'] ?? 1) !== 0
|| !is_uploaded_file($file['tmp_name'])) {
$this->setMessage(Text::_('COM_MOKOJOOMBACKUP_SNAPSHOT_IMPORT_NO_FILE'), 'error');
$this->setRedirect(Route::_('index.php?option=com_mokosuitebackup&view=snapshots', false));
return;
}
$result = SnapshotPortable::ingestZip($file['tmp_name']);
if (!empty($result['success'])) {
foreach ($result['warnings'] ?? [] as $warning) {
$this->app->enqueueMessage($warning, 'warning');
}
$this->setMessage($result['message']);
} else {
$this->setMessage($result['message'], 'error');
}
$this->setRedirect(Route::_('index.php?option=com_mokosuitebackup&view=snapshots', false));
}
/** /**
* Create a new content snapshot. * Create a new content snapshot.
*/ */
@@ -304,7 +304,18 @@ class BackupEngine
$this->log(' Upload complete: ' . $result['message']); $this->log(' Upload complete: ' . $result['message']);
if (!empty($restoreScriptPath) && is_file($restoreScriptPath)) { if (!empty($restoreScriptPath) && is_file($restoreScriptPath)) {
$uploader->upload($restoreScriptPath, basename($restoreScriptPath)); $scriptName = basename($restoreScriptPath);
$this->log(' Uploading restore script: ' . $scriptName . '...');
$scriptResult = $uploader->upload($restoreScriptPath, $scriptName);
if (!empty($scriptResult['success'])) {
$this->log(' Restore script uploaded: ' . ($scriptResult['message'] ?? $scriptName));
} else {
/* Never silent: a missing restore script makes the archive
much harder to restore, so surface it as a warning. */
$uploadFailed = true;
$this->log(' WARNING: Restore script upload failed: ' . ($scriptResult['message'] ?? 'unknown error'));
}
} }
} else { } else {
$uploadFailed = true; $uploadFailed = true;
@@ -500,24 +511,7 @@ class BackupEngine
*/ */
private function createUploaderFromParams(string $type, array $params): RemoteUploaderInterface private function createUploaderFromParams(string $type, array $params): RemoteUploaderInterface
{ {
$prefixMap = ['ftp' => 'ftp_', 'sftp' => 'sftp_', 's3' => 's3_', 'google_drive' => 'gdrive_']; return RemoteUploaderFactory::create($type, $params);
$prefix = $prefixMap[$type] ?? '';
$prefixed = [];
foreach ($params as $key => $value) {
$prefixed[$prefix . $key] = $value;
}
$fake = (object) $prefixed;
return match ($type) {
'ftp' => new FtpUploader($fake),
'sftp' => new SftpUploader($fake),
'google_drive' => new GoogleDriveUploader($fake),
's3' => new S3Uploader($fake),
default => throw new \InvalidArgumentException('Unknown remote storage type: ' . $type),
};
} }
/** /**
@@ -89,6 +89,52 @@ class FtpUploader implements RemoteUploaderInterface
} }
} }
public function delete(string $remoteName): array
{
if (!extension_loaded('ftp')) {
return ['success' => false, 'message' => 'PHP ext-ftp is required for FTP deletes. Enable it in php.ini.'];
}
if (empty($this->host)) {
return ['success' => false, 'message' => 'FTP host is not configured'];
}
$conn = $this->connect();
if (!$conn) {
return ['success' => false, 'message' => 'Failed to connect to FTP server ' . $this->host . ':' . $this->port];
}
try {
if (!@ftp_login($conn, $this->username, $this->password)) {
throw new \RuntimeException('FTP login failed for user: ' . $this->username);
}
if ($this->passive) {
ftp_pasv($conn, true);
}
$remoteFile = $this->remotePath . '/' . $remoteName;
/* Already-absent files: ftp_delete returns false, but if the file
isn't there the retention goal is already met, so probe size to
decide. ftp_size returns -1 when the file does not exist. */
if (@ftp_size($conn, $remoteFile) < 0) {
return ['success' => true, 'message' => 'FTP: nothing to delete (' . $remoteFile . ')'];
}
if (!@ftp_delete($conn, $remoteFile)) {
throw new \RuntimeException('ftp_delete failed for: ' . $remoteFile);
}
return ['success' => true, 'message' => 'Deleted from FTP: ' . $remoteFile];
} catch (\Throwable $e) {
return ['success' => false, 'message' => 'FTP delete failed: ' . $e->getMessage()];
} finally {
@ftp_close($conn);
}
}
public function testConnection(): array public function testConnection(): array
{ {
if (empty($this->host)) { if (empty($this->host)) {
@@ -81,6 +81,59 @@ class GoogleDriveUploader implements RemoteUploaderInterface
} }
} }
public function delete(string $remoteName): array
{
if (!extension_loaded('curl')) {
return ['success' => false, 'message' => 'PHP ext-curl is required for Google Drive deletes. Enable it in php.ini.'];
}
if (empty($this->clientId) || empty($this->refreshToken)) {
return ['success' => false, 'message' => 'Google Drive credentials not configured'];
}
try {
$accessToken = $this->getAccessToken();
/* Drive has no path addressing — resolve the file id(s) by name
within the configured folder, then delete each match. */
$q = "name = '" . str_replace("'", "\\'", $remoteName) . "' and trashed = false";
if (!empty($this->folderId)) {
$q .= " and '" . $this->folderId . "' in parents";
}
$listUrl = self::API_URL . '/files?q=' . rawurlencode($q) . '&fields=' . rawurlencode('files(id,name)') . '&pageSize=25';
$response = $this->curlRequest('GET', $listUrl, null, [
'Authorization: Bearer ' . $accessToken,
]);
if ($response['code'] !== 200) {
throw new \RuntimeException('Drive file lookup failed (HTTP ' . $response['code'] . ')');
}
$files = json_decode($response['body'], true)['files'] ?? [];
if (empty($files)) {
return ['success' => true, 'message' => 'Google Drive: nothing to delete (' . $remoteName . ')'];
}
foreach ($files as $file) {
$del = $this->curlRequest('DELETE', self::API_URL . '/files/' . rawurlencode($file['id']), null, [
'Authorization: Bearer ' . $accessToken,
]);
/* 204 = deleted, 404 = already gone — both acceptable. */
if ($del['code'] !== 204 && $del['code'] !== 200 && $del['code'] !== 404) {
return ['success' => false, 'message' => 'Google Drive delete failed for ' . $remoteName . ' (HTTP ' . $del['code'] . ')'];
}
}
return ['success' => true, 'message' => 'Deleted from Google Drive: ' . $remoteName];
} catch (\Throwable $e) {
return ['success' => false, 'message' => 'Google Drive delete failed: ' . $e->getMessage()];
}
}
public function testConnection(): array public function testConnection(): array
{ {
if (empty($this->clientId) || empty($this->refreshToken)) { if (empty($this->clientId) || empty($this->refreshToken)) {
@@ -204,7 +204,8 @@ ORIG,
'ok' => $backupCount > 0, 'ok' => $backupCount > 0,
'hint' => 'Place one or more backup ZIP files in the same directory as ' . basename($_SERVER['SCRIPT_NAME']), 'hint' => 'Place one or more backup ZIP files in the same directory as ' . basename($_SERVER['SCRIPT_NAME']),
]; ];
REPL REPL,
$php
); );
/* Modify remaining pre-checks to use getSelectedBackupFile() */ /* Modify remaining pre-checks to use getSelectedBackupFile() */
@@ -52,15 +52,25 @@ class PlaceholderResolver
{ {
$now = new \DateTimeImmutable('now'); $now = new \DateTimeImmutable('now');
/* Resolve hostname: prefer HTTP_HOST (web), then try Joomla config (CLI), then system hostname */ /* Resolve hostname: prefer the real request host (web), then the
$rawHost = $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? ''; configured live_site (CLI), then the system hostname. Joomla's console
fills the request host with the placeholder 'joomla.invalid' (an
RFC 6761 reserved TLD used as a CLI sentinel), so treat that — along
with empty/localhost — as unusable and fall through; otherwise every
CLI-triggered backup would be named 'joomla.invalid_…'. */
$unusable = static function (string $h): bool {
$h = strtolower(trim($h));
if (empty($rawHost) || $rawHost === 'localhost') { return $h === '' || $h === 'localhost' || $h === 'joomla.invalid';
};
$rawHost = (string) ($_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? '');
if ($unusable($rawHost)) {
try { try {
$app = Factory::getApplication(); $liveSite = (string) Factory::getApplication()->get('live_site', '');
$liveSite = $app->get('live_site', '');
if (!empty($liveSite)) { if ($liveSite !== '') {
$parsed = parse_url($liveSite, PHP_URL_HOST); $parsed = parse_url($liveSite, PHP_URL_HOST);
if (!empty($parsed)) { if (!empty($parsed)) {
@@ -68,12 +78,13 @@ class PlaceholderResolver
} }
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
/* fallback */ /* fall through to system hostname */
} }
} }
if (empty($rawHost)) { if ($unusable($rawHost)) {
$rawHost = php_uname('n'); $sysHost = php_uname('n');
$rawHost = $sysHost !== '' ? $sysHost : 'site';
} }
$hostname = preg_replace('/[^a-zA-Z0-9._-]/', '', $rawHost); $hostname = preg_replace('/[^a-zA-Z0-9._-]/', '', $rawHost);

Some files were not shown because too many files have changed in this diff Show More