Compare commits

...

85 Commits

Author SHA1 Message Date
mokogit-actions[bot] cec177af39 chore(version): pre-release bump to 02.67.09-dev [skip ci] 2026-07-18 21:46:03 +00:00
mokogit-actions[bot] 194b4c8bed chore(version): pre-release bump to 02.67.07-dev [skip ci] 2026-07-18 21:43:52 +00:00
jmiller 24d6205139 Merge pull request 'chore(gitignore): ignore .gemini/ and local wiki/docs per ecosystem standard' (#305) from chore/ignore-gemini-wiki into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 42s
2026-07-18 21:31:09 +00:00
jmiller 23bb630132 chore(gitignore): ignore .gemini/ and local wiki/docs per ecosystem standard
Generic: Project CI / Lint & Validate (pull_request) Waiting to run
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Waiting to run
Universal: PR Check / Secret Scan (pull_request) Waiting to run
Universal: PR Check / Validate PR (pull_request) Waiting to run
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Waiting to run
Branch Cleanup / Delete merged branch (pull_request) Waiting to run
RC Revert / Rename rc/ back to dev/ (pull_request) Waiting to run
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 41s
Re-cut from dev to resolve base divergence.

Authored-by: Moko Consulting
2026-07-18 16:30:59 -05:00
jmiller 7e93078b9b Merge pull request 'fix(backup): Cancel Stalled fatal, stalled self-heal, runbackup overlay, update site' (#310) from fix/backup-cancel-stall-runbackup-updatesite 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 44s
2026-07-18 21:29:20 +00:00
jmiller a96cad9f97 Merge branch 'fix/backup-cancel-stall-runbackup-updatesite' of https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteBackup into fix/backup-cancel-stall-runbackup-updatesite
Generic: Project CI / Lint & Validate (pull_request) Waiting to run
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Waiting to run
Universal: PR Check / Secret Scan (pull_request) Waiting to run
Universal: PR Check / Validate PR (pull_request) Waiting to run
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Waiting to run
Branch Cleanup / Delete merged branch (pull_request) Waiting to run
RC Revert / Rename rc/ back to dev/ (pull_request) Waiting to run
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 41s
# Conflicts:
#	.mokogit/workflows/issue-branch.yml
#	SECURITY.md
#	source/packages/com_mokosuitebackup/mokosuitebackup.xml
#	source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml
#	source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml
#	source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml
#	source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml
#	source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml
#	source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml
#	source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml
#	source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml
#	source/pkg_mokosuitebackup.xml
2026-07-18 16:28:27 -05:00
jmiller ba2d065428 Merge remote-tracking branch 'origin/dev' into fix/backup-cancel-stall-runbackup-updatesite
# Conflicts:
#	.mokogit/workflows/issue-branch.yml
#	SECURITY.md
#	source/packages/com_mokosuitebackup/mokosuitebackup.xml
#	source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml
#	source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml
#	source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml
#	source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml
#	source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml
#	source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml
#	source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml
#	source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml
#	source/pkg_mokosuitebackup.xml
2026-07-18 16:25:47 -05:00
mokogit-actions[bot] 166444b845 chore(version): pre-release bump to 02.67.05-dev [skip ci] 2026-07-18 20:30:00 +00:00
jmiller 93b7437e0c fix(backup): address PR review — transactional update site, safer stalled sweep
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 41s
Generic: Project CI / Lint & Validate (pull_request) Has been cancelled
Universal: PR Check / Branch Policy (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
Silent-failure-hunter:
- ensureUpdateSite(): wrap the two inserts in a transaction so a failed link
  insert cannot orphan a #__update_sites row (the idempotency guard checks only
  the link table, so an orphan would be re-created on every retry). Surface
  failures via Log::add(jerror) + enqueueMessage instead of a silent error_log(),
  matching restoreDownloadKey().

Code-reviewer:
- markStalledAsFailed(): raise threshold to a generous 6h (well above
  PreflightCheck's 30-min per-profile start guard) so a long browser/CLI backup
  is not reaped mid-flight; delete partial archives for reaped rows to match
  PreflightCheck; add a WARNING breadcrumb in the catch. Real fix (heartbeat
  column) tracked as a follow-up in the stalled-backup issue.

Authored-by: Moko Consulting
Claude-Session: https://claude.ai/code/session_019V1WPYojhKyyPHewfUsPEz
2026-07-18 15:27:55 -05:00
mokogit-actions[bot] 36098fa606 chore(version): pre-release bump to 02.67.04-dev [skip ci] 2026-07-18 20:08:17 +00:00
jmiller cf191be338 fix(backup): repair Cancel Stalled fatal, stalled-run self-heal, runbackup overlay, update site
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 36s
- BackupsController::cancelStalled()/purge(): use Factory::getContainer()
  instead of the protected CMSApplication::getContainer() (fatal on Cancel Stalled)
- BackupStatusHelper::markStalledAsFailed(): flip orphaned "running" records
  older than a 1h threshold to "fail"; swept when the records list loads
- backup detail view: render "Not finished" for a null/zero backupend
  instead of the bogus "Monday, November 29, -0001"
- runbackup: full-screen black overlay (fixed, z-indexed) instead of an
  in-admin-chrome card, matching the lock overlay
- script.php ensureUpdateSite(): recreate a missing package update site in
  postflight so the "update site not found" banner self-heals

Authored-by: Moko Consulting
Claude-Session: https://claude.ai/code/session_019V1WPYojhKyyPHewfUsPEz
2026-07-18 15:04:10 -05:00
mokogit-actions[bot] 1476a6bc41 chore(version): pre-release bump to 02.67.02-dev [skip ci] 2026-07-17 02:13:06 +00:00
git-actions[bot] 739bb4db3c chore(version): auto-bump patch 02.67.01-dev [skip ci] 2026-07-17 02:12:52 +00:00
git-actions[bot] 1f49c49ebd chore: promote changelog [Unreleased] → [02.67.00]
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 / 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 / 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
Generic: Standards Compliance / Compliance Summary (push) Has been cancelled
Generic: Standards Compliance / Version Consistency Check (push) Successful in 45s
Generic: Standards Compliance / Code Duplication Detection (push) Successful in 43s
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
2026-07-17 00:33:27 +00:00
git-actions[bot] 64875771a7 chore(release): build 02.67.00 [skip ci] 2026-07-17 00:33:16 +00:00
jmiller 605e45a967 Release: dev -> main (#303) — pre-update backup hardening + config lang fix
Generic: Standards Compliance / Secret Scanning (push) Failing after 5s
Generic: Standards Compliance / License Header Validation (push) Successful in 4s
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 5s
Generic: Standards Compliance / Documentation Quality Check (push) Successful in 4s
Generic: Standards Compliance / README Completeness Check (push) Failing after 4s
Generic: Standards Compliance / Git Repository Hygiene (push) Successful in 7s
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 4s
Generic: Standards Compliance / Insecure Code Pattern Detection (push) Successful in 4s
Generic: Standards Compliance / Version Consistency Check (push) Successful in 50s
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 7s
Generic: Standards Compliance / TODO/FIXME Tracking (push) Successful in 4s
Generic: Standards Compliance / Code Complexity Analysis (push) Successful in 48s
Generic: Standards Compliance / Code Duplication Detection (push) Successful in 47s
Generic: Standards Compliance / Broken Link Detection (push) Successful in 6s
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 3s
Generic: Standards Compliance / Dependency Vulnerability Scanning (push) Successful in 52s
Generic: Standards Compliance / Unused Dependencies Check (push) Successful in 57s
Generic: Standards Compliance / Terraform Configuration Validation (push) Successful in 9s
Generic: Standards Compliance / Enterprise Readiness Check (push) Failing after 50s
Generic: Standards Compliance / Repository Health Check (push) Failing after 50s
Generic: Standards Compliance / Compliance Summary (push) Has been cancelled
https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-17 00:32:56 +00:00
mokogit-actions[bot] d7639ec3d9 chore(version): pre-release bump to 02.66.07-dev [skip ci]
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 34s
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
RC Revert / Rename rc/ back to dev/ (pull_request) Has been cancelled
2026-07-17 00:26:33 +00:00
git-actions[bot] 9c37bc6303 chore(version): auto-bump patch 02.66.06-dev [skip ci] 2026-07-17 00:26:14 +00:00
jmiller c01b0ab112 Merge branch 'fix/preaction-skip-ttl' into dev
Universal: Auto Version Bump / Version Bump (push) Successful in 11s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 30s
fix(preupdate): time-bound the pre-action skip flag

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-16 19:24:56 -05:00
jmiller 462aed0e51 fix(preupdate): time-bound the pre-action skip flag
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 7s
The one-shot "client already backed up" skip flag was a bare boolean with no
expiry. If a user ran the full-screen pre-update backup then abandoned the flow
(closed the tab before the re-fired update fired), the flag lingered for the
whole session and could suppress a LATER, unrelated pre-action backup — a real
update running with no backup.

Store the unix time the flag was armed (AjaxController::armPreactionSkip) and
honour the skip only within PREACTION_SKIP_TTL (300s), consuming it one-shot.
A stale flag is now ignored and the server backs up as normal.

Completes the pre-update double-backup hardening: server-side arming stops the
"ack fails -> double backup" direction; the TTL stops the "abandoned flow ->
skipped backup" direction. Fail-safe both ways.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-16 19:24:37 -05:00
mokogit-actions[bot] c7f0a122a4 chore(version): pre-release bump to 02.66.05-dev [skip ci] 2026-07-17 00:20:45 +00:00
git-actions[bot] 5fac72b5ed chore(version): auto-bump patch 02.66.04-dev [skip ci] 2026-07-17 00:20:27 +00:00
jmiller 3efadf941f Merge branch 'fix/preupdate-double-backup' into dev
Universal: Auto Version Bump / Version Bump (push) Successful in 13s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 28s
fix(preupdate): arm pre-action skip flag server-side to stop double backup

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-16 19:18:56 -05:00
jmiller dabc556e18 fix(preupdate): arm pre-action skip flag server-side to stop double backup
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 32s
The one-shot flag that tells plg_system_mokosuitebackup to skip its synchronous
pre-update/pre-uninstall backup was set only by the client-side ajax.preupdateAck
POST, whose failure the runbackup screen swallows in a catch(). If that POST
failed, the re-fired extension update ran a SECOND backup behind the black
"update is running" overlay — the "backup runs twice" symptom.

Arm the skip flag server-side in AjaxController::step the moment a backup that
was launched as a pre-update/pre-uninstall front completes (keyed by the
msb_action the runbackup screen now passes on each step). This is authoritative
and independent of the fragile ack POST, which is kept as a redundant fallback.

Failure-safe by design: the flag is armed ONLY on genuine completion (done, no
error), so a failed or cancelled backup never arms it — a real update is never
left un-backed-up. Worst case is one redundant backup, never a missing one.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-16 19:18:30 -05:00
jmiller 6717f0ed68 Release: merge dev to main
Generic: Standards Compliance / Secret Scanning (push) Failing after 4s
Generic: Standards Compliance / License Header Validation (push) Successful in 5s
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 5s
Generic: Standards Compliance / Documentation Quality Check (push) Successful in 5s
Generic: Standards Compliance / README Completeness Check (push) Failing after 4s
Generic: Standards Compliance / Git Repository Hygiene (push) Successful in 7s
Generic: Standards Compliance / Script Integrity Validation (push) Successful in 6s
Generic: Standards Compliance / File Naming Standards (push) Successful in 4s
Generic: Standards Compliance / Line Length Check (push) Successful in 5s
Generic: Standards Compliance / Insecure Code Pattern Detection (push) Successful in 5s
Generic: Standards Compliance / Version Consistency Check (push) Successful in 51s
Generic: Standards Compliance / Dead Code Detection (push) Successful in 7s
Generic: Standards Compliance / File Size Limits (push) Successful in 4s
Generic: Standards Compliance / Binary File Detection (push) Successful in 7s
Generic: Standards Compliance / TODO/FIXME Tracking (push) Successful in 4s
Generic: Standards Compliance / Code Complexity Analysis (push) Successful in 52s
Generic: Standards Compliance / Code Duplication Detection (push) Successful in 52s
Generic: Standards Compliance / Broken Link Detection (push) Successful in 4s
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 53s
Generic: Standards Compliance / Unused Dependencies Check (push) Successful in 55s
Generic: Standards Compliance / Terraform Configuration Validation (push) Successful in 9s
Generic: Standards Compliance / Enterprise Readiness Check (push) Failing after 50s
Generic: Standards Compliance / Repository Health Check (push) Failing after 50s
Generic: Standards Compliance / Compliance Summary (push) Has been cancelled
- feat(manifest): component 'Component - MokoSuiteBackup' pretty name via <element> tag (#299)
- fix(lang): Options page shows raw COM_MOKOSUITEBACKUP_CONFIGURATION (#234)

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i

# Conflicts:
#	.mokogit/workflows/issue-branch.yml
#	CHANGELOG.md
#	SECURITY.md
#	source/packages/com_mokosuitebackup/mokosuitebackup.xml
#	source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml
#	source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml
#	source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml
#	source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml
#	source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml
#	source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml
#	source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml
#	source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml
#	source/pkg_mokosuitebackup.xml
2026-07-16 17:00:31 -05:00
mokogit-actions[bot] a6de828fec chore(version): pre-release bump to 02.66.03-dev [skip ci] 2026-07-16 21:56:44 +00:00
git-actions[bot] a5e0775b0b chore(version): auto-bump patch 02.66.02-dev [skip ci] 2026-07-16 21:56:27 +00:00
jmiller cba074b0aa Merge branch 'fix/config-lang-stem' into dev
Universal: Auto Version Bump / Version Bump (push) Successful in 13s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 27s
fix(lang): Options page shows raw COM_MOKOSUITEBACKUP_CONFIGURATION (#234)

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-16 16:55:51 -05:00
jmiller 455689532e chore: sync issue-branch.yml from Template-Joomla [skip ci] 2026-07-16 21:38:35 +00:00
jmiller a9882b9b51 fix(lang): Options page shows raw COM_MOKOSUITEBACKUP_CONFIGURATION
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 34s
Joomla's com_config derives the Options-page title and component name from
the extension element (com_mokosuitebackup), but the language files defined
those keys only under the legacy COM_MOKOJOOMBACKUP_ stem, so the Options
screen rendered the raw constant.

Add the element-derived COM_MOKOSUITEBACKUP and COM_MOKOSUITEBACKUP_CONFIGURATION
keys to all four language files (en-GB/en-US, .ini and .sys.ini), mirroring the
already-migrated COM_MOKOSUITEBACKUP_ACTION_* ACL keys. The 400+ explicitly
referenced COM_MOKOJOOMBACKUP_* keys are unaffected.

Refs #234

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-16 14:45:02 -05:00
mokogit-actions[bot] 2d382cd62b chore(version): pre-release bump to 02.66.01-dev [skip ci] 2026-07-16 19:30:54 +00:00
jmiller db9734b156 Merge pull request 'feat(manifest): component uses Type - Name pretty name via <element> tag' (#299) from feat/component-typename-display 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 30s
2026-07-16 19:27:52 +00:00
jmiller a4c8b72733 feat(manifest): give the component a "Type - Name" pretty name safely
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Secret Scan (pull_request) Successful in 7s
Generic: Project CI / Lint & Validate (pull_request) Successful in 11s
Universal: PR Check / Validate PR (pull_request) Failing after 9s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 9s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
All sub-extensions already use the "Type - Name" convention; the
component was the exception because its <name> derives the element
(com_ + sanitized name), so "Component - MokoSuiteBackup" would produce
com_component-mokosuitebackup and orphan the real component.

Fix: pin the element with an explicit <element>com_mokosuitebackup</element>
tag. Joomla's InstallerAdapter::getElement() reads <element> before
falling back to <name>, so the element stays com_mokosuitebackup (matches
the existing record on upgrade — no orphan) while the Extensions manager
now shows "Component - MokoSuiteBackup". Manifest filename stays
mokosuitebackup.xml. Verified against Joomla 6.1 InstallerAdapter/
ComponentAdapter and manifest XML is well-formed.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-16 14:27:24 -05:00
git-actions[bot] 92ba36bc8d chore: promote changelog [Unreleased] → [02.66.00]
Generic: Standards Compliance / Secret Scanning (push) Failing after 4s
Generic: Standards Compliance / License Header Validation (push) Successful in 4s
Generic: Standards Compliance / Repository Structure Validation (push) Successful in 3s
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 5s
Generic: Standards Compliance / README Completeness Check (push) Failing after 4s
Generic: Standards Compliance / Git Repository Hygiene (push) Successful in 5s
Generic: Standards Compliance / Line Length Check (push) Successful in 4s
Generic: Standards Compliance / Script Integrity Validation (push) Successful in 6s
Generic: Standards Compliance / File Naming Standards (push) Successful in 4s
Generic: Standards Compliance / Insecure Code Pattern Detection (push) Successful in 4s
Generic: Standards Compliance / Version Consistency Check (push) Successful in 50s
Generic: Standards Compliance / Dead Code Detection (push) Successful in 6s
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 / 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
Generic: Standards Compliance / Code Complexity Analysis (push) Successful in 48s
Generic: Standards Compliance / Code Duplication Detection (push) Successful in 49s
Generic: Standards Compliance / Compliance Summary (push) Has been cancelled
Generic: Standards Compliance / Dependency Vulnerability Scanning (push) Successful in 49s
2026-07-16 19:22:48 +00:00
git-actions[bot] 2b6f3d709f chore(release): build 02.66.00 [skip ci] 2026-07-16 19:22:38 +00:00
jmiller 102746e834 Merge pull request 'Release: dev -> main (package install redeclare fix + backlog)' (#302) from release/dev-to-main-r3 into main
Generic: Standards Compliance / Secret Scanning (push) Failing after 4s
Generic: Standards Compliance / License Header Validation (push) Successful in 4s
Generic: Standards Compliance / Repository Structure Validation (push) Successful in 4s
Generic: Standards Compliance / Coding Standards Check (push) Successful in 6s
Generic: Standards Compliance / Workflow Configuration Check (push) Failing after 5s
Generic: Standards Compliance / Documentation Quality Check (push) Successful in 5s
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 4s
Generic: Standards Compliance / Insecure Code Pattern Detection (push) Successful in 4s
Generic: Standards Compliance / Version Consistency Check (push) Successful in 52s
Generic: Standards Compliance / Dead Code Detection (push) Successful in 7s
Generic: Standards Compliance / File Size Limits (push) Successful in 4s
Generic: Standards Compliance / Binary File Detection (push) Successful in 7s
Generic: Standards Compliance / TODO/FIXME Tracking (push) Successful in 4s
Generic: Standards Compliance / Code Complexity Analysis (push) Successful in 52s
Generic: Standards Compliance / Code Duplication Detection (push) Successful in 52s
Generic: Standards Compliance / Broken Link Detection (push) Successful in 4s
Generic: Standards Compliance / API Documentation Coverage (push) Successful in 5s
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 54s
Generic: Standards Compliance / Unused Dependencies Check (push) Successful in 57s
Generic: Standards Compliance / Terraform Configuration Validation (push) Successful in 9s
Generic: Standards Compliance / Enterprise Readiness Check (push) Failing after 52s
Generic: Standards Compliance / Repository Health Check (push) Failing after 51s
Generic: Standards Compliance / Compliance Summary (push) Has been cancelled
2026-07-16 19:21:59 +00:00
mokogit-actions[bot] 1f189349f5 chore(version): pre-release bump to 02.65.04-dev [skip ci]
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 32s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
2026-07-16 19:21:10 +00:00
jmiller 28adcd0778 Merge pull request 'fix(install): guard package script against Cannot redeclare class fatal' (#301) from fix/package-script-redeclare-guard 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 33s
2026-07-16 19:20:43 +00:00
mokogit-actions[bot] 3644a99d07 chore(version): pre-release bump to 02.65.03-dev [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
2026-07-16 19:17:07 +00:00
jmiller c70f024201 fix(install): guard package script against "Cannot redeclare class" fatal
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 44s
Generic: Project CI / Lint & Validate (pull_request) Has been cancelled
Universal: PR Check / Branch Policy (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
Package updates could fatal with a compile-time "Cannot redeclare class
Pkg_MokoSuiteBackupInstallerScript" when Joomla extracts/includes the
package script.php twice in one request (two tmp/install_* dirs). Because
the class is a top-level unconditional declaration it is early-bound at
compile time, so a runtime return can't prevent the redeclare — wrapping
it in `if (!class_exists(...)) { ... }` makes it a conditional (runtime)
declaration, so the second include is a no-op instead of a fatal.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-16 14:16:09 -05:00
mokogit-actions[bot] 9677edcf8b chore(version): pre-release bump to 02.65.02-dev [skip ci] 2026-07-16 18:59:59 +00:00
git-actions[bot] 90e6fe07ce chore(version): auto-bump patch 02.65.01-dev [skip ci] 2026-07-16 18:59:43 +00:00
git-actions[bot] 87c8a9a2ca chore: promote changelog [Unreleased] → [02.65.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 5s
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 7s
Generic: Standards Compliance / Script Integrity Validation (push) Successful in 5s
Generic: Standards Compliance / Line Length Check (push) Successful in 4s
Generic: Standards Compliance / File Naming Standards (push) Successful in 4s
Generic: Standards Compliance / Insecure Code Pattern Detection (push) Successful in 3s
Generic: Standards Compliance / Version Consistency Check (push) Successful in 45s
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 5s
Generic: Standards Compliance / TODO/FIXME Tracking (push) Successful in 3s
Generic: Standards Compliance / Code Complexity Analysis (push) Successful in 40s
Generic: Standards Compliance / Code Duplication Detection (push) Successful in 43s
Generic: Standards Compliance / Broken Link Detection (push) Successful in 5s
Generic: Standards Compliance / API Documentation Coverage (push) Successful in 5s
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 54s
Generic: Standards Compliance / Unused Dependencies Check (push) Successful in 58s
Generic: Standards Compliance / Terraform Configuration Validation (push) Successful in 8s
Generic: Standards Compliance / Enterprise Readiness Check (push) Failing after 51s
Universal: Auto Version Bump / Version Bump (push) Successful in 11s
Generic: Standards Compliance / Repository Health Check (push) Failing after 51s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 27s
Generic: Standards Compliance / Compliance Summary (push) Has been cancelled
2026-07-16 18:44:09 +00:00
git-actions[bot] 21bcb0a54b chore(release): build 02.65.00 [skip ci] 2026-07-16 18:43:59 +00:00
jmiller 84fd06bf2d Merge pull request 'Release: dev -> main (restore fixes, theming, schema-checker, quickicon hardening)' (#300) from release/dev-to-main-r2 into main
Generic: Standards Compliance / Secret Scanning (push) Failing after 4s
Generic: Standards Compliance / License Header Validation (push) Successful in 4s
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 4s
Generic: Standards Compliance / Git Repository Hygiene (push) Successful in 7s
Generic: Standards Compliance / Script Integrity Validation (push) Successful in 5s
Generic: Standards Compliance / File Naming Standards (push) Successful in 4s
Generic: Standards Compliance / Line Length Check (push) Successful in 6s
Generic: Standards Compliance / Insecure Code Pattern Detection (push) Successful in 5s
Generic: Standards Compliance / Version Consistency Check (push) Successful in 50s
Generic: Standards Compliance / Dead Code Detection (push) Successful in 6s
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 3s
Generic: Standards Compliance / Code Complexity Analysis (push) Successful in 45s
Generic: Standards Compliance / Code Duplication Detection (push) Successful in 44s
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 3s
Generic: Standards Compliance / Performance Metrics (push) Successful in 4s
Generic: Standards Compliance / Dependency Vulnerability Scanning (push) Successful in 48s
Generic: Standards Compliance / Unused Dependencies Check (push) Successful in 51s
Generic: Standards Compliance / Terraform Configuration Validation (push) Successful in 7s
Generic: Standards Compliance / Enterprise Readiness Check (push) Failing after 47s
Generic: Standards Compliance / Repository Health Check (push) Failing after 46s
Generic: Standards Compliance / Compliance Summary (push) Has been cancelled
2026-07-16 18:43:04 +00:00
jmiller 9a82fc97f2 Merge remote-tracking branch 'origin/dev' into release/dev-to-main-r2
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 32s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Project CI / Lint & Validate (pull_request) Successful in 10s
Universal: PR Check / Secret Scan (pull_request) Successful in 6s
Universal: PR Check / Validate PR (pull_request) Failing after 9s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Standards Compliance / Secret Scanning (pull_request) Failing after 4s
Generic: Standards Compliance / License Header Validation (pull_request) Successful in 4s
Generic: Standards Compliance / Repository Structure Validation (pull_request) Successful in 4s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 20s
Generic: Standards Compliance / Coding Standards Check (pull_request) Successful in 5s
Generic: Standards Compliance / Workflow Configuration Check (pull_request) Failing after 5s
Generic: Standards Compliance / Documentation Quality Check (pull_request) Successful in 4s
Generic: Standards Compliance / README Completeness Check (pull_request) Failing after 4s
Generic: Standards Compliance / Git Repository Hygiene (pull_request) Successful in 6s
Generic: Standards Compliance / Script Integrity Validation (pull_request) Successful in 6s
Generic: Standards Compliance / Line Length Check (pull_request) Successful in 5s
Generic: Standards Compliance / File Naming Standards (pull_request) Successful in 4s
Generic: Standards Compliance / Insecure Code Pattern Detection (pull_request) Successful in 4s
Generic: Standards Compliance / Version Consistency Check (pull_request) Successful in 49s
Generic: Standards Compliance / Dead Code Detection (pull_request) Successful in 8s
Generic: Standards Compliance / File Size Limits (pull_request) Successful in 4s
Generic: Standards Compliance / Binary File Detection (pull_request) Successful in 6s
Generic: Standards Compliance / TODO/FIXME Tracking (pull_request) Successful in 4s
Generic: Standards Compliance / Code Complexity Analysis (pull_request) Successful in 51s
Generic: Standards Compliance / Code Duplication Detection (pull_request) Successful in 52s
Generic: Standards Compliance / Broken Link Detection (pull_request) Successful in 4s
Generic: Standards Compliance / API Documentation Coverage (pull_request) Successful in 4s
Generic: Standards Compliance / Accessibility Check (pull_request) Successful in 4s
Generic: Standards Compliance / Performance Metrics (pull_request) Successful in 4s
Generic: Standards Compliance / Dependency Vulnerability Scanning (pull_request) Successful in 50s
Generic: Standards Compliance / Unused Dependencies Check (pull_request) Successful in 53s
Generic: Standards Compliance / Terraform Configuration Validation (pull_request) Successful in 8s
Generic: Standards Compliance / Enterprise Readiness Check (pull_request) Failing after 51s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Generic: Standards Compliance / Repository Health Check (pull_request) Failing after 52s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
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
# Conflicts:
#	.mokogit/workflows/issue-branch.yml
2026-07-16 13:40:57 -05:00
mokogit-actions[bot] 22e8ebe832 chore(version): pre-release bump to 02.64.13-dev [skip ci] 2026-07-16 18:09:26 +00:00
jmiller b1b81fd107 Merge pull request 'fix(quickicon): never let the dashboard widget crash the admin page' (#298) from fix/quickicon-harden 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-16 18:09:12 +00:00
mokogit-actions[bot] 747e50bf62 chore(version): pre-release bump to 02.64.12-dev [skip ci]
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
2026-07-16 18:09:03 +00:00
jmiller 7e58ca7598 fix(quickicon): never let the dashboard widget crash the admin page (#297)
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Secret Scan (pull_request) Successful in 7s
Generic: Project CI / Lint & Validate (pull_request) Successful in 11s
Universal: PR Check / Validate PR (pull_request) Failing after 9s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 8s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 23s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
The mod_quickicon helper array_merge()s a defaults array into each icon a
plugin returns; if a plugin's DB query throws or it returns a malformed
(unwrapped) icon, the whole admin dashboard can 500 with an array_merge
TypeError. The current handler is structurally correct (icons are wrapped
in a list, matching core plg_quickicon_joomlaupdate), so this hardens it:

- Extract the query/state logic into buildStatusIcon().
- Wrap it in try/catch — on any Throwable, log and contribute no icon
  instead of bubbling into the dashboard render.
- Document why the [$icon] list-wrapping is load-bearing.

Closes #297.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-16 13:07:26 -05:00
mokogit-actions[bot] 54d7710783 chore(version): pre-release bump to 02.64.11-dev [skip ci] 2026-07-16 18:07:20 +00:00
jmiller f3bcf9d79e Merge pull request 'fix(restore): surface DatabaseImporter errors + quote-aware split' (#294) from fix/databaseimporter-surface-errors 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 30s
2026-07-16 18:06:56 +00:00
jmiller 028e1c7e80 fix(restore): surface DatabaseImporter errors + quote-aware split (#188)
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 7s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Secret Scan (pull_request) Successful in 8s
Universal: PR Check / Validate PR (pull_request) Failing after 12s
Generic: Project CI / Lint & Validate (pull_request) Successful in 15s
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 12s
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
RestoreEngine ignored DatabaseImporter::hasErrors(), so the non-stepped
restore always reported success even when SQL statements failed. It now
logs the failures and returns a warning ("database may be incomplete").
DatabaseImporter's splitter is also quote/backtick/escape-aware so a ; or
newline inside a value no longer splits a statement, and comments are only
skipped between statements — matching the stepped-engine fix (#291).

Closes #188.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-16 13:06:44 -05:00
mokogit-actions[bot] c42962c919 chore(version): pre-release bump to 02.64.10-dev [skip ci] 2026-07-16 18:02:27 +00:00
mokogit-actions[bot] 3616f49570 chore(version): pre-release bump to 02.64.09-dev [skip ci] 2026-07-16 18:02:01 +00:00
jmiller ed2dbee579 Merge pull request 'fix: default ntfy server to ntfy.mokoconsulting.tech' (#293) from fix/ntfy-default-url 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-16 18:01:33 +00:00
jmiller 95d2b3375c Merge pull request 'fix: snapshots restore type labels show raw type' (#295) from fix/snapshots-typelabel 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 37s
2026-07-16 18:01:24 +00:00
jmiller 2b6dbdac23 fix: snapshots restore type labels show raw type (#170)
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 4s
typeLabels[TYPE] referenced an undefined uppercase variable; the forEach
parameter is lowercase `type`, so every type label rendered as the raw
type string instead of its translated label.

Closes #170.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-16 12:04:02 -05:00
jmiller 313b96b070 fix: default ntfy server to ntfy.mokoconsulting.tech (#166)
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 3s
Replaces the old public ntfy.sh default in the runtime defaults: profile
form default/hint, NotificationSender fallbacks, the ntfy_server column
default in install.mysql.sql, and the en-GB field description. ntfy.sh is
still documented as the public alternative.

Closes #166.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-16 11:57:26 -05:00
jmiller 775942e9f7 Merge pull request 'Release: dev → main — schema-checker fix + unified progress theme' (#282) from release/dev-to-main into main 2026-07-16 14:36:06 +00:00
jmiller 532c5171dc Merge remote-tracking branch 'origin/dev' into release/dev-to-main
# Conflicts:
#	.mokogit/.mokogit/CLAUDE.md
#	.mokogit/.mokogit/workflows/branch-cleanup.yml
#	.mokogit/.mokogit/workflows/issue-branch.yml
#	.mokogit/CLAUDE.md
#	.mokogit/workflows/auto-release.yml
#	.mokogit/workflows/branch-cleanup.yml
#	.mokogit/workflows/ci-joomla.yml
#	.mokogit/workflows/issue-branch.yml
#	.mokogit/workflows/pr-metadata-check.yml
#	.mokogit/workflows/pre-release.yml
#	.mokogit/workflows/rc-revert.yml
#	.mokogitea/CLAUDE.md
#	.mokogitea/workflows/branch-cleanup.yml
#	.mokogitea/workflows/issue-branch.yml
2026-07-16 09:35:02 -05:00
jmiller d3e0b32737 fix(ci): harden branch-cleanup.yml against Actions injection (sync from Template-Generic) 2026-07-15 03:25:22 +00:00
jmiller 353adf17c7 fix(ci): harden issue-branch.yml against Actions injection (sync from Template-Generic) 2026-07-15 03:25:21 +00:00
jmiller 4ea0761706 Merge pull request 'chore(rebrand): fix residual gitea (space-path + named files)' (#275) from chore/rebrand-gitea-residual into main 2026-07-15 01:40:19 +00:00
jmiller 828aa3100e chore(rebrand): fix residual gitea (space-path files + gitea-named files)
Claude-Session: https://claude.ai/code/session_01DQEMmJPe61ya7HDfA6BHP8
2026-07-14 20:40:16 -05:00
jmiller c7b38067f6 Merge pull request 'chore(rebrand): gitea -> git (MokoGIT, .mokogitea -> .mokogit)' (#274) from chore/rebrand-gitea-to-git into main 2026-07-15 01:21:37 +00:00
jmiller 745f85d512 chore(rebrand): gitea -> git (MokoGIT brand, .mokogitea -> .mokogit)
Case-sensitive gitea->git: brand MokoGitea->MokoGIT, MOKOGITEA_TOKEN->
MOKOGIT_TOKEN, GITEA_URL/ORG/REPO->GIT_*, gitea-actions[bot]->git-actions,
ntfy gitea-*->git-*, .mokogitea/->.mokogit/. Protected: literal .gitea and
git.mokoconsulting.tech.

Claude-Session: https://claude.ai/code/session_01DQEMmJPe61ya7HDfA6BHP8
2026-07-14 20:21:34 -05:00
jmiller 823a36edbb chore: sync version-set.yml from Template-Joomla [skip ci] 2026-07-14 21:16:41 +00:00
jmiller 96b811a95c chore: sync standards-compliance.yml from Template-Joomla [skip ci] 2026-07-14 21:16:40 +00:00
jmiller b16970a857 chore: sync repo-health.yml from Template-Joomla [skip ci] 2026-07-14 21:16:39 +00:00
jmiller fe74431b7e chore: sync rc-revert.yml from Template-Joomla [skip ci] 2026-07-14 21:16:38 +00:00
jmiller 6cad0ed325 chore: sync pre-release.yml from Template-Joomla [skip ci] 2026-07-14 21:16:37 +00:00
jmiller 6fcd18742e chore: sync pr-metadata-check.yml from Template-Joomla [skip ci] 2026-07-14 21:16:36 +00:00
jmiller 204fbe9349 chore: sync pr-check.yml from Template-Joomla [skip ci] 2026-07-14 21:16:35 +00:00
jmiller 8228e2b851 chore: sync notify.yml from Template-Joomla [skip ci] 2026-07-14 21:16:34 +00:00
jmiller 5d053e3a23 chore: sync issue-branch.yml from Template-Joomla [skip ci] 2026-07-14 21:16:33 +00:00
jmiller 52d4f56158 chore: sync gitleaks.yml from Template-Joomla [skip ci] 2026-07-14 21:16:32 +00:00
jmiller f31f90eb86 chore: sync cleanup.yml from Template-Joomla [skip ci] 2026-07-14 21:16:31 +00:00
jmiller 6e068ebd1d chore: sync ci-joomla.yml from Template-Joomla [skip ci] 2026-07-14 21:16:30 +00:00
jmiller 117816e5a5 chore: sync ci-issue-reporter.yml from Template-Joomla [skip ci] 2026-07-14 21:16:29 +00:00
jmiller 31948f3e16 chore: sync ci-generic.yml from Template-Joomla [skip ci] 2026-07-14 21:16:28 +00:00
jmiller 18804ab76e chore: sync cascade-dev.yml from Template-Joomla [skip ci] 2026-07-14 21:16:27 +00:00
jmiller ef69edb18d chore: sync branch-cleanup.yml from Template-Joomla [skip ci] 2026-07-14 21:16:26 +00:00
jmiller e97a5c17c7 chore: sync auto-release.yml from Template-Joomla [skip ci] 2026-07-14 21:16:25 +00:00
jmiller 4ea1d311ac chore: sync auto-bump.yml from Template-Joomla [skip ci] 2026-07-14 21:16:24 +00:00
60 changed files with 518 additions and 90 deletions
+7
View File
@@ -211,3 +211,10 @@ profile.ps1
# ============================================================
wiki/
docs/
# ============================================================
# AI client instructions (not version controlled)
# ============================================================
/GEMINI.md
/MOKOAI.md
.mokoai/
+1 -1
View File
@@ -5,7 +5,7 @@
# FILE INFORMATION
# DEFGROUP: MokoGIT.Workflow
# INGROUP: MokoCLI.Automation
# VERSION: 01.00.00
# VERSION: 02.67.09
# BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch"
+25 -10
View File
@@ -1,26 +1,41 @@
# Changelog
## [Unreleased]
## [02.67.00] --- 2026-07-17
## [02.67.00] --- 2026-07-17
### Fixed
- Pre-update / pre-uninstall skip flag is now **time-bounded**. It records the moment it was armed and is honoured only within a short TTL (5 min), always consumed one-shot. Previously a bare boolean: if a user ran the full-screen pre-update backup then abandoned the flow (closed the tab before the update re-fired), the flag lingered for the whole session and could silently suppress a later, unrelated pre-action backup — i.e. a real update running with **no** backup. Now a stale flag is ignored and the server backs up as normal.
- Pre-update / pre-uninstall backup can no longer run **twice**. The one-shot "skip the synchronous server-side backup" flag is now armed **server-side** the moment the full-screen backup completes (in `AjaxController::step`, keyed by the `msb_action` it is fronting), instead of relying solely on the best-effort client `ajax.preupdateAck` POST (whose failure was silently swallowed in a `catch()`). If that ack ever fails to land, the re-fired extension update/uninstall no longer triggers a second backup behind the "update is running" overlay. The flag is armed **only** on genuine completion (`done`, no error), so a failed or cancelled backup still lets the server back up before the action — the failure mode stays "one extra backup", never "no backup".
- Component **Options** page showed the raw `COM_MOKOSUITEBACKUP_CONFIGURATION` constant instead of a translated title. Joomla's `com_config` derives the Options-page title (and the component name) from the extension *element* (`com_mokosuitebackup`), but the language files defined those keys only under the legacy `COM_MOKOJOOMBACKUP_` stem. Added the element-derived `COM_MOKOSUITEBACKUP` and `COM_MOKOSUITEBACKUP_CONFIGURATION` keys to all four language files (en-GB/en-US, `.ini` and `.sys.ini`), matching the already-migrated `COM_MOKOSUITEBACKUP_ACTION_*` ACL keys. The 400+ explicitly-referenced `COM_MOKOJOOMBACKUP_*` keys are unaffected and left as-is. (#234)
- **Cancel Stalled no longer throws a fatal error.** `BackupsController::cancelStalled()` (and `purge()`) fetched the database via `$this->app->getContainer()`, but `CMSApplication::getContainer()` is `protected` — clicking **Cancel Stalled** died with *"Call to protected method Joomla\CMS\Application\CMSApplication::getContainer() from scope ...\BackupsController"*, leaving no UI path to clear a stuck backup. Both call sites now use `Factory::getContainer()`, matching the rest of the component.
- **Interrupted backups no longer stay stuck in "running" forever.** When the driving window or CLI process dies mid-run (closed tab, PHP timeout, killed cron), the record was orphaned in `running` with no end time, showed as a phantom in-progress backup on the dashboard, and — until the fix above — could not be cleared. The records list now self-heals on load via `BackupStatusHelper::markStalledAsFailed()`, which flips `running` records older than a generous 6-hour threshold to `fail` and deletes their partial archives (the threshold sits well above `PreflightCheck`'s 30-minute per-profile start guard so a genuinely long-running browser/CLI backup is never reaped mid-flight; a per-step heartbeat column is the real fix, tracked as a follow-up).
- **Backup detail no longer shows a bogus "Monday, November 29, -0001" end time.** An unfinished backup stores the null date `0000-00-00 00:00:00`, which `HTMLHelper::_('date', …)` rendered as a fake far-past date. The detail view now shows **Not finished** for a null/zero `backupend`.
- **Update site now self-heals.** When the package's `#__update_sites` row is missing, the records view warned *"MokoSuiteBackup update site not found. Reinstall the package to register the update server."* The install script's `postflight` now recreates the update site (and re-attaches a preserved download key) via `ensureUpdateSite()` on the next install/update — no manual reinstall required.
### Changed
- **Run-backup progress screen is now a true full-screen black overlay.** `view=runbackup` rendered as a small Bootstrap card inside the admin chrome (sidebar/toolbar still visible); it now covers the whole viewport on black — matching the "Backup in progress" lock overlay other admin tabs show — with a centered dark panel and light-on-dark text.
## [02.66.00] --- 2026-07-16
## [02.65.00] --- 2026-07-16
## [02.65.00] --- 2026-07-16
### Added
- Backup- and restore-progress modals (backups view) now have the same controls as the full-screen runbackup screen: an "automatically return to the list when finished" checkbox, a Cancel button (double-confirmed) while running, and terminal action buttons. Backup: View backup record / Back to backups on success, Retry / Back on failure. Restore matches, with a stronger cancel warning (a restore cannot be rolled back mid-flight) and no record link.
### Fixed
- ntfy default server URL now points to `ntfy.mokoconsulting.tech` (was the public `ntfy.sh`) in the profile form default/hint, `NotificationSender` fallbacks, the `ntfy_server` column default, and the field description. (#166)
- Non-stepped restore (`RestoreEngine`) now surfaces database-import failures instead of always reporting a clean success: it checks `DatabaseImporter::hasErrors()`, logs the failed statements, and returns a `warning` with a "database may be incomplete" message. `DatabaseImporter`'s statement splitter is also now quote-aware (a `;`/newline inside a value no longer splits a statement) with comments only skipped between statements. (#188)
- Restore Cancel no longer orphans server-side state. The stepped-restore modal's Cancel button now calls a new `ajax.restoreCancel` action that deletes the restore staging directory and session/state files, and `SteppedSession::cleanupOldSessions()` gained a backstop that reaps orphaned `mokosuitebackup-restore-*` staging dirs (for restores abandoned by closing the browser). Previously a cancelled/abandoned restore left the extracted staging dir behind indefinitely.
- Stepped restore no longer reports a partial database import as a clean "Restore complete". `SteppedRestoreEngine::stepDatabase()` now counts failed statements and surfaces the total (the completion message warns and the modal keeps the result on-screen instead of auto-returning). The statement splitter is also quote-aware — a `;` or newline inside a string value no longer splits a statement mid-value — and comment lines are only skipped between statements. (Sibling of #188 for the stepped/standalone paths; see #291.)
- Database view "One Problem" on MokoSuiteBackup: reconciled the `#__mokosuitebackup_profiles` schema-checker output with the update-file history. Corrected the stale `TINYINT` definition in `01.01.01.sql` to the final `VARCHAR(20)` type, fixed the `MODIFY COLUMN` parser trip-up in `01.39.00.sql`, and rewrote the retired `sftp_*` `ADD COLUMN` statements in `01.35.00.sql`/`01.36.00.sql` as bare `ADD` so Joomla's checker no longer demands columns that `02.52.25` and the postflight purge deliberately remove. Joomla's schema checker now reports no problems.
### Changed
- Component now shows the "Type - Name" pretty name `Component - MokoSuiteBackup` in the Extensions manager, consistent with the other sub-extensions. Achieved safely with an `<element>com_mokosuitebackup</element>` tag in the manifest (Joomla reads `<element>` before `<name>` for the extension element), so the element stays `com_mokosuitebackup` and no duplicate/orphaned component is created.
- Backup/restore progress modals in the backups view now use the same full-screen black, big-text, animated Atum-coloured progress-bar theme as the `runbackup` screen, so all backup progress surfaces match. New `media/css/backup-progress.css` scopes the black to the modal (the list page is unaffected until a modal opens).
- Standalone MokoRestore wizard (generated `restore.php`) re-branded to the full-black MokoSuite theme — black background, big header, dark panels/forms, and MokoSuite accent colours — so the restore experience matches the rest of the system instead of the old light blue "Joomla-styled" look.
## [02.64.00] --- 2026-07-14
## [02.64.00] --- 2026-07-14
## [02.63.00] --- 2026-07-13
## [02.62.00] --- 2026-07-13
## [02.61.00] --- 2026-07-13
## [02.60.00] --- 2026-07-12
+1 -1
View File
@@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla
INGROUP: Template-Joomla.Documentation
REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
PATH: /SECURITY.md
VERSION: 02.64.06
VERSION: 02.67.09
BRIEF: Security vulnerability reporting and handling policy
-->
@@ -292,8 +292,8 @@
label="COM_MOKOJOOMBACKUP_FIELD_NTFY_SERVER"
description="COM_MOKOJOOMBACKUP_FIELD_NTFY_SERVER_DESC"
maxlength="512"
default="https://ntfy.sh"
hint="https://ntfy.sh"
default="https://ntfy.mokoconsulting.tech"
hint="https://ntfy.mokoconsulting.tech"
/>
<field
name="ntfy_token"
@@ -7,6 +7,13 @@
COM_MOKOJOOMBACKUP="MokoSuiteBackup"
COM_MOKOJOOMBACKUP_SHORT="Backup"
COM_MOKOJOOMBACKUP_CONFIGURATION="MokoSuiteBackup Options"
; Framework-derived keys: Joomla builds the component name and the Options-page
; title from the element (com_mokosuitebackup), NOT from the legacy
; COM_MOKOJOOMBACKUP_ stem, so they must exist under the new stem too (mirrors
; the COM_MOKOSUITEBACKUP_ACTION_* ACL keys). Without these the Options screen
; shows the raw COM_MOKOSUITEBACKUP_CONFIGURATION constant. See #234.
COM_MOKOSUITEBACKUP="MokoSuiteBackup"
COM_MOKOSUITEBACKUP_CONFIGURATION="MokoSuiteBackup Options"
COM_MOKOJOOMBACKUP_DESCRIPTION="Full-site backup and restore for Joomla"
; Submenu
@@ -116,6 +123,7 @@ COM_MOKOJOOMBACKUP_FIELD_ORIGIN="Origin"
COM_MOKOJOOMBACKUP_FIELD_SIZE="Total Size"
COM_MOKOJOOMBACKUP_FIELD_START="Start Time"
COM_MOKOJOOMBACKUP_FIELD_END="End Time"
COM_MOKOJOOMBACKUP_NOT_FINISHED="Not finished"
COM_MOKOJOOMBACKUP_FIELD_ARCHIVE="Archive Name"
COM_MOKOJOOMBACKUP_FIELD_FILES_COUNT="Files Count"
COM_MOKOJOOMBACKUP_FIELD_TABLES_COUNT="Tables Count"
@@ -259,7 +267,7 @@ COM_MOKOJOOMBACKUP_FIELD_NTFY_SPACER_DESC="<strong>Push Notifications (ntfy)</st
COM_MOKOJOOMBACKUP_FIELD_NTFY_TOPIC="ntfy Topic"
COM_MOKOJOOMBACKUP_FIELD_NTFY_TOPIC_DESC="The ntfy topic to publish notifications to. Leave blank to disable push notifications."
COM_MOKOJOOMBACKUP_FIELD_NTFY_SERVER="ntfy Server"
COM_MOKOJOOMBACKUP_FIELD_NTFY_SERVER_DESC="URL of the ntfy server. Default is the public ntfy.sh service. Use your own server URL for self-hosted instances."
COM_MOKOJOOMBACKUP_FIELD_NTFY_SERVER_DESC="URL of the ntfy server. Default is the Moko Consulting ntfy server (ntfy.mokoconsulting.tech). Use your own server URL for self-hosted instances, or https://ntfy.sh for the public service."
COM_MOKOJOOMBACKUP_FIELD_NTFY_TOKEN="Access Token"
COM_MOKOJOOMBACKUP_FIELD_NTFY_TOKEN_DESC="Optional access token for private ntfy topics. Leave blank for public topics."
@@ -7,6 +7,9 @@
COM_MOKOJOOMBACKUP="MokoSuiteBackup"
COM_MOKOJOOMBACKUP_SHORT="Backup"
COM_MOKOJOOMBACKUP_DESCRIPTION="Full-site backup and restore for Joomla — database, files, and configuration."
; Element-derived aliases (see the .ini file for the rationale). #234
COM_MOKOSUITEBACKUP="MokoSuiteBackup"
COM_MOKOSUITEBACKUP_CONFIGURATION="MokoSuiteBackup Options"
COM_MOKOJOOMBACKUP_SUBMENU_DASHBOARD="Dashboard"
COM_MOKOJOOMBACKUP_SUBMENU_BACKUPS="Backup Records"
COM_MOKOJOOMBACKUP_SUBMENU_PROFILES="Backup Profiles"
@@ -7,6 +7,13 @@
COM_MOKOJOOMBACKUP="MokoSuiteBackup"
COM_MOKOJOOMBACKUP_SHORT="Backup"
COM_MOKOJOOMBACKUP_DESCRIPTION="Full-site backup and restore for Joomla"
; Framework-derived keys: Joomla builds the component name and the Options-page
; title from the element (com_mokosuitebackup), NOT from the legacy
; COM_MOKOJOOMBACKUP_ stem, so they must exist under the new stem too (mirrors
; the COM_MOKOSUITEBACKUP_ACTION_* ACL keys). Without these the Options screen
; shows the raw COM_MOKOSUITEBACKUP_CONFIGURATION constant. See #234.
COM_MOKOSUITEBACKUP="MokoSuiteBackup"
COM_MOKOSUITEBACKUP_CONFIGURATION="MokoSuiteBackup Options"
COM_MOKOJOOMBACKUP_SUBMENU_DASHBOARD="Dashboard"
COM_MOKOJOOMBACKUP_SUBMENU_BACKUPS="Backup Records"
COM_MOKOJOOMBACKUP_SUBMENU_PROFILES="Backup Profiles"
@@ -6,6 +6,9 @@
COM_MOKOJOOMBACKUP="MokoSuiteBackup"
COM_MOKOJOOMBACKUP_SHORT="Backup"
; Element-derived aliases (see the .ini file for the rationale). #234
COM_MOKOSUITEBACKUP="MokoSuiteBackup"
COM_MOKOSUITEBACKUP_CONFIGURATION="MokoSuiteBackup Options"
COM_MOKOJOOMBACKUP_DESCRIPTION="Full-site backup and restore for Joomla — database, files, and configuration."
COM_MOKOJOOMBACKUP_SUBMENU_DASHBOARD="Dashboard"
COM_MOKOJOOMBACKUP_SUBMENU_BACKUPS="Backup Records"
@@ -24,14 +24,40 @@ body {
--msb-danger: var(--template-danger-color, var(--bs-danger, #c52128));
--msb-track: var(--template-bg-light, var(--bs-gray-200, #edf0f5));
padding: 0 1rem;
/* Full-screen black overlay: cover the whole admin chrome (sidebar, toolbar)
so the run screen matches the "Backup in progress" lock overlay other admin
tabs show, instead of appearing as a small card inside the admin template. */
position: fixed;
inset: 0;
z-index: 2000;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
background-color: #000;
color: #f8f9fa;
overflow: auto;
}
/* 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. */
/* Centered dark panel on the black backdrop. overflow:hidden clips the
full-bleed progress bar to the panel's rounded corners. */
.msb-runbackup .card {
overflow: hidden;
width: 100%;
max-width: 720px;
background-color: #121417 !important;
border: 1px solid #2a2f37;
box-shadow: 0 1rem 3rem rgba(0, 0, 0, .6);
}
/* Light-on-dark text — override Bootstrap muted greys that disappear on black. */
.msb-runbackup .card,
.msb-runbackup .card .text-muted {
color: #f8f9fa !important;
}
.msb-runbackup #msb-rb-subtitle {
color: #adb5bd !important;
}
.msb-rb-progress {
@@ -7,17 +7,18 @@
-->
<extension type="component" method="upgrade">
<!--
IMPORTANT: a component's <name> drives the Joomla-derived extension
element (com_ + sanitized name), so it MUST stay element-safe:
"MokoSuiteBackup" -> com_mokosuitebackup. Do NOT apply the "Type - Name"
display convention here — "Component - MokoSuiteBackup" derives
com_component-mokosuitebackup, which registers a duplicate component and
orphans the real one. Packages are exempt because they set the element
via <packagename>; plugins/modules are exempt because <name> is only a
display label there.
The <element> tag PINS this extension's element to com_mokosuitebackup.
Joomla's InstallerAdapter::getElement() reads <element> BEFORE falling
back to <name> (libraries/src/Installer/InstallerAdapter.php), so the
display <name> can safely use the "Type - Name" convention without the
element being derived as com_component-mokosuitebackup (which would
register a duplicate component and orphan the real one). Without the
<element> tag, "Component - MokoSuiteBackup" would break the install.
NB: the manifest filename must stay mokosuitebackup.xml (short element).
-->
<name>MokoSuiteBackup</name>
<version>02.64.06</version>
<element>com_mokosuitebackup</element>
<name>Component - MokoSuiteBackup</name>
<version>02.67.09</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -26,7 +26,7 @@ CREATE TABLE IF NOT EXISTS `#__mokosuitebackup_profiles` (
`retention_days` INT(11) NOT NULL DEFAULT 0 COMMENT 'Delete backups older than N days; 0 = unlimited',
`retention_count` INT(11) NOT NULL DEFAULT 0 COMMENT 'Keep newest N backups; 0 = unlimited',
`ntfy_topic` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'ntfy topic name',
`ntfy_server` VARCHAR(512) NOT NULL DEFAULT 'https://ntfy.sh' COMMENT 'ntfy server URL',
`ntfy_server` VARCHAR(512) NOT NULL DEFAULT 'https://ntfy.mokoconsulting.tech' COMMENT 'ntfy server URL',
`ntfy_token` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'ntfy access token (optional)',
`published` TINYINT(1) NOT NULL DEFAULT 1,
`created` DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
@@ -0,0 +1 @@
/* 02.64.09 — no schema changes */
@@ -0,0 +1 @@
/* 02.64.10 — no schema changes */
@@ -0,0 +1 @@
/* 02.64.11 — no schema changes */
@@ -0,0 +1 @@
/* 02.64.12 — no schema changes */
@@ -0,0 +1 @@
/* 02.64.13 — no schema changes */
@@ -0,0 +1 @@
/* 02.65.00 — no schema changes */
@@ -0,0 +1 @@
/* 02.65.01 — no schema changes */
@@ -0,0 +1 @@
/* 02.65.02 — no schema changes */
@@ -0,0 +1 @@
/* 02.65.03 — no schema changes */
@@ -0,0 +1 @@
/* 02.65.04 — no schema changes */
@@ -0,0 +1 @@
/* 02.66.00 — no schema changes */
@@ -0,0 +1 @@
/* 02.66.01 — no schema changes */
@@ -0,0 +1 @@
/* 02.66.02 — no schema changes */
@@ -0,0 +1 @@
/* 02.66.03 — no schema changes */
@@ -0,0 +1 @@
/* 02.66.04 — no schema changes */
@@ -0,0 +1 @@
/* 02.66.05 — no schema changes */
@@ -0,0 +1 @@
/* 02.66.06 — no schema changes */
@@ -0,0 +1 @@
/* 02.66.07 — no schema changes */
@@ -0,0 +1 @@
/* 02.67.00 — no schema changes */
@@ -0,0 +1 @@
/* 02.67.01 — no schema changes */
@@ -0,0 +1 @@
/* 02.67.02 — no schema changes */
@@ -0,0 +1 @@
/* 02.67.04 — no schema changes */
@@ -0,0 +1 @@
/* 02.67.05 — no schema changes */
@@ -0,0 +1 @@
/* 02.67.07 — no schema changes */
@@ -0,0 +1 @@
/* 02.67.09 — no schema changes */
@@ -81,6 +81,19 @@ class AjaxController extends BaseController
$engine = new SteppedBackupEngine();
$result = $engine->runStep($sessionId);
/* Server-authoritative pre-action skip. When a backup that was launched as a
pre-update / pre-uninstall front COMPLETES here, arm the one-shot skip flag
on the SERVER — rather than relying solely on the client-side
ajax.preupdateAck POST, which is best-effort and swallows its own failure
in a catch(). This guarantees the imminent re-fired update/uninstall does
not trigger a SECOND synchronous backup even if that ack never lands.
Armed ONLY on genuine completion (done, no error), so a failed or cancelled
backup never arms it — preserving "no successful pre-action backup ⇒ the
server still backs up before the action". */
if (!empty($result['done']) && empty($result['error'])) {
$this->armPreactionSkip($this->input->getCmd('action', ''));
}
$this->sendJson($result);
}
@@ -116,16 +129,35 @@ class AjaxController extends BaseController
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', '');
$this->armPreactionSkip($this->app->getInput()->getCmd('action', ''));
$this->sendJson(['error' => false, 'message' => 'Pre-action backup acknowledged']);
}
/**
* Arm the one-shot session skip flag for a specific pre-action, so the
* imminent re-fired extension update/uninstall skips its synchronous
* server-side backup exactly once (runPreActionBackup consumes it). Keyed by
* action so an update never suppresses a later, unrelated uninstall. A blank
* or unknown action (core Joomla update, manual "Backup Now") arms nothing.
*
* Called from two places: ajax.step on genuine completion (authoritative) and
* ajax.preupdateAck (best-effort client ack) — either one arming the flag is
* enough, and re-arming it is harmless.
*
* Stores the unix time it was armed (not a bare bool) so the plugin can honour
* the skip only within a short TTL and never let a stale flag from an abandoned
* backup suppress a genuine, unrelated pre-action backup later in the session.
*/
private function armPreactionSkip(string $action): void
{
$session = Factory::getSession();
if ($action === 'update') {
$session->set('mokosuitebackup.skip_preaction_backup_before_update', true);
$session->set('mokosuitebackup.skip_preaction_backup_before_update', time());
} elseif ($action === 'uninstall') {
$session->set('mokosuitebackup.skip_preaction_backup_before_uninstall', true);
$session->set('mokosuitebackup.skip_preaction_backup_before_uninstall', time());
}
$this->sendJson(['error' => false, 'message' => 'Pre-action backup acknowledged']);
}
/**
@@ -12,6 +12,7 @@ namespace Joomla\Component\MokoSuiteBackup\Administrator\Controller;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\AdminController;
use Joomla\CMS\Router\Route;
@@ -193,7 +194,7 @@ class BackupsController extends AdminController
$cutoff = $cutoffDate . ' 00:00:00';
$db = $this->app->getContainer()->get('DatabaseDriver');
$db = Factory::getContainer()->get('DatabaseDriver');
$query = $db->getQuery(true)
->select($db->quoteName('id'))
->from($db->quoteName('#__mokosuitebackup_records'))
@@ -260,7 +261,7 @@ class BackupsController extends AdminController
return;
}
$db = $this->app->getContainer()->get('DatabaseDriver');
$db = Factory::getContainer()->get('DatabaseDriver');
$cancelled = 0;
$skipped = 0;
@@ -61,34 +61,37 @@ class DatabaseImporter
while (($line = fgets($handle)) !== false) {
$trimmed = trim($line);
// Skip empty lines
if ($trimmed === '') {
continue;
}
// Skip single-line comments
if (str_starts_with($trimmed, '--') || str_starts_with($trimmed, '#')) {
continue;
}
// Handle multi-line comments
if (str_starts_with($trimmed, '/*')) {
$inMultiLineComment = true;
}
if ($inMultiLineComment) {
if (str_contains($trimmed, '*/')) {
$inMultiLineComment = false;
// Comment/blank skipping applies only BETWEEN statements — never
// inside an accumulating statement, where a line may be part of a
// multi-line string value that happens to start with --, # or /*.
if ($currentStatement === '') {
if ($trimmed === '') {
continue;
}
continue;
if (str_starts_with($trimmed, '--') || str_starts_with($trimmed, '#')) {
continue;
}
if (str_starts_with($trimmed, '/*')) {
$inMultiLineComment = true;
}
if ($inMultiLineComment) {
if (str_contains($trimmed, '*/')) {
$inMultiLineComment = false;
}
continue;
}
}
// Accumulate the statement
$currentStatement .= $line;
// Check if statement is complete (ends with semicolon)
if (str_ends_with($trimmed, ';')) {
// A statement ends at a ';' that is NOT inside a quoted string
// literal, so a ';' or newline in a value does not split it early.
if ($this->isCompleteStatement($currentStatement)) {
$statement = trim($currentStatement);
$currentStatement = '';
@@ -136,6 +139,52 @@ class DatabaseImporter
return $statementsExecuted;
}
/**
* Whether an accumulated SQL string is a complete statement: it ends with
* ';' that is NOT inside a quoted string literal. Tracks single/double/
* backtick quoting (with backslash escapes; MySQL '' doubling falls out of
* the toggle naturally) so a ';' or newline inside a value does not
* terminate the statement early.
*/
private function isCompleteStatement(string $sql): bool
{
$len = \strlen($sql);
$inSingle = false;
$inDouble = false;
$inBacktick = false;
for ($i = 0; $i < $len; $i++) {
$ch = $sql[$i];
if ($inSingle) {
if ($ch === '\\') { $i++; continue; }
if ($ch === "'") { $inSingle = false; }
continue;
}
if ($inDouble) {
if ($ch === '\\') { $i++; continue; }
if ($ch === '"') { $inDouble = false; }
continue;
}
if ($inBacktick) {
if ($ch === '`') { $inBacktick = false; }
continue;
}
if ($ch === "'") { $inSingle = true; }
elseif ($ch === '"') { $inDouble = true; }
elseif ($ch === '`') { $inBacktick = true; }
}
if ($inSingle || $inDouble || $inBacktick) {
return false;
}
return str_ends_with(rtrim($sql), ';');
}
/**
* Non-fatal errors from the last import(), if any. A non-empty result
* means the restore completed with problems and should be treated as a
@@ -153,7 +153,7 @@ class NotificationSender
private static function sendNtfy(object $profile, object $record, bool $success): bool
{
$topic = trim($profile->ntfy_topic ?? '');
$server = trim($profile->ntfy_server ?? 'https://ntfy.sh');
$server = trim($profile->ntfy_server ?? 'https://ntfy.mokoconsulting.tech');
$token = trim($profile->ntfy_token ?? '');
if ($topic === '') {
@@ -428,7 +428,7 @@ class NotificationSender
private static function sendRestoreNtfy(object $profile, string $type, array $details): bool
{
$topic = trim($profile->ntfy_topic ?? '');
$server = trim($profile->ntfy_server ?? 'https://ntfy.sh');
$server = trim($profile->ntfy_server ?? 'https://ntfy.mokoconsulting.tech');
$token = trim($profile->ntfy_token ?? '');
if ($topic === '') {
@@ -122,6 +122,8 @@ class RestoreEngine
}
// Step 4: Import database
$dbErrorCount = 0;
if ($restoreDb) {
$sqlFile = $this->stagingDir . '/database.sql';
@@ -130,6 +132,16 @@ class RestoreEngine
$importer = new DatabaseImporter();
$tableCount = $importer->import($sqlFile);
$this->log('Database imported: ' . $tableCount . ' statements executed');
if ($importer->hasErrors()) {
$dbErrors = $importer->getErrors();
$dbErrorCount = count($dbErrors);
$this->log('WARNING: ' . $dbErrorCount . ' database statement(s) FAILED during import — the database may be incomplete:');
foreach (array_slice($dbErrors, 0, 5) as $err) {
$this->log(' - ' . substr($err, 0, 200));
}
}
} else {
$this->log('No database.sql found in archive — skipping database restore');
}
@@ -172,7 +184,10 @@ class RestoreEngine
return [
'success' => true,
'message' => 'Restore complete from: ' . basename($archivePath),
'warning' => $dbErrorCount > 0,
'message' => $dbErrorCount > 0
? 'Restore finished, but ' . $dbErrorCount . ' database statement(s) failed — the database may be incomplete. Check the log.'
: 'Restore complete from: ' . basename($archivePath),
'log' => implode("\n", $this->log),
];
} catch (\Throwable $e) {
@@ -13,6 +13,7 @@ namespace Joomla\Component\MokoSuiteBackup\Administrator\Helper;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Log\Log;
/**
* Lightweight helper for external consumers (bridge plugins, MCP servers, CLI tools)
@@ -83,6 +84,84 @@ class BackupStatusHelper
return $record ?: null;
}
/**
* Seconds a record may sit in "running" before the records-list sweep treats it
* as stalled. Deliberately far more generous than
* PreflightCheck::STALE_TIMEOUT_MINUTES (30 min): that guard reaps per-profile at
* backup *start* (and blocks a concurrent run), whereas this sweep runs on every
* records-list load and must never reap a genuinely long-running browser/CLI
* backup mid-flight. Without a per-step heartbeat column, age can only be measured
* from backupstart a heartbeat column is the real fix (tracked in the
* stalled-backup issue).
*/
private const STALLED_TIMEOUT_SECONDS = 21600;
/**
* Mark backup records stuck in "running" as failed and clean up their partial
* archives.
*
* The stepped engine writes status='running' with a null backupend when a run
* starts, and only flips it on completion/failure. If the driving window or CLI
* process dies mid-run (closed tab, PHP timeout, killed cron) the record is
* orphaned in "running" forever the dashboard then shows a phantom in-progress
* backup and "recent failures" never reflects reality, and (until the cancel
* fix) it could not be cleared from the UI.
*
* This complements PreflightCheck::checkRunningBackup(), which reaps per-profile
* at backup start; here we clear truly-dead phantom rows shown on the records
* list. A deliberately generous threshold is used so an in-flight scheduled/CLI
* backup (which runs without the admin's browser lock) is not killed prematurely.
* Timestamps use date('Y-m-d H:i:s') to match how the engine writes
* backupstart/backupend (avoiding a UTC-vs-server-time mismatch).
*
* @param int $timeoutSeconds Age past which a "running" record is treated as stalled (min 60).
* @return int Number of records marked failed.
*/
public static function markStalledAsFailed(int $timeoutSeconds = self::STALLED_TIMEOUT_SECONDS): int
{
try {
$db = Factory::getContainer()->get('DatabaseDriver');
$cutoff = date('Y-m-d H:i:s', time() - max(60, $timeoutSeconds));
// Select stalled rows first so we can also delete their partial archive
// files, matching PreflightCheck::checkRunningBackup()'s cleanup.
$query = $db->getQuery(true)
->select($db->quoteName(['id', 'absolute_path']))
->from($db->quoteName('#__mokosuitebackup_records'))
->where($db->quoteName('status') . ' = ' . $db->quote('running'))
->where($db->quoteName('backupstart') . ' < ' . $db->quote($cutoff));
$rows = $db->setQuery($query)->loadObjectList();
if (empty($rows)) {
return 0;
}
$now = date('Y-m-d H:i:s');
foreach ($rows as $row) {
$update = $db->getQuery(true)
->update($db->quoteName('#__mokosuitebackup_records'))
->set($db->quoteName('status') . ' = ' . $db->quote('fail'))
->set($db->quoteName('backupend') . ' = ' . $db->quote($now))
->where($db->quoteName('id') . ' = ' . (int) $row->id);
$db->setQuery($update)->execute();
if (!empty($row->absolute_path) && is_file($row->absolute_path)) {
@unlink($row->absolute_path);
}
}
return \count($rows);
} catch (\Throwable $e) {
// Non-critical housekeeping — never break the records list over it, but
// leave a breadcrumb so a persistent failure (e.g. a missing column after
// a botched migration) is diagnosable instead of silently recurring.
Log::add('MokoSuiteBackup: markStalledAsFailed() failed: ' . $e->getMessage(), Log::WARNING, 'jerror');
return 0;
}
}
/**
* Get a full status summary for heartbeat payloads.
*
@@ -17,6 +17,7 @@ use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Toolbar\ToolbarHelper;
use Joomla\Component\MokoSuiteBackup\Administrator\Helper\BackupStatusHelper;
class HtmlView extends BaseHtmlView
{
@@ -28,6 +29,10 @@ class HtmlView extends BaseHtmlView
public function display($tpl = null): void
{
// Self-heal any backups orphaned in "running" by a died window/CLI before
// listing, so the records list never shows a phantom in-progress backup.
BackupStatusHelper::markStalledAsFailed();
$this->items = $this->get('Items');
$this->pagination = $this->get('Pagination');
$this->state = $this->get('State');
@@ -62,7 +62,20 @@ $ajaxUrl = Route::_('index.php?option=com_mokosuitebackup&format=json', false)
</tr>
<tr>
<th scope="row"><?php echo Text::_('COM_MOKOJOOMBACKUP_FIELD_END'); ?></th>
<td><?php echo HTMLHelper::_('date', $this->item->backupend, Text::_('DATE_FORMAT_LC2')); ?></td>
<td>
<?php
// A running or interrupted backup has no end time — the column stores the
// null date ('0000-00-00 00:00:00'), which HTMLHelper renders as the bogus
// "Monday, November 29, -0001". Show a placeholder instead of a fake date.
$backupEnd = (string) $this->item->backupend;
if ($backupEnd === '' || strncmp($backupEnd, '0000-00-00', 10) === 0) {
echo '<span class="text-muted">' . $this->escape(Text::_('COM_MOKOJOOMBACKUP_NOT_FINISHED')) . '</span>';
} else {
echo HTMLHelper::_('date', $backupEnd, Text::_('DATE_FORMAT_LC2'));
}
?>
</td>
</tr>
<tr>
<th scope="row"><?php echo Text::_('COM_MOKOJOOMBACKUP_FIELD_ARCHIVE'); ?></th>
@@ -349,7 +349,11 @@ $this->getDocument()->addStyleSheet(Uri::root(true) . '/media/com_mokosuitebacku
var done = false;
while (!done && !cancelled) {
var step = await post({ task: 'ajax.step', session_id: init.session_id });
/* Pass the pre-action so the SERVER can arm the one-shot skip flag the
moment this backup completes (see AjaxController::step) the black
"update is running" overlay then never fronts a second backup even
if the completion ack POST below fails to land. */
var step = await post({ task: 'ajax.step', session_id: init.session_id, action: CFG.action || '' });
if (!step || step.error) {
throw new Error((step && step.message) || 'Backup step failed');
@@ -447,7 +447,7 @@ $listDirn = $this->escape($this->state->get('list.direction'));
var label = document.createElement('label');
label.className = 'form-check-label';
label.setAttribute('for', 'mb-rtype-' + type);
label.textContent = typeLabels[TYPE] || type;
label.textContent = typeLabels[type] || type;
div.appendChild(input);
div.appendChild(label);
@@ -8,7 +8,7 @@
-->
<extension type="module" client="administrator" method="upgrade">
<name>Module - MokoSuiteBackup - cPanel</name>
<version>02.64.06</version>
<version>02.67.09</version>
<creationDate>2026-06-23</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
-->
<extension type="plugin" group="actionlog" method="upgrade">
<name>Action Log - MokoSuiteBackup</name>
<version>02.64.06</version>
<version>02.67.09</version>
<creationDate>2026-06-04</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
-->
<extension type="plugin" group="console" method="upgrade">
<name>Console - MokoSuiteBackup</name>
<version>02.64.06</version>
<version>02.67.09</version>
<creationDate>2026-06-04</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
-->
<extension type="plugin" group="content" method="upgrade">
<name>Content - MokoSuiteBackup</name>
<version>02.64.06</version>
<version>02.67.09</version>
<creationDate>2026-06-04</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="quickicon" method="upgrade">
<name>Quick Icon - MokoSuiteBackup</name>
<version>02.64.06</version>
<version>02.67.09</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -39,6 +39,34 @@ final class MokoSuiteBackupQuickicon extends CMSPlugin implements SubscriberInte
return;
}
// A dashboard quick-icon must NEVER be able to crash the whole admin page.
// Build the icon defensively; on any failure, contribute nothing and log.
try {
$icon = $this->buildStatusIcon();
} catch (\Throwable $e) {
error_log('MokoSuiteBackup quickicon widget failed: ' . $e->getMessage());
return;
}
// The module (mod_quickicon) expects each plugin to append a LIST of icons
// to the 'result' argument; it then iterates that list and array_merge()s a
// defaults array into each icon. Appending a bare icon dict instead of a
// [ list ] makes it iterate the dict's string values and crash in
// array_merge — so the wrapping [$icon] here is load-bearing.
$result = $event->getArgument('result', []);
$result[] = [$icon];
$event->setArgument('result', $result);
}
/**
* Build the single status quick-icon (backup freshness / failures).
*
* @return array A mod_quickicon icon definition.
*/
private function buildStatusIcon(): array
{
$db = Factory::getDbo();
// Get last completed backup
@@ -91,20 +119,15 @@ final class MokoSuiteBackupQuickicon extends CMSPlugin implements SubscriberInte
$subtitle = $ago . ' | ' . ($stats->total ?? 0) . ' backups | ' . $sizeTotal . ' MB';
}
$result = $event->getArgument('result', []);
$result[] = [
[
'link' => 'index.php?option=com_mokosuitebackup&view=backups',
'image' => $warning ? 'icon-warning' : 'icon-database',
'icon' => $warning ? 'icon-warning' : 'icon-database',
'text' => Text::_($text),
'linkadd' => $subtitle ? '<br><small>' . htmlspecialchars($subtitle) . '</small>' : '',
'id' => 'plg_quickicon_mokosuitebackup',
'group' => 'MOD_QUICKICON_MAINTENANCE',
],
return [
'link' => 'index.php?option=com_mokosuitebackup&view=backups',
'image' => $warning ? 'icon-warning' : 'icon-database',
'icon' => $warning ? 'icon-warning' : 'icon-database',
'text' => Text::_($text),
'linkadd' => $subtitle ? '<br><small>' . htmlspecialchars($subtitle) . '</small>' : '',
'id' => 'plg_quickicon_mokosuitebackup',
'group' => 'MOD_QUICKICON_MAINTENANCE',
];
$event->setArgument('result', $result);
}
private function timeAgo(string $datetime): string
@@ -7,7 +7,7 @@
-->
<extension type="plugin" group="system" method="upgrade">
<name>System - MokoSuiteBackup</name>
<version>02.64.06</version>
<version>02.67.09</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -35,6 +35,17 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface
/** @var array<string,bool> Per-request cache of the one-shot "client already backed up" skip flag. */
private array $preactionSkip = [];
/**
* Max age (seconds) of the client "already backed up" skip flag that this
* plugin will honour. The flag stores the unix time it was armed (by the
* full-screen backup's ajax.step completion / ajax.preupdateAck); the re-fired
* update/uninstall follows within a second or two, so a small window is ample.
* Bounding it means a backup that was run then ABANDONED (browser closed before
* the update re-fired) cannot silently suppress a genuine, unrelated pre-action
* backup minutes or hours later in the same session.
*/
private const PREACTION_SKIP_TTL = 300;
public static function getSubscribedEvents(): array
{
return [
@@ -648,12 +659,16 @@ JS;
$skipKey = 'mokosuitebackup.skip_preaction_' . $paramName;
if (!array_key_exists($paramName, $this->preactionSkip)) {
$this->preactionSkip[$paramName] = (bool) $session->get($skipKey, false);
$session->set($skipKey, false);
/* The flag stores the unix time it was armed. Consume it one-shot (reset
to 0) and honour the skip ONLY if it was armed within the TTL a
stale flag from an abandoned backup must not suppress a real one. */
$armedAt = (int) $session->get($skipKey, 0);
$session->set($skipKey, 0);
$this->preactionSkip[$paramName] = ($armedAt > 0 && (time() - $armedAt) <= self::PREACTION_SKIP_TTL);
}
if ($this->preactionSkip[$paramName]) {
// Client already backed up; skip this whole re-fired (possibly batch) request.
// Client already backed up (recently); skip this re-fired (possibly batch) request.
$this->preactionRan[$paramName] = true;
return;
@@ -7,7 +7,7 @@
-->
<extension type="plugin" group="task" method="upgrade">
<name>Task - MokoSuiteBackup</name>
<version>02.64.06</version>
<version>02.67.09</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
-->
<extension type="plugin" group="webservices" method="upgrade">
<name>Web Services - MokoSuiteBackup</name>
<version>02.64.06</version>
<version>02.67.09</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
+1 -1
View File
@@ -8,7 +8,7 @@
<extension type="package" method="upgrade">
<name>Package - MokoSuiteBackup</name>
<packagename>mokosuitebackup</packagename>
<version>02.64.06</version>
<version>02.67.09</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
+97
View File
@@ -15,6 +15,12 @@ use Joomla\CMS\Language\Text;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Router\Route;
// Guard against Joomla including this package install script twice in a single
// request (the package update flow can extract to two tmp dirs and include both
// script.php files), which otherwise fatals with a compile-time "Cannot
// redeclare class Pkg_MokoSuiteBackupInstallerScript". Declaring the class
// conditionally makes the second include a runtime no-op instead of a fatal.
if (!class_exists('Pkg_MokoSuiteBackupInstallerScript', false)) {
class Pkg_MokoSuiteBackupInstallerScript
{
/**
@@ -146,6 +152,11 @@ class Pkg_MokoSuiteBackupInstallerScript
return;
}
/* Self-heal the package update site if it is missing (the records view warns
"update site not found" otherwise). Runs before restoreDownloadKey so a
preserved download key can re-attach to the recreated site. */
$this->ensureUpdateSite();
/* Restore the download key preserved before the update re-registered the site */
if ($type === 'update') {
$this->restoreDownloadKey();
@@ -1062,6 +1073,91 @@ class Pkg_MokoSuiteBackupInstallerScript
}
}
/**
* Ensure the package has an update site registered.
*
* Joomla normally creates this row from the manifest <updateservers> on install,
* but if it is ever missing (partial install, manual DB surgery, a package that
* predates the update-server tag) the records view warns "update site not found"
* and the extension can never pull updates. Recreate it idempotently so the
* install is self-healing no manual reinstall required.
*/
private function ensureUpdateSite(): void
{
$updateUrl = 'https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteBackup/updates.xml';
try {
$db = Factory::getDbo();
/* Resolve the package extension id. */
$query = $db->getQuery(true)
->select($db->quoteName('extension_id'))
->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' = ' . $db->quote('pkg_mokosuitebackup'))
->where($db->quoteName('type') . ' = ' . $db->quote('package'))
->setLimit(1);
$extensionId = (int) $db->setQuery($query)->loadResult();
if ($extensionId === 0) {
return;
}
/* Already linked to an update site? Nothing to do. */
$query = $db->getQuery(true)
->select('COUNT(*)')
->from($db->quoteName('#__update_sites_extensions'))
->where($db->quoteName('extension_id') . ' = ' . $extensionId);
if ((int) $db->setQuery($query)->loadResult() > 0) {
return;
}
/* Create the update site and link it to the package, preserving any
download key we saved earlier in this run. Both inserts run in a
transaction: the idempotency guard above checks only the LINK table, so
a site row left behind by a failed link insert would be re-created on
every retry accumulating orphaned duplicates. All-or-nothing avoids that. */
$db->transactionStart();
try {
$site = (object) [
'name' => 'MokoSuiteBackup Updates',
'type' => 'extension',
'location' => $updateUrl,
'enabled' => 1,
'last_check_timestamp' => 0,
'extra_query' => !empty($this->savedDownloadKey) ? 'dlid=' . $this->savedDownloadKey : '',
];
$db->insertObject('#__update_sites', $site, 'update_site_id');
$link = (object) [
'update_site_id' => (int) $site->update_site_id,
'extension_id' => $extensionId,
];
$db->insertObject('#__update_sites_extensions', $link);
$db->transactionCommit();
} catch (\Throwable $e) {
$db->transactionRollback();
throw $e;
}
} catch (\Throwable $e) {
/* Surface the failure like restoreDownloadKey() rather than hiding it in
the server log: this runs at install/update time (an observed moment)
and a silent failure leaves the admin in the exact "update site not
found" state this method exists to fix, with no reason given. */
Log::add('MokoSuiteBackup: ensureUpdateSite() failed: ' . $e->getMessage(), Log::WARNING, 'jerror');
Factory::getApplication()->enqueueMessage(
'<strong>MokoSuiteBackup</strong> — the update site could not be registered automatically, '
. 'so update notifications may not appear. You can add it manually in the '
. '<a href="index.php?option=com_installer&view=updatesites&filter[search]=pkg_mokosuitebackup">Update Sites</a> manager.',
'warning'
);
}
}
/**
* Show post-install license key prompt.
*/
@@ -1091,3 +1187,4 @@ class Pkg_MokoSuiteBackupInstallerScript
} catch (\Exception $e) {}
}
}
} // end class_exists guard