Compare commits

...

151 Commits

Author SHA1 Message Date
mokogitea-actions[bot] 8df658c5da chore(version): pre-release bump to 02.64.02-dev [skip ci] 2026-07-14 20:40:49 +00:00
gitea-actions[bot] 6c79443be0 chore(version): auto-bump patch 02.64.01-dev [skip ci] 2026-07-14 20:40:32 +00:00
gitea-actions[bot] 78d120d428 chore: promote changelog [Unreleased] → [02.64.00]
Generic: Standards Compliance / Secret Scanning (push) Failing after 4s
Generic: Standards Compliance / License Header Validation (push) Successful in 3s
Generic: Standards Compliance / Repository Structure Validation (push) Successful in 3s
Generic: Standards Compliance / Coding Standards Check (push) Successful in 4s
Generic: Standards Compliance / Workflow Configuration Check (push) Failing after 4s
Generic: Standards Compliance / Documentation Quality Check (push) Successful in 3s
Generic: Standards Compliance / README Completeness Check (push) Failing after 3s
Generic: Standards Compliance / Git Repository Hygiene (push) Successful in 4s
Generic: Standards Compliance / Script Integrity Validation (push) Successful in 5s
Generic: Standards Compliance / Line Length Check (push) Successful in 3s
Generic: Standards Compliance / File Naming Standards (push) Successful in 4s
Generic: Standards Compliance / Version Consistency Check (push) Successful in 41s
Generic: Standards Compliance / Insecure Code Pattern Detection (push) Successful in 4s
Generic: Standards Compliance / Dead Code Detection (push) Successful in 6s
Generic: Standards Compliance / File Size Limits (push) Successful in 4s
Generic: Standards Compliance / Binary File Detection (push) Successful in 6s
Generic: Standards Compliance / TODO/FIXME Tracking (push) Successful in 3s
Generic: Standards Compliance / Code Complexity Analysis (push) Successful in 43s
Generic: Standards Compliance / Code Duplication Detection (push) Successful in 41s
Generic: Standards Compliance / Broken Link Detection (push) Successful in 3s
Generic: Standards Compliance / API Documentation Coverage (push) Successful in 3s
Generic: Standards Compliance / Accessibility Check (push) Successful in 4s
Generic: Standards Compliance / Performance Metrics (push) Successful in 3s
Generic: Standards Compliance / Dependency Vulnerability Scanning (push) Successful in 41s
Generic: Standards Compliance / Unused Dependencies Check (push) Successful in 50s
Generic: Standards Compliance / Enterprise Readiness Check (push) Failing after 42s
Generic: Standards Compliance / Terraform Configuration Validation (push) Successful in 10s
Generic: Standards Compliance / Repository Health Check (push) Failing after 45s
Universal: Auto Version Bump / Version Bump (push) Successful in 9s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 25s
Generic: Standards Compliance / Compliance Summary (push) Has been cancelled
2026-07-14 20:33:23 +00:00
gitea-actions[bot] aa2473e4ac chore(release): build 02.64.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-14 20:33:14 +00:00
jmiller 78556d2775 Merge pull request 'Release: dev -> main (runbackup UX + safety)' (#273) from dev into main
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Secret Scan (pull_request) Successful in 5s
Generic: Project CI / Lint & Validate (pull_request) Successful in 10s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
Generic: Standards Compliance / Secret Scanning (push) Failing after 5s
Generic: Standards Compliance / License Header Validation (push) Successful in 4s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 10s
Generic: Standards Compliance / Repository Structure Validation (push) Successful in 4s
Generic: Standards Compliance / Coding Standards Check (push) Successful in 5s
Generic: Standards Compliance / Workflow Configuration Check (push) Failing after 4s
Generic: Standards Compliance / Documentation Quality Check (push) Successful in 4s
Generic: Standards Compliance / README Completeness Check (push) Failing after 5s
Generic: Standards Compliance / Git Repository Hygiene (push) Successful in 6s
Generic: Standards Compliance / Script Integrity Validation (push) Successful in 6s
Generic: Standards Compliance / Line Length Check (push) Successful in 5s
Generic: Standards Compliance / File Naming Standards (push) Successful in 3s
Generic: Standards Compliance / Insecure Code Pattern Detection (push) Successful in 3s
Generic: Standards Compliance / Version Consistency Check (push) Successful in 50s
Generic: Standards Compliance / Dead Code Detection (push) Successful in 5s
Generic: Standards Compliance / File Size Limits (push) Successful in 3s
Generic: Standards Compliance / Binary File Detection (push) Successful in 6s
Generic: Standards Compliance / TODO/FIXME Tracking (push) Successful in 4s
Generic: Standards Compliance / Code Complexity Analysis (push) Successful in 50s
Generic: Standards Compliance / Code Duplication Detection (push) Successful in 50s
Generic: Standards Compliance / Broken Link Detection (push) Successful in 3s
Generic: Standards Compliance / API Documentation Coverage (push) Successful in 4s
Generic: Standards Compliance / Accessibility Check (push) Successful in 4s
Generic: Standards Compliance / Performance Metrics (push) Successful in 4s
Generic: Standards Compliance / Dependency Vulnerability Scanning (push) Successful in 44s
Generic: Standards Compliance / Unused Dependencies Check (push) Successful in 50s
Generic: Standards Compliance / Terraform Configuration Validation (push) Successful in 8s
Generic: Standards Compliance / Enterprise Readiness Check (push) Failing after 42s
Generic: Standards Compliance / Repository Health Check (push) Failing after 42s
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
Generic: Standards Compliance / Compliance Summary (push) Has been cancelled
2026-07-14 20:32:59 +00:00
mokogitea-actions[bot] 46dabae61c chore(version): pre-release bump to 02.63.11-dev [skip ci]
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 27s
2026-07-14 20:32:52 +00:00
gitea-actions[bot] e21574a917 chore(version): auto-bump patch 02.63.10-dev [skip ci] 2026-07-14 20:32:41 +00:00
jmiller 79a914f930 Merge remote-tracking branch 'origin/dev' into dev
Universal: Auto Version Bump / Version Bump (push) Successful in 8s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 19s
2026-07-14 15:32:27 -05:00
jmiller fe90f0024b @
chore: back-merge main into dev (reconcile release version stamps)

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
@
2026-07-14 15:31:52 -05:00
mokogitea-actions[bot] e9eb94ccfd chore(version): pre-release bump to 02.63.09-dev [skip ci] 2026-07-14 20:30:44 +00:00
jmiller 7e1a76a7cb Merge pull request 'fix(runbackup): failed pre-update backup blocks the update' (#272) from fix/no-update-on-error into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 19s
2026-07-14 20:30:15 +00:00
jmiller c48fd3b8eb fix(runbackup): a failed pre-update backup blocks the update
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Generic: Project CI / Tests (pull_request) Failing after 7s
Generic: Project CI / Lint & Validate (pull_request) Successful in 11s
Universal: PR Check / Secret Scan (pull_request) Successful in 6s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 21s
Universal: PR Check / Validate PR (pull_request) Failing after 18s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
If the pre-update/uninstall backup fails, the update must not run. The
completion screen no longer offers "Continue without backup" on error —
the only options are Retry (re-run the backup) or Back to dashboard (bail
out without updating). The status message makes clear the update was not
started.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-14 15:30:03 -05:00
mokogitea-actions[bot] f5423bab5f chore(version): pre-release bump to 02.63.07-dev [skip ci] 2026-07-14 20:18:42 +00:00
jmiller 4b17842496 Merge pull request 'feat(runbackup): cancel button with double confirmation' (#271) from feat/runbackup-cancel 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 22s
2026-07-14 20:18:25 +00:00
jmiller 85c0967e89 feat(runbackup): cancel button with double confirmation
Generic: Project CI / Lint & Validate (pull_request) Successful in 9s
Generic: Project CI / Tests (pull_request) Failing after 5s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Universal: PR Check / Secret Scan (pull_request) Successful in 5s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 7s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 16s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Adds a "Cancel backup" button on the backup progress screen (visible while
the backup runs). Cancelling requires TWO confirmations; on confirm it
stops the stepped-backup loop, best-effort cancels the record server-side
(ajax.cancelBackup), and returns to the dashboard WITHOUT continuing any
pending update. The button is hidden once the backup completes or fails.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-14 15:17:38 -05:00
mokogitea-actions[bot] d89812cbfb chore(version): pre-release bump to 02.63.06-dev [skip ci] 2026-07-14 15:35:56 +00:00
jmiller 79efde483e Merge pull request 'feat(runbackup): do-not-close warning + update-running overlay' (#270) from feat/runbackup-dontclose-warning into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 23s
2026-07-14 15:35:39 +00:00
jmiller c567890bfa feat(runbackup): "do not close window" warning + "update is running" overlay
Generic: Project CI / Lint & Validate (pull_request) Successful in 10s
Generic: Project CI / Tests (pull_request) Failing after 5s
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Universal: PR Check / Secret Scan (pull_request) Successful in 6s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 7s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 17s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
- Backup progress screen shows a prominent warning not to close or switch
  windows until the backup finishes (hidden once it completes/errors).
- After the backup hands back to an extension update, a full-screen
  "update is running — please wait" overlay is shown while the re-fired
  update POST processes, so the user knows it is working (not stuck).

Adds language strings (component + plugin) and a cfg.updateRunningText option.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-14 10:35:23 -05:00
mokogitea-actions[bot] ba070f004d chore(version): pre-release bump to 02.63.05-dev [skip ci] 2026-07-14 15:13:02 +00:00
jmiller ce15cecf85 Merge pull request 'fix(installer): extension update auto-continues after pre-update backup' (#269) from fix/extension-refire 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-14 15:12:30 +00:00
jmiller 2b4af3ce28 @
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 21s
Generic: Project CI / Tests (pull_request) Failing after 5s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 9s
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Universal: PR Check / Secret Scan (pull_request) Successful in 5s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 8s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 18s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
fix(installer): extension update now auto-continues after pre-update backup

Two bugs in the client-side resume path made the extension update return
to the selection screen (requiring a manual re-select + re-submit) instead
of continuing after the backup:

1. The re-fire clicked the OUTER <joomla-toolbar-button> (which carries the
   task attr), but the web component binds its click handler to the INNER
   <button>. Clicking the wrapper did nothing. Now clicks the inner element.
2. list-selection toolbar buttons start DISABLED and only enable when
   boxchecked fires a `change` event. We set boxchecked.value but never
   dispatched change, so the button stayed disabled and executeTask() no-opd.
2026-07-14 10:11:32 -05:00
jmiller a1cba42f43 chore: sync version-set.yml from Template-Joomla [skip ci] 2026-07-14 04:45:47 +00:00
jmiller 9eef484585 chore: sync repo-health.yml from Template-Joomla [skip ci] 2026-07-14 04:45:46 +00:00
jmiller ff14a1dcba chore: sync rc-revert.yml from Template-Joomla [skip ci] 2026-07-14 04:45:45 +00:00
jmiller 925b27f2dc chore: sync pre-release.yml from Template-Joomla [skip ci] 2026-07-14 04:45:44 +00:00
jmiller 4e519b4e7c chore: sync pr-metadata-check.yml from Template-Joomla [skip ci] 2026-07-14 04:45:43 +00:00
jmiller 118c1cbe2f chore: sync pr-check.yml from Template-Joomla [skip ci] 2026-07-14 04:45:42 +00:00
jmiller 05aec9b315 chore: sync notify.yml from Template-Joomla [skip ci] 2026-07-14 04:45:40 +00:00
jmiller b345e42cd0 chore: sync gitleaks.yml from Template-Joomla [skip ci] 2026-07-14 04:45:39 +00:00
jmiller f99e39efa3 chore: sync ci-joomla.yml from Template-Joomla [skip ci] 2026-07-14 04:45:38 +00:00
jmiller ae5e1f48dd chore: sync ci-issue-reporter.yml from Template-Joomla [skip ci] 2026-07-14 04:45:36 +00:00
jmiller 2ddd6c27cd chore: sync ci-generic.yml from Template-Joomla [skip ci] 2026-07-14 04:45:35 +00:00
jmiller 362e75a9a8 chore: sync cascade-dev.yml from Template-Joomla [skip ci] 2026-07-14 04:45:34 +00:00
jmiller cb4c227853 chore: sync branch-cleanup.yml from Template-Joomla [skip ci] 2026-07-14 04:45:33 +00:00
jmiller b8e8487f6b chore: sync auto-release.yml from Template-Joomla [skip ci] 2026-07-14 04:45:32 +00:00
jmiller 7abe3ef035 chore: sync auto-bump.yml from Template-Joomla [skip ci] 2026-07-14 04:45:31 +00:00
jmiller 344881809d Merge pull request 'chore(ci): remove template-only sync workflows from root' (#268) from chore/remove-template-sync-leaks into main
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
Cascade Main -> Dev / Cascade main -> dev (push) Has been cancelled
Generic: Standards Compliance / Secret Scanning (push) Has been cancelled
Generic: Standards Compliance / License Header Validation (push) Has been cancelled
Generic: Standards Compliance / Repository Structure Validation (push) Has been cancelled
Generic: Standards Compliance / Coding Standards Check (push) Has been cancelled
Generic: Standards Compliance / Version Consistency Check (push) Has been cancelled
Generic: Standards Compliance / Workflow Configuration Check (push) Has been cancelled
Generic: Standards Compliance / Documentation Quality Check (push) Has been cancelled
Generic: Standards Compliance / README Completeness Check (push) Has been cancelled
Generic: Standards Compliance / Git Repository Hygiene (push) Has been cancelled
Generic: Standards Compliance / Script Integrity Validation (push) Has been cancelled
Generic: Standards Compliance / Line Length Check (push) Has been cancelled
Generic: Standards Compliance / File Naming Standards (push) Has been cancelled
Generic: Standards Compliance / Insecure Code Pattern Detection (push) Has been cancelled
Generic: Standards Compliance / Code Complexity Analysis (push) Has been cancelled
Generic: Standards Compliance / Code Duplication Detection (push) Has been cancelled
Generic: Standards Compliance / Dead Code Detection (push) Has been cancelled
Generic: Standards Compliance / File Size Limits (push) Has been cancelled
Generic: Standards Compliance / Binary File Detection (push) Has been cancelled
Generic: Standards Compliance / TODO/FIXME Tracking (push) Has been cancelled
Generic: Standards Compliance / Dependency Vulnerability Scanning (push) Has been cancelled
Generic: Standards Compliance / Unused Dependencies Check (push) Has been cancelled
Generic: Standards Compliance / Broken Link Detection (push) Has been cancelled
Generic: Standards Compliance / API Documentation Coverage (push) Has been cancelled
Generic: Standards Compliance / Accessibility Check (push) Has been cancelled
Generic: Standards Compliance / Performance Metrics (push) Has been cancelled
Generic: Standards Compliance / Enterprise Readiness Check (push) Has been cancelled
Generic: Standards Compliance / Repository Health Check (push) Has been cancelled
Generic: Standards Compliance / Terraform Configuration Validation (push) 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: Standards Compliance / Compliance Summary (push) Has been cancelled
2026-07-13 20:46:34 +00:00
jmiller 47d4023108 chore(ci): remove template-only sync workflows from root
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 24s
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
Generic: Standards Compliance / Secret Scanning (pull_request) Has been cancelled
Generic: Standards Compliance / License Header Validation (pull_request) Has been cancelled
Generic: Standards Compliance / Repository Structure Validation (pull_request) Has been cancelled
Generic: Standards Compliance / Coding Standards Check (pull_request) Has been cancelled
Generic: Standards Compliance / Version Consistency Check (pull_request) Has been cancelled
Generic: Standards Compliance / Workflow Configuration Check (pull_request) Has been cancelled
Generic: Standards Compliance / Documentation Quality Check (pull_request) Has been cancelled
Generic: Standards Compliance / README Completeness Check (pull_request) Has been cancelled
Generic: Standards Compliance / Git Repository Hygiene (pull_request) Has been cancelled
Generic: Standards Compliance / Script Integrity Validation (pull_request) Has been cancelled
Generic: Standards Compliance / Line Length Check (pull_request) Has been cancelled
Generic: Standards Compliance / File Naming Standards (pull_request) Has been cancelled
Generic: Standards Compliance / Insecure Code Pattern Detection (pull_request) Has been cancelled
Generic: Standards Compliance / Code Complexity Analysis (pull_request) Has been cancelled
Generic: Standards Compliance / Code Duplication Detection (pull_request) Has been cancelled
Generic: Standards Compliance / Dead Code Detection (pull_request) Has been cancelled
Generic: Standards Compliance / File Size Limits (pull_request) Has been cancelled
Generic: Standards Compliance / Binary File Detection (pull_request) Has been cancelled
Generic: Standards Compliance / TODO/FIXME Tracking (pull_request) Has been cancelled
Generic: Standards Compliance / Dependency Vulnerability Scanning (pull_request) Has been cancelled
Generic: Standards Compliance / Unused Dependencies Check (pull_request) Has been cancelled
Generic: Standards Compliance / Broken Link Detection (pull_request) Has been cancelled
Generic: Standards Compliance / API Documentation Coverage (pull_request) Has been cancelled
Generic: Standards Compliance / Accessibility Check (pull_request) Has been cancelled
Generic: Standards Compliance / Performance Metrics (pull_request) Has been cancelled
Generic: Standards Compliance / Enterprise Readiness Check (pull_request) Has been cancelled
Generic: Standards Compliance / Repository Health Check (pull_request) Has been cancelled
Generic: Standards Compliance / Terraform Configuration Validation (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
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
Generic: Standards Compliance / Compliance Summary (pull_request) Has been cancelled
Template-only (gated Template-*), dead no-ops in child repos, inherited at scaffold. Real sync lives in templates' workflows/custom/.
2026-07-13 20:46:29 +00:00
jmiller 373662b0d3 chore: sync standards-compliance.yml from Template-Generic [skip ci] 2026-07-13 19:51:49 +00:00
jmiller c7ea43edcf chore: sync pr-check.yml from Template-Generic [skip ci] 2026-07-13 16:22:34 +00:00
jmiller dbdb3a0e8a chore: sync cascade-dev.yml from Template-Generic [skip ci] 2026-07-13 16:22:32 +00:00
jmiller 1127b23d1e chore: sync auto-release.yml from Template-Generic [skip ci] 2026-07-13 16:22:30 +00:00
mokogitea-actions[bot] 1c41cdc03b chore(version): pre-release bump to 02.63.02-dev [skip ci] 2026-07-13 15:56:55 +00:00
jmiller 79b23215eb Merge pull request 'feat(runbackup): record link new window + harden leave warning' (#267) from feat/runbackup-record-newtab 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-13 15:56:37 +00:00
jmiller bd2effd89c @
Generic: Project CI / Tests (pull_request) Failing after 5s
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
Generic: Project CI / Lint & Validate (pull_request) Successful in 8s
Universal: PR Check / Secret Scan (pull_request) Successful in 5s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 7s
Universal: PR Check / Validate PR (pull_request) Failing after 17s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
feat(runbackup): open record link in new window; harden leave warning

- The "View backup record" link on the pre-update completion screen now
  opens in a new window (target=_blank rel=noopener), so viewing the
  record does not leave the "Continue the update" flow.
- beforeunload leave-warning now also returns the message (broader browser
  support) so closing the window mid-backup reliably prompts a warning.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
@
2026-07-13 10:56:29 -05:00
jmiller 1c646332e2 chore: sync workflow-sync-trigger.yml from Template-Generic [skip ci] 2026-07-13 15:50:07 +00:00
jmiller 2deaa4faa4 chore: sync version-set.yml from Template-Generic [skip ci] 2026-07-13 15:50:07 +00:00
jmiller 629b8be470 chore: sync sync-on-merge.yml from Template-Generic [skip ci] 2026-07-13 15:50:06 +00:00
jmiller d2dd8a8da4 chore: sync standards-compliance.yml from Template-Generic [skip ci] 2026-07-13 15:50:05 +00:00
jmiller ba90ce3365 chore: sync repo-health.yml from Template-Generic [skip ci] 2026-07-13 15:50:04 +00:00
jmiller 2de8ab942b chore: sync rc-revert.yml from Template-Generic [skip ci] 2026-07-13 15:50:04 +00:00
jmiller 3ccad868bf chore: sync pre-release.yml from Template-Generic [skip ci] 2026-07-13 15:50:03 +00:00
jmiller 4606f21a05 chore: sync pr-check.yml from Template-Generic [skip ci] 2026-07-13 15:50:02 +00:00
jmiller aae23b46f0 chore: sync notify.yml from Template-Generic [skip ci] 2026-07-13 15:50:01 +00:00
jmiller 08135beaf0 chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-07-13 15:50:01 +00:00
jmiller 2cd83e0369 chore: sync gitleaks.yml from Template-Generic [skip ci] 2026-07-13 15:50:00 +00:00
jmiller a52ed25325 chore: sync cleanup.yml from Template-Generic [skip ci] 2026-07-13 15:49:59 +00:00
jmiller 3c00fc2422 chore: sync ci-issue-reporter.yml from Template-Generic [skip ci] 2026-07-13 15:49:58 +00:00
jmiller c08015c324 chore: sync ci-generic.yml from Template-Generic [skip ci] 2026-07-13 15:49:58 +00:00
jmiller 90aa2cd58b chore: sync cascade-dev.yml from Template-Generic [skip ci] 2026-07-13 15:49:57 +00:00
jmiller f17367456a chore: sync branch-cleanup.yml from Template-Generic [skip ci] 2026-07-13 15:49:57 +00:00
jmiller 92c4818410 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-07-13 15:49:56 +00:00
jmiller a5db8fba5b chore: sync auto-bump.yml from Template-Generic [skip ci] 2026-07-13 15:49:55 +00:00
jmiller a133d80f5f chore: sync standards-compliance.yml from Template-Generic [skip ci] 2026-07-13 15:29:14 +00:00
mokogitea-actions[bot] 6da7ef0b69 chore(version): pre-release bump to 02.63.01-dev [skip ci] 2026-07-13 15:06:15 +00:00
jmiller 65cf9fbf9f chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-07-13 10:12:01 +00:00
gitea-actions[bot] 29e5a8c85b chore(release): build 02.63.00 [skip ci] 2026-07-13 09:51:34 +00:00
mokogitea-actions[bot] cbc8627fb7 chore(version): pre-release bump to 02.62.05-dev [skip ci] 2026-07-13 09:50:29 +00:00
gitea-actions[bot] f1dfc18f3d chore(version): auto-bump patch 02.62.04-dev [skip ci] 2026-07-13 09:50:15 +00:00
jmiller 17ff3ce702 Merge pull request 'Release: dev -> main' (#265) from dev into main
Sync Workflows to Repos / sync (push) Has been skipped
Cascade Main -> Dev / Cascade main -> dev (push) Successful in 3s
Generic: Project CI / Lint & Validate (pull_request) Successful in 9s
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
Generic: Project CI / Tests (pull_request) Failing after 5s
Universal: PR Check / Secret Scan (pull_request) Successful in 4s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 7s
Universal: PR Check / Validate PR (pull_request) Failing after 19s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
2026-07-13 09:39:38 +00:00
mokogitea-actions[bot] 20fcd1c39c Merge remote-tracking branch 'origin/main' into dev
Generic: Project CI / Tests (pull_request) Failing after 6s
Generic: Project CI / Lint & Validate (pull_request) Successful in 9s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 4s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 6s
Universal: PR Check / Wiki Update Reminder (pull_request) Successful in 2s
Universal: PR Check / Require Docs Update (pull_request) Failing after 5s
Universal: PR Check / Secret Scan (pull_request) Successful in 5s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 7s
Universal: PR Check / Validate PR (pull_request) Failing after 18s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Has been skipped
Universal: Auto Version Bump / Version Bump (push) Successful in 9s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 22s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 25s
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Has been cancelled
Joomla: Extension CI / PHPStan Analysis (pull_request) Has been cancelled
Joomla: Extension CI / Build RC Pre-Release (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
# Conflicts:
#	.mokogitea/workflows/issue-branch.yml
2026-07-13 04:39:03 -05:00
mokogitea-actions[bot] 3fb82bc03c chore(version): pre-release bump to 02.62.03-dev [skip ci] 2026-07-13 09:37:20 +00:00
mokogitea-actions[bot] 499436a8d0 chore(version): pre-release bump to 02.62.02-dev [skip ci] 2026-07-13 09:36:51 +00:00
jmiller a004b9c0d9 chore(sync): CI notify retry + JEXEC whole-file scan from Template-Generic
Universal: Auto Version Bump / Version Bump (push) Successful in 8s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 24s
2026-07-13 09:31:49 +00:00
jmiller 5b1cf9ea8e chore(sync): CI notify retry + JEXEC whole-file scan from Template-Generic
Universal: Auto Version Bump / Version Bump (push) Successful in 7s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 19s
2026-07-13 09:31:48 +00:00
jmiller 86fe470158 chore: sync pr-check.yml from Template-Generic [skip ci] 2026-07-13 09:28:49 +00:00
jmiller a6e196ffd0 chore: sync notify.yml from Template-Generic [skip ci] 2026-07-13 09:28:48 +00:00
jmiller d43b9405c0 chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-07-13 09:28:47 +00:00
mokogitea-actions[bot] 8506573898 chore(version): pre-release bump to 02.62.01-dev [skip ci] 2026-07-13 08:47:36 +00:00
jmiller 6ad5046f82 Merge pull request 'chore(sync): cascade main -> dev' (#264) from main into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 17s
2026-07-13 08:47:20 +00:00
gitea-actions[bot] 20a760097b chore(release): build 02.62.00 [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
2026-07-13 08:47:15 +00:00
jmiller b378794a05 Merge pull request 'Release: dev → main (auto-continue checkbox + PHP 8.3)' (#263) from dev into main
Sync Workflows to Repos / sync (push) Has been skipped
Cascade Main -> Dev / Cascade main -> dev (push) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Generic: Project CI / Tests (pull_request) Failing after 6s
Universal: PR Check / Secret Scan (pull_request) Successful in 5s
Generic: Project CI / Lint & Validate (pull_request) Successful in 10s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 8s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
2026-07-13 08:46:59 +00:00
mokogitea-actions[bot] dc23f4dd5e chore(version): pre-release bump to 02.61.06-dev [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 23s
2026-07-13 01:52:14 +00:00
mokogitea-actions[bot] 9a5f4dbbb2 chore(version): pre-release bump to 02.61.05-dev [skip ci] 2026-07-13 01:51:29 +00:00
mokogitea-actions[bot] 45066237b0 chore(version): pre-release bump to 02.61.04-dev [skip ci] 2026-07-13 01:51:05 +00:00
jmiller 2296c2f754 Merge pull request 'fix: require PHP 8.3 (Joomla 6)' (#262) from fix/php83-v2 into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 19s
2026-07-13 01:47:12 +00:00
jmiller 7416c30925 @
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 4s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Generic: Project CI / Tests (pull_request) Failing after 5s
Universal: PR Check / Secret Scan (pull_request) Successful in 5s
Generic: Project CI / Lint & Validate (pull_request) Successful in 8s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 9s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
docs: update CLAUDE.md MVC/DI notes from Joomla 4/5 to Joomla 6

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

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

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
@
2026-07-12 20:46:35 -05:00
jmiller 0a60436f3d Merge pull request 'feat(runbackup): opt-in auto-continue + manual continue' (#261) from feat/runbackup-autocontinue into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 19s
2026-07-13 01:44:55 +00:00
jmiller a2362d5400 @
Generic: Project CI / Tests (pull_request) Failing after 4s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 8s
Universal: PR Check / Secret Scan (pull_request) Successful in 5s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 7s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
feat(runbackup): opt-in auto-continue + manual continue on pre-update screen

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
@
2026-07-12 18:13:51 -05:00
mokogitea-actions[bot] d9d6ee9181 chore(version): pre-release bump to 02.59.02-dev [skip ci] 2026-07-12 22:48:45 +00:00
jmiller f02c4d4c9d Merge pull request 'fix: pre-action backup fires for every extension (not one per 10 min)' (#250) from fix/preaction-per-action-backup into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 17s
2026-07-12 22:48:33 +00:00
mokogitea-actions[bot] 2df4b280eb chore(version): pre-release bump to 02.59.01-dev [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
RC Revert / Rename rc/ back to dev/ (pull_request) Has been cancelled
2026-07-12 22:48:27 +00:00
jmiller 3eb2562abd @
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 18s
fix: pre-action backup fires for every extension, not one per 10 min

Root cause of "pre-update backup only fires on the backup extension":
a coarse 600s session throttle. Whenever ANY pre-action backup ran
(including updating MokoSuiteBackup itself, or a core Joomla update), the
key mokosuitebackup.preaction_backup_before_update was armed for 10
minutes. onBeforeCompileHead read it as `recentBackup` and DISABLED the
client-side interceptor for all extensions, and runPreActionBackup was
throttled by the same window. So after updating one extension, every
other extension update silently skipped its backup for 10 minutes.

Replace the time window with "one backup per Update action":
- runPreActionBackup dedupes per-request (a batch update fires the event
  once per extension in a single request -> back up once) via instance
  flags, and consumes a ONE-SHOT session skip flag for the re-fired
  client update/uninstall (skip once, next distinct action backs up).
- ajax.preupdateAck sets the one-shot flag for the SPECIFIC action
  (update|uninstall), passed from installer-backup.js via &msb_action and
  surfaced by Runbackup HtmlView -> CFG.action. A core update passes no
  action, so it never suppresses an extension backup.
- onBeforeCompileHead drops the recentBackup time-suppression (the
  re-fire loop is already guarded client-side by window.__msbResuming).
- The core Joomla update path uses its own key
  (mokosuitebackup.core_update_backed_up), decoupled from extensions.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
@
2026-07-12 17:48:09 -05:00
gitea-actions[bot] 9a02b7d903 chore(release): build 02.59.00 [skip ci] 2026-07-12 22:19:05 +00:00
jmiller d4fca16a13 Merge pull request 'Release: dev → main (02.58.x)' (#248) from dev into main
Cascade Main -> Dev / Cascade main -> dev (push) Has been cancelled
Sync Workflows to Repos / sync (push) Has been cancelled
2026-07-12 22:18:48 +00:00
jmiller 90f08207ea @
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 22s
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
RC Revert / Rename rc/ back to dev/ (pull_request) Has been cancelled
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Has been cancelled
chore: back-merge main into dev (absorb workflow template syncs)

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

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
@
2026-07-12 17:18:28 -05:00
mokogitea-actions[bot] 3ef9bdaa48 chore(version): pre-release bump to 02.58.37-dev [skip ci] 2026-07-12 22:14:58 +00:00
jmiller 7a7e804647 Merge pull request 'fix(review): open-redirect + btoa + honest duplicate-mode txn' (#249) from fix/prerelease-review into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 16s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Has been cancelled
Joomla: Extension CI / Lint & Validate (pull_request) Has been cancelled
Joomla: Extension CI / Release Readiness Check (pull_request) Has been cancelled
Universal: PR Check / Branch Policy (pull_request) Has been cancelled
Universal: PR Check / Require Docs Update (pull_request) Has been cancelled
Universal: PR Check / Wiki Update Reminder (pull_request) Has been cancelled
Universal: PR Check / Secret Scan (pull_request) Has been cancelled
Universal: PR Check / Validate PR (pull_request) Has been cancelled
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Has been cancelled
Generic: Repo Health / Access control (pull_request) Has been cancelled
Generic: Repo Health / Site Health (pull_request) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Has been cancelled
Joomla: Extension CI / PHPStan Analysis (pull_request) Has been cancelled
Joomla: Extension CI / Build RC Pre-Release (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
2026-07-12 22:14:43 +00:00
mokogitea-actions[bot] 498f62e975 chore(version): pre-release bump to 02.58.36-dev [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
RC Revert / Rename rc/ back to dev/ (pull_request) Has been cancelled
2026-07-12 22:14:30 +00:00
jmiller f9c1c6b186 @
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 16s
Generic: Project CI / Lint & Validate (pull_request) Has been cancelled
Universal: PR Check / Branch Policy (pull_request) Has been cancelled
Universal: PR Check / Require Docs Update (pull_request) Has been cancelled
Universal: PR Check / Wiki Update Reminder (pull_request) Has been cancelled
Universal: PR Check / Secret Scan (pull_request) Has been cancelled
Universal: PR Check / Validate PR (pull_request) Has been cancelled
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
fix(review): open-redirect, btoa UTF-8 safety, honest duplicate-mode txn

Pre-release review findings on dev -> main:

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

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

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

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
@
2026-07-12 17:14:10 -05:00
mokogitea-actions[bot] d84ccfca31 chore(version): pre-release bump to 02.58.35-dev [skip ci] 2026-07-12 21:43:00 +00:00
jmiller 8a428c6cbb Merge pull request 'style(runbackup): full-width backup card' (#247) from fix/runbackup-fw2 into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 18s
2026-07-12 21:42:36 +00:00
jmiller 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 b894c95f6b chore: sync pre-release.yml from Template-Generic [skip ci] 2026-07-06 17:04:53 +00:00
jmiller e3bd4a36ff chore: sync pr-check.yml from Template-Generic [skip ci] 2026-07-06 03:50:41 +00:00
jmiller da9265e329 chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-07-06 03:50:41 +00:00
jmiller b9c0ddf2b9 chore: sync ci-generic.yml from Template-Generic [skip ci] 2026-07-06 03:50:40 +00:00
jmiller 41e8bffd26 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-07-06 03:50:39 +00:00
97 changed files with 4632 additions and 653 deletions
+4 -4
View File
@@ -7,7 +7,7 @@ Full-site backup and restore for Joomla — database, files, and configuration.
| Field | Value | | Field | Value |
|---|---| |---|---|
| **Package** | `pkg_mokosuitebackup` | | **Package** | `pkg_mokosuitebackup` |
| **Language** | PHP 8.1+ | | **Language** | PHP 8.3+ |
| **Branch** | develop on `dev`, merge to `main` (protected) | | **Branch** | develop on `dev`, merge to `main` (protected) |
| **Wiki** | [MokoSuiteBackup Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteBackup/wiki) | | **Wiki** | [MokoSuiteBackup Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteBackup/wiki) |
@@ -29,7 +29,7 @@ Joomla **package** with four sub-extensions:
### com_mokosuitebackup (Component) ### com_mokosuitebackup (Component)
- Admin backend for managing backup profiles and records - Admin backend for managing backup profiles and records
- Backup engine: `Engine/BackupEngine`, `Engine/DatabaseDumper`, `Engine/FileScanner`, `Engine/Archiver` - Backup engine: `Engine/BackupEngine`, `Engine/DatabaseDumper`, `Engine/FileScanner`, `Engine/Archiver`
- Joomla 4/5 MVC: Controllers, Models, Views, Tables - Joomla 6 MVC: Controllers, Models, Views, Tables
- Namespace: `Joomla\Component\MokoSuiteBackup\Administrator` - Namespace: `Joomla\Component\MokoSuiteBackup\Administrator`
- DB tables: `#__mokosuitebackup_profiles`, `#__mokosuitebackup_records` - DB tables: `#__mokosuitebackup_profiles`, `#__mokosuitebackup_records`
- CLI: `cli/mokosuitebackup.php` for cron-based backups - CLI: `cli/mokosuitebackup.php` for cron-based backups
@@ -64,8 +64,8 @@ Joomla **package** with four sub-extensions:
## Coding Standards ## Coding Standards
- PHP 8.1+ minimum - PHP 8.3+ minimum (Joomla 6)
- Joomla 4/5 DI container pattern: `services/provider.php` → Extension class - Joomla 6 DI container pattern: `services/provider.php` → Extension class
- Legacy stub `.php` file required for plugin loader but empty - Legacy stub `.php` file required for plugin loader but empty
- `SubscriberInterface` for event subscription (not `on*` method naming) - `SubscriberInterface` for event subscription (not `on*` method naming)
- `bind() → check() → store()` for Table operations (not `save()`) - `bind() → check() → store()` for Table operations (not `save()`)
+5 -6
View File
@@ -4,8 +4,8 @@
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow # DEFGROUP: MokoGitea.Workflow
# INGROUP: mokocli.Release # INGROUP: MokoCLI.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/auto-bump.yml # PATH: /.mokogitea/workflows/auto-bump.yml
# VERSION: 09.02.00 # VERSION: 09.02.00
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits) # BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
@@ -34,8 +34,7 @@ jobs:
if: >- if: >-
!contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[skip ci]') &&
!contains(github.event.head_commit.message, '[skip bump]') && !contains(github.event.head_commit.message, '[skip bump]') &&
!startsWith(github.event.head_commit.message, 'Merge pull request') && !startsWith(github.event.head_commit.message, 'Merge pull request')
!startsWith(github.event.repository.name, 'Template-')
steps: steps:
- name: Checkout - name: Checkout
@@ -44,7 +43,7 @@ jobs:
token: ${{ secrets.MOKOGITEA_TOKEN }} token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 1 fetch-depth: 1
- name: Setup mokocli tools - name: Setup MokoCLI tools
run: | run: |
if ! command -v composer &> /dev/null; then if ! command -v composer &> /dev/null; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1 sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
@@ -53,7 +52,7 @@ jobs:
echo "MOKO_CLI=/opt/mokocli/cli" >> "$GITHUB_ENV" echo "MOKO_CLI=/opt/mokocli/cli" >> "$GITHUB_ENV"
else else
git clone --depth 1 --branch main --quiet \ git clone --depth 1 --branch main --quiet \
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/MokoCLI.git" \ "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/mokocli.git" \
/tmp/mokocli /tmp/mokocli
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
echo "MOKO_CLI=/tmp/mokocli/cli" >> "$GITHUB_ENV" echo "MOKO_CLI=/tmp/mokocli/cli" >> "$GITHUB_ENV"
+17 -48
View File
@@ -4,17 +4,17 @@
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow # DEFGROUP: MokoGitea.Workflow
# INGROUP: mokocli.Release # INGROUP: MokoCLI.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/auto-release.yml # PATH: /templates/workflows/universal/auto-release.yml.template
# VERSION: 05.01.00 # VERSION: 05.01.00
# BRIEF: Universal build & release detects platform from manifest.xml # BRIEF: Universal build & release detects platform from MokoGitea repo metadata (API)
# #
# +=======================================================================+ # +=======================================================================+
# | UNIVERSAL BUILD & RELEASE PIPELINE | # | UNIVERSAL BUILD & RELEASE PIPELINE |
# +=======================================================================+ # +=======================================================================+
# | | # | |
# | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. | # | Reads MokoGitea repo metadata (joomla|dolibarr|generic) to branch logic. |
# | | # | |
# | Platform-specific: | # | Platform-specific: |
# | joomla: XML manifest, type-prefixed packages | # | joomla: XML manifest, type-prefixed packages |
@@ -81,7 +81,7 @@ jobs:
fetch-depth: 1 fetch-depth: 1
submodules: recursive submodules: recursive
- name: Setup mokocli tools - name: Setup MokoCLI tools
env: env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
@@ -95,7 +95,7 @@ jobs:
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1 sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
fi fi
rm -rf /tmp/mokocli rm -rf /tmp/mokocli
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoCLI.git CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
cd /tmp/mokocli cd /tmp/mokocli
composer install --no-dev --no-interaction --quiet composer install --no-dev --no-interaction --quiet
@@ -104,39 +104,11 @@ jobs:
- name: Rename branch to rc - name: Rename branch to rc
run: | run: |
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" php ${MOKO_CLI}/branch_rename.php \
AUTH="Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" --from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \
FROM="${{ github.event.pull_request.head.ref || 'dev' }}" --token "${{ secrets.MOKOGITEA_TOKEN }}" \
PR="${{ github.event.pull_request.number }}" --api-base "${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \
--pr "${{ github.event.pull_request.number }}"
# Resolve the source branch HEAD commit.
SRC_JSON=$(curl -sf -H "$AUTH" "${API_BASE}/branches/${FROM}") \
|| { echo "::error::Source branch ${FROM} not found"; exit 1; }
SRC_SHA=$(printf '%s' "$SRC_JSON" | python3 -c "import sys, json; print(json.load(sys.stdin)['commit']['id'])" 2>/dev/null || true)
[ -n "$SRC_SHA" ] || { echo "::error::Could not resolve HEAD of ${FROM}"; exit 1; }
# Point rc at the source commit. If rc already exists (a protected branch that
# cannot be deleted), force-update its ref in place instead of delete+recreate:
# deleting a protected branch fails, which then makes the recreate return HTTP 409.
if curl -sf -o /dev/null -H "$AUTH" "${API_BASE}/branches/rc"; then
echo "rc exists - force-updating to ${FROM} (${SRC_SHA})"
curl -sf -X PATCH -H "$AUTH" -H "Content-Type: application/json" \
"${API_BASE}/git/refs/heads/rc" -d "{\"sha\":\"${SRC_SHA}\",\"force\":true}" \
|| { echo "::error::Failed to force-update rc (CI token needs force-push on the protected rc branch)"; exit 1; }
else
echo "Creating rc from ${FROM}"
curl -sf -X POST -H "$AUTH" -H "Content-Type: application/json" \
"${API_BASE}/branches" -d "{\"new_branch_name\":\"rc\",\"old_branch_name\":\"${FROM}\"}" \
|| { echo "::error::Failed to create rc from ${FROM}"; exit 1; }
fi
# Repoint the PR at rc, then delete the old source branch (non-fatal).
if [ -n "$PR" ]; then
curl -s -X PATCH -H "$AUTH" -H "Content-Type: application/json" \
"${API_BASE}/pulls/${PR}" -d '{"head":"rc"}' >/dev/null || true
fi
curl -s -X DELETE -H "$AUTH" "${API_BASE}/branches/${FROM}" >/dev/null || true
echo "Renamed ${FROM} -> rc"
- name: Checkout rc and configure git - name: Checkout rc and configure git
run: | run: |
@@ -231,7 +203,7 @@ jobs:
fi fi
echo "No conflict markers found" echo "No conflict markers found"
- name: Setup mokocli tools - name: Setup MokoCLI tools
env: env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
@@ -246,7 +218,7 @@ jobs:
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1 sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
fi fi
rm -rf /tmp/mokocli rm -rf /tmp/mokocli
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoCLI.git CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
cd /tmp/mokocli cd /tmp/mokocli
composer install --no-dev --no-interaction --quiet composer install --no-dev --no-interaction --quiet
@@ -380,12 +352,9 @@ jobs:
import sys import sys
version, date = sys.argv[1], sys.argv[2] version, date = sys.argv[1], sys.argv[2]
content = open('CHANGELOG.md').read() content = open('CHANGELOG.md').read()
marker = f'## [{version}]' old = '## [Unreleased]'
# Idempotent: only promote when this version header isn't already present, new = f'## [Unreleased]\n\n## [{version}] --- {date}'
# otherwise re-runs (or same-version builds) create empty duplicate headers. content = content.replace(old, new, 1)
if marker not in content:
new = f'## [Unreleased]\n\n{marker} --- {date}'
content = content.replace('## [Unreleased]', new, 1)
open('CHANGELOG.md', 'w').write(content) open('CHANGELOG.md', 'w').write(content)
" "$VERSION" "$DATE" " "$VERSION" "$DATE"
git add CHANGELOG.md git add CHANGELOG.md
+2 -2
View File
@@ -4,8 +4,8 @@
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow # DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoStandards.Universal # INGROUP: MokoCLI.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/branch-cleanup.yml # PATH: /.mokogitea/workflows/branch-cleanup.yml
# VERSION: 01.00.00 # VERSION: 01.00.00
# BRIEF: Delete feature branches after PR merge # BRIEF: Delete feature branches after PR merge
+41 -82
View File
@@ -1,106 +1,65 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech> # Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow # DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoStandards.Cascade # INGROUP: MokoCLI.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic # BRIEF: Reset dev to main after each release (cascade). Moved out of auto-release Step 11.
# PATH: /.mokogitea/workflows/cascade-dev.yml #
# VERSION: 02.00.00 # +========================================================================+
# BRIEF: Cascade main -> dev via PR; auto-merge only if conflict-free, else notify # | CASCADE MAIN -> DEV |
# +========================================================================+
# | dev mirrors main and is reset on every release. |
# | delete+recreate cannot run against a protected branch, so this |
# | force-pushes main -> dev. The automation identity is force-push |
# | allowlisted on dev via branch-protection.yml. |
# | Runs AFTER the release workflow completes, so dev picks up the |
# | fully version-bumped + changelog-promoted main (not the pre-bump |
# | state). Force-push (not merge) => no version-file conflicts. |
# +========================================================================+
name: "Cascade Main -> Dev" name: "Cascade Main -> Dev"
on: on:
push: workflow_run:
branches: workflows: ["Universal: Build & Release"]
- main types: [completed]
workflow_dispatch: workflow_dispatch:
concurrency:
group: cascade-dev-${{ github.repository }}
cancel-in-progress: true
permissions: permissions:
contents: write contents: write
pull-requests: write
env:
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
# ntfy destination is configured via repo or org variables (org vars are inherited).
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
NTFY_TOPIC: ${{ vars.CASCADE_NTFY_TOPIC || vars.NTFY_TOPIC || 'gitea-releases' }}
jobs: jobs:
cascade: cascade:
name: Cascade main -> dev name: Reset dev to main
if: >-
!startsWith(github.event.repository.name, 'Template-') &&
(github.event_name == 'workflow_dispatch' ||
github.event.workflow_run.conclusion == 'success')
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Open main -> dev PR (auto-merge if clean, else notify) - name: Force dev to main
env: env:
TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
SERVER: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
REPO: ${{ github.repository }} REPO: ${{ github.repository }}
run: | run: |
set -uo pipefail set -euo pipefail
API="${MOKOGITEA_URL}/api/v1/repos/${REPO}" git init -q sync && cd sync
AUTH="Authorization: token ${TOKEN}" git config user.email "mokogitea-actions[bot]@mokoconsulting.tech"
jqnum() { python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('$1',''))" 2>/dev/null; } git config user.name "mokogitea-actions[bot]"
git remote add origin "https://x-access-token:${TOKEN}@${SERVER#https://}/${REPO}.git"
git fetch -q --depth=1 origin main
# 0. dev must exist if git ls-remote --exit-code --heads origin dev >/dev/null 2>&1; then
if ! curl -sf -H "$AUTH" "${API}/branches/dev" >/dev/null 2>&1; then # dev exists -> force it to match main (dev is a mirror of main)
echo "No dev branch - nothing to cascade."; exit 0 git push --force origin FETCH_HEAD:refs/heads/dev
fi echo "dev reset to main" >> "$GITHUB_STEP_SUMMARY"
# 1. is main ahead of dev?
AHEAD=$(curl -sf -H "$AUTH" "${API}/compare/dev...main" \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('total_commits',0))" 2>/dev/null || echo 0)
if [ "${AHEAD:-0}" -eq 0 ]; then
echo "dev already up to date with main."; exit 0
fi
echo "main is ${AHEAD} commit(s) ahead of dev."
# 2. reuse an open main->dev PR, else create one
PR=$(curl -sf -H "$AUTH" "${API}/pulls?state=open&base=dev" \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(next((str(p['number']) for p in d if p.get('head',{}).get('ref')=='main'), ''))" 2>/dev/null || echo "")
if [ -z "$PR" ]; then
RESP=$(curl -s -H "$AUTH" -H "Content-Type: application/json" -X POST "${API}/pulls" \
-d '{"head":"main","base":"dev","title":"chore(sync): cascade main -> dev","body":"Automated cascade of main into dev. Auto-merges only if conflict-free; otherwise left open for manual resolution."}')
PR=$(printf '%s' "$RESP" | jqnum number)
if [ -z "$PR" ]; then
echo "::warning::Could not open cascade PR: $RESP"; exit 0
fi
echo "Opened cascade PR #${PR}"
else else
echo "Reusing open cascade PR #${PR}" # dev missing (e.g. renamed to rc mid-cycle) -> recreate from main
fi git push origin FETCH_HEAD:refs/heads/dev
echo "dev created from main" >> "$GITHUB_STEP_SUMMARY"
# 3. wait for MokoGitea to compute mergeability (conflict detection)
MERGEABLE=""
for _ in 1 2 3 4 5 6; do
MERGEABLE=$(curl -sf -H "$AUTH" "${API}/pulls/${PR}" | jqnum mergeable)
case "$MERGEABLE" in True|False) break ;; esac
sleep 3
done
echo "mergeable=${MERGEABLE}"
notify() {
curl -sS \
-H "Title: ${REPO}: dev cascade needs manual merge" \
-H "Tags: warning,twisted_rightwards_arrows" \
-H "Priority: high" \
-H "Click: ${MOKOGITEA_URL}/${REPO}/pulls/${PR}" \
-d "main -> dev cascade PR #${PR} $1 It was NOT auto-merged; resolve it manually." \
"${NTFY_URL}/${NTFY_TOPIC}" || true
}
# 4. auto-merge only if conflict-free; otherwise notify
if [ "$MERGEABLE" = "True" ]; then
CODE=$(curl -s -o /tmp/merge.json -w "%{http_code}" -H "$AUTH" -H "Content-Type: application/json" \
-X POST "${API}/pulls/${PR}/merge" -d '{"Do":"merge","merge_when_checks_succeed":true}')
if [ "$CODE" -ge 200 ] && [ "$CODE" -lt 300 ]; then
echo "Cascade PR #${PR} merged (or scheduled to merge when checks pass)."
else
echo "::warning::Auto-merge returned HTTP ${CODE}: $(cat /tmp/merge.json)"
notify "could not be auto-merged (HTTP ${CODE})."
fi
else
echo "::warning::Cascade PR #${PR} has conflicts (mergeable=${MERGEABLE}); sending notification."
notify "has conflicts and cannot be merged automatically."
fi fi
+1 -1
View File
@@ -4,7 +4,7 @@
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow # DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoStandards.CI # INGROUP: MokoCLI.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
# VERSION: 01.00.00 # VERSION: 01.00.00
+3 -3
View File
@@ -4,8 +4,8 @@
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow # DEFGROUP: MokoGitea.Workflow
# INGROUP: mokocli.Universal # INGROUP: MokoCLI.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# 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 MokoGitea issue when a CI gate fails. # BRIEF: Reusable workflow — creates/updates a MokoGitea issue when a CI gate fails.
@@ -52,7 +52,7 @@ jobs:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: | run: |
MOKOGITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}" MOKOGITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
git clone --depth 1 --filter=blob:none --sparse "${MOKOGITEA_URL}/MokoConsulting/MokoCLI.git" /tmp/mokocli git clone --depth 1 --filter=blob:none --sparse "${MOKOGITEA_URL}/MokoConsulting/mokocli.git" /tmp/mokocli
cd /tmp/mokocli && git sparse-checkout set cli/ci_issue_reporter.sh cd /tmp/mokocli && git sparse-checkout set cli/ci_issue_reporter.sh
- name: Report CI failure - name: Report CI failure
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -4,8 +4,8 @@
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow # DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoStandards.Maintenance # INGROUP: MokoCLI.Maintenance
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/cleanup.yml # PATH: /.mokogitea/workflows/cleanup.yml
# VERSION: 01.00.00 # VERSION: 01.00.00
# BRIEF: Scheduled cleanup — delete merged branches and old workflow runs # BRIEF: Scheduled cleanup — delete merged branches and old workflow runs
+3 -3
View File
@@ -4,9 +4,9 @@
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow # DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoStandards.Security # INGROUP: MokoCLI.Security
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/gitleaks.yml # PATH: /templates/workflows/gitleaks.yml.template
# VERSION: 01.00.00 # VERSION: 01.00.00
# BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens # BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens
# #
+1 -1
View File
@@ -5,7 +5,7 @@
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow # DEFGROUP: MokoGitea.Workflow
# INGROUP: mokocli.Automation # INGROUP: mokocli.Automation
# VERSION: 02.58.26 # VERSION: 02.64.02
# 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"
+2 -2
View File
@@ -4,8 +4,8 @@
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow # DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoStandards.Notifications # INGROUP: MokoCLI.Notifications
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/notify.yml # PATH: /.mokogitea/workflows/notify.yml
# VERSION: 01.00.00 # VERSION: 01.00.00
# BRIEF: Push notifications via ntfy on release success or workflow failure # BRIEF: Push notifications via ntfy on release success or workflow failure
+9 -84
View File
@@ -4,9 +4,9 @@
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow # DEFGROUP: MokoGitea.Workflow
# INGROUP: mokocli.CI # INGROUP: MokoCLI.CI
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/pr-check.yml # PATH: /templates/workflows/universal/pr-check.yml.template
# VERSION: 09.23.00 # VERSION: 09.23.00
# BRIEF: PR gate — branch policy + code validation before merge # BRIEF: PR gate — branch policy + code validation before merge
@@ -47,15 +47,15 @@ jobs:
fi fi
;; ;;
fix/*|bugfix/*) fix/*|bugfix/*)
if [ "$BASE" != "dev" ] && [ "$BASE" != "main" ]; then if [ "$BASE" != "dev" ]; then
ALLOWED=false ALLOWED=false
REASON="Fix branches must target 'dev' or 'main', not '${BASE}'" REASON="Fix branches must target 'dev', not '${BASE}'"
fi fi
;; ;;
patch/*) patch/*)
if [ "$BASE" != "dev" ] && [ "$BASE" != "rc" ] && [ "$BASE" != "main" ]; then if [ "$BASE" != "dev" ] && [ "$BASE" != "rc" ]; then
ALLOWED=false ALLOWED=false
REASON="Patch branches must target 'dev', 'rc', or 'main', not '${BASE}'" REASON="Patch branches must target 'dev' or 'rc', not '${BASE}'"
fi fi
;; ;;
hotfix/*) hotfix/*)
@@ -86,8 +86,7 @@ jobs:
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY
echo "- \`feature/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY echo "- \`feature/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
echo "- \`fix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY echo "- \`fix/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
echo "- \`patch/*\` → \`dev\`, \`rc\`, or \`main\`" >> $GITHUB_STEP_SUMMARY
echo "- \`hotfix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY echo "- \`hotfix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY
echo "- \`dev\` → \`main\`" >> $GITHUB_STEP_SUMMARY echo "- \`dev\` → \`main\`" >> $GITHUB_STEP_SUMMARY
echo "- \`rc/*\` → \`main\`" >> $GITHUB_STEP_SUMMARY echo "- \`rc/*\` → \`main\`" >> $GITHUB_STEP_SUMMARY
@@ -97,80 +96,6 @@ jobs:
echo "Branch policy: OK (${HEAD} → ${BASE})" echo "Branch policy: OK (${HEAD} → ${BASE})"
echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY
# ── Docs Update Gate (main PRs) ─────────────────────────────────────────
require-docs:
name: Require Docs Update
runs-on: ubuntu-latest
# Enforce only on PRs merging into main: README.md + CHANGELOG.md must both be updated.
if: ${{ github.base_ref == 'main' }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Require README.md and CHANGELOG.md in the PR diff
run: |
BASE="${{ github.event.pull_request.base.sha }}"
HEAD="${{ github.event.pull_request.head.sha }}"
CHANGED="$(git diff --name-only "$BASE" "$HEAD" 2>/dev/null || true)"
if [ -z "$CHANGED" ]; then
git fetch -q origin "${{ github.base_ref }}" 2>/dev/null || true
CHANGED="$(git diff --name-only "origin/${{ github.base_ref }}...HEAD" 2>/dev/null || true)"
fi
echo "Changed files in PR:"
echo "$CHANGED"
MISSING=""
echo "$CHANGED" | grep -qxE 'README\.md' || MISSING="README.md"
echo "$CHANGED" | grep -qxE 'CHANGELOG\.md' || MISSING="${MISSING:+$MISSING, }CHANGELOG.md"
if [ -n "$MISSING" ]; then
echo "::error::PRs into main must update: ${MISSING}"
{
echo "## Docs Update Required"
echo ""
echo "PRs merging into \`main\` must update both **README.md** and **CHANGELOG.md**."
echo ""
echo "Not updated in this PR: **${MISSING}**"
} >> "$GITHUB_STEP_SUMMARY"
exit 1
fi
echo "Docs update present (README.md + CHANGELOG.md)"
echo "## Docs Update: Passed" >> "$GITHUB_STEP_SUMMARY"
# ── Wiki Update Reminder (main PRs, non-blocking) ───────────────────────
wiki-reminder:
name: Wiki Update Reminder
runs-on: ubuntu-latest
if: ${{ github.base_ref == 'main' }}
steps:
- name: Remind to update the wiki
env:
TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
SERVER: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
REPO: ${{ github.repository }}
PR: ${{ github.event.pull_request.number }}
run: |
set -uo pipefail
{
echo "## Wiki Update Reminder"
echo ""
echo "Docs are **wiki-first** at MokoConsulting. If this change affects behavior, usage, configuration, or standards, update the repo wiki:"
echo ""
echo "- ${SERVER}/${REPO}/wiki"
echo ""
echo "_Non-blocking reminder._"
} >> "$GITHUB_STEP_SUMMARY"
# Post a single PR comment (idempotent via hidden marker); best-effort, never fails.
API="${SERVER}/api/v1/repos/${REPO}/issues/${PR}/comments"
if [ -n "${TOKEN:-}" ] && [ -n "${PR:-}" ]; then
existing="$(curl -sf -H "Authorization: token ${TOKEN}" "$API" 2>/dev/null | grep -c 'wiki-reminder' || true)"
if [ "${existing:-0}" -eq 0 ]; then
curl -sf -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" -X POST "$API" \
-d '{"body":"<!-- wiki-reminder -->\n\n**Wiki reminder:** docs are wiki-first -- if this PR changes behavior, usage, config, or standards, please update the repo wiki before/after merge. _(non-blocking)_"}' >/dev/null 2>&1 || true
fi
fi
echo "Wiki reminder emitted (non-blocking)."
# ── Secret Scanning ────────────────────────────────────────────────── # ── Secret Scanning ──────────────────────────────────────────────────
gitleaks: gitleaks:
name: Secret Scan name: Secret Scan
@@ -350,7 +275,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 (MokoSuite site)" echo "::warning::No Joomla manifest found (WaaS site)"
exit 0 exit 0
fi fi
echo "Manifest: ${MANIFEST}" echo "Manifest: ${MANIFEST}"
+9 -7
View File
@@ -3,8 +3,8 @@
# 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/mokocli # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /templates/workflows/joomla/pr-metadata-check.yml.template # PATH: /templates/workflows/joomla/pr-metadata-check.yml.template
# VERSION: 01.00.00 # VERSION: 01.00.00
@@ -20,7 +20,7 @@ permissions:
contents: read contents: read
env: env:
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }} GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }} GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
@@ -28,12 +28,14 @@ jobs:
validate-metadata: validate-metadata:
name: "Validate Joomla Metadata" name: "Validate Joomla Metadata"
runs-on: ubuntu-latest runs-on: ubuntu-latest
# Skip on template repos (Template-*) — they have no real extension manifest to validate.
if: ${{ !startsWith(github.event.repository.name, 'Template-') }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup mokocli tools - name: Setup MokoCLI tools
env: env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
@@ -55,14 +57,14 @@ jobs:
- name: Validate metadata against Joomla manifest - name: Validate metadata against Joomla manifest
env: env:
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: | run: |
php ${MOKO_CLI}/joomla_metadata_validate.php \ php ${MOKO_CLI}/joomla_metadata_validate.php \
--path . \ --path . \
--token "${GITEA_TOKEN}" \ --token "${MOKOGITEA_TOKEN}" \
--org "${GITEA_ORG}" \ --org "${GITEA_ORG}" \
--repo "${GITEA_REPO}" \ --repo "${GITEA_REPO}" \
--api-base "${GITEA_URL}/api/v1" \ --api-base "${MOKOGITEA_URL}/api/v1" \
--ci --ci
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
+5 -5
View File
@@ -4,9 +4,9 @@
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow # DEFGROUP: MokoGitea.Workflow
# INGROUP: mokocli.Release # INGROUP: MokoCLI.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/pre-release.yml # PATH: /templates/workflows/universal/pre-release.yml.template
# VERSION: 05.02.00 # VERSION: 05.02.00
# BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches # BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches
@@ -69,7 +69,7 @@ jobs:
run: | run: |
git submodule foreach --quiet 'git checkout main && git pull --quiet origin main' 2>/dev/null || true git submodule foreach --quiet 'git checkout main && git pull --quiet origin main' 2>/dev/null || true
- name: Setup mokocli tools - name: Setup MokoCLI tools
env: env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
@@ -84,7 +84,7 @@ jobs:
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1 sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
fi fi
rm -rf /tmp/mokocli rm -rf /tmp/mokocli
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoCLI.git CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
+3 -4
View File
@@ -4,8 +4,8 @@
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow # DEFGROUP: MokoGitea.Workflow
# INGROUP: mokocli.Universal # INGROUP: MokoCLI.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/rc-revert.yml # PATH: /.mokogitea/workflows/rc-revert.yml
# VERSION: 09.23.00 # VERSION: 09.23.00
# BRIEF: Rename rc/ branch back to dev/ when PR is closed without merge # BRIEF: Rename rc/ branch back to dev/ when PR is closed without merge
@@ -25,8 +25,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: >- if: >-
github.event.pull_request.merged == false && github.event.pull_request.merged == false &&
startsWith(github.event.pull_request.head.ref, 'rc/') && startsWith(github.event.pull_request.head.ref, 'rc/')
!startsWith(github.event.repository.name, 'Template-')
steps: steps:
- name: Rename branch - name: Rename branch
+3 -3
View File
@@ -7,9 +7,9 @@
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow # DEFGROUP: MokoGitea.Workflow
# INGROUP: mokocli.Validation # INGROUP: MokoCLI.Validation
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/repo-health.yml # PATH: /templates/workflows/joomla/repo_health.yml.template
# VERSION: 09.23.00 # VERSION: 09.23.00
# BRIEF: Enforces repository guardrails by validating scripts governance, tooling availability, and core repository health artifacts. # BRIEF: Enforces repository guardrails by validating scripts governance, tooling availability, and core repository health artifacts.
# ============================================================================ # ============================================================================
File diff suppressed because it is too large Load Diff
-32
View File
@@ -1,32 +0,0 @@
name: Sync Workflows to Repos
on:
push:
branches:
- main
paths:
- '.mokogitea/workflows/**'
jobs:
sync:
runs-on: ubuntu-latest
if: ${{ startsWith(github.event.repository.name, 'Template-') }}
steps:
- name: Checkout mokocli
uses: actions/checkout@v4
with:
repository: MokoConsulting/MokoCLI
token: ${{ secrets.MOKOGITEA_TOKEN }}
- name: Setup PHP
uses: https://git.mokoconsulting.tech/MokoConsulting/.mokogitea/raw/branch/main/actions/setup-php@v1
with:
php-version: '8.1'
- name: Install dependencies
run: composer install --no-dev --no-interaction
- name: Sync workflows to generic repos
run: php automation/bulk_sync.php --platform generic --org MokoConsulting --workflows-only --auto-merge --token "${{ secrets.MOKOGITEA_TOKEN }}"
env:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
+1 -2
View File
@@ -4,7 +4,7 @@
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow.Template # DEFGROUP: MokoGitea.Workflow.Template
# INGROUP: MokoStandards.CI # INGROUP: MokoCLI.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
# VERSION: 01.00.00 # VERSION: 01.00.00
@@ -34,7 +34,6 @@ jobs:
set-version: set-version:
name: Set Version to ${{ inputs.version }} name: Set Version to ${{ inputs.version }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ !startsWith(github.event.repository.name, 'Template-') }}
steps: steps:
- name: Validate version format - name: Validate version format
@@ -1,82 +0,0 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# INGROUP: mokocli.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogitea/workflows/workflow-sync-trigger.yml
# VERSION: 01.01.00
# BRIEF: Trigger workflow sync to live repos when a PR is merged to main
name: "Universal: Workflow Sync Trigger"
on:
workflow_dispatch:
pull_request:
types: [closed]
branches:
- main
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
sync:
name: Sync workflows to live repos
runs-on: ubuntu-latest
if: >-
startsWith(github.event.repository.name, 'Template-') &&
(github.event_name == 'workflow_dispatch' ||
(github.event.pull_request.merged == true &&
!contains(github.event.pull_request.title, '[skip sync]')))
steps:
- name: Determine platform from repo name
id: platform
run: |
REPO="${{ github.event.repository.name }}"
case "$REPO" in
Template-Joomla) PLATFORM="joomla" ;;
Template-Dolibarr) PLATFORM="dolibarr" ;;
Template-Go) PLATFORM="go" ;;
Template-MCP) PLATFORM="mcp" ;;
Template-Generic) PLATFORM="" ;;
*) PLATFORM="" ;;
esac
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
echo "Platform: ${PLATFORM:-all}"
- name: Clone mokocli
env:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: |
MOKOGITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
git clone --depth 1 "${MOKOGITEA_URL}/MokoConsulting/MokoCLI.git" /tmp/mokocli
- name: Install PHP
run: |
if ! command -v php &> /dev/null; then
apt-get update -qq && apt-get install -y -qq php-cli php-json php-curl > /dev/null 2>&1
fi
- name: Install dependencies
run: |
cd /tmp/mokocli
composer install --no-dev --no-interaction --quiet 2>/dev/null || true
- name: Run workflow sync
env:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: |
ARGS="--token ${MOKOGITEA_TOKEN}"
ARGS="${ARGS} --org ${{ vars.GITEA_ORG || github.repository_owner }}"
ARGS="${ARGS} --phase repos"
PLATFORM="${{ steps.platform.outputs.platform }}"
if [ -n "$PLATFORM" ]; then
ARGS="${ARGS} --platform-filter ${PLATFORM}"
fi
php /tmp/mokocli/cli/workflow_sync.php ${ARGS}
+6 -73
View File
@@ -1,81 +1,14 @@
# Changelog # Changelog
## [Unreleased] ## [Unreleased]
### Fixed ## [02.64.00] --- 2026-07-14
- 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 ## [02.64.00] --- 2026-07-14
- **Snapshot transfer + master→slave injection (#237):** download a content snapshot as a portable **`.msbsnap`** file (zip of `manifest.json` + `snapshot.json` + a sha256 integrity check); **import** one on another site via a new Import button on the Snapshots page (materialises a local snapshot record you can then restore); and a **direct injection API**`POST /api/index.php/v1/mokosuitebackup/snapshot/inject` — so a **master** site can push a snapshot straight into a **slave**'s Web Services API. Two conflict modes ship now: **overwrite** (replace matching items by ID — master wins) and **create** (skip existing, add only new), selectable per inject call or defaulted in **Options → Snapshot Transfer**. A receiving site must opt in via the new *Allow Snapshot Injection* setting. A third **duplicate** mode inserts everything as brand-new records with **remapped IDs** (articles, categories, modules — plus their tags, custom-field values and featured flags) using Joomla's Table API so assets/UCM/nested-sets stay valid; each item is inserted defensively (a bad item is skipped and logged, never fatal).
- Full-screen backup now also fronts **extension updates and uninstalls** (Extensions → Update / Manage), not just core Joomla updates. Because `com_installer`'s `update.update` / `manage.remove` are POST actions (CSRF token + a checked `cid[]` list) that a server-side redirect can't cleanly resume, this is done client-side: the toolbar Update/Uninstall click is intercepted, the selection is captured, the full-screen backup runs, and on return the original selection is restored and the real POST form is re-submitted. Gated by `backup_before_update` / `backup_before_uninstall`, super-user only, and skipped if a backup already ran this session.
- 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 ## [02.63.00] --- 2026-07-13
- 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 ## [02.62.00] --- 2026-07-13
- 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 ## [02.61.00] --- 2026-07-13
### Added ## [02.60.00] --- 2026-07-12
- 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)
## [02.56.08] --- 2026-07-05
### Fixed
- Component manifest `<name>` reverted to the element-safe `MokoSuiteBackup`. The "Type - Name" convention derived element `com_component-mokosuitebackup`, registering a duplicate component and leaving the real `com_mokosuitebackup` orphaned on the old version. (#213)
## [02.56.07] --- 2026-07-05
### Security
- Mask the FTP password in `AjaxController::maskSecrets()`/`mergeExistingSecrets()` — stored FTP remotes no longer leak their password via `listRemotes`. (#169)
- Stop the API single-item view (`Backups/JsonapiView`) leaking the server `absolute_path`. (#187)
- SFTP uploader uses `StrictHostKeyChecking=accept-new` instead of `no`. (#182)
### Fixed
- `DatabaseDumper` writes a real newline after `DROP TABLE` (was a literal `\n`). (#179)
- Profile-picker forms order by `id` instead of the dropped `ordering` column. (#180)
- `uninstall.mysql.sql` now drops the snapshots table. (#181)
- CLI snapshot delete uses the `data_file` column (was non-existent `file_path`). (#184)
- Remove the duplicate pre-update backup (content plugin no longer subscribes to `onExtensionBeforeUpdate`). (#186)
- `DatabaseImporter` collects non-fatal statement errors (`getErrors()`/`hasErrors()`). (#188)
+1 -1
View File
@@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla
INGROUP: Template-Joomla.Documentation INGROUP: Template-Joomla.Documentation
REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
PATH: /SECURITY.md PATH: /SECURITY.md
VERSION: 02.58.26 VERSION: 02.64.02
BRIEF: Security vulnerability reporting and handling policy BRIEF: Security vulnerability reporting and handling policy
--> -->
+1 -1
View File
@@ -12,7 +12,7 @@
} }
], ],
"require": { "require": {
"php": ">=8.1", "php": ">=8.3",
"ext-zip": "*", "ext-zip": "*",
"ext-pdo": "*", "ext-pdo": "*",
"ext-pdo_mysql": "*", "ext-pdo_mysql": "*",
@@ -349,6 +349,15 @@ COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_ANYWAY="Continue without backup"
COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD="Back to dashboard" COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD="Back to dashboard"
COM_MOKOJOOMBACKUP_RUNBACKUP_VIEW_RECORD="View backup record" COM_MOKOJOOMBACKUP_RUNBACKUP_VIEW_RECORD="View backup record"
COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING="A backup is in progress. Leaving now will cancel it." COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING="A backup is in progress. Leaving now will cancel it."
COM_MOKOJOOMBACKUP_RUNBACKUP_AUTOCONTINUE="Automatically continue the update when the backup finishes"
COM_MOKOJOOMBACKUP_RUNBACKUP_DONT_CLOSE="Please do not close this window or switch to another window until the backup finishes."
COM_MOKOJOOMBACKUP_RUNBACKUP_UPDATE_RUNNING="Backup complete. The update is now running — please wait and do not close this window…"
COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL="Cancel backup"
COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL_CONFIRM1="Cancel the backup that is in progress?"
COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL_CONFIRM2="Are you sure? The backup will be stopped and the update will NOT continue."
COM_MOKOJOOMBACKUP_RUNBACKUP_CANCELLING="Cancelling…"
COM_MOKOJOOMBACKUP_RUNBACKUP_FAILED_NO_UPDATE="Backup failed — the update was not started. Retry the backup, or go back without updating."
COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_UPDATE="Continue the update"
COM_MOKOJOOMBACKUP_CONFIG_CLEANUP="Cleanup Defaults" COM_MOKOJOOMBACKUP_CONFIG_CLEANUP="Cleanup Defaults"
COM_MOKOJOOMBACKUP_CONFIG_MAX_AGE="Max Backup Age (days)" COM_MOKOJOOMBACKUP_CONFIG_MAX_AGE="Max Backup Age (days)"
@@ -71,6 +71,15 @@ COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_ANYWAY="Continue without backup"
COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD="Back to dashboard" COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD="Back to dashboard"
COM_MOKOJOOMBACKUP_RUNBACKUP_VIEW_RECORD="View backup record" COM_MOKOJOOMBACKUP_RUNBACKUP_VIEW_RECORD="View backup record"
COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING="A backup is in progress. Leaving now will cancel it." COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING="A backup is in progress. Leaving now will cancel it."
COM_MOKOJOOMBACKUP_RUNBACKUP_AUTOCONTINUE="Automatically continue the update when the backup finishes"
COM_MOKOJOOMBACKUP_RUNBACKUP_DONT_CLOSE="Please do not close this window or switch to another window until the backup finishes."
COM_MOKOJOOMBACKUP_RUNBACKUP_UPDATE_RUNNING="Backup complete. The update is now running — please wait and do not close this window…"
COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL="Cancel backup"
COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL_CONFIRM1="Cancel the backup that is in progress?"
COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL_CONFIRM2="Are you sure? The backup will be stopped and the update will NOT continue."
COM_MOKOJOOMBACKUP_RUNBACKUP_CANCELLING="Cancelling…"
COM_MOKOJOOMBACKUP_RUNBACKUP_FAILED_NO_UPDATE="Backup failed — the update was not started. Retry the backup, or go back without updating."
COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_UPDATE="Continue the update"
COM_MOKOJOOMBACKUP_CONFIG_CLEANUP="Cleanup Defaults" COM_MOKOJOOMBACKUP_CONFIG_CLEANUP="Cleanup Defaults"
COM_MOKOJOOMBACKUP_CONFIG_MAX_AGE="Max Backup Age (days)" COM_MOKOJOOMBACKUP_CONFIG_MAX_AGE="Max Backup Age (days)"
@@ -0,0 +1,86 @@
/*
* @package MokoSuiteBackup
* @subpackage com_mokosuitebackup
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*
* Full-screen backup progress screen (view=runbackup). Loaded via the Joomla
* Web Asset Manager (asset com_mokosuitebackup.runbackup).
*/
/* Full-screen backup runs on a black backdrop. */
html,
body {
background-color: #000 !important;
}
/* Colours are pulled from the Atum admin template's own CSS custom properties
so the screen matches whatever theme the site runs, with Bootstrap and then
a literal as progressive fallbacks. */
.msb-runbackup {
--msb-accent: var(--template-link-color, var(--bs-link-color, #2a69b8));
--msb-accent2: var(--template-special-color, var(--msb-accent));
--msb-success: var(--template-success-color, var(--bs-success, #2f7d32));
--msb-danger: var(--template-danger-color, var(--bs-danger, #c52128));
--msb-track: var(--template-bg-light, var(--bs-gray-200, #edf0f5));
padding: 0 1rem;
}
/* overflow:hidden clips the full-bleed bar to the card's rounded corners.
The card's gray background comes from a Bootstrap utility class on the
element itself (see the template), not a rule here. */
.msb-runbackup .card {
overflow: hidden;
}
.msb-rb-progress {
height: 2.5rem;
background: var(--msb-track);
overflow: hidden;
}
.msb-rb-bar {
position: relative;
height: 100%;
width: 0;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-weight: 600;
font-size: .95rem;
white-space: nowrap;
background-color: var(--msb-accent);
background-image: linear-gradient(90deg, var(--msb-accent), var(--msb-accent2));
transition: width .35s ease;
}
.msb-rb-bar.is-animated::after {
content: "";
position: absolute;
inset: 0;
background-image: linear-gradient(45deg, rgba(255, 255, 255, .18) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .18) 50%, rgba(255, 255, 255, .18) 75%, transparent 75%, transparent);
background-size: 1rem 1rem;
animation: msb-rb-stripes 1s linear infinite;
}
.msb-rb-bar.is-success {
background-image: none;
background-color: var(--msb-success);
}
.msb-rb-bar.is-danger {
background-image: none;
background-color: var(--msb-danger);
}
@keyframes msb-rb-stripes {
from {
background-position: 1rem 0;
}
to {
background-position: 0 0;
}
}
@@ -9,6 +9,11 @@
"name": "com_mokosuitebackup.admin", "name": "com_mokosuitebackup.admin",
"type": "style", "type": "style",
"uri": "com_mokosuitebackup/css/admin.css" "uri": "com_mokosuitebackup/css/admin.css"
},
{
"name": "com_mokosuitebackup.runbackup",
"type": "style",
"uri": "com_mokosuitebackup/css/runbackup.css"
} }
] ]
} }
@@ -17,7 +17,7 @@
display label there. display label there.
--> -->
<name>MokoSuiteBackup</name> <name>MokoSuiteBackup</name>
<version>02.58.26</version> <version>02.64.02</version>
<creationDate>2026-06-02</creationDate> <creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -0,0 +1 @@
/* 02.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 */
@@ -0,0 +1 @@
/* 02.60.01 — no schema changes */
@@ -0,0 +1 @@
/* 02.60.02 — no schema changes */
@@ -0,0 +1 @@
/* 02.60.03 — no schema changes */
@@ -0,0 +1 @@
/* 02.60.04 — no schema changes */
@@ -0,0 +1 @@
/* 02.60.05 — no schema changes */
@@ -0,0 +1 @@
/* 02.60.06 — no schema changes */
@@ -0,0 +1 @@
/* 02.61.00 — no schema changes */
@@ -0,0 +1 @@
/* 02.61.01 — no schema changes */
@@ -0,0 +1 @@
/* 02.61.04 — no schema changes */
@@ -0,0 +1 @@
/* 02.61.05 — no schema changes */
@@ -0,0 +1 @@
/* 02.61.06 — no schema changes */
@@ -0,0 +1 @@
/* 02.62.00 — no schema changes */
@@ -0,0 +1 @@
/* 02.62.01 — no schema changes */
@@ -0,0 +1 @@
/* 02.62.02 — no schema changes */
@@ -0,0 +1 @@
/* 02.62.03 — no schema changes */
@@ -0,0 +1 @@
/* 02.62.04 — no schema changes */
@@ -0,0 +1 @@
/* 02.62.05 — no schema changes */
@@ -0,0 +1 @@
/* 02.63.00 — no schema changes */
@@ -0,0 +1 @@
/* 02.63.01 — no schema changes */
@@ -0,0 +1 @@
/* 02.63.02 — no schema changes */
@@ -0,0 +1 @@
/* 02.63.05 — no schema changes */
@@ -0,0 +1 @@
/* 02.63.06 — no schema changes */
@@ -0,0 +1 @@
/* 02.63.07 — no schema changes */
@@ -0,0 +1 @@
/* 02.63.09 — no schema changes */
@@ -0,0 +1 @@
/* 02.63.10 — no schema changes */
@@ -0,0 +1 @@
/* 02.63.11 — no schema changes */
@@ -0,0 +1 @@
/* 02.64.00 — no schema changes */
@@ -0,0 +1 @@
/* 02.64.01 — no schema changes */
@@ -0,0 +1 @@
/* 02.64.02 — no schema changes */
@@ -89,11 +89,11 @@ class AjaxController extends BaseController
* *
* Called by the full-screen backup screen after a successful backup so the * Called by the full-screen backup screen after a successful backup so the
* imminent server-side onExtensionBeforeUpdate / onExtensionBeforeUninstall * imminent server-side onExtensionBeforeUpdate / onExtensionBeforeUninstall
* backup is skipped — it arms the same 10-minute throttle keys the system * backup is skipped once — it sets a ONE-SHOT skip flag (keyed by the
* plugin checks, preventing a duplicate backup when the update / uninstall * `action` the backup was fronting) that plg_system_mokosuitebackup
* then proceeds. Arms BOTH keys so a single ack covers whichever action the * consumes on the re-fired update/uninstall request, so exactly that one
* full-screen backup was fronting. * action is not double-backed-up while later, distinct actions still are.
* POST: task=ajax.preupdateAck * POST: task=ajax.preupdateAck&action=update|uninstall
*/ */
public function preupdateAck(): void public function preupdateAck(): void
{ {
@@ -109,11 +109,21 @@ class AjaxController extends BaseController
return; return;
} }
// Same keys + semantics as plg_system_mokosuitebackup::runPreActionBackup(). /* Set a ONE-SHOT skip flag for the specific action the full-screen backup
was fronting. runPreActionBackup() consumes it on the re-fired
update/uninstall request (once), so the synchronous backup is skipped
that one time but the NEXT distinct action still backs up. Keyed by
action so an update ack never suppresses a later, unrelated uninstall.
For a core Joomla update (no action) neither flag is set — a core
update doesn't fire onExtensionBefore(Update|Uninstall). */
$action = $this->app->getInput()->getCmd('action', '');
$session = Factory::getSession(); $session = Factory::getSession();
$now = time();
$session->set('mokosuitebackup.preaction_backup_before_update', $now); if ($action === 'update') {
$session->set('mokosuitebackup.preaction_backup_before_uninstall', $now); $session->set('mokosuitebackup.skip_preaction_backup_before_update', true);
} elseif ($action === 'uninstall') {
$session->set('mokosuitebackup.skip_preaction_backup_before_uninstall', true);
}
$this->sendJson(['error' => false, 'message' => 'Pre-action backup acknowledged']); $this->sendJson(['error' => false, 'message' => 'Pre-action backup acknowledged']);
} }
@@ -480,8 +480,8 @@ function actionPreflight(): array
$checks[] = [ $checks[] = [
'label' => 'PHP Version', 'label' => 'PHP Version',
'value' => PHP_VERSION, 'value' => PHP_VERSION,
'ok' => version_compare(PHP_VERSION, '8.1', '>='), 'ok' => version_compare(PHP_VERSION, '8.3', '>='),
'hint' => 'Joomla 4/5 requires PHP 8.1+', 'hint' => 'Joomla 6 requires PHP 8.3+',
]; ];
$checks[] = [ $checks[] = [
@@ -125,12 +125,24 @@ class SnapshotRestoreEngine
$prefix = $db->getPrefix(); $prefix = $db->getPrefix();
$totalRows = 0; $totalRows = 0;
try {
$db->transactionStart();
// Build list of tables to restore based on selected types // Build list of tables to restore based on selected types
$tablesToRestore = $this->getTablesToRestore($restoreTypes); $tablesToRestore = $this->getTablesToRestore($restoreTypes);
/* Duplicate mode inserts brand-new records through Joomla's Table API,
whose Nested tables (categories, tags) issue LOCK TABLES — which
implicitly COMMITS any open transaction in MySQL/InnoDB. Wrapping it
in transactionStart()/Rollback() would be a false atomicity promise:
the first category/tag store would commit and defeat the rollback.
Duplicate mode is therefore best-effort and per-item defensive (a bad
item is skipped and logged, never fatal) rather than all-or-nothing.
The raw-DB modes (replace/create/merge) remain fully transactional. */
$useTransaction = ($mode !== 'duplicate');
try {
if ($useTransaction) {
$db->transactionStart();
}
if ($mode === 'duplicate') { if ($mode === 'duplicate') {
$totalRows = $this->restoreDuplicate($data, $restoreTypes); $totalRows = $this->restoreDuplicate($data, $restoreTypes);
} else { } else {
@@ -156,7 +168,9 @@ class SnapshotRestoreEngine
} }
} }
if ($useTransaction) {
$db->transactionCommit(); $db->transactionCommit();
}
$this->log('Restore complete: ' . $totalRows . ' total rows'); $this->log('Restore complete: ' . $totalRows . ' total rows');
@@ -188,12 +202,16 @@ class SnapshotRestoreEngine
'log' => implode("\n", $this->log), 'log' => implode("\n", $this->log),
]; ];
} catch (\Throwable $e) { } catch (\Throwable $e) {
if ($useTransaction) {
try { try {
$db->transactionRollback(); $db->transactionRollback();
$this->log('Transaction rolled back'); $this->log('Transaction rolled back');
} catch (\Exception $rollbackEx) { } catch (\Exception $rollbackEx) {
$this->log('Rollback failed: ' . $rollbackEx->getMessage()); $this->log('Rollback failed: ' . $rollbackEx->getMessage());
} }
} else {
$this->log('Duplicate mode is not transactional; records already inserted are left in place.');
}
$this->log('FATAL: ' . $e->getMessage()); $this->log('FATAL: ' . $e->getMessage());
@@ -104,7 +104,7 @@ class DashboardModel extends BaseDatabaseModel
// PHP version // PHP version
$checks[] = (object) [ $checks[] = (object) [
'label' => 'PHP Version', 'label' => 'PHP Version',
'status' => version_compare(PHP_VERSION, '8.1.0', '>='), 'status' => version_compare(PHP_VERSION, '8.3.0', '>='),
'detail' => PHP_VERSION, 'detail' => PHP_VERSION,
]; ];
@@ -40,6 +40,9 @@ class HtmlView extends BaseHtmlView
public bool $autostart = true; public bool $autostart = true;
/** Which action the backup fronts ('update' | 'uninstall' | ''); drives the completion ack skip flag. */
public string $action = '';
public function display($tpl = null): void public function display($tpl = null): void
{ {
$input = Factory::getApplication()->getInput(); $input = Factory::getApplication()->getInput();
@@ -48,6 +51,7 @@ class HtmlView extends BaseHtmlView
$this->description = $input->getString('description', ''); $this->description = $input->getString('description', '');
$this->returnUrl = $input->getRaw('returnurl', ''); $this->returnUrl = $input->getRaw('returnurl', '');
$this->autostart = (bool) $input->getInt('autostart', 1); $this->autostart = (bool) $input->getInt('autostart', 1);
$this->action = $input->getCmd('msb_action', '');
if ($this->profileId <= 0) { if ($this->profileId <= 0) {
$this->profileId = 1; $this->profileId = 1;
@@ -14,13 +14,18 @@ use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text; use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route; use Joomla\CMS\Router\Route;
use Joomla\CMS\Session\Session; use Joomla\CMS\Session\Session;
use Joomla\CMS\Uri\Uri;
HTMLHelper::_('behavior.formvalidator'); HTMLHelper::_('behavior.formvalidator');
HTMLHelper::_('behavior.keepalive'); HTMLHelper::_('behavior.keepalive');
HTMLHelper::_('bootstrap.modal'); HTMLHelper::_('bootstrap.modal');
/* Component CSS via the Web Asset Manager (declared in media/joomla.asset.json) */ /* Component CSS. Registered in media/joomla.asset.json, but this component's WAM
styles don't emit on the admin document (asset resolves without error yet no
<link> renders), so attach the external stylesheet directly — reliably output
via <jdoc:include type="styles" />. */
$this->getDocument()->getWebAssetManager()->useStyle('com_mokosuitebackup.admin'); $this->getDocument()->getWebAssetManager()->useStyle('com_mokosuitebackup.admin');
$this->getDocument()->addStyleSheet(Uri::root(true) . '/media/com_mokosuitebackup/css/admin.css');
$profileId = (int) $this->item->id; $profileId = (int) $this->item->id;
$token = Session::getFormToken(); $token = Session::getFormToken();
@@ -34,6 +34,14 @@ if ($this->returnUrl !== '') {
$decoded = base64_decode($this->returnUrl, true); $decoded = base64_decode($this->returnUrl, true);
$raw = ($decoded !== false && $decoded !== '') ? $decoded : $this->returnUrl; $raw = ($decoded !== false && $decoded !== '') ? $decoded : $this->returnUrl;
/* Reject any backslash or control char outright: legitimate Joomla admin
return URLs never contain them, and a browser normalises a leading "/\"
to "//", turning a would-be root-relative path into a protocol-relative
open redirect (e.g. "/\evil.com" → "//evil.com"). */
if (preg_match('/[\x00-\x1f\x7f\\\\]/', $raw)) {
$raw = '';
}
$isAbsoluteHttp = (bool) preg_match('#^https?://#i', $raw); $isAbsoluteHttp = (bool) preg_match('#^https?://#i', $raw);
$isRootRelative = isset($raw[0]) && $raw[0] === '/' && (!isset($raw[1]) || $raw[1] !== '/'); $isRootRelative = isset($raw[0]) && $raw[0] === '/' && (!isset($raw[1]) || $raw[1] !== '/');
@@ -63,6 +71,7 @@ $config = [
'profileId' => $this->profileId, 'profileId' => $this->profileId,
'description' => $this->description, 'description' => $this->description,
'returnUrl' => $safeReturnUrl, 'returnUrl' => $safeReturnUrl,
'action' => $this->action,
'dashboardUrl' => $dashboardUrl, 'dashboardUrl' => $dashboardUrl,
'recordUrl' => Route::_('index.php?option=com_mokosuitebackup&view=backup&id=__MSBID__', false), 'recordUrl' => Route::_('index.php?option=com_mokosuitebackup&view=backup&id=__MSBID__', false),
'autostart' => $this->autostart, 'autostart' => $this->autostart,
@@ -77,12 +86,27 @@ $config = [
'dashboard' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD'), 'dashboard' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD'),
'viewRecord' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_VIEW_RECORD'), 'viewRecord' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_VIEW_RECORD'),
'leaveWarn' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING'), 'leaveWarn' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING'),
'continueUpdate' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_UPDATE'),
'cancelConfirm1' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL_CONFIRM1'),
'cancelConfirm2' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL_CONFIRM2'),
'cancelling' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_CANCELLING'),
'failedNoUpdate' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_FAILED_NO_UPDATE'),
], ],
]; ];
// Screen CSS lives in an external media stylesheet (media/com_mokosuitebackup/
// css/runbackup.css), not an inline <style> block. It is registered in
// media/joomla.asset.json, but this component's Web Asset Manager styles do not
// emit on the admin document (the asset resolves without error yet no <link> is
// produced — the same is true of com_mokosuitebackup.admin), so we attach the
// stylesheet directly to the document, which reliably renders via <jdoc:include
// type="styles" />.
$this->getDocument()->getWebAssetManager()->useStyle('com_mokosuitebackup.runbackup');
$this->getDocument()->addStyleSheet(Uri::root(true) . '/media/com_mokosuitebackup/css/runbackup.css');
?> ?>
<div class="msb-runbackup" style="max-width:640px;margin:3rem auto;padding:0 1rem;"> <div class="msb-runbackup">
<div class="card shadow-sm"> <div class="card shadow-sm bg-body-secondary">
<div class="card-body p-4"> <div class="card-body p-4 pb-3">
<h1 class="h4 mb-1" id="msb-rb-title"> <h1 class="h4 mb-1" id="msb-rb-title">
<span class="icon-archive" aria-hidden="true"></span> <span class="icon-archive" aria-hidden="true"></span>
<?php echo Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_TITLE'); ?> <?php echo Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_TITLE'); ?>
@@ -97,20 +121,42 @@ $config = [
); ?> ); ?>
</p> </p>
<div class="progress mb-3" style="height:1.75rem;"> <div id="msb-rb-phase" class="fw-bold mb-1"></div>
<div id="msb-rb-bar" class="progress-bar progress-bar-striped progress-bar-animated" <div id="msb-rb-status" class="mb-0"><?php echo htmlspecialchars(Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_STARTING'), ENT_QUOTES, 'UTF-8'); ?></div>
role="progressbar" style="width:0;" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">0%</div>
<div id="msb-rb-warn" class="alert alert-warning fw-bold mt-3 mb-0" role="alert">
<span class="icon-warning" aria-hidden="true"></span>
<?php echo htmlspecialchars(Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_DONT_CLOSE'), ENT_QUOTES, 'UTF-8'); ?>
</div> </div>
<div id="msb-rb-phase" class="fw-bold mb-1"></div> <?php if ($safeReturnUrl !== '') : ?>
<div id="msb-rb-status" class="text-muted small mb-3"><?php echo htmlspecialchars(Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_STARTING'), ENT_QUOTES, 'UTF-8'); ?></div> <!-- Pre-update/uninstall flow: opt in to auto-continue, or get a manual "Continue" button on completion. -->
<div class="form-check mt-3" id="msb-rb-autocontinue-wrap">
<input class="form-check-input" type="checkbox" id="msb-rb-autocontinue" checked>
<label class="form-check-label" for="msb-rb-autocontinue">
<?php echo htmlspecialchars(Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_AUTOCONTINUE'), ENT_QUOTES, 'UTF-8'); ?>
</label>
</div>
<?php endif; ?>
<div id="msb-rb-actions" class="d-none"> <div id="msb-rb-actions" class="d-none mt-3">
<button type="button" id="msb-rb-retry" class="btn btn-primary d-none"></button> <button type="button" id="msb-rb-retry" class="btn btn-primary d-none"></button>
<a href="#" id="msb-rb-record" class="btn btn-primary d-none"></a> <a href="#" id="msb-rb-record" class="btn btn-primary d-none" target="_blank" rel="noopener"></a>
<a href="#" id="msb-rb-continue" class="btn btn-warning d-none"></a> <a href="#" id="msb-rb-continue" class="btn btn-warning d-none"></a>
<a href="<?php echo $dashboardUrl; ?>" id="msb-rb-dashboard" class="btn btn-secondary d-none"></a> <a href="<?php echo $dashboardUrl; ?>" id="msb-rb-dashboard" class="btn btn-secondary d-none"></a>
</div> </div>
<!-- Cancel is available while the backup is running; requires a double confirmation. -->
<div id="msb-rb-cancel-wrap" class="mt-3">
<button type="button" id="msb-rb-cancel" class="btn btn-outline-danger">
<?php echo htmlspecialchars(Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL'), ENT_QUOTES, 'UTF-8'); ?>
</button>
</div>
</div>
<!-- Full-bleed progress bar, edge-to-edge along the bottom of the card. -->
<div class="msb-rb-progress" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
<div id="msb-rb-bar" class="msb-rb-bar is-animated">0%</div>
</div> </div>
</div> </div>
</div> </div>
@@ -122,27 +168,61 @@ $config = [
var CFG = <?php echo json_encode($config); ?>; var CFG = <?php echo json_encode($config); ?>;
var L = CFG.labels || {}; var L = CFG.labels || {};
var running = false; var running = false;
var cancelled = false;
var lastRecordId = 0; var lastRecordId = 0;
var el = { var el = {
bar: document.getElementById('msb-rb-bar'), bar: document.getElementById('msb-rb-bar'),
track: document.querySelector('.msb-rb-progress'),
phase: document.getElementById('msb-rb-phase'), phase: document.getElementById('msb-rb-phase'),
status: document.getElementById('msb-rb-status'), status: document.getElementById('msb-rb-status'),
warn: document.getElementById('msb-rb-warn'),
title: document.getElementById('msb-rb-title'), title: document.getElementById('msb-rb-title'),
actions: document.getElementById('msb-rb-actions'), actions: document.getElementById('msb-rb-actions'),
retry: document.getElementById('msb-rb-retry'), retry: document.getElementById('msb-rb-retry'),
record: document.getElementById('msb-rb-record'), record: document.getElementById('msb-rb-record'),
continue: document.getElementById('msb-rb-continue'), continue: document.getElementById('msb-rb-continue'),
dashboard: document.getElementById('msb-rb-dashboard') dashboard: document.getElementById('msb-rb-dashboard'),
autocontinue: document.getElementById('msb-rb-autocontinue'),
autocontinueWrap: document.getElementById('msb-rb-autocontinue-wrap'),
cancel: document.getElementById('msb-rb-cancel'),
cancelWrap: document.getElementById('msb-rb-cancel-wrap')
}; };
function hideCancel() { if (el.cancelWrap) { el.cancelWrap.classList.add('d-none'); } }
/* Cancel the running backup — requires DOUBLE confirmation. Stops the step
loop, tells the server to cancel the record, and leaves to the dashboard
WITHOUT continuing any pending update. */
if (el.cancel) {
el.cancel.addEventListener('click', function () {
if (!running || cancelled) { return; }
if (!window.confirm(L.cancelConfirm1 || 'Cancel the backup that is in progress?')) { return; }
if (!window.confirm(L.cancelConfirm2 || 'Are you sure? The backup will be stopped and the update will NOT continue.')) { return; }
cancelled = true;
running = false; /* also disarms the leave-warning */
el.cancel.disabled = true;
if (el.warn) { el.warn.classList.add('d-none'); }
setStatus(L.cancelling || 'Cancelling…');
var go = function () { window.location.href = CFG.dashboardUrl; };
/* Best-effort server-side cancel of the record, then leave. */
if (lastRecordId > 0) {
post({ task: 'ajax.cancelBackup', id: lastRecordId }).then(go, go);
} else {
go();
}
});
}
function setBar(pct, striped) { function setBar(pct, striped) {
pct = Math.max(0, Math.min(100, parseInt(pct, 10) || 0)); pct = Math.max(0, Math.min(100, parseInt(pct, 10) || 0));
el.bar.style.width = pct + '%'; el.bar.style.width = pct + '%';
el.bar.textContent = pct + '%'; el.bar.textContent = pct + '%';
el.bar.setAttribute('aria-valuenow', pct); if (el.track) { el.track.setAttribute('aria-valuenow', pct); }
if (!striped) { if (!striped) {
el.bar.classList.remove('progress-bar-striped', 'progress-bar-animated'); el.bar.classList.remove('is-animated');
} }
} }
function setPhase(t) { el.phase.textContent = t || ''; } function setPhase(t) { el.phase.textContent = t || ''; }
@@ -167,21 +247,44 @@ $config = [
} }
async function onComplete() { async function onComplete() {
el.bar.classList.remove('progress-bar-striped', 'progress-bar-animated'); el.bar.classList.remove('is-animated');
el.bar.classList.add('bg-success'); el.bar.classList.add('is-success');
setBar(100, false); setBar(100, false);
el.title.textContent = L.complete || 'Backup complete'; el.title.textContent = L.complete || 'Backup complete';
if (el.warn) { el.warn.classList.add('d-none'); }
hideCancel();
/* Arm the pre-action throttles so a following server-side onExtension /* Set the one-shot skip flag for THIS action so the following
Before(Update|Uninstall) backup is skipped (no duplicate backup). server-side onExtensionBefore(Update|Uninstall) backup is skipped once
(no duplicate backup) without suppressing later, distinct actions.
Awaited so it lands before we hand back to the update/uninstall. */ Awaited so it lands before we hand back to the update/uninstall. */
try { await post({ task: 'ajax.preupdateAck' }); } catch (e) {} try { await post({ task: 'ajax.preupdateAck', action: CFG.action || '' }); } catch (e) {}
if (CFG.returnUrl) { if (CFG.returnUrl) {
/* Pre-update / pre-uninstall flow: continue the original action. /* Pre-update / pre-uninstall flow. If "auto-continue" is ticked
No "view record" button here — we're handing back to Joomla. */ (default) hand back to Joomla automatically; otherwise stop here
and offer a link to the new backup record plus a manual
"Continue the update" button. */
if (el.autocontinueWrap) { el.autocontinueWrap.classList.add('d-none'); }
var autoContinue = !el.autocontinue || el.autocontinue.checked;
if (autoContinue) {
setStatus(L.continuing || 'Continuing…'); setStatus(L.continuing || 'Continuing…');
window.setTimeout(function () { window.location.href = CFG.returnUrl; }, 1200); window.setTimeout(function () { window.location.href = CFG.returnUrl; }, 1200);
} else {
setStatus(L.complete || 'Backup complete');
showActions();
if (lastRecordId > 0 && CFG.recordUrl) {
showBtn(el.record, L.viewRecord || 'View backup record',
CFG.recordUrl.replace('__MSBID__', String(lastRecordId)));
}
el.continue.classList.remove('btn-warning');
el.continue.classList.add('btn-success');
showBtn(el.continue, L.continueUpdate || 'Continue the update', CFG.returnUrl);
}
} else { } else {
/* Manual "Backup Now": offer to view the record that was just made. */ /* Manual "Backup Now": offer to view the record that was just made. */
setStatus(L.complete || 'Backup complete'); setStatus(L.complete || 'Backup complete');
@@ -198,32 +301,36 @@ $config = [
function onError(message) { function onError(message) {
running = false; running = false;
el.bar.classList.remove('progress-bar-striped', 'progress-bar-animated'); el.bar.classList.remove('is-animated');
el.bar.classList.add('bg-danger'); el.bar.classList.add('is-danger');
if (el.warn) { el.warn.classList.add('d-none'); }
el.title.textContent = L.failed || 'Backup failed'; el.title.textContent = L.failed || 'Backup failed';
setStatus(message || 'Backup failed'); /* Pre-update/uninstall flow: the backup failed, so the update must NOT
run. We deliberately do NOT offer a "continue without backup" option
here — the only ways forward are to retry the backup or go back. */
setStatus(CFG.returnUrl ? (L.failedNoUpdate || 'Backup failed — the update was not started. Retry the backup, or go back without updating.') : (message || 'Backup failed'));
hideCancel();
showActions(); showActions();
el.retry.textContent = L.retry || 'Retry'; el.retry.textContent = L.retry || 'Retry';
el.retry.classList.remove('d-none'); el.retry.classList.remove('d-none');
el.retry.onclick = function () { el.retry.onclick = function () {
el.retry.classList.add('d-none'); el.retry.classList.add('d-none');
el.continue.classList.add('d-none'); el.bar.classList.remove('is-danger');
el.bar.classList.remove('bg-danger'); el.bar.classList.add('is-animated');
el.bar.classList.add('progress-bar-striped', 'progress-bar-animated');
run(); run();
}; };
if (CFG.returnUrl) {
showBtn(el.continue, L.continue || 'Continue without backup', CFG.returnUrl);
}
showBtn(el.dashboard, L.dashboard || 'Back to dashboard', CFG.dashboardUrl); showBtn(el.dashboard, L.dashboard || 'Back to dashboard', CFG.dashboardUrl);
} }
async function run() { async function run() {
if (running) { return; } if (running) { return; }
running = true; running = true;
cancelled = false;
el.actions.classList.add('d-none'); el.actions.classList.add('d-none');
if (el.cancelWrap) { el.cancelWrap.classList.remove('d-none'); }
if (el.cancel) { el.cancel.disabled = false; }
setBar(0, true); setBar(0, true);
setPhase(''); setPhase('');
setStatus(L.starting || 'Starting backup…'); setStatus(L.starting || 'Starting backup…');
@@ -241,7 +348,7 @@ $config = [
if (init.record_id) { lastRecordId = init.record_id; } if (init.record_id) { lastRecordId = init.record_id; }
var done = false; var done = false;
while (!done) { while (!done && !cancelled) {
var step = await post({ task: 'ajax.step', session_id: init.session_id }); var step = await post({ task: 'ajax.step', session_id: init.session_id });
if (!step || step.error) { if (!step || step.error) {
@@ -255,16 +362,27 @@ $config = [
done = step.done || false; done = step.done || false;
} }
/* User cancelled mid-flight — the click handler already stops the
loop and navigates away, so do not fall through to completion. */
if (cancelled) { return; }
running = false; running = false;
await onComplete(); await onComplete();
} catch (err) { } catch (err) {
if (cancelled) { return; }
onError(err && err.message ? err.message : String(err)); onError(err && err.message ? err.message : String(err));
} }
} }
/* Warn before leaving while a backup is mid-flight. */ /* Warn before closing/leaving the window while a backup is mid-flight.
Return the message too — some browsers need it, others show a generic
prompt, but any non-empty value triggers the confirmation dialog. */
window.addEventListener('beforeunload', function (e) { window.addEventListener('beforeunload', function (e) {
if (running) { e.preventDefault(); e.returnValue = L.leaveWarn || ''; } if (!running) { return undefined; }
var msg = L.leaveWarn || 'A backup is in progress. Leaving now will cancel it.';
e.preventDefault();
e.returnValue = msg;
return msg;
}); });
if (CFG.autostart) { if (CFG.autostart) {
@@ -8,7 +8,7 @@
--> -->
<extension type="module" client="administrator" method="upgrade"> <extension type="module" client="administrator" method="upgrade">
<name>Module - MokoSuiteBackup - cPanel</name> <name>Module - MokoSuiteBackup - cPanel</name>
<version>02.58.26</version> <version>02.64.02</version>
<creationDate>2026-06-23</creationDate> <creationDate>2026-06-23</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
--> -->
<extension type="plugin" group="actionlog" method="upgrade"> <extension type="plugin" group="actionlog" method="upgrade">
<name>Action Log - MokoSuiteBackup</name> <name>Action Log - MokoSuiteBackup</name>
<version>02.58.26</version> <version>02.64.02</version>
<creationDate>2026-06-04</creationDate> <creationDate>2026-06-04</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
--> -->
<extension type="plugin" group="console" method="upgrade"> <extension type="plugin" group="console" method="upgrade">
<name>Console - MokoSuiteBackup</name> <name>Console - MokoSuiteBackup</name>
<version>02.58.26</version> <version>02.64.02</version>
<creationDate>2026-06-04</creationDate> <creationDate>2026-06-04</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
--> -->
<extension type="plugin" group="content" method="upgrade"> <extension type="plugin" group="content" method="upgrade">
<name>Content - MokoSuiteBackup</name> <name>Content - MokoSuiteBackup</name>
<version>02.58.26</version> <version>02.64.02</version>
<creationDate>2026-06-04</creationDate> <creationDate>2026-06-04</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="quickicon" method="upgrade"> <extension type="plugin" group="quickicon" method="upgrade">
<name>Quick Icon - MokoSuiteBackup</name> <name>Quick Icon - MokoSuiteBackup</name>
<version>02.58.26</version> <version>02.64.02</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>
@@ -14,3 +14,4 @@ PLG_SYSTEM_MOKOJOOMBACKUP_BACKING_UP="Backing up…"
PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_COMPLETE="Backup complete — safe to update." PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_COMPLETE="Backup complete — safe to update."
PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_FAILED="Backup failed" PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_FAILED="Backup failed"
PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_STARTING="Starting backup…" PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_STARTING="Starting backup…"
PLG_SYSTEM_MOKOSUITEBACKUP_UPDATE_RUNNING="Backup complete. The update is now running — please wait and do not close this window…"
@@ -14,3 +14,4 @@ PLG_SYSTEM_MOKOJOOMBACKUP_BACKING_UP="Backing up…"
PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_COMPLETE="Backup complete — safe to update." PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_COMPLETE="Backup complete — safe to update."
PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_FAILED="Backup failed" PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_FAILED="Backup failed"
PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_STARTING="Starting backup…" PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_STARTING="Starting backup…"
PLG_SYSTEM_MOKOSUITEBACKUP_UPDATE_RUNNING="Backup complete. The update is now running — please wait and do not close this window…"
@@ -1,17 +1,17 @@
/** /**
* MokoSuiteBackup — full-screen backup before an EXTENSION update / uninstall. * MokoSuiteBackup — full-screen backup before an EXTENSION update / uninstall.
* *
* Injected by plg_system_mokosuitebackup on the Extensions → Update and * Injected by plg_system_mokosuitebackup on Extensions → Update and
* Extensions → Manage pages. Unlike the core Joomla update (which has a * Extensions → Manage. Joomla 6 renders those toolbar buttons as web
* server-side page load we can redirect), com_installer's update.update and * components (`<button task="update.update">`), NOT inline
* manage.remove are POST actions with a CSRF token and a checked cid[] list — * onclick="Joomla.submitbutton(...)", so wrapping Joomla.submitbutton is
* so we intercept CLIENT-SIDE: capture the selection, go to the full-screen * unreliable. Instead we add a CAPTURE-phase click listener on any element
* backup page, and on return restore the selection and re-submit the real POST * carrying a matching `task` attribute — it runs before the toolbar web
* form (fresh token) so the update/uninstall proceeds. * component, so we can stop it, run the full-screen backup, and on return
* restore the checked cid[] selection and re-fire the button.
* *
* The full-screen page arms the pre-action throttle on success (ajax.preupdate * The full-screen page arms the pre-action throttle on success, so the
* Ack), so the server-side onExtensionBefore(Update|Uninstall) backup is then * server-side onExtensionBefore(Update|Uninstall) backup is then skipped.
* skipped — no duplicate backup.
* *
* Config: Joomla.getOptions('plg_system_mokosuitebackup.installer'). * Config: Joomla.getOptions('plg_system_mokosuitebackup.installer').
*/ */
@@ -28,26 +28,106 @@
var STORAGE_KEY = 'msbInstallerBackup'; var STORAGE_KEY = 'msbInstallerBackup';
/* Toolbar tasks to intercept, gated by which feature is enabled. */
var TASKS = {}; var TASKS = {};
if (cfg.backupBeforeUpdate) { TASKS['update.update'] = true; } if (cfg.backupBeforeUpdate) { TASKS['update.update'] = true; }
if (cfg.backupBeforeUninstall) { TASKS['manage.remove'] = true; } if (cfg.backupBeforeUninstall) { TASKS['manage.remove'] = true; TASKS['manage.uninstall'] = true; }
function checkedCids() { function checkedCids() {
var form = document.getElementById('adminForm'); var form = document.getElementById('adminForm');
var cids = []; var cids = [];
if (form) { if (form) {
form.querySelectorAll('input[name="cid[]"]:checked').forEach(function (b) { form.querySelectorAll('input[name="cid[]"]:checked').forEach(function (b) { cids.push(b.value); });
cids.push(b.value);
});
} }
return cids; return cids;
} }
/* On return from the backup screen: restore the selection and re-submit. */ /* Intercept the toolbar Update/Uninstall click in the CAPTURE phase, before
function resumeIfNeeded() { the toolbar web component's own handler runs. */
document.addEventListener('click', function (e) {
if (window.__msbResuming || cfg.recentBackup) {
return;
}
var node = e.target;
var btn = (node && node.closest) ? node.closest('[task]') : null;
if (!btn) {
return;
}
var task = btn.getAttribute('task');
if (!TASKS[task]) {
return;
}
var cids = checkedCids();
/* Nothing selected — let Joomla show its own "please select" notice. */
if (!cids.length) {
return;
}
var ret = window.location.href;
ret += (ret.indexOf('?') === -1 ? '?' : '&') + 'msb_resume=1';
/* Tell the backup screen which action it is fronting so its completion
ack sets the matching one-shot skip flag (and not the other). */
var action = (task === 'update.update') ? 'update' : 'uninstall';
/* Build the redirect BEFORE we swallow the click. btoa() throws on
non-Latin1 input (e.g. a UTF-8 filter value in the query string), so
encode as UTF-8 first; if it still fails, bail WITHOUT preventing the
click so Joomla's own update/uninstall proceeds normally. */
var target;
try {
target = cfg.runbackupUrl
+ '&profile_id=' + encodeURIComponent(cfg.profileId)
+ '&msb_action=' + action
+ '&returnurl=' + encodeURIComponent(btoa(unescape(encodeURIComponent(ret))));
} catch (x) {
return;
}
e.preventDefault();
e.stopImmediatePropagation();
try {
sessionStorage.setItem(STORAGE_KEY, JSON.stringify({ task: task, cids: cids }));
} catch (x) {}
window.location.href = target;
}, true);
/* Full-screen "update is running" overlay shown while the re-fired update
POST processes, so the user knows it is working and not to close/switch. */
function showUpdateOverlay() {
if (document.getElementById('msb-update-overlay')) { return; }
var o = document.createElement('div');
o.id = 'msb-update-overlay';
o.setAttribute('role', 'status');
o.style.cssText = 'position:fixed;inset:0;z-index:2147483647;background:#000;color:#fff;'
+ 'display:flex;flex-direction:column;align-items:center;justify-content:center;'
+ 'text-align:center;padding:2rem;font-size:1.15rem;font-weight:600;';
var spin = document.createElement('span');
spin.className = 'spinner-border mb-3';
spin.setAttribute('aria-hidden', 'true');
var msg = document.createElement('div');
msg.textContent = (cfg && cfg.updateRunningText)
|| 'Backup complete. The update is now running — please wait and do not close this window…';
o.appendChild(spin);
o.appendChild(msg);
document.body.appendChild(o);
}
/* On return from the backup screen: restore the selection and re-fire. */
function resume() {
var params = new URLSearchParams(window.location.search); var params = new URLSearchParams(window.location.search);
if (params.get('msb_resume') !== '1') { if (params.get('msb_resume') !== '1') {
@@ -55,15 +135,14 @@
} }
var raw = null; var raw = null;
try { raw = sessionStorage.getItem(STORAGE_KEY); } catch (e) {} try { raw = sessionStorage.getItem(STORAGE_KEY); sessionStorage.removeItem(STORAGE_KEY); } catch (x) {}
try { sessionStorage.removeItem(STORAGE_KEY); } catch (e) {}
if (!raw) { if (!raw) {
return; return;
} }
var data; var data;
try { data = JSON.parse(raw); } catch (e) { data = null; } try { data = JSON.parse(raw); } catch (x) { data = null; }
if (!data || !data.task) { if (!data || !data.task) {
return; return;
@@ -71,72 +150,51 @@
var form = document.getElementById('adminForm'); var form = document.getElementById('adminForm');
if (!form) { if (form) {
return;
}
(data.cids || []).forEach(function (id) { (data.cids || []).forEach(function (id) {
var box = form.querySelector('input[name="cid[]"][value="' + id + '"]'); var box = form.querySelector('input[name="cid[]"][value="' + id + '"]');
if (box) { box.checked = true; } if (box) { box.checked = true; }
}); });
/* Re-run the original toolbar action; the wrapper lets it through /* Restore boxchecked AND fire its `change` event. Joomla's toolbar web
because the resume flag is set. */ component (joomla-toolbar-button) keeps a list-selection button
DISABLED until boxchecked changes via a change event — setting the
value alone leaves the button disabled, so executeTask() would no-op
and the update never submits (it just returns to the list). */
var bc = form.querySelector('input[name="boxchecked"]');
if (bc) {
bc.value = (data.cids || []).length;
bc.dispatchEvent(new Event('change', { bubbles: true }));
}
}
window.__msbResuming = true; window.__msbResuming = true;
if (window.Joomla && typeof Joomla.submitbutton === 'function') { /* Show a full-screen "update is running" overlay so the user gets clear
feedback that the update is proceeding while the re-fired POST runs.
It persists until the page navigates away (the update finishes and
redirects), matching the black full-screen backup screen. */
showUpdateOverlay();
/* Re-fire the toolbar action. The `task` attribute is on the OUTER
<joomla-toolbar-button>, but its click handler is bound to the INNER
<button>/<a>, so click that inner element (clicking the wrapper does
nothing). Fall back to Joomla.submitbutton if the button isn't found. */
window.setTimeout(function () {
var wc = document.querySelector('[task="' + data.task + '"]');
var inner = wc ? (wc.querySelector('button, a') || wc) : null;
if (inner) {
inner.click();
} else if (window.Joomla && typeof Joomla.submitbutton === 'function') {
Joomla.submitbutton(data.task); Joomla.submitbutton(data.task);
} else if (typeof window.submitbutton === 'function') {
window.submitbutton(data.task);
} }
}, 300);
} }
/* Wrap Joomla.submitbutton to intercept the update/uninstall tasks. */ if (document.readyState === 'loading') {
function wrapSubmit() { document.addEventListener('DOMContentLoaded', resume);
var J = window.Joomla; } else {
resume();
if (!J || typeof J.submitbutton !== 'function' || J.submitbutton.__msbWrapped) {
return;
} }
var original = J.submitbutton;
var wrapped = function (task) {
/* Skip when resuming after a backup, when a backup already ran this
session (throttle window), or for tasks we don't handle. */
if (window.__msbResuming || cfg.recentBackup || !TASKS[task]) {
return original.apply(J, arguments);
}
var cids = checkedCids();
/* Nothing selected — let Joomla show its own "please select" notice
and don't run a pointless backup. */
if (!cids.length) {
return original.apply(J, arguments);
}
try {
sessionStorage.setItem(STORAGE_KEY, JSON.stringify({ task: task, cids: cids }));
} catch (e) {}
var ret = window.location.href;
ret += (ret.indexOf('?') === -1 ? '?' : '&') + 'msb_resume=1';
window.location.href = cfg.runbackupUrl
+ '&profile_id=' + encodeURIComponent(cfg.profileId)
+ '&returnurl=' + encodeURIComponent(btoa(ret));
};
wrapped.__msbWrapped = true;
J.submitbutton = wrapped;
}
/* Wrap as early as possible, and again once the DOM/core JS is ready. */
wrapSubmit();
document.addEventListener('DOMContentLoaded', function () {
wrapSubmit();
resumeIfNeeded();
});
})(); })();
@@ -7,7 +7,7 @@
--> -->
<extension type="plugin" group="system" method="upgrade"> <extension type="plugin" group="system" method="upgrade">
<name>System - MokoSuiteBackup</name> <name>System - MokoSuiteBackup</name>
<version>02.58.26</version> <version>02.64.02</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>
@@ -15,6 +15,7 @@ defined('_JEXEC') or die;
use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Document\HtmlDocument; use Joomla\CMS\Document\HtmlDocument;
use Joomla\CMS\Factory; use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Plugin\CMSPlugin; use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Router\Route; use Joomla\CMS\Router\Route;
use Joomla\CMS\Session\Session; use Joomla\CMS\Session\Session;
@@ -28,6 +29,12 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface
{ {
protected $autoloadLanguage = true; protected $autoloadLanguage = true;
/** @var array<string,bool> Per-request guard: at most one pre-action backup per request (dedupes a batch update). */
private array $preactionRan = [];
/** @var array<string,bool> Per-request cache of the one-shot "client already backed up" skip flag. */
private array $preactionSkip = [];
public static function getSubscribedEvents(): array public static function getSubscribedEvents(): array
{ {
return [ return [
@@ -223,23 +230,21 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface
return; return;
} }
// Already backed up this session (throttle window)? Then the JS should
// let the action proceed without a fresh backup.
$session = Factory::getSession();
$throttleK = $view === 'update'
? 'mokosuitebackup.preaction_backup_before_update'
: 'mokosuitebackup.preaction_backup_before_uninstall';
$lastRun = (int) $session->get($throttleK, 0);
$recent = $lastRun > 0 && (time() - $lastRun) < 600;
$doc->getWebAssetManager()->useScript('core'); $doc->getWebAssetManager()->useScript('core');
/* No time-based "recently backed up" suppression here: each distinct
Update/Uninstall click should get its own full-screen backup. The
re-fired click after a backup is guarded client-side by
window.__msbResuming, and the synchronous server-side fallback is
deduped per-request + by a one-shot skip flag (see runPreActionBackup),
so there is no double backup without blocking distinct actions. */
$doc->addScriptOptions('plg_system_mokosuitebackup.installer', [ $doc->addScriptOptions('plg_system_mokosuitebackup.installer', [
'runbackupUrl' => Route::_('index.php?option=com_mokosuitebackup&view=runbackup&tmpl=component&autostart=1', false), 'runbackupUrl' => Route::_('index.php?option=com_mokosuitebackup&view=runbackup&tmpl=component&autostart=1', false),
'profileId' => (int) $params->get('default_profile', 1), 'profileId' => (int) $params->get('default_profile', 1),
'backupBeforeUpdate' => $view === 'update' && (bool) $beforeUpdate, 'backupBeforeUpdate' => $view === 'update' && (bool) $beforeUpdate,
'backupBeforeUninstall' => $view === 'manage' && (bool) $beforeUninst, 'backupBeforeUninstall' => $view === 'manage' && (bool) $beforeUninst,
'recentBackup' => $recent, 'recentBackup' => false,
'updateRunningText' => Text::_('PLG_SYSTEM_MOKOSUITEBACKUP_UPDATE_RUNNING'),
]); ]);
$doc->addScript(Uri::root(true) . '/media/plg_system_mokosuitebackup/js/installer-backup.js', [], ['defer' => true]); $doc->addScript(Uri::root(true) . '/media/plg_system_mokosuitebackup/js/installer-backup.js', [], ['defer' => true]);
@@ -313,16 +318,20 @@ JS;
$session = Factory::getSession(); $session = Factory::getSession();
// Returned from the backup screen: arm the throttle so the synchronous // Returned from the backup screen: mark this core update backed up so we
// onExtensionBeforeUpdate backup doesn't run again, then let it proceed. // don't redirect to the backup screen again on a page revisit, then let
// it proceed. Uses a DEDICATED core-update key — a core update does not
// fire onExtensionBefore(Update|Uninstall), so it must not touch the
// extension pre-action flags (which would suppress extension backups).
if ((int) $input->getInt('is_backed_up', 0) === 1) { if ((int) $input->getInt('is_backed_up', 0) === 1) {
$session->set('mokosuitebackup.preaction_backup_before_update', time()); $session->set('mokosuitebackup.core_update_backed_up', time());
return; return;
} }
// Backed up recently already — let the update proceed. // Backed up this core update recently already — let the update proceed
$lastRun = (int) $session->get('mokosuitebackup.preaction_backup_before_update', 0); // without redirecting to the backup screen again.
$lastRun = (int) $session->get('mokosuitebackup.core_update_backed_up', 0);
if ($lastRun > 0 && (time() - $lastRun) < 600) { if ($lastRun > 0 && (time() - $lastRun) < 600) {
return; return;
@@ -525,17 +534,37 @@ JS;
return; return;
} }
// Throttle: only run once per 10 minutes to prevent duplicate /* One backup per Update action — NOT a 10-minute time throttle. The old
// backups when multiple extensions are updated in a batch 600s window suppressed every extension's pre-action backup for 10
$session = Factory::getSession(); minutes after any single one ran (e.g. after updating this very
$sessionKey = 'mokosuitebackup.preaction_' . $paramName; extension), so distinct updates silently went un-backed-up. Instead:
$lastRun = $session->get($sessionKey, 0);
if (time() - $lastRun < 600) { (a) dedupe within a single request — a batch update fires this handler
once per extension, all in the same request, so back up only once;
(b) honour a one-shot session skip flag set by ajax.preupdateAck when
the client-side full-screen backup already ran and is re-firing the
real update/uninstall — consumed once, so the NEXT distinct action
backs up again. */
if (!empty($this->preactionRan[$paramName])) {
return; return;
} }
$session->set($sessionKey, time()); $session = Factory::getSession();
$skipKey = 'mokosuitebackup.skip_preaction_' . $paramName;
if (!array_key_exists($paramName, $this->preactionSkip)) {
$this->preactionSkip[$paramName] = (bool) $session->get($skipKey, false);
$session->set($skipKey, false);
}
if ($this->preactionSkip[$paramName]) {
// Client already backed up; skip this whole re-fired (possibly batch) request.
$this->preactionRan[$paramName] = true;
return;
}
$this->preactionRan[$paramName] = true;
$profileId = (int) $params->get('default_profile', 1); $profileId = (int) $params->get('default_profile', 1);
@@ -7,7 +7,7 @@
--> -->
<extension type="plugin" group="task" method="upgrade"> <extension type="plugin" group="task" method="upgrade">
<name>Task - MokoSuiteBackup</name> <name>Task - MokoSuiteBackup</name>
<version>02.58.26</version> <version>02.64.02</version>
<creationDate>2026-06-02</creationDate> <creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
--> -->
<extension type="plugin" group="webservices" method="upgrade"> <extension type="plugin" group="webservices" method="upgrade">
<name>Web Services - MokoSuiteBackup</name> <name>Web Services - MokoSuiteBackup</name>
<version>02.58.26</version> <version>02.64.02</version>
<creationDate>2026-06-02</creationDate> <creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
+1 -1
View File
@@ -8,7 +8,7 @@
<extension type="package" method="upgrade"> <extension type="package" method="upgrade">
<name>Package - MokoSuiteBackup</name> <name>Package - MokoSuiteBackup</name>
<packagename>mokosuitebackup</packagename> <packagename>mokosuitebackup</packagename>
<version>02.58.26</version> <version>02.64.02</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>