Compare commits

...

105 Commits

Author SHA1 Message Date
gitea-actions[bot] ce5f9a8dd6 chore(version): auto-bump patch 02.44.04-rc [skip ci] 2026-06-21 00:38:40 +00:00
Jonathan Miller e55acc464a Merge remote-tracking branch 'origin/dev' into rc
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Blocked by required conditions
Platform: moko-platform CI / CI Summary (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 5s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 8s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 35s
Generic: Project CI / Lint & Validate (pull_request) Successful in 35s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 36s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 38s
Universal: Auto Version Bump / Version Bump (push) Successful in 12s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 9s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 32s
2026-06-20 19:38:22 -05:00
Jonathan Miller 6ed0a9f221 Merge remote-tracking branch 'origin/rc' into rc 2026-06-20 19:38:21 -05:00
Jonathan Miller 072297a75d fix(cpanel): always start collapsed, remove setting
Cpanel module is always collapsed by default. Removed the collapsed
toggle from module settings since it should not be configurable.
2026-06-20 19:37:36 -05:00
gitea-actions[bot] c0adfe41dd chore(version): auto-bump patch 02.44.03-dev [skip ci] 2026-06-21 00:15:31 +00:00
Jonathan Miller 579b0f13d8 Merge remote-tracking branch 'origin/dev' into rc
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Blocked by required conditions
Platform: moko-platform CI / CI Summary (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 6s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 8s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: Auto Version Bump / Version Bump (push) Successful in 13s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 12s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 13s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 40s
Generic: Project CI / Lint & Validate (pull_request) Successful in 42s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 45s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 46s
2026-06-20 19:15:05 -05:00
gitea-actions[bot] 2b7913f6e1 chore(version): auto-bump patch 02.44.03-rc [skip ci] 2026-06-21 00:14:51 +00:00
Jonathan Miller 07482df7b9 Merge remote-tracking branch 'origin/rc' into rc
Universal: Auto Version Bump / Version Bump (push) Successful in 8s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 9s
2026-06-20 19:14:37 -05:00
Jonathan Miller 61f96cba64 fix(menu): pin com_mokosuitehq at top of sidebar menu
The element name is com_mokosuitehq not com_mokosuiteclienthq.
The sort logic was checking the wrong name so HQ was never pinned.
2026-06-20 19:12:59 -05:00
Jonathan Miller 71f5d161e9 style(menu): simplify sidebar child item indent 2026-06-20 19:05:38 -05:00
gitea-actions[bot] 2db043412f chore(version): auto-bump patch 02.44.02-dev [skip ci] 2026-06-20 23:55:31 +00:00
Jonathan Miller c913f12621 Merge remote-tracking branch 'origin/dev' into rc
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Blocked by required conditions
Platform: moko-platform CI / CI Summary (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Project CI / Lint & Validate (pull_request) Failing after 3s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 6s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 7s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Universal: Auto Version Bump / Version Bump (push) Successful in 12s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 11s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 35s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 37s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 42s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 39s
2026-06-20 18:55:13 -05:00
gitea-actions[bot] 772c40bb56 chore(version): auto-bump patch 02.44.02-rc [skip ci] 2026-06-20 23:54:29 +00:00
Jonathan Miller 49cc5becf6 fix(heartbeat): show success/error notification to admin on install/update
Universal: Auto Version Bump / Version Bump (push) Successful in 8s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 8s
Both install-time and runtime heartbeat now enqueue visible messages
for all outcomes — success, HTTP error, and connection failure — so
the admin always knows whether the heartbeat registered.
2026-06-20 18:54:17 -05:00
gitea-actions[bot] 738c878067 chore(version): auto-bump patch 02.44.01-dev [skip ci] 2026-06-20 23:37:52 +00:00
gitea-actions[bot] aa5f3ab06a chore(version): auto-bump patch 02.44.01-rc [skip ci] 2026-06-20 23:34:46 +00:00
Jonathan Miller 9326e2d11a fix(heartbeat): rotate RSA keys, fix backup bridge StatusHelper path
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Blocked by required conditions
Platform: moko-platform CI / CI Summary (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 6s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 9s
Universal: Auto Version Bump / Version Bump (push) Successful in 13s
Generic: Project CI / Lint & Validate (pull_request) Successful in 13s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 10s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 31s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 30s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 33s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 29s
- Replace broken RSA key pair (had Windows \r\n line endings in PEM)
  with fresh Unix-style keys that pass OpenSSL verify
- Fix backup bridge: use correct Helper\BackupStatusHelper path and
  getStatusSummary() method per MokoSuite convention
- Align fallback query output with StatusHelper return format
  (latest/totals structure instead of flat keys)
2026-06-20 18:34:13 -05:00
jmiller b78987733c chore: sync repo-health.yml from Template-Generic [skip ci]
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Failing after 2s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 19s
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Failing after 2m22s
2026-06-20 18:06:02 -05:00
jmiller 585b02811e chore: sync rc-revert.yml from Template-Generic [skip ci] 2026-06-20 18:06:01 -05:00
jmiller 3abece1399 chore: sync pr-check.yml from Template-Generic [skip ci] 2026-06-20 18:06:01 -05:00
jmiller 55b4b54908 chore: sync cleanup.yml from Template-Generic [skip ci] 2026-06-20 18:06:01 -05:00
jmiller b5b382000e ci: sync security-audit.yml from Template-Joomla [skip ci] 2026-06-20 18:06:00 -05:00
jmiller 452913f2d8 ci: sync repo-health.yml from Template-Joomla [skip ci] 2026-06-20 18:06:00 -05:00
jmiller 250bfb06f8 ci: sync rc-revert.yml from Template-Joomla [skip ci] 2026-06-20 18:05:59 -05:00
jmiller 5c4e59b9ac ci: sync pr-check.yml from Template-Joomla [skip ci] 2026-06-20 18:05:59 -05:00
jmiller d85c36fe3b ci: sync issue-branch.yml from Template-Joomla [skip ci] 2026-06-20 18:05:59 -05:00
jmiller 0482407013 ci: sync cleanup.yml from Template-Joomla [skip ci] 2026-06-20 18:05:58 -05:00
jmiller a3a6fdba4d chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-06-20 18:05:58 -05:00
jmiller 4074e08107 chore: sync ci-generic.yml from Template-Generic [skip ci] 2026-06-20 18:05:58 -05:00
jmiller 96b5930761 ci: sync ci-generic.yml from Template-Joomla [skip ci] 2026-06-20 18:05:57 -05:00
jmiller 30e91810c7 ci: sync cascade-dev.yml from Template-Joomla [skip ci] 2026-06-20 18:05:57 -05:00
jmiller b6b412de84 ci: sync branch-cleanup.yml from Template-Joomla [skip ci] 2026-06-20 18:05:56 -05:00
jmiller 50a3056f1e ci: sync auto-release.yml from Template-Joomla [skip ci] 2026-06-20 18:05:56 -05:00
gitea-actions[bot] 7ba6e2e709 chore: promote changelog [Unreleased] → [02.44.00] 2026-06-20 18:05:56 -05:00
gitea-actions[bot] e1e65b77b5 chore(release): build 02.44.00 [skip ci] 2026-06-20 18:05:55 -05:00
jmiller 75bddcd688 chore: sync workflow-sync-trigger.yml from Template-Generic [skip ci] 2026-06-20 18:05:54 -05:00
jmiller 928bfbe8ff chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-06-20 18:05:53 -05:00
gitea-actions[bot] db299f70a5 chore: promote changelog [Unreleased] → [02.43.00] 2026-06-20 18:05:53 -05:00
gitea-actions[bot] b64c6f57f6 chore(release): build 02.43.00 [skip ci] 2026-06-20 18:05:52 -05:00
jmiller db9d33b46d ci: sync ci-generic.yml from Template-Joomla [skip ci] 2026-06-20 18:05:50 -05:00
gitea-actions[bot] 17438477f9 chore: promote changelog [Unreleased] → [02.42.00] 2026-06-20 18:05:50 -05:00
gitea-actions[bot] 970f362378 chore(release): build 02.42.00 [skip ci] 2026-06-20 18:05:49 -05:00
jmiller 350b3c5f29 ci: sync cascade-dev.yml from Template-Joomla [skip ci] 2026-06-20 18:05:48 -05:00
jmiller 045f7fbb31 ci: sync branch-cleanup.yml from Template-Joomla [skip ci] 2026-06-20 18:05:48 -05:00
jmiller 5fa639acd1 ci: sync auto-release.yml from Template-Joomla [skip ci] 2026-06-20 18:05:47 -05:00
gitea-actions[bot] 5ae2b7590d chore(version): auto-bump patch 02.41.02-rc [skip ci] 2026-06-20 22:54:48 +00:00
Jonathan Miller 0e03837ec9 fix(heartbeat): align signature headers with HQ expectations
Universal: Auto Version Bump / Version Bump (push) Successful in 8s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 8s
Rename X-MokoSuiteClient-Signature/Timestamp to X-MokoSuite-Signature/
Timestamp to match what HeartbeatController reads as HTTP_X_MOKOSUITE_*.
2026-06-20 17:53:54 -05:00
Jonathan Miller 7510c9f018 fix(heartbeat): correct API route from mokosuiteclienthq to mokosuitehq
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Blocked by required conditions
Platform: moko-platform CI / CI Summary (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 7s
Generic: Project CI / Lint & Validate (pull_request) Successful in 17s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 7s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Failing after 2s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 13s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 53s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 53s
Universal: Auto Version Bump / Version Bump (push) Successful in 12s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 10s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 20s
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Failing after 5m50s
The HQ component is com_mokosuitehq, not com_mokosuiteclienthq.
The heartbeat endpoint was returning 404 because the route prefix
was wrong.
2026-06-20 15:52:01 -05:00
Jonathan Miller 62a5828634 Merge remote-tracking branch 'origin/rc' into rc
Universal: Auto Version Bump / Version Bump (push) Successful in 8s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 8s
2026-06-20 15:31:58 -05:00
Jonathan Miller 617f9e7b3e Merge remote-tracking branch 'origin/main' into rc 2026-06-20 15:19:57 -05:00
gitea-actions[bot] 55df3a8c7c chore(version): auto-bump patch 02.41.01-rc [skip ci] 2026-06-20 20:04:37 +00:00
Jonathan Miller 0dcd27cece fix: update heartbeat URL from waas.dev to suite.dev.mokoconsulting.tech
Universal: Auto Version Bump / Version Bump (push) Successful in 40s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 23s
Domain was renamed — heartbeat was silently failing because the old
hostname no longer resolves.
2026-06-20 15:03:49 -05:00
jmiller 7f3c2b1021 ci: sync auto-bump.yml from Template-Joomla [skip ci] 2026-06-20 19:59:07 +00:00
jmiller d56f1485fc ci: sync ci-generic.yml from Template-Joomla [skip ci] 2026-06-20 19:05:58 +00:00
gitea-actions[bot] 13bccc041a chore: promote changelog [Unreleased] → [02.42.00]
Generic: Project CI / Tests (push) Blocked by required conditions
Generic: Project CI / Lint & Validate (push) Successful in 9s
2026-06-20 19:05:27 +00:00
gitea-actions[bot] cb50f0360c chore(release): build 02.42.00 [skip ci] 2026-06-20 19:05:21 +00:00
jmiller 0f92612188 ci: sync cascade-dev.yml from Template-Joomla [skip ci] 2026-06-20 19:03:17 +00:00
jmiller f971d10344 ci: sync branch-cleanup.yml from Template-Joomla [skip ci] 2026-06-20 19:02:44 +00:00
jmiller 119cf4575d ci: sync auto-release.yml from Template-Joomla [skip ci] 2026-06-20 19:01:03 +00:00
jmiller 81415b07ed ci: sync auto-bump.yml from Template-Joomla [skip ci] 2026-06-20 18:53:49 +00:00
jmiller a6f8b25c33 ci: sync pre-release workflow from Template-Joomla
Generic: Project CI / Tests (push) Blocked by required conditions
Generic: Project CI / Lint & Validate (push) Successful in 14s
2026-06-20 18:49:28 +00:00
jmiller ac99f85732 ci: add Joomla metadata validation workflow for PRs
Generic: Project CI / Tests (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Project CI / Lint & Validate (push) Successful in 12s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 35s
2026-06-20 18:39:06 +00:00
jmiller 1fa3cb80be Merge pull request 'fix(install): enable ticket/offline plugins, consolidate monitor into core' (#218) from rc into main
Generic: Project CI / Tests (push) Blocked by required conditions
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Blocked by required conditions
Platform: moko-platform CI / CI Summary (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 17s
Generic: Project CI / Lint & Validate (push) Successful in 13s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 6s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Project CI / Lint & Validate (pull_request) Successful in 14s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 12s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 39s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 48s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 52s
Universal: Auto Version Bump / Version Bump (push) Has been skipped
2026-06-20 17:25:53 +00:00
Jonathan Miller 1d7ea1a2cc Merge remote-tracking branch 'origin/main' into rc
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Blocked by required conditions
Platform: moko-platform CI / CI Summary (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Universal: Auto Version Bump / Version Bump (push) Failing after 12s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 14s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Failing after 16s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 5s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 5s
Generic: Project CI / Lint & Validate (pull_request) Successful in 12s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 6s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 4s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 29s
2026-06-20 12:24:59 -05:00
jmiller bd5bd8476c fix: rename moko-platform to mokocli + changelog promotion in workflows
Generic: Project CI / Tests (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Project CI / Lint & Validate (push) Successful in 27s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 34s
2026-06-20 17:15:51 +00:00
jmiller 61b4963e2d fix: rename moko-platform to mokocli + changelog promotion in workflows
Generic: Project CI / Tests (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Project CI / Lint & Validate (push) Successful in 10s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 32s
2026-06-20 17:15:50 +00:00
jmiller 21168193e1 fix: rename moko-platform to mokocli + changelog promotion in workflows
Generic: Project CI / Tests (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Project CI / Lint & Validate (push) Successful in 32s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 37s
2026-06-20 17:15:50 +00:00
jmiller cfc2778d95 fix: rename moko-platform to mokocli + changelog promotion in workflows
Generic: Project CI / Tests (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Project CI / Lint & Validate (push) Successful in 10s
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Repo Health / Site Health (push) Has been skipped
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 38s
2026-06-20 17:15:49 +00:00
jmiller 8365093fc3 fix: rename moko-platform to mokocli + changelog promotion in workflows
Generic: Project CI / Tests (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Project CI / Lint & Validate (push) Successful in 10s
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 37s
2026-06-20 17:15:48 +00:00
jmiller 8a2b59865f fix: rename moko-platform to mokocli + changelog promotion in workflows
Generic: Project CI / Tests (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Project CI / Lint & Validate (push) Successful in 10s
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (push) Has been skipped
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 32s
2026-06-20 17:15:48 +00:00
Jonathan Miller 7e6699479f fix(install): enable and protect backup bridge plugin on install
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Blocked by required conditions
Platform: moko-platform CI / CI Summary (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (push) Has been skipped
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 4s
Universal: Auto Version Bump / Version Bump (push) Failing after 10s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 7s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request_target) Failing after 13s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 49s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 47s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 49s
Add mokosuiteclient_backup to enablePlugin() and protectExtensions()
lists so the backup bridge plugin is enabled on install/update and
cannot be accidentally disabled by admins.
2026-06-20 12:15:14 -05:00
Jonathan Miller bc60161e16 Merge remote-tracking branch 'origin/dev' into rc
Generic: Project CI / Tests (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Blocked by required conditions
Platform: moko-platform CI / CI Summary (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 4s
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Project CI / Lint & Validate (push) Successful in 12s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 7s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Failing after 11s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request_target) Failing after 11s
Generic: Project CI / Lint & Validate (pull_request) Successful in 39s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 33s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 41s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 44s
# Conflicts:
#	source/packages/plg_task_mokosuiteclientdemo/mokosuiteclientdemo.xml
#	source/packages/plg_task_mokosuiteclientsync/mokosuiteclientsync.xml
2026-06-20 11:59:38 -05:00
Jonathan Miller fb52d3ed53 fix(heartbeat): add diagnostic logging to all bail-out points
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 6s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 6s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Universal: Auto Version Bump / Version Bump (push) Failing after 10s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request_target) Failing after 9s
Generic: Project CI / Lint & Validate (pull_request) Successful in 27s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 29s
Log reason when sendHeartbeat() exits early: missing params, empty
health token, or unconfigured base URL. Helps diagnose why heartbeat
doesn't reach MokoSuiteClientHQ after install/update.
2026-06-20 11:54:24 -05:00
Jonathan Miller f8c70f2bef fix(install): migrate monitor params before retiring the plugin
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Blocked by required conditions
Platform: moko-platform CI / CI Summary (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (push) Has been skipped
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 4s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 5s
Universal: Auto Version Bump / Version Bump (push) Failing after 7s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 5s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request_target) Failing after 9s
Universal: PR Check / Validate PR (pull_request) Failing after 3s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 25s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 27s
Move migrateMonitorParams() before removeRetiredExtensions() so the
monitor plugin's params (base_url, signing_key) are copied to the
core plugin before the monitor row is deleted from #__extensions.
2026-06-20 11:50:53 -05:00
Jonathan Miller f1d483aa38 fix(heartbeat): add error logging, fix params persistence race
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Blocked by required conditions
Platform: moko-platform CI / CI Summary (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Access control (push) Successful in 1s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 7s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 6s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 4s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 49s
Generic: Project CI / Lint & Validate (pull_request) Successful in 50s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 53s
Universal: Auto Version Bump / Version Bump (push) Failing after 8s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request_target) Failing after 7s
- Add Log::add() to empty catch blocks in sendHeartbeat(), checkHeartbeat(),
  and fetchLocalHealth() so failures are diagnosable
- Add curl_error() check and non-2xx logging to script.php sendHeartbeat()
- Replace $extension->store() with targeted DB update in checkHeartbeat()
  to avoid overwriting params modified by migrateMonitorParams()
2026-06-20 11:40:17 -05:00
gitea-actions[bot] 99f0901fd2 chore(release): build 02.41.00-rc [skip ci] 2026-06-20 16:38:27 +00:00
Jonathan Miller 6dcacfb6cc Merge remote-tracking branch 'origin/main' into rc
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Blocked by required conditions
Platform: moko-platform CI / CI Summary (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 8s
Generic: Project CI / Lint & Validate (pull_request) Successful in 19s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 11s
Universal: PR Check / Validate PR (pull_request) Failing after 11s
Generic: Repo Health / Access control (pull_request) Successful in 3s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 59s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 49s
Universal: Auto Version Bump / Version Bump (push) Failing after 6s
Universal: Build & Release / Promote to RC (pull_request) Successful in 12s
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request_target) Failing after 9s
# Conflicts:
#	.mokogitea/manifest.xml
#	src/packages/com_mokowaas/mokowaas.xml
#	src/packages/plg_system_mokowaas/Extension/MokoWaaS.php
#	src/packages/plg_system_mokowaas/Field/AllowedIpsField.php
#	src/packages/plg_system_mokowaas/Field/CurrentIpField.php
#	src/packages/plg_system_mokowaas/Field/DemoTaskInfoField.php
#	src/packages/plg_system_mokowaas/Field/NextResetField.php
#	src/packages/plg_system_mokowaas/Field/SnapshotTablesField.php
#	src/packages/plg_system_mokowaas/mokowaas.xml
2026-06-20 10:59:08 -05:00
Jonathan Miller 72134bba28 fix(install): enable ticket/offline plugins, consolidate monitor into core
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 10s
- enablePlugin() handles empty element columns with fallback match by name
- Retire monitor plugin: migrate config (base_url, signing_key) to core
- Runtime heartbeat moved from monitor plugin to core plugin
- DisplayController heartbeat reads from core plugin params
- ensureAdminModule() direct DB update for existing modules (fixes
  checked_out blocking, cpanel access level 6→3, menu ordering -1)
- Add missing ticket automation language strings (IMAP, autoclose)
- Remove monitor from cpanel plugin grid
2026-06-20 10:33:01 -05:00
gitea-actions[bot] 29c58a9400 chore(release): build 02.35.00 [skip ci] 2026-06-19 07:15:04 +00:00
jmiller cddfbe5256 Merge pull request 'fix: remove deprecated .mokogitea/manifest.xml' (#217) from fix into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Project CI / Lint & Validate (push) Successful in 37s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 41s
Generic: Project CI / Tests (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (push) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been cancelled
Platform: moko-platform CI / CI Summary (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-19 07:08:57 +00:00
Jonathan Miller d692db4a69 fix: remove deprecated .mokogitea/manifest.xml — metadata managed via API
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 5s
Generic: Project CI / Lint & Validate (pull_request) Successful in 11s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 5s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 9s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 46s
Branch Cleanup / Delete merged branch (pull_request) Failing after 2s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request_target) Failing after 8s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 8s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Has been cancelled
Joomla: Extension CI / PHPStan Analysis (pull_request) Has been cancelled
Joomla: Extension CI / Build RC Pre-Release (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Has been cancelled
Platform: moko-platform CI / CI Summary (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 Issues (pull_request) Has been cancelled
2026-06-19 02:04:36 -05:00
jmiller 888f37c334 fix(ci): detect rebuild by branch name not version suffix [skip ci] 2026-06-19 01:51:59 +00:00
jmiller 87d62f00e3 fix(ci): detect rebuild by branch name not version suffix [skip ci] 2026-06-19 01:51:34 +00:00
Jonathan Miller c1696bce41 fix(ci): detect rebuild by branch name not version suffix [skip ci] 2026-06-18 20:35:27 -05:00
gitea-actions[bot] 17d66fcf1e chore(version): pre-release bump to 02.40.00-rc [skip ci] 2026-06-19 01:14:34 +00:00
jmiller 74361a4124 ci: patch bump on same-branch rebuilds, minor only on elevation [skip ci] 2026-06-19 00:40:39 +00:00
jmiller ab163dfe06 ci: patch bump on same-branch rebuilds, minor only on elevation [skip ci] 2026-06-19 00:18:33 +00:00
jmiller 6592b5459c ci: patch bump on same-branch rebuilds, minor only on elevation [skip ci] 2026-06-19 00:18:01 +00:00
Jonathan Miller 30d9e1a108 ci: patch bump on same-branch rebuilds, minor only on elevation [skip ci]
RC rebuilds on the rc branch now get patch bumps (02.37.00-rc →
02.37.01-rc) instead of minor bumps. Minor bumps only happen on
branch elevations (dev→rc, rc→stable).
2026-06-18 19:13:32 -05:00
gitea-actions[bot] fab6572777 chore(version): pre-release bump to 02.39.00-rc [skip ci] 2026-06-19 00:11:34 +00:00
Jonathan Miller bf30c3db5b fix(api): type-safe update, filtered pagination, status sync, int cast assign
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: Auto Version Bump / Version Bump (push) Successful in 8s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
- API update() now uses getInt/getString instead of raw input
- API update() syncs status/status_id and priority/priority_id
- API update() returns 404 if ticket not found
- Pagination total count now uses filtered query (was unfiltered)
- AutomationEngine assign action casts value to int
2026-06-18 19:09:42 -05:00
Jonathan Miller 134b9b3693 fix(security+reliability): address PR review — ACL guards, error logging, path traversal
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Repo Health / Site Health (push) Has been skipped
Universal: Auto Version Bump / Version Bump (push) Successful in 9s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Security:
- Add return after all jsonForbidden() calls (13 methods) to prevent
  ACL bypass if $app->close() fails to terminate
- Add throw after requireAuth() in REST API controller (same pattern)
- Add path traversal guard to AttachmentService::getAbsolutePath()
  using realpath + prefix check

Error handling:
- Log install notification email failures instead of empty catch
- Log DB errors in getUserEmail(), getNotificationConfig(),
  getComponentConfig() instead of silent fallbacks
- Log PHP upload error codes in AttachmentService
- Check Folder::create() return value before upload loop
- Fix searchKb() missing return on short query + log DB errors
- Fix ntfy push to capture curl_error() on connection failure
- Upgrade AutomationEngine inner catch to LOG_ERROR with rule ID
2026-06-18 19:05:57 -05:00
gitea-actions[bot] ccb76132a5 chore(version): pre-release bump to 02.38.00-rc [skip ci] 2026-06-18 20:52:35 +00:00
Jonathan Miller c8a267eed0 chore: remove legacy src/ directory (pre-rename MokoWaaS code) [skip ci] 2026-06-18 15:50:39 -05:00
Jonathan Miller 6c1ab6607b fix: rename installer class Pkg_Mokosuite → Pkg_Mokosuiteclient [skip ci] 2026-06-18 15:09:03 -05:00
Jonathan Miller c2c2f40147 fix: set EXTENSION_NAME=mokosuiteclient in Makefile for updates.xml [skip ci] 2026-06-18 14:57:56 -05:00
gitea-actions[bot] bd39fb487c chore(version): pre-release bump to 02.37.00-rc [skip ci] 2026-06-18 17:54:58 +00:00
Jonathan Miller 2e00512741 fix: rename all language keys MOKOSUITE→MOKOSUITECLIENT, auto-enable monitor plugin
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (push) Has been skipped
Universal: Auto Version Bump / Version Bump (push) Successful in 7s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
- XML manifests used old PLG_SYSTEM_MOKOSUITE_ / MOD_MOKOSUITE_ / COM_MOKOSUITE_
  prefixes that didn't match the INI files (PLG_SYSTEM_MOKOSUITECLIENT_ etc.),
  causing untranslated labels in admin UI
- Also fixed PLG_TASK_MOKOSUITEDEMO/SYNC and DBIP language keys
- Added mokosuiteclient_monitor to auto-enable list in package install script
  so heartbeat registration works on fresh install
2026-06-18 12:47:50 -05:00
Jonathan Miller 823d2e4f0e ci: add changelog extraction to promote-rc job in auto-release [skip ci] 2026-06-18 12:15:32 -05:00
gitea-actions[bot] 42d4f6e553 chore(version): pre-release bump to 02.36.00-rc [skip ci] 2026-06-18 15:45:07 +00:00
Jonathan Miller b8dfd500e4 fix: swap demo/sync plugin manifests — contents were in wrong files
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (push) Has been skipped
Universal: Auto Version Bump / Version Bump (push) Successful in 7s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-18 10:44:46 -05:00
gitea-actions[bot] ca37dc9cbd chore(release): build 02.35.00-rc [skip ci] 2026-06-18 15:00:02 +00:00
jmiller f22c8d8575 ci: deploy full pre-release workflow from mokoplatform [skip ci] 2026-06-18 13:47:53 +00:00
jmiller b89ae8cef5 revert: re-enable auto-bump on dev push [skip ci] 2026-06-17 04:46:08 +00:00
jmiller 2ce9847ac5 ci: disable auto-bump on push to dev [skip ci] 2026-06-16 18:21:12 +00:00
80 changed files with 1205 additions and 7198 deletions
-26
View File
@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Moko Platform Repository Manifest
See: https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home
-->
<moko-platform xmlns="https://standards.mokoconsulting.tech/moko-platform/1.0" schema-version="1.0">
<identity>
<name>MokoSuiteClient</name>
<display-name>Package - MokoSuiteClient</display-name>
<org>MokoConsulting</org>
<description>White-label identity, security hardening, and tenant restriction layer for Suite-managed Joomla environments</description>
<version>02.34.84</version>
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
</identity>
<governance>
<platform>joomla</platform>
<standards-version>05.00.00</standards-version>
<standards-source>https://git.mokoconsulting.tech/MokoConsulting/moko-platform</standards-source>
<last-synced>2026-05-28T20:00:00+00:00</last-synced>
</governance>
<build>
<language>PHP</language>
<package-type>package</package-type>
<entry-point>source/</entry-point>
</build>
</moko-platform>
+9 -9
View File
@@ -4,8 +4,8 @@
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: Gitea.Workflow
# INGROUP: mokoplatform.Release # INGROUP: mokocli.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/auto-bump.yml # PATH: /.mokogitea/workflows/auto-bump.yml
# VERSION: 09.02.00 # VERSION: 09.02.00
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits) # BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
@@ -43,19 +43,19 @@ jobs:
token: ${{ secrets.MOKOGITEA_TOKEN }} token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 1 fetch-depth: 1
- name: Setup mokoplatform tools - name: Setup mokocli tools
run: | run: |
if ! command -v composer &> /dev/null; then if ! command -v composer &> /dev/null; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1 sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
fi fi
if [ -d "/opt/mokoplatform/cli" ]; then if [ -d "/opt/mokocli/cli" ]; then
echo "MOKO_CLI=/opt/mokoplatform/cli" >> "$GITHUB_ENV" echo "MOKO_CLI=/opt/mokocli/cli" >> "$GITHUB_ENV"
else else
git clone --depth 1 --branch main --quiet \ git clone --depth 1 --branch main --quiet \
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/mokoplatform.git" \ "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/mokocli.git" \
/tmp/mokoplatform-api /tmp/mokocli
cd /tmp/mokoplatform-api && composer install --no-dev --no-interaction --quiet cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
echo "MOKO_CLI=/tmp/mokoplatform-api/cli" >> "$GITHUB_ENV" echo "MOKO_CLI=/tmp/mokocli/cli" >> "$GITHUB_ENV"
fi fi
- name: Bump version - name: Bump version
+65 -34
View File
@@ -4,8 +4,8 @@
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Release # INGROUP: mokocli.Release
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform # REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli
# PATH: /templates/workflows/universal/auto-release.yml.template # PATH: /templates/workflows/universal/auto-release.yml.template
# VERSION: 05.00.00 # VERSION: 05.00.00
# BRIEF: Universal build & release detects platform from manifest.xml # BRIEF: Universal build & release detects platform from manifest.xml
@@ -66,25 +66,25 @@ jobs:
token: ${{ secrets.MOKOGITEA_TOKEN }} token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 1 fetch-depth: 1
- name: Setup moko-platform tools - name: Setup mokocli tools
env: env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: | run: |
if [ -f /opt/moko-platform/cli/version_bump.php ] && [ -f /opt/moko-platform/vendor/autoload.php ]; then if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
echo Using pre-installed /opt/moko-platform echo Using pre-installed /opt/mokocli
echo MOKO_CLI=/opt/moko-platform/cli >> $GITHUB_ENV echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV
else else
echo Falling back to fresh clone echo Falling back to fresh clone
if ! command -v composer > /dev/null 2>&1; then if ! command -v composer > /dev/null 2>&1; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1 sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
fi fi
rm -rf /tmp/moko-platform-api rm -rf /tmp/mokocli
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/moko-platform-api git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
cd /tmp/moko-platform-api cd /tmp/mokocli
composer install --no-dev --no-interaction --quiet composer install --no-dev --no-interaction --quiet
echo MOKO_CLI=/tmp/moko-platform-api/cli >> $GITHUB_ENV echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
fi fi
- name: Rename branch to rc - name: Rename branch to rc
@@ -112,16 +112,19 @@ jobs:
- name: Update RC release notes from CHANGELOG.md - name: Update RC release notes from CHANGELOG.md
run: | run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
# Extract [Unreleased] section from changelog
NOTES=""
if [ -f "CHANGELOG.md" ]; then if [ -f "CHANGELOG.md" ]; then
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md) NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
[ -z "$NOTES" ] && NOTES="Release candidate"
else
NOTES="Release candidate"
fi fi
[ -z "$NOTES" ] && NOTES="Release candidate"
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \ # Find the RC release and update its body
"${API_BASE}/releases/tags/release-candidate" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true) RELEASE_ID=$(curl -sf -H "Authorization: token ${TOKEN}" \
"${API_BASE}/releases/tags/release-candidate" \
| python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
if [ -n "$RELEASE_ID" ]; then if [ -n "$RELEASE_ID" ]; then
python3 -c " python3 -c "
@@ -132,7 +135,7 @@ jobs:
'${API_BASE}/releases/${RELEASE_ID}', '${API_BASE}/releases/${RELEASE_ID}',
data=payload, method='PATCH', data=payload, method='PATCH',
headers={ headers={
'Authorization': 'token ${{ secrets.MOKOGITEA_TOKEN }}', 'Authorization': 'token ${TOKEN}',
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}) })
urllib.request.urlopen(req) urllib.request.urlopen(req)
@@ -180,26 +183,26 @@ jobs:
fi fi
echo "No conflict markers found" echo "No conflict markers found"
- name: Setup moko-platform tools - name: Setup mokocli tools
env: env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}' COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}'
run: | run: |
if [ -f /opt/moko-platform/cli/version_bump.php ] && [ -f /opt/moko-platform/vendor/autoload.php ]; then if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
echo Using pre-installed /opt/moko-platform echo Using pre-installed /opt/mokocli
echo MOKO_CLI=/opt/moko-platform/cli >> $GITHUB_ENV echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV
else else
echo Falling back to fresh clone echo Falling back to fresh clone
if ! command -v composer > /dev/null 2>&1; then if ! command -v composer > /dev/null 2>&1; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1 sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
fi fi
rm -rf /tmp/moko-platform-api rm -rf /tmp/mokocli
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/moko-platform-api git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
cd /tmp/moko-platform-api cd /tmp/mokocli
composer install --no-dev --no-interaction --quiet composer install --no-dev --no-interaction --quiet
echo MOKO_CLI=/tmp/moko-platform-api/cli >> $GITHUB_ENV echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
fi fi
- name: "Determine version bump level" - name: "Determine version bump level"
@@ -225,22 +228,32 @@ jobs:
--path . --stability stable ${BUMP_FLAG} --branch main \ --path . --stability stable ${BUMP_FLAG} --branch main \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --token "${{ secrets.MOKOGITEA_TOKEN }}"
- name: Update release notes from CHANGELOG.md - name: Update release notes and promote changelog
run: | run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
# Get the stable release info (version and ID)
RELEASE_JSON=$(curl -sf -H "Authorization: token ${TOKEN}" \
"${API_BASE}/releases/tags/stable" 2>/dev/null || echo '{}')
RELEASE_ID=$(python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" <<< "$RELEASE_JSON" 2>/dev/null || true)
# Extract version from release name (e.g. "06.17.00" or "v06.17.00")
VERSION=$(python3 -c "
import json, sys, re
r = json.load(sys.stdin)
name = r.get('name', '')
m = re.search(r'(\d+\.\d+\.\d+)', name)
print(m.group(1) if m else '')
" <<< "$RELEASE_JSON" 2>/dev/null || true)
# Extract [Unreleased] section from changelog # Extract [Unreleased] section from changelog
NOTES=""
if [ -f "CHANGELOG.md" ]; then if [ -f "CHANGELOG.md" ]; then
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md) NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
[ -z "$NOTES" ] && NOTES="Stable release"
else
NOTES="Stable release"
fi fi
[ -z "$NOTES" ] && NOTES="Stable release"
# Update release body via API # Update release body via API
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
"${API_BASE}/releases/tags/stable" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
if [ -n "$RELEASE_ID" ]; then if [ -n "$RELEASE_ID" ]; then
python3 -c " python3 -c "
import json, urllib.request import json, urllib.request
@@ -250,7 +263,7 @@ jobs:
'${API_BASE}/releases/${RELEASE_ID}', '${API_BASE}/releases/${RELEASE_ID}',
data=payload, method='PATCH', data=payload, method='PATCH',
headers={ headers={
'Authorization': 'token ${{ secrets.MOKOGITEA_TOKEN }}', 'Authorization': 'token ${TOKEN}',
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}) })
urllib.request.urlopen(req) urllib.request.urlopen(req)
@@ -258,6 +271,24 @@ jobs:
echo "Release notes updated from CHANGELOG.md" echo "Release notes updated from CHANGELOG.md"
fi fi
# Promote [Unreleased] → [version] in CHANGELOG.md and reset
if [ -n "$VERSION" ] && [ -f "CHANGELOG.md" ]; then
DATE=$(date +%Y-%m-%d)
python3 -c "
import sys
version, date = sys.argv[1], sys.argv[2]
content = open('CHANGELOG.md').read()
old = '## [Unreleased]'
new = f'## [Unreleased]\n\n## [{version}] --- {date}'
content = content.replace(old, new, 1)
open('CHANGELOG.md', 'w').write(content)
" "$VERSION" "$DATE"
git add CHANGELOG.md
git commit -m "chore: promote changelog [Unreleased] → [${VERSION}]" || true
git push origin main || true
echo "Changelog promoted: [Unreleased] → [${VERSION}]"
fi
# -- STEP 9: Mirror to GitHub (stable only) -------------------------------- # -- STEP 9: Mirror to GitHub (stable only) --------------------------------
- name: "Step 9: Mirror release to GitHub" - name: "Step 9: Mirror release to GitHub"
if: >- if: >-
+1 -1
View File
@@ -5,7 +5,7 @@
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Universal # INGROUP: MokoStandards.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/branch-cleanup.yml # PATH: /.mokogitea/workflows/branch-cleanup.yml
# VERSION: 01.00.00 # VERSION: 01.00.00
# BRIEF: Delete feature branches after PR merge # BRIEF: Delete feature branches after PR merge
-7
View File
@@ -13,13 +13,6 @@
name: "Generic: Project CI" name: "Generic: Project CI"
on: on:
push:
branches:
- main
- dev
- dev/**
- rc/**
- version/**
pull_request: pull_request:
branches: branches:
- main - main
+1 -1
View File
@@ -5,7 +5,7 @@
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Automation # INGROUP: moko-platform.Automation
# VERSION: 02.34.84 # VERSION: 02.44.04
# BRIEF: Auto-create feature branch when an issue is opened # BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch" name: "Universal: Issue Branch"
+2 -2
View File
@@ -4,8 +4,8 @@
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.CI # INGROUP: mokocli.CI
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform # REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli
# PATH: /templates/workflows/universal/pr-check.yml.template # PATH: /templates/workflows/universal/pr-check.yml.template
# VERSION: 09.23.00 # VERSION: 09.23.00
# BRIEF: PR gate — branch policy + code validation before merge # BRIEF: PR gate — branch policy + code validation before merge
@@ -0,0 +1,71 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Validation
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /templates/workflows/joomla/pr-metadata-check.yml.template
# VERSION: 01.00.00
# BRIEF: Validate MokoGitea metadata matches Joomla extension manifest on PRs
name: "Joomla: Metadata Validation"
on:
pull_request:
types: [opened, synchronize, reopened, converted_to_draft, ready_for_review]
permissions:
contents: read
env:
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
jobs:
validate-metadata:
name: "Validate Joomla Metadata"
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup mokocli tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: |
if [ -f /opt/mokocli/cli/joomla_metadata_validate.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
echo Using pre-installed /opt/mokocli
echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV
else
echo Falling back to fresh clone
if ! command -v composer > /dev/null 2>&1; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
fi
rm -rf /tmp/mokocli
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
fi
- name: Validate metadata against Joomla manifest
env:
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: |
php ${MOKO_CLI}/joomla_metadata_validate.php \
--path . \
--token "${GITEA_TOKEN}" \
--org "${GITEA_ORG}" \
--repo "${GITEA_REPO}" \
--api-base "${GITEA_URL}/api/v1" \
--ci
if [ $? -ne 0 ]; then
echo "::error::Joomla metadata mismatch — update delivery will fail. Run 'php cli/joomla_metadata_validate.php' locally to see details."
exit 1
fi
+36 -27
View File
@@ -4,23 +4,26 @@
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: Gitea.Workflow
# INGROUP: mokoplatform.Release # INGROUP: mokocli.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /templates/workflows/universal/pre-release.yml.template # PATH: /templates/workflows/universal/pre-release.yml.template
# VERSION: 05.01.00 # VERSION: 05.01.00
# BRIEF: Manual pre-release -- builds dev/alpha/beta/rc packages from any branch # BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches
name: "Universal: Pre-Release" name: "Universal: Pre-Release"
on: on:
pull_request: push:
types: [closed]
branches: branches:
- dev - dev
pull_request_target: - 'fix/**'
types: [synchronize, opened, reopened] - 'patch/**'
branches: - 'hotfix/**'
- main - 'bugfix/**'
- 'chore/**'
- alpha
- beta
- rc
workflow_dispatch: workflow_dispatch:
inputs: inputs:
stability: stability:
@@ -43,12 +46,11 @@ env:
jobs: jobs:
build: build:
name: "Build Pre-Release (${{ inputs.stability || 'development' }})" name: "Build Pre-Release (${{ inputs.stability || github.ref_name }})"
runs-on: release runs-on: release
if: >- if: >-
github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' && github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'dev') || github.event_name == 'push'
(github.event_name == 'pull_request_target' && github.event.pull_request.base.ref == 'main')
steps: steps:
- name: Checkout - name: Checkout
@@ -56,40 +58,47 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
token: ${{ secrets.MOKOGITEA_TOKEN }} token: ${{ secrets.MOKOGITEA_TOKEN }}
ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || '' }} ref: ${{ github.ref_name }}
- name: Setup mokoplatform tools - name: Setup mokocli tools
env: env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: | run: |
# Use pre-installed /opt/mokoplatform if available (updated by cron every 6h) # Use pre-installed /opt/mokocli if available (updated by cron every 6h)
if [ -f /opt/mokoplatform/cli/version_bump.php ] && [ -f /opt/mokoplatform/cli/manifest_element.php ] && [ -f /opt/mokoplatform/vendor/autoload.php ]; then if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/cli/manifest_element.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
echo Using pre-installed /opt/mokoplatform echo Using pre-installed /opt/mokocli
echo MOKO_CLI=/opt/mokoplatform/cli >> $GITHUB_ENV echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV
else else
echo Falling back to fresh clone echo Falling back to fresh clone
if ! command -v composer > /dev/null 2>&1; then if ! command -v composer > /dev/null 2>&1; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1 sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
fi fi
rm -rf /tmp/mokoplatform-api rm -rf /tmp/mokocli
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokoplatform.git CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokoplatform-api git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
cd /tmp/mokoplatform-api && composer install --no-dev --no-interaction --quiet cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
echo MOKO_CLI=/tmp/mokoplatform-api/cli >> $GITHUB_ENV echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
fi fi
- name: Detect platform - name: Detect platform
id: platform id: platform
run: | run: |
# Auto-detect and update platform if not set in manifest
php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true
php ${MOKO_CLI}/manifest_read.php --path . --github-output php ${MOKO_CLI}/manifest_read.php --path . --github-output
- name: Resolve metadata and bump version - name: Resolve metadata and bump version
id: meta id: meta
run: | run: |
# Auto-detect stability: RC for PRs targeting main, else use input or default to development # Auto-detect stability from branch name on push, or use input on dispatch
if [ "${{ github.event_name }}" = "pull_request_target" ] && [ "${{ github.event.pull_request.base.ref }}" = "main" ]; then if [ "${{ github.event_name }}" = "push" ]; then
STABILITY="release-candidate" case "${{ github.ref_name }}" in
rc) STABILITY="release-candidate" ;;
alpha) STABILITY="alpha" ;;
beta) STABILITY="beta" ;;
*) STABILITY="development" ;;
esac
else else
STABILITY="${{ inputs.stability || 'development' }}" STABILITY="${{ inputs.stability || 'development' }}"
fi fi
@@ -164,7 +173,7 @@ jobs:
php ${MOKO_CLI}/release_create.php \ php ${MOKO_CLI}/release_create.php \
--path . --version "$VERSION" --tag "$TAG" \ --path . --version "$VERSION" --tag "$TAG" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \ --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --branch dev --prerelease --repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease
- name: Update release notes from CHANGELOG.md - name: Update release notes from CHANGELOG.md
run: | run: |
+4 -3
View File
@@ -7,8 +7,8 @@
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Validation # INGROUP: mokocli.Validation
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform # REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli
# PATH: /templates/workflows/joomla/repo_health.yml.template # PATH: /templates/workflows/joomla/repo_health.yml.template
# VERSION: 09.23.00 # VERSION: 09.23.00
# BRIEF: Enforces repository guardrails by validating scripts governance, tooling availability, and core repository health artifacts. # BRIEF: Enforces repository guardrails by validating scripts governance, tooling availability, and core repository health artifacts.
@@ -33,7 +33,8 @@ on:
- scripts - scripts
- repo - repo
pull_request: pull_request:
push: branches:
- main
permissions: permissions:
contents: read contents: read
@@ -0,0 +1,73 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoPlatform.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
# PATH: /.mokogitea/workflows/workflow-sync-trigger.yml
# VERSION: 01.01.00
# BRIEF: Trigger workflow sync to live repos when a PR is merged to main
name: "Universal: Workflow Sync Trigger"
on:
pull_request:
types: [closed]
branches:
- main
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
sync:
name: Sync workflows to live repos
runs-on: ubuntu-latest
if: >-
github.event.pull_request.merged == true &&
!contains(github.event.pull_request.title, '[skip sync]')
steps:
- name: Determine platform from repo name
id: platform
run: |
REPO="${{ github.event.repository.name }}"
case "$REPO" in
Template-Joomla) PLATFORM="joomla" ;;
Template-Dolibarr) PLATFORM="dolibarr" ;;
Template-Go) PLATFORM="go" ;;
Template-MCP) PLATFORM="mcp" ;;
Template-Generic) PLATFORM="" ;;
*) PLATFORM="" ;;
esac
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
echo "Platform: ${PLATFORM:-all}"
- name: Clone mokoplatform
env:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: |
GITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
git clone --depth 1 "${GITEA_URL}/MokoConsulting/mokoplatform.git" /tmp/mokoplatform
- name: Install dependencies
run: |
cd /tmp/mokoplatform
composer install --no-dev --no-interaction --quiet 2>/dev/null || true
- name: Run workflow sync
env:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: |
ARGS="--token ${MOKOGITEA_TOKEN}"
ARGS="${ARGS} --org ${{ vars.GITEA_ORG || github.repository_owner }}"
ARGS="${ARGS} --phase repos"
PLATFORM="${{ steps.platform.outputs.platform }}"
if [ -n "$PLATFORM" ]; then
ARGS="${ARGS} --platform-filter ${PLATFORM}"
fi
php /tmp/mokoplatform/cli/workflow_sync.php ${ARGS}
+7 -182
View File
@@ -14,196 +14,21 @@
INGROUP: MokoSuiteClient.Documentation INGROUP: MokoSuiteClient.Documentation
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
PATH: ./CHANGELOG.md PATH: ./CHANGELOG.md
VERSION: 02.34.84 VERSION: 02.44.04
BRIEF: Version history using `Keep a Changelog` BRIEF: Version history using `Keep a Changelog`
--> -->
# Changelog # Changelog
## [Unreleased] ## [Unreleased]
### Changed ## [02.44.00] --- 2026-06-20
- **Full rename: MokoSuite → MokoSuiteClient** — repo, all Joomla element names (com_mokosuiteclient, plg_system_mokosuiteclient, mod_mokosuiteclient_*, etc.), PHP classes, language files, folder structure, and manifest references. This is the client tracker for the MokoSuite platform.
### Added ## [02.44.00] --- 2026-06-20
- RSA-signed heartbeat authentication — private key in monitor plugin manifest, public key on MokoSuiteClientHQ
- Monitor plugin base_url set via manifest (hidden from admin UI), propagated via update server
- Send Heartbeat button on health token field for manual heartbeat testing
- Font Awesome 7 loaded in admin backend — picks up MokoOnyx Kit code if present, falls back to bundled FA7 Free or FA6 CDN
- MokoWaaS → MokoSuiteClient database table migration in install script (create new, copy data, drop old)
- MokoWaaS → MokoSuiteClient extension param migration — copies params from all old mokowaas plugins/modules/component, then removes old entries and filesystem remnants
- Ticket contact linking — optional FK to Joomla contact records with display in list and detail views
- Multi-assignee tickets — junction table supports multiple users and user groups per ticket
- Customizable ticket statuses — admin-configurable lookup table replaces hardcoded ENUM (title, color, is_closed flag)
- Customizable ticket priorities — admin-configurable lookup table with weight and color
- Joomla custom fields integration for tickets (context: com_mokosuiteclient.ticket) with field groups assignable per category
- MokoWaaS/MokoWaaSHQ migration bridge repos with updates.xml redirecting existing installs to MokoSuiteClient/HQ
- Pre-release workflow triggers on push to dev/alpha/beta/rc branches (deployed to all 11 repos)
### Removed ## [02.43.00] --- 2026-06-20
- PerfectPublisher webservices plugin (no longer needed)
### Fixed ## [02.43.00] --- 2026-06-20
- Download key lost on update: cleanupStaleUpdateSites used old /raw/branch/main/ URL format, deleting the manifest-registered update site that held the key
## [02.35.00] - 2026-06-06 ## [02.42.00] --- 2026-06-20
### Added ## [02.42.00] --- 2026-06-20
- Core plugin stripped to heartbeat-only config (~5,500 lines removed)
- Extension catalog (catalog.xml) with update server discovery (#186)
- Download key preservation across Joomla updates (#187)
- Remote login endpoint for MokoSuiteClientHQ auto-login
- Provision reset API for new client setup (hits, versions, tokens)
- Setup required banner after provision reset
- Support verification PIN (MOKO-XXXX-XXXX)
- mod_mokosuiteclient_categories — auto-category tree menu (#184)
- Cache/temp split button in status bar
- Dashboard version tiles for component and modules
- Monitor plugin sends full health payload to MokoSuiteClientHQ
- Firewall: block_frontend_superuser, own trusted_ip_entry.xml
- DevTools: reset download keys toggle
### Changed
- Renamed src/ to source/ (#188)
- Service classes relocated to owning plugins
- API controller execute() signatures fixed (#183)
- Joomla 5/6 event compatibility in DevTools and Monitor
- Dead placeholder resolver removed from install script
### Fixed
- Firewall subform paths after core cleanup
- Missing Security Headers language strings
## [02.34.00] - 2026-06-04
### Added
- Database Tools view — table status, optimize, repair, session purge (#127)
- Cache Cleanup view — directory size reporting and one-click cleanup (#128)
- mod_mokosuiteclient_cache — one-click cache cleaner button in admin status bar (replaces Regular Labs Cache Cleaner)
- mod_mokosuiteclient_menu — collapsible admin sidebar menu using native MetisMenu classes (like Community Builder)
- SSL certificate expiry monitoring in cpanel module (#148)
- MokoSuiteClient-specific update badge (blue) separate from other updates in cpanel module
- migrateUpdateServerUrls() — rewrites all Moko extension update server URLs to clean /updates.xml on install/update
- fixMenuIcons() — sets menu_icon params on submenu items (Joomla only renders img on level 1)
- setupCacheModule() — registers cache cleaner module in status bar position on install
- Component config.xml for Joomla Options modal (#149)
- preflight() ALTER for #__extensions.element default (MySQL strict mode fix)
- Retire MokoJoomTOS, MokoATS-Automation, MokoDPCalendarAPI, MokoGalleryCalendar on install
- MokoJoomTOS settings auto-migrate to mokosuiteclient_offline before removal
- dev-release and pre-release workflows with changelog extraction into release notes
- RC pre-release consolidates dev patches into clean minor version bump
### Changed
- Move security hardening methods (protectPlugin, ensureProtectedFlag, isOurExtension) from core plugin to firewall plugin (#155)
- Admin menu module uses native Joomla MetisMenu CSS classes
- Helpdesk icon changed to fa-handshake-angle, .htaccess to fa-solid fa-file-code
- clearCache purges all cache files recursively (replaces Regular Labs Cache Cleaner behavior)
- License key warning moved from every-page onAfterRoute to package postflight only
- Update server URL changed to dynamic MokoGitea feed
- Component manifest adds `<languages>` for global language dir deployment
- Privacy and WAF Log added to component manifest submenu
- MokoOnyx template removed from package manifest (separate repo/release)
### Removed
- Static updates.xml — MokoGitea generates update feed dynamically from releases
- update-server.yml workflow — replaced by pre-release.yml
### Fixed
- Tickets list showing raw `<em>Unassigned</em>` HTML instead of italic text
- Cache cleaner CSRF failure — token now sent as POST FormData
- Admin menu icons missing for Helpdesk and .htaccess Maker
- Firewall install error "Field 'element' doesn't have a default value" (MySQL strict mode)
## [02.32] - 2026-06-02
### Added
- Admin control panel dashboard in com_mokosuiteclient with site info bar, feature plugin grid, and quick actions
- Feature plugin architecture — MokoSuiteClient features split into toggleable plugins managed from the dashboard
- plg_system_mokosuiteclient_firewall — HTTPS enforcement, trusted IPs, session timeout, upload restrictions, password policy
- plg_system_mokosuiteclient_tenant — Installer, sysinfo, config, template, and menu restrictions for non-master users
- plg_system_mokosuiteclient_devtools — Dev mode, hit counter reset, content version cleanup
- plg_system_mokosuiteclient_monitor — Grafana heartbeat integration and health monitoring
- MokoSuiteClientHelper utility class for shared master-user detection across feature plugins
- AJAX plugin toggle — enable/disable feature plugins directly from the dashboard
- Clear cache quick action on dashboard
- Static updates.xml for update server (licensing system deferred)
- Automatic param migration from core plugin to feature plugins on upgrade
### Changed
- com_mokosuiteclient upgraded from API-only to full admin component with dashboard views
- Package manifest updated with 4 new feature plugin entries (10 extensions total)
- Update server URL changed to static raw file endpoint
- Core plugin slimmed — security, tenant, devtools, and monitor features extracted to dedicated plugins
### Removed
- License key validation (licensing system not ready — will return in future release)
- Dynamic MokoGitea update feed dependency (replaced with static updates.xml)
## [02.31] - 2026-06-01
### Added
- License key support via Joomla's native Update Sites download key system (dlid)
- Update server URL migrated from static XML to MokoGitea's dynamic update feed endpoint
- Legacy static update site URLs auto-migrated to dynamic endpoint on install/update
- Persistent admin warning when no license key is configured in Update Sites
- Daily heartbeat validation of license key against MokoGitea — warns if key is invalid or expired
- Stale/duplicate update site cleanup on install/update (removes old static URL entries and orphaned records)
- Content sync rewritten — bulk MokoSuiteClient API endpoints (syncclear + syncpush) replace per-item Joomla API calls
- Sync task per-instance config: target URL, health token, content type checkboxes (articles, categories, menus, modules)
- Bulk sync completes in under 5 seconds (clear + push in 2-3 HTTP requests)
- Asset table and nested set tree repair after sync push on target site
- Enhanced dev mode: disables caching, enables Joomla + MokoOnyx debug, suppresses hit recording, shows offline on primary domain
- Dev mode off: clears content versions, resets hits, disables debug, takes site online
- Hardcoded dev alias (dev.{primary_domain}) with noindex/nofollow — bypasses offline mode for development
- Primary domain auto-detected on first config save
### Changed
- Branding, master user, support URL, and admin colors are now hardcoded (no longer configurable)
- Master user enforcement is always active (toggle removed)
- Diagnostics + maintenance merged into default config tab
- Emergency access moved to Security tab
- Content sync configuration moved from system plugin to individual scheduled task instances
### Removed
- Static `updates.xml` — update feed is now generated dynamically by MokoGitea from git releases
- Basic branding config tab (brand name, company name, support URL)
- Visual branding config tab (colors, icon, custom CSS)
- Suite Access config tab (master user toggle, master email)
- Content Sync config tab (targets now in scheduled tasks)
- Site Aliases config tab (hardcoded to dev.{primary_domain})
- File sync (images/, files/, media/) — sync is API/DB content only
## [02.29] - 2026-05-31
### Added
- `allow_extension_updates` param — separate update rights from installer restrictions; tenants can update extensions by default even when the installer is restricted
- Hardcoded master usernames — multiple privileged users supported with identical access
### Fixed
- Emergency access IP whitelist: empty `allowed_ips` now permits all IPs (was blocking everyone)
- Emergency access reads `allowed_ips` from plugin params instead of global config
- `plg_task_mokosuiteclientsync` — Joomla Scheduled Task plugin for automatic content sync to remote sites
- Community Builder tables added to demo reset safe table list
- API endpoint `POST /api/index.php/v1/mokosuiteclient/install` — install extensions from a remote ZIP URL
- Demo Mode with configurable warning banner on frontend when enabled
- Demo banner countdown now shows weeks/days/months for longer intervals instead of raw hours
- `DemoResetService` — baseline snapshot and restore for DB tables + media files
- API endpoints `POST /?mokosuiteclient=reset` and `POST /?mokosuiteclient=snapshot` (query-string)
- REST endpoints `POST /api/v1/mokosuiteclient/reset` and `GET/POST /api/v1/mokosuiteclient/snapshot`
- `plg_task_mokosuiteclientdemo` — Joomla Scheduled Task plugin for automatic demo site reset
- Admin toggles: Take Snapshot Now and Restore Baseline Now in plugin config
- Content Sync: one-way push of articles, categories, menus, and modules to remote MokoSuiteClient sites
- Content Sync: API endpoints `POST /?mokosuiteclient=sync` (sender) and `POST /?mokosuiteclient=sync-receive` (receiver)
- Content Sync: REST endpoints `POST /api/v1/mokosuiteclient/sync` and `POST /api/v1/mokosuiteclient/sync-receive`
- Content Sync: configurable sync targets with URL + API token in plugin settings
- Package installer: protect all MokoSuiteClient extensions (not just system plugin) and ensure update server stays enabled
- Package installer: clean up legacy `mokosuiteclientbrand` extension entries and files on install/update
- API endpoint `GET /?mokosuiteclient=extensions` and `GET /api/v1/mokosuiteclient/extensions` — list installed extensions with version, status, and update server info
## [02.20] --- 2026-05-28
+1 -1
View File
@@ -14,7 +14,7 @@
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Documentation INGROUP: MokoSuiteClient.Documentation
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.34.84 VERSION: 02.44.04
PATH: ./CODE_OF_CONDUCT.md PATH: ./CODE_OF_CONDUCT.md
BRIEF: Reference + packaging repo for Moko Consulting Developer GPT Other Default BRIEF: Reference + packaging repo for Moko Consulting Developer GPT Other Default
--> -->
+1 -1
View File
@@ -19,7 +19,7 @@
DEFGROUP: mokoconsulting-tech.MokoSuiteClientBrand DEFGROUP: mokoconsulting-tech.MokoSuiteClientBrand
INGROUP: MokoStandards.Governance INGROUP: MokoStandards.Governance
REPO: https://github.com/mokoconsulting-tech/MokoSuiteClientBrand REPO: https://github.com/mokoconsulting-tech/MokoSuiteClientBrand
VERSION: 02.34.84 VERSION: 02.44.04
PATH: /GOVERNANCE.md PATH: /GOVERNANCE.md
BRIEF: Project governance rules, roles, and decision process for MokoSuiteClientBrand BRIEF: Project governance rules, roles, and decision process for MokoSuiteClientBrand
--> -->
+1 -1
View File
@@ -15,7 +15,7 @@
INGROUP: MokoSuiteClient.Documentation INGROUP: MokoSuiteClient.Documentation
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
PATH: ./LICENSE.md PATH: ./LICENSE.md
VERSION: 02.34.84 VERSION: 02.44.04
BRIEF: Project license (GPL-3.0-or-later) BRIEF: Project license (GPL-3.0-or-later)
--> -->
GNU GENERAL PUBLIC LICENSE GNU GENERAL PUBLIC LICENSE
+3 -3
View File
@@ -12,10 +12,10 @@
# ============================================================================== # ==============================================================================
# Extension Configuration # Extension Configuration
EXTENSION_NAME := mokoexample EXTENSION_NAME := mokosuiteclient
EXTENSION_TYPE := module EXTENSION_TYPE := package
# Options: module, plugin, component, package, template # Options: module, plugin, component, package, template
EXTENSION_VERSION := 1.0.0 EXTENSION_VERSION := 02.35.00
# Module Configuration (for modules only) # Module Configuration (for modules only)
MODULE_TYPE := site MODULE_TYPE := site
+1 -1
View File
@@ -9,7 +9,7 @@
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient INGROUP: MokoSuiteClient
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient
VERSION: 02.34.84 VERSION: 02.44.04
PATH: /README.md PATH: /README.md
BRIEF: MokoSuiteClient platform plugin for Joomla BRIEF: MokoSuiteClient platform plugin for Joomla
--> -->
+1 -1
View File
@@ -23,7 +23,7 @@ DEFGROUP: [PROJECT_NAME]
INGROUP: [PROJECT_NAME].Documentation INGROUP: [PROJECT_NAME].Documentation
REPO: [REPOSITORY_URL] REPO: [REPOSITORY_URL]
PATH: /SECURITY.md PATH: /SECURITY.md
VERSION: 02.34.84 VERSION: 02.44.04
BRIEF: Security vulnerability reporting and handling policy BRIEF: Security vulnerability reporting and handling policy
--> -->
+2 -2
View File
@@ -11,13 +11,13 @@
INGROUP: MokoSuiteClient.Build INGROUP: MokoSuiteClient.Build
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
FILE: build-guide.md FILE: build-guide.md
VERSION: 02.34.84 VERSION: 02.44.04
PATH: /docs/guides/ PATH: /docs/guides/
BRIEF: Build and packaging guide for the MokoSuiteClient system plugin BRIEF: Build and packaging guide for the MokoSuiteClient system plugin
NOTE: Defines environment setup, repository layout, packaging rules, and release preparation NOTE: Defines environment setup, repository layout, packaging rules, and release preparation
--> -->
# MokoSuiteClient Build Guide (VERSION: 02.34.84) # MokoSuiteClient Build Guide (VERSION: 02.44.04)
## 1. Purpose ## 1. Purpose
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Guides INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.34.84 VERSION: 02.44.04
PATH: /docs/guides/configuration-guide.md PATH: /docs/guides/configuration-guide.md
BRIEF: Configuration guide for the MokoSuiteClient system plugin BRIEF: Configuration guide for the MokoSuiteClient system plugin
NOTE: Defines plugin parameters, expected behaviors, and recommended defaults NOTE: Defines plugin parameters, expected behaviors, and recommended defaults
--> -->
# MokoSuiteClient Configuration Guide (VERSION: 02.34.84) # MokoSuiteClient Configuration Guide (VERSION: 02.44.04)
## 1. Objective ## 1. Objective
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Guides INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.34.84 VERSION: 02.44.04
PATH: /docs/guides/installation-guide.md PATH: /docs/guides/installation-guide.md
BRIEF: Installation guide for the MokoSuiteClient system plugin BRIEF: Installation guide for the MokoSuiteClient system plugin
NOTE: First document in the guide set NOTE: First document in the guide set
--> -->
# MokoSuiteClient Installation Guide (VERSION: 02.34.84) # MokoSuiteClient Installation Guide (VERSION: 02.44.04)
## Introduction ## Introduction
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Guides INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.34.84 VERSION: 02.44.04
PATH: /docs/guides/operations-guide.md PATH: /docs/guides/operations-guide.md
BRIEF: Operational guide for administering and managing the MokoSuiteClient system plugin BRIEF: Operational guide for administering and managing the MokoSuiteClient system plugin
NOTE: Defines lifecycle, responsibilities, and operational behaviors NOTE: Defines lifecycle, responsibilities, and operational behaviors
--> -->
# MokoSuiteClient Operations Guide (VERSION: 02.34.84) # MokoSuiteClient Operations Guide (VERSION: 02.44.04)
## Introduction ## Introduction
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Guides INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.34.84 VERSION: 02.44.04
PATH: /docs/guides/rollback-and-recovery-guide.md PATH: /docs/guides/rollback-and-recovery-guide.md
BRIEF: Rollback and recovery guide for restoring stable operation after plugin related incidents BRIEF: Rollback and recovery guide for restoring stable operation after plugin related incidents
NOTE: Completes the core guide set for Suite plugin governance NOTE: Completes the core guide set for Suite plugin governance
--> -->
# MokoSuiteClient Rollback and Recovery Guide (VERSION: 02.34.84) # MokoSuiteClient Rollback and Recovery Guide (VERSION: 02.44.04)
## Introduction ## Introduction
+2 -2
View File
@@ -7,13 +7,13 @@
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Guides INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.34.84 VERSION: 02.44.04
PATH: /docs/guides/testing-guide.md PATH: /docs/guides/testing-guide.md
BRIEF: Testing guide for MokoSuiteClient v02.01.08 BRIEF: Testing guide for MokoSuiteClient v02.01.08
NOTE: Covers manual test procedures for language overrides, install/uninstall, and configuration NOTE: Covers manual test procedures for language overrides, install/uninstall, and configuration
--> -->
# MokoSuiteClient Testing Guide (VERSION: 02.34.84) # MokoSuiteClient Testing Guide (VERSION: 02.44.04)
## 1. Prerequisites ## 1. Prerequisites
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Guides INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.34.84 VERSION: 02.44.04
PATH: /docs/guides/troubleshooting-guide.md PATH: /docs/guides/troubleshooting-guide.md
BRIEF: Troubleshooting guide for diagnosing and resolving issues related to the MokoSuiteClient plugin BRIEF: Troubleshooting guide for diagnosing and resolving issues related to the MokoSuiteClient plugin
NOTE: Designed for administrators and Suite operations teams NOTE: Designed for administrators and Suite operations teams
--> -->
# MokoSuiteClient Troubleshooting Guide (VERSION: 02.34.84) # MokoSuiteClient Troubleshooting Guide (VERSION: 02.44.04)
## Introduction ## Introduction
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Guides INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.34.84 VERSION: 02.44.04
PATH: /docs/guides/upgrade-and-versioning-guide.md PATH: /docs/guides/upgrade-and-versioning-guide.md
BRIEF: Guide for updating, versioning, and maintaining the MokoSuiteClient plugin BRIEF: Guide for updating, versioning, and maintaining the MokoSuiteClient plugin
NOTE: Defines release flow, version rules, and upgrade validation NOTE: Defines release flow, version rules, and upgrade validation
--> -->
# MokoSuiteClient Upgrade and Versioning Guide (VERSION: 02.34.84) # MokoSuiteClient Upgrade and Versioning Guide (VERSION: 02.44.04)
## Introduction ## Introduction
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Documentation INGROUP: MokoSuiteClient.Documentation
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.34.84 VERSION: 02.44.04
PATH: /docs/index.md PATH: /docs/index.md
BRIEF: Master index of all documentation for the MokoSuiteClient plugin BRIEF: Master index of all documentation for the MokoSuiteClient plugin
NOTE: Automatically maintained index for all guide canvases NOTE: Automatically maintained index for all guide canvases
--> -->
# MokoSuiteClient Documentation Index (VERSION: 02.34.84) # MokoSuiteClient Documentation Index (VERSION: 02.44.04)
## Introduction ## Introduction
+2 -2
View File
@@ -11,12 +11,12 @@
INGROUP: MokoSuiteClient INGROUP: MokoSuiteClient
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
PATH: /docs/plugin-basic.md PATH: /docs/plugin-basic.md
VERSION: 02.34.84 VERSION: 02.44.04
BRIEF: Baseline documentation for the MokoSuiteClient system plugin BRIEF: Baseline documentation for the MokoSuiteClient system plugin
NOTE: Foundational reference for internal and external stakeholders NOTE: Foundational reference for internal and external stakeholders
--> -->
# MokoSuiteClient Plugin Overview (VERSION: 02.34.84) # MokoSuiteClient Plugin Overview (VERSION: 02.44.04)
## Introduction ## Introduction
+1 -1
View File
@@ -10,7 +10,7 @@ DEFGROUP: MokoSuiteClient.Documentation
INGROUP: MokoStandards.Templates INGROUP: MokoStandards.Templates
REPO: https://github.com/mokoconsulting-tech/MokoSuiteClient REPO: https://github.com/mokoconsulting-tech/MokoSuiteClient
PATH: /docs/update-server.md PATH: /docs/update-server.md
VERSION: 02.34.84 VERSION: 02.44.04
BRIEF: How this extension's Joomla update server file (update.xml) is managed BRIEF: How this extension's Joomla update server file (update.xml) is managed
--> -->
@@ -90,22 +90,22 @@ class DisplayController extends BaseController
try try
{ {
$monitorPlugin = \Joomla\CMS\Plugin\PluginHelper::getPlugin('system', 'mokosuiteclient_monitor'); $corePlugin = \Joomla\CMS\Plugin\PluginHelper::getPlugin('system', 'mokosuiteclient');
if (!$monitorPlugin) if (!$corePlugin)
{ {
$this->jsonResponse(['success' => false, 'message' => 'Monitor plugin not enabled.']); $this->jsonResponse(['success' => false, 'message' => 'Core plugin not enabled.']);
return; return;
} }
$params = new \Joomla\Registry\Registry($monitorPlugin->params); $params = new \Joomla\Registry\Registry($corePlugin->params);
$baseUrl = rtrim($params->get('base_url', ''), '/'); $baseUrl = rtrim($params->get('monitor_base_url', ''), '/');
// Fall back to manifest XML default if not yet saved in params // Fall back to manifest XML default
if (empty($baseUrl)) if (empty($baseUrl))
{ {
$manifestFile = JPATH_PLUGINS . '/system/mokosuiteclient_monitor/mokosuiteclient_monitor.xml'; $manifestFile = JPATH_PLUGINS . '/system/mokosuiteclient/mokosuiteclient.xml';
if (is_file($manifestFile)) if (is_file($manifestFile))
{ {
@@ -113,7 +113,7 @@ class DisplayController extends BaseController
if ($xml) if ($xml)
{ {
foreach ($xml->xpath('//field[@name="base_url"]') as $field) foreach ($xml->xpath('//field[@name="monitor_base_url"]') as $field)
{ {
$baseUrl = rtrim((string) $field['default'], '/'); $baseUrl = rtrim((string) $field['default'], '/');
break; break;
@@ -124,14 +124,12 @@ class DisplayController extends BaseController
if (empty($baseUrl)) if (empty($baseUrl))
{ {
$this->jsonResponse(['success' => false, 'message' => 'MokoSuiteClientHQ URL not configured in monitor plugin.']); $this->jsonResponse(['success' => false, 'message' => 'MokoSuiteClientHQ URL not configured.']);
return; return;
} }
$corePlugin = \Joomla\CMS\Plugin\PluginHelper::getPlugin('system', 'mokosuiteclient'); $healthToken = $params->get('health_api_token', '');
$coreParams = new \Joomla\Registry\Registry($corePlugin ? $corePlugin->params : '{}');
$healthToken = $coreParams->get('health_api_token', '');
if (empty($healthToken)) if (empty($healthToken))
{ {
@@ -156,12 +154,12 @@ class DisplayController extends BaseController
// RSA sign the request // RSA sign the request
$headers = ['Content-Type: application/json']; $headers = ['Content-Type: application/json'];
$signingKeyB64 = $params->get('signing_key', ''); $signingKeyB64 = $params->get('monitor_signing_key', '');
// Fall back to manifest XML default if not yet saved in params // Fall back to manifest XML default
if (empty($signingKeyB64)) if (empty($signingKeyB64))
{ {
$manifestFile = JPATH_PLUGINS . '/system/mokosuiteclient_monitor/mokosuiteclient_monitor.xml'; $manifestFile = JPATH_PLUGINS . '/system/mokosuiteclient/mokosuiteclient.xml';
if (is_file($manifestFile)) if (is_file($manifestFile))
{ {
@@ -169,7 +167,7 @@ class DisplayController extends BaseController
if ($xml) if ($xml)
{ {
foreach ($xml->xpath('//field[@name="signing_key"]') as $field) foreach ($xml->xpath('//field[@name="monitor_signing_key"]') as $field)
{ {
$signingKeyB64 = (string) $field['default']; $signingKeyB64 = (string) $field['default'];
break; break;
@@ -190,13 +188,13 @@ class DisplayController extends BaseController
if (openssl_sign($message, $signature, $privateKey, OPENSSL_ALGO_SHA256)) if (openssl_sign($message, $signature, $privateKey, OPENSSL_ALGO_SHA256))
{ {
$headers[] = 'X-MokoSuiteClient-Signature: ' . base64_encode($signature); $headers[] = 'X-MokoSuite-Signature: ' . base64_encode($signature);
$headers[] = 'X-MokoSuiteClient-Timestamp: ' . $timestamp; $headers[] = 'X-MokoSuite-Timestamp: ' . $timestamp;
} }
} }
} }
$endpoint = $baseUrl . '/api/index.php/v1/mokosuiteclienthq/heartbeat'; $endpoint = $baseUrl . '/api/index.php/v1/mokosuitehq/heartbeat';
$ch = curl_init($endpoint); $ch = curl_init($endpoint);
curl_setopt_array($ch, [ curl_setopt_array($ch, [
@@ -500,6 +498,7 @@ class DisplayController extends BaseController
if (strlen($query) < 3) if (strlen($query) < 3)
{ {
$this->jsonResponse(['results' => []]); $this->jsonResponse(['results' => []]);
return;
} }
try try
@@ -527,7 +526,8 @@ class DisplayController extends BaseController
} }
catch (\Throwable $e) catch (\Throwable $e)
{ {
$this->jsonResponse(['results' => []]); Log::add('KB search failed: ' . $e->getMessage(), Log::ERROR, 'mokosuiteclient');
$this->jsonResponse(['results' => [], 'error' => 'Search unavailable']);
} }
} }
@@ -575,7 +575,7 @@ class DisplayController extends BaseController
public function saveCategory() public function saveCategory()
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); } if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); return; }
$input = Factory::getApplication()->getInput(); $input = Factory::getApplication()->getInput();
$db = Factory::getDbo(); $db = Factory::getDbo();
$id = $input->getInt('id', 0); $id = $input->getInt('id', 0);
@@ -600,7 +600,7 @@ class DisplayController extends BaseController
public function deleteCategory() public function deleteCategory()
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); } if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); return; }
$db = Factory::getDbo(); $db = Factory::getDbo();
$db->setQuery($db->getQuery(true)->delete('#__mokosuiteclient_ticket_categories')->where('id = ' . Factory::getApplication()->getInput()->getInt('id', 0)))->execute(); $db->setQuery($db->getQuery(true)->delete('#__mokosuiteclient_ticket_categories')->where('id = ' . Factory::getApplication()->getInput()->getInt('id', 0)))->execute();
$this->jsonResponse(['success' => true, 'message' => 'Category deleted.']); $this->jsonResponse(['success' => true, 'message' => 'Category deleted.']);
@@ -609,7 +609,7 @@ class DisplayController extends BaseController
public function reorderCategory() public function reorderCategory()
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); } if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); return; }
$order = json_decode(Factory::getApplication()->getInput()->getRaw('order', '[]'), true); $order = json_decode(Factory::getApplication()->getInput()->getRaw('order', '[]'), true);
if (!is_array($order)) { $this->jsonResponse(['success' => false, 'message' => 'Invalid order']); return; } if (!is_array($order)) { $this->jsonResponse(['success' => false, 'message' => 'Invalid order']); return; }
$db = Factory::getDbo(); $db = Factory::getDbo();
@@ -622,7 +622,7 @@ class DisplayController extends BaseController
public function saveCanned() public function saveCanned()
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); } if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); return; }
$input = Factory::getApplication()->getInput(); $input = Factory::getApplication()->getInput();
$db = Factory::getDbo(); $db = Factory::getDbo();
$data = (object) [ $data = (object) [
@@ -640,7 +640,7 @@ class DisplayController extends BaseController
public function deleteCanned() public function deleteCanned()
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); } if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); return; }
$db = Factory::getDbo(); $db = Factory::getDbo();
$db->setQuery($db->getQuery(true)->delete('#__mokosuiteclient_ticket_canned')->where('id = ' . Factory::getApplication()->getInput()->getInt('id', 0)))->execute(); $db->setQuery($db->getQuery(true)->delete('#__mokosuiteclient_ticket_canned')->where('id = ' . Factory::getApplication()->getInput()->getInt('id', 0)))->execute();
$this->jsonResponse(['success' => true, 'message' => 'Canned response deleted.']); $this->jsonResponse(['success' => true, 'message' => 'Canned response deleted.']);
@@ -649,7 +649,7 @@ class DisplayController extends BaseController
public function reorderCanned() public function reorderCanned()
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); } if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); return; }
$order = json_decode(Factory::getApplication()->getInput()->getRaw('order', '[]'), true); $order = json_decode(Factory::getApplication()->getInput()->getRaw('order', '[]'), true);
if (!is_array($order)) { $this->jsonResponse(['success' => false, 'message' => 'Invalid order']); return; } if (!is_array($order)) { $this->jsonResponse(['success' => false, 'message' => 'Invalid order']); return; }
$db = Factory::getDbo(); $db = Factory::getDbo();
@@ -662,7 +662,7 @@ class DisplayController extends BaseController
public function uploadAttachment() public function uploadAttachment()
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); } if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); return; }
$input = Factory::getApplication()->getInput(); $input = Factory::getApplication()->getInput();
$ticketId = $input->getInt('ticket_id', 0); $ticketId = $input->getInt('ticket_id', 0);
$replyId = $input->getInt('reply_id', 0) ?: null; $replyId = $input->getInt('reply_id', 0) ?: null;
@@ -675,7 +675,7 @@ class DisplayController extends BaseController
public function downloadAttachment() public function downloadAttachment()
{ {
if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); } if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); return; }
$id = Factory::getApplication()->getInput()->getInt('id', 0); $id = Factory::getApplication()->getInput()->getInt('id', 0);
$db = Factory::getDbo(); $db = Factory::getDbo();
$db->setQuery($db->getQuery(true)->select('*')->from('#__mokosuiteclient_ticket_attachments')->where('id = ' . $id)); $db->setQuery($db->getQuery(true)->select('*')->from('#__mokosuiteclient_ticket_attachments')->where('id = ' . $id));
@@ -696,7 +696,7 @@ class DisplayController extends BaseController
public function deleteAttachment() public function deleteAttachment()
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); } if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); return; }
$id = Factory::getApplication()->getInput()->getInt('id', 0); $id = Factory::getApplication()->getInput()->getInt('id', 0);
$ok = \Moko\Component\MokoSuiteClient\Administrator\Service\AttachmentService::delete($id); $ok = \Moko\Component\MokoSuiteClient\Administrator\Service\AttachmentService::delete($id);
$this->jsonResponse(['success' => $ok, 'message' => $ok ? 'Attachment deleted' : 'Not found']); $this->jsonResponse(['success' => $ok, 'message' => $ok ? 'Attachment deleted' : 'Not found']);
@@ -705,7 +705,7 @@ class DisplayController extends BaseController
public function rateTicket() public function rateTicket()
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); } if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); return; }
$input = Factory::getApplication()->getInput(); $input = Factory::getApplication()->getInput();
$ticketId = $input->getInt('ticket_id', 0); $ticketId = $input->getInt('ticket_id', 0);
$rating = $input->getInt('rating', 0); $rating = $input->getInt('rating', 0);
@@ -728,7 +728,7 @@ class DisplayController extends BaseController
public function saveAutomation() public function saveAutomation()
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('core.admin')) { $this->jsonForbidden(); } if (!$this->checkAcl('core.admin')) { $this->jsonForbidden(); return; }
$input = Factory::getApplication()->getInput(); $input = Factory::getApplication()->getInput();
$db = Factory::getDbo(); $db = Factory::getDbo();
$data = (object) [ $data = (object) [
@@ -749,7 +749,7 @@ class DisplayController extends BaseController
public function deleteAutomation() public function deleteAutomation()
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('core.admin')) { $this->jsonForbidden(); } if (!$this->checkAcl('core.admin')) { $this->jsonForbidden(); return; }
$db = Factory::getDbo(); $db = Factory::getDbo();
$db->setQuery($db->getQuery(true)->delete('#__mokosuiteclient_ticket_automation')->where('id = ' . Factory::getApplication()->getInput()->getInt('id', 0)))->execute(); $db->setQuery($db->getQuery(true)->delete('#__mokosuiteclient_ticket_automation')->where('id = ' . Factory::getApplication()->getInput()->getInt('id', 0)))->execute();
$this->jsonResponse(['success' => true, 'message' => 'Rule deleted.']); $this->jsonResponse(['success' => true, 'message' => 'Rule deleted.']);
@@ -758,7 +758,7 @@ class DisplayController extends BaseController
public function toggleAutomation() public function toggleAutomation()
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('core.admin')) { $this->jsonForbidden(); } if (!$this->checkAcl('core.admin')) { $this->jsonForbidden(); return; }
$input = Factory::getApplication()->getInput(); $input = Factory::getApplication()->getInput();
$db = Factory::getDbo(); $db = Factory::getDbo();
$db->setQuery($db->getQuery(true)->update('#__mokosuiteclient_ticket_automation') $db->setQuery($db->getQuery(true)->update('#__mokosuiteclient_ticket_automation')
@@ -770,7 +770,7 @@ class DisplayController extends BaseController
public function reorderAutomation() public function reorderAutomation()
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('core.admin')) { $this->jsonForbidden(); } if (!$this->checkAcl('core.admin')) { $this->jsonForbidden(); return; }
$order = json_decode(Factory::getApplication()->getInput()->getRaw('order', '[]'), true); $order = json_decode(Factory::getApplication()->getInput()->getRaw('order', '[]'), true);
if (!is_array($order)) { $this->jsonResponse(['success' => false, 'message' => 'Invalid order']); return; } if (!is_array($order)) { $this->jsonResponse(['success' => false, 'message' => 'Invalid order']); return; }
$db = Factory::getDbo(); $db = Factory::getDbo();
@@ -52,8 +52,9 @@ class AttachmentService
$ticketDir = self::STORAGE_DIR . '/' . $ticketId; $ticketDir = self::STORAGE_DIR . '/' . $ticketId;
if (!is_dir($ticketDir)) { if (!is_dir($ticketDir) && !Folder::create($ticketDir)) {
Folder::create($ticketDir); Log::add("Failed to create attachment directory: {$ticketDir}", Log::ERROR, 'mokosuiteclient');
return [];
} }
$userId = (int) Factory::getUser()->id; $userId = (int) Factory::getUser()->id;
@@ -62,6 +63,7 @@ class AttachmentService
for ($i = 0, $count = count($files['name']); $i < $count; $i++) for ($i = 0, $count = count($files['name']); $i < $count; $i++)
{ {
if ($files['error'][$i] !== UPLOAD_ERR_OK) { if ($files['error'][$i] !== UPLOAD_ERR_OK) {
Log::add("Attachment upload error for '{$files['name'][$i]}': PHP error code {$files['error'][$i]}", Log::WARNING, 'mokosuiteclient');
continue; continue;
} }
@@ -127,9 +129,13 @@ class AttachmentService
/** /**
* Get the absolute filesystem path for an attachment. * Get the absolute filesystem path for an attachment.
*/ */
public static function getAbsolutePath(object $attachment): string public static function getAbsolutePath(object $attachment): ?string
{ {
return self::STORAGE_DIR . '/' . $attachment->filepath; $path = realpath(self::STORAGE_DIR . '/' . $attachment->filepath);
if ($path === false || !str_starts_with($path, realpath(self::STORAGE_DIR))) {
return null;
}
return $path;
} }
/** /**
@@ -137,8 +137,9 @@ class AutomationEngine
break; break;
case 'assign': case 'assign':
if ($ticketId) { $assignId = (int) $value;
$db->setQuery("UPDATE {$db->quoteName('#__mokosuiteclient_tickets')} SET assigned_to = {$db->quote($value)}, modified = {$db->quote(Factory::getDate()->toSql())} WHERE id = {$ticketId}")->execute(); if ($ticketId && $assignId > 0) {
$db->setQuery("UPDATE {$db->quoteName('#__mokosuiteclient_tickets')} SET assigned_to = {$assignId}, modified = {$db->quote(Factory::getDate()->toSql())} WHERE id = {$ticketId}")->execute();
} }
break; break;
@@ -187,7 +188,7 @@ class AutomationEngine
} }
catch (\Throwable $e) catch (\Throwable $e)
{ {
Log::add("Automation action {$type} failed: " . $e->getMessage(), Log::WARNING, 'mokosuiteclient'); Log::add("Automation action '{$type}' failed for rule #{$rule->id}: " . $e->getMessage(), Log::ERROR, 'mokosuiteclient');
} }
} }
} }
@@ -305,6 +305,7 @@ class NotificationService
} }
catch (\Throwable $e) catch (\Throwable $e)
{ {
Log::add('Failed to look up email for user ID ' . $userId . ': ' . $e->getMessage(), Log::WARNING, 'mokosuiteclient');
return null; return null;
} }
} }
@@ -331,6 +332,7 @@ class NotificationService
} }
catch (\Throwable $e) catch (\Throwable $e)
{ {
Log::add('Failed to load notification config: ' . $e->getMessage(), Log::ERROR, 'mokosuiteclient');
return []; return [];
} }
} }
@@ -397,12 +399,16 @@ class NotificationService
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 5); curl_setopt($ch, CURLOPT_TIMEOUT, 5);
curl_exec($ch); $response = curl_exec($ch);
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch); curl_close($ch);
if ($httpCode < 200 || $httpCode >= 300) if ($response === false)
{
Log::add("Ntfy push connection failed for event {$event}: " . $curlError, Log::WARNING, 'mokosuiteclient');
}
elseif ($httpCode < 200 || $httpCode >= 300)
{ {
Log::add("Ntfy push failed (HTTP {$httpCode}) for event {$event}", Log::WARNING, 'mokosuiteclient'); Log::add("Ntfy push failed (HTTP {$httpCode}) for event {$event}", Log::WARNING, 'mokosuiteclient');
} }
@@ -35,7 +35,7 @@ class HtmlView extends BaseHtmlView
protected function addToolbar(): void protected function addToolbar(): void
{ {
ToolbarHelper::title(Text::_('COM_MOKOSUITE_TICKET_SETTINGS'), 'cog'); ToolbarHelper::title(Text::_('COM_MOKOSUITECLIENT_TICKET_SETTINGS'), 'cog');
ToolbarHelper::back('JTOOLBAR_BACK', 'index.php?option=com_mokosuiteclient&view=tickets'); ToolbarHelper::back('JTOOLBAR_BACK', 'index.php?option=com_mokosuiteclient&view=tickets');
} }
} }
@@ -68,8 +68,9 @@ class TicketsController extends BaseController
$tickets = $db->loadObjectList() ?: []; $tickets = $db->loadObjectList() ?: [];
// Total count // Total count (with same filters applied)
$countQuery = $db->getQuery(true)->select('COUNT(*)')->from('#__mokosuiteclient_tickets'); $countQuery = clone $query;
$countQuery->clear('select')->clear('order')->select('COUNT(*)');
$db->setQuery($countQuery); $db->setQuery($countQuery);
$total = (int) $db->loadResult(); $total = (int) $db->loadResult();
@@ -193,14 +194,18 @@ class TicketsController extends BaseController
$id = $input->getInt('id', 0); $id = $input->getInt('id', 0);
$db = Factory::getDbo(); $db = Factory::getDbo();
// Type-safe input extraction
$fields = []; $fields = [];
$updatable = ['status', 'status_id', 'priority', 'priority_id', 'category_id', 'assigned_to']; $intFields = ['status_id', 'priority_id', 'category_id', 'assigned_to'];
$strFields = ['status', 'priority'];
foreach ($updatable as $field) { foreach ($intFields as $field) {
$value = $input->get($field, null, 'raw'); $value = $input->getInt($field, 0);
if ($value !== null) { if ($value > 0) { $fields[$field] = $value; }
$fields[$field] = $value;
} }
foreach ($strFields as $field) {
$value = $input->getString($field, '');
if ($value !== '') { $fields[$field] = $value; }
} }
if (empty($fields)) { if (empty($fields)) {
@@ -208,14 +213,43 @@ class TicketsController extends BaseController
return; return;
} }
// Sync status/status_id if only one is provided
if (isset($fields['status']) && !isset($fields['status_id'])) {
$q = $db->getQuery(true)->select('id')->from('#__mokosuiteclient_ticket_statuses')
->where($db->quoteName('alias') . ' = ' . $db->quote($fields['status']));
$resolved = (int) $db->setQuery($q, 0, 1)->loadResult();
if ($resolved) { $fields['status_id'] = $resolved; }
} elseif (isset($fields['status_id']) && !isset($fields['status'])) {
$q = $db->getQuery(true)->select('alias')->from('#__mokosuiteclient_ticket_statuses')
->where('id = ' . (int) $fields['status_id']);
$alias = $db->setQuery($q, 0, 1)->loadResult();
if ($alias) { $fields['status'] = $alias; }
}
if (isset($fields['priority']) && !isset($fields['priority_id'])) {
$q = $db->getQuery(true)->select('id')->from('#__mokosuiteclient_ticket_priorities')
->where($db->quoteName('alias') . ' = ' . $db->quote($fields['priority']));
$resolved = (int) $db->setQuery($q, 0, 1)->loadResult();
if ($resolved) { $fields['priority_id'] = $resolved; }
} elseif (isset($fields['priority_id']) && !isset($fields['priority'])) {
$q = $db->getQuery(true)->select('alias')->from('#__mokosuiteclient_ticket_priorities')
->where('id = ' . (int) $fields['priority_id']);
$alias = $db->setQuery($q, 0, 1)->loadResult();
if ($alias) { $fields['priority'] = $alias; }
}
$sets = []; $sets = [];
foreach ($fields as $k => $v) { foreach ($fields as $k => $v) {
$sets[] = $db->quoteName($k) . ' = ' . $db->quote($v); $sets[] = $db->quoteName($k) . ' = ' . (is_int($v) ? $v : $db->quote($v));
} }
$sets[] = 'modified = ' . $db->quote(Factory::getDate()->toSql()); $sets[] = 'modified = ' . $db->quote(Factory::getDate()->toSql());
$db->setQuery('UPDATE ' . $db->quoteName('#__mokosuiteclient_tickets') . ' SET ' . implode(', ', $sets) . ' WHERE id = ' . $id)->execute(); $db->setQuery('UPDATE ' . $db->quoteName('#__mokosuiteclient_tickets') . ' SET ' . implode(', ', $sets) . ' WHERE id = ' . $id)->execute();
if ($db->getAffectedRows() === 0) {
$this->sendJson(404, ['error' => 'Ticket not found']);
return;
}
$this->sendJson(200, ['id' => $id, 'message' => 'Ticket updated', 'updated' => array_keys($fields)]); $this->sendJson(200, ['id' => $id, 'message' => 'Ticket updated', 'updated' => array_keys($fields)]);
} }
@@ -264,6 +298,7 @@ class TicketsController extends BaseController
$user = Factory::getUser(); $user = Factory::getUser();
if (!$user->authorise($action, $asset)) { if (!$user->authorise($action, $asset)) {
$this->sendJson(403, ['error' => 'Not authorized']); $this->sendJson(403, ['error' => 'Not authorized']);
throw new \RuntimeException('Not authorized', 403);
} }
} }
@@ -20,7 +20,7 @@
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version> <version>02.44.04</version>
<description>MokoSuiteClient admin dashboard and REST API. Provides a control panel for managing MokoSuiteClient feature plugins, site health monitoring, and remote management endpoints.</description> <description>MokoSuiteClient admin dashboard and REST API. Provides a control panel for managing MokoSuiteClient feature plugins, site health monitoring, and remote management endpoints.</description>
<namespace path="src">Moko\Component\MokoSuiteClient</namespace> <namespace path="src">Moko\Component\MokoSuiteClient</namespace>
@@ -7,8 +7,8 @@
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version> <version>02.44.04</version>
<description>MOD_MOKOSUITE_CACHE_DESC</description> <description>MOD_MOKOSUITECLIENT_CACHE_DESC</description>
<namespace path="src">Moko\Module\MokoSuiteCache</namespace> <namespace path="src">Moko\Module\MokoSuiteCache</namespace>
<files> <files>
@@ -7,7 +7,7 @@
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version> <version>02.44.04</version>
<description>MOD_MOKOSUITECLIENT_CATEGORIES_DESC</description> <description>MOD_MOKOSUITECLIENT_CATEGORIES_DESC</description>
<namespace path="src">Moko\Module\MokoSuiteClientCategories</namespace> <namespace path="src">Moko\Module\MokoSuiteClientCategories</namespace>
@@ -7,8 +7,8 @@
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version> <version>02.44.04</version>
<description>MOD_MOKOSUITE_CPANEL_DESC</description> <description>MOD_MOKOSUITECLIENT_CPANEL_DESC</description>
<namespace path="src">Moko\Module\MokoSuiteCpanel</namespace> <namespace path="src">Moko\Module\MokoSuiteCpanel</namespace>
<files> <files>
@@ -25,64 +25,56 @@
<config> <config>
<fields name="params"> <fields name="params">
<fieldset name="basic" <fieldset name="basic"
label="MOD_MOKOSUITE_CPANEL_FIELDSET_DISPLAY" label="MOD_MOKOSUITECLIENT_CPANEL_FIELDSET_DISPLAY"
description="MOD_MOKOSUITE_CPANEL_FIELDSET_DISPLAY_DESC"> description="MOD_MOKOSUITECLIENT_CPANEL_FIELDSET_DISPLAY_DESC">
<field name="collapsed" type="radio" default="1"
label="MOD_MOKOSUITE_CPANEL_COLLAPSED_LABEL"
description="MOD_MOKOSUITE_CPANEL_COLLAPSED_DESC"
layout="joomla.form.field.radio.switcher">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field name="show_health" type="radio" default="1" <field name="show_health" type="radio" default="1"
label="MOD_MOKOSUITE_CPANEL_SHOW_HEALTH_LABEL" label="MOD_MOKOSUITECLIENT_CPANEL_SHOW_HEALTH_LABEL"
layout="joomla.form.field.radio.switcher"> layout="joomla.form.field.radio.switcher">
<option value="0">JHIDE</option> <option value="0">JHIDE</option>
<option value="1">JSHOW</option> <option value="1">JSHOW</option>
</field> </field>
<field name="show_stats" type="radio" default="1" <field name="show_stats" type="radio" default="1"
label="MOD_MOKOSUITE_CPANEL_SHOW_STATS_LABEL" label="MOD_MOKOSUITECLIENT_CPANEL_SHOW_STATS_LABEL"
description="MOD_MOKOSUITE_CPANEL_SHOW_STATS_DESC" description="MOD_MOKOSUITECLIENT_CPANEL_SHOW_STATS_DESC"
layout="joomla.form.field.radio.switcher"> layout="joomla.form.field.radio.switcher">
<option value="0">JHIDE</option> <option value="0">JHIDE</option>
<option value="1">JSHOW</option> <option value="1">JSHOW</option>
</field> </field>
<field name="show_disk" type="radio" default="1" <field name="show_disk" type="radio" default="1"
label="MOD_MOKOSUITE_CPANEL_SHOW_DISK_LABEL" label="MOD_MOKOSUITECLIENT_CPANEL_SHOW_DISK_LABEL"
layout="joomla.form.field.radio.switcher"> layout="joomla.form.field.radio.switcher">
<option value="0">JHIDE</option> <option value="0">JHIDE</option>
<option value="1">JSHOW</option> <option value="1">JSHOW</option>
</field> </field>
<field name="show_ip" type="radio" default="1" <field name="show_ip" type="radio" default="1"
label="MOD_MOKOSUITE_CPANEL_SHOW_IP_LABEL" label="MOD_MOKOSUITECLIENT_CPANEL_SHOW_IP_LABEL"
layout="joomla.form.field.radio.switcher"> layout="joomla.form.field.radio.switcher">
<option value="0">JHIDE</option> <option value="0">JHIDE</option>
<option value="1">JSHOW</option> <option value="1">JSHOW</option>
</field> </field>
<field name="show_plugins" type="radio" default="1" <field name="show_plugins" type="radio" default="1"
label="MOD_MOKOSUITE_CPANEL_SHOW_PLUGINS_LABEL" label="MOD_MOKOSUITECLIENT_CPANEL_SHOW_PLUGINS_LABEL"
layout="joomla.form.field.radio.switcher"> layout="joomla.form.field.radio.switcher">
<option value="0">JHIDE</option> <option value="0">JHIDE</option>
<option value="1">JSHOW</option> <option value="1">JSHOW</option>
</field> </field>
<field name="show_actions" type="radio" default="1" <field name="show_actions" type="radio" default="1"
label="MOD_MOKOSUITE_CPANEL_SHOW_ACTIONS_LABEL" label="MOD_MOKOSUITECLIENT_CPANEL_SHOW_ACTIONS_LABEL"
description="MOD_MOKOSUITE_CPANEL_SHOW_ACTIONS_DESC" description="MOD_MOKOSUITECLIENT_CPANEL_SHOW_ACTIONS_DESC"
layout="joomla.form.field.radio.switcher"> layout="joomla.form.field.radio.switcher">
<option value="0">JHIDE</option> <option value="0">JHIDE</option>
<option value="1">JSHOW</option> <option value="1">JSHOW</option>
</field> </field>
<field name="show_versions" type="radio" default="1" <field name="show_versions" type="radio" default="1"
label="MOD_MOKOSUITE_CPANEL_SHOW_VERSIONS_LABEL" label="MOD_MOKOSUITECLIENT_CPANEL_SHOW_VERSIONS_LABEL"
description="MOD_MOKOSUITE_CPANEL_SHOW_VERSIONS_DESC" description="MOD_MOKOSUITECLIENT_CPANEL_SHOW_VERSIONS_DESC"
layout="joomla.form.field.radio.switcher"> layout="joomla.form.field.radio.switcher">
<option value="0">JHIDE</option> <option value="0">JHIDE</option>
<option value="1">JSHOW</option> <option value="1">JSHOW</option>
@@ -22,7 +22,7 @@ $healthOk = $healthOk ?? true;
$counts = $counts ?? (object) ['articles' => 0, 'users' => 0, 'extensions' => 0, 'updates' => 0]; $counts = $counts ?? (object) ['articles' => 0, 'users' => 0, 'extensions' => 0, 'updates' => 0];
$disk = $disk ?? (object) ['free_mb' => null, 'total_mb' => null]; $disk = $disk ?? (object) ['free_mb' => null, 'total_mb' => null];
$currentIp = $currentIp ?? ''; $currentIp = $currentIp ?? '';
$collapsed = $params->get('collapsed', 0); $collapsed = true;
$showHealth = $params->get('show_health', 1); $showHealth = $params->get('show_health', 1);
$showStats = $params->get('show_stats', 1); $showStats = $params->get('show_stats', 1);
$showDisk = $params->get('show_disk', 1); $showDisk = $params->get('show_disk', 1);
@@ -48,7 +48,6 @@ $labels = [
'mokosuiteclient_firewall' => 'Firewall', 'mokosuiteclient_firewall' => 'Firewall',
'mokosuiteclient_tenant' => 'Tenant', 'mokosuiteclient_tenant' => 'Tenant',
'mokosuiteclient_devtools' => 'DevTools', 'mokosuiteclient_devtools' => 'DevTools',
'mokosuiteclient_monitor' => 'Monitor',
]; ];
$diskPct = ($disk->total_mb && $disk->total_mb > 0) $diskPct = ($disk->total_mb && $disk->total_mb > 0)
@@ -7,7 +7,7 @@
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version> <version>02.44.04</version>
<description>MokoSuiteClient admin sidebar menu — renders a dedicated MokoSuiteClient section in the admin menu before Joomla's default menu.</description> <description>MokoSuiteClient admin sidebar menu — renders a dedicated MokoSuiteClient section in the admin menu before Joomla's default menu.</description>
<namespace path="src">Moko\Module\MokoSuiteClientMenu</namespace> <namespace path="src">Moko\Module\MokoSuiteClientMenu</namespace>
@@ -3,7 +3,7 @@
* MokoSuiteClient Admin Sidebar Menu * MokoSuiteClient Admin Sidebar Menu
* *
* Each installed Moko component gets its own top-level collapsible section. * Each installed Moko component gets its own top-level collapsible section.
* com_mokosuiteclienthq is always pinned first. com_mokosuiteclient uses static views * com_mokosuitehq is always pinned first. com_mokosuiteclient uses static views
* as children. All other components auto-discover their submenu items. * as children. All other components auto-discover their submenu items.
*/ */
@@ -109,13 +109,13 @@ else
]; ];
} }
// ── Sort: com_mokosuiteclienthq first, then alphabetical by title ───────── // ── Sort: com_mokosuitehq first, then alphabetical by title ─────────
$hq = null; $hq = null;
$rest = []; $rest = [];
foreach ($mokoComponents as $key => $comp) foreach ($mokoComponents as $key => $comp)
{ {
if ($key === 'com_mokosuiteclienthq') if ($key === 'com_mokosuitehq')
{ {
$hq = $comp; $hq = $comp;
} }
@@ -139,8 +139,7 @@ foreach ($rest as $comp)
?> ?>
<style> <style>
.sidebar-wrapper .mokosuiteclient-ext-item > a { padding-inline-start: 1.5rem; } .sidebar-wrapper .mokosuiteclient-ext-child > a { padding-inline-start: 0.5rem; }
.sidebar-wrapper .mokosuiteclient-ext-child > a { padding-inline-start: 2.5rem; }
</style> </style>
<ul class="nav flex-column main-nav"> <ul class="nav flex-column main-nav">
@@ -22,7 +22,7 @@
* DEFGROUP: Joomla.Plugin * DEFGROUP: Joomla.Plugin
* INGROUP: MokoSuiteClient * INGROUP: MokoSuiteClient
* REPO: https://github.com/mokoconsulting-tech/mokosuiteclient * REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
* VERSION: 02.34.84 * VERSION: 02.44.04
* PATH: /src/Extension/MokoSuiteClient.php * PATH: /src/Extension/MokoSuiteClient.php
* NOTE: Core system plugin for MokoSuiteClient admin tools suite * NOTE: Core system plugin for MokoSuiteClient admin tools suite
*/ */
@@ -36,6 +36,7 @@ use Joomla\CMS\Factory;
use Joomla\CMS\Log\Log; use Joomla\CMS\Log\Log;
use Joomla\CMS\Plugin\CMSPlugin; use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Uri\Uri; use Joomla\CMS\Uri\Uri;
use Joomla\CMS\Version;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
/** /**
@@ -164,6 +165,7 @@ class MokoSuiteClient extends CMSPlugin implements BootableExtensionInterface
$this->handleOneTimeLogin(); $this->handleOneTimeLogin();
$this->checkSetupRequired(); $this->checkSetupRequired();
$this->ensureAdminModulesActive(); $this->ensureAdminModulesActive();
$this->checkHeartbeat();
} }
} }
@@ -2392,4 +2394,274 @@ class MokoSuiteClient extends CMSPlugin implements BootableExtensionInterface
); );
} }
} }
// ── Heartbeat Monitor ─────────────────────────────────────────
/**
* Send heartbeat to MokoSuiteClientHQ when the package version changes.
* Runs once per session on admin page load.
*/
private function checkHeartbeat(): void
{
try
{
if ($this->params->get('heartbeat_enabled', '1') === '0')
{
return;
}
$session = Factory::getSession();
if ($session->get('mokosuiteclient.heartbeat_sent', false))
{
return;
}
$lastVersion = $this->params->get('_last_heartbeat_version', '');
$currentVersion = $this->getPluginVersion();
if ($lastVersion === $currentVersion)
{
return;
}
$session->set('mokosuiteclient.heartbeat_sent', true);
$this->sendRuntimeHeartbeat();
// Persist version marker with a targeted DB update to avoid overwriting
// params that may have been modified by migrateMonitorParams() in the same request
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName('params'))
->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' = ' . $db->quote('mokosuiteclient'))
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' . $db->quote('system'));
$freshParams = json_decode((string) $db->setQuery($query)->loadResult(), true) ?: [];
$freshParams['_last_heartbeat_version'] = $currentVersion;
$db->setQuery(
$db->getQuery(true)
->update($db->quoteName('#__extensions'))
->set($db->quoteName('params') . ' = ' . $db->quote(json_encode($freshParams)))
->where($db->quoteName('element') . ' = ' . $db->quote('mokosuiteclient'))
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
)->execute();
}
catch (\Throwable $e)
{
Log::add('Heartbeat check failed: ' . $e->getMessage(), Log::WARNING, 'mokosuiteclient');
}
}
/**
* Send heartbeat data to MokoSuiteClientHQ.
* RSA-signed: client signs domain|timestamp|token with its private key.
*/
private function sendRuntimeHeartbeat(): void
{
$baseUrl = rtrim($this->params->get('monitor_base_url', ''), '/');
// Fall back to manifest XML default
if (empty($baseUrl))
{
$manifestFile = JPATH_PLUGINS . '/system/mokosuiteclient/mokosuiteclient.xml';
if (is_file($manifestFile))
{
$xml = simplexml_load_file($manifestFile);
if ($xml)
{
foreach ($xml->xpath('//field[@name="monitor_base_url"]') as $field)
{
$baseUrl = rtrim((string) $field['default'], '/');
break;
}
}
}
}
if (empty($baseUrl))
{
return;
}
$healthToken = $this->params->get('health_api_token', '');
if (empty($healthToken))
{
return;
}
$siteUrl = rtrim(Uri::root(), '/');
$domain = parse_url($siteUrl, PHP_URL_HOST) ?: '';
if (empty($domain))
{
return;
}
$config = Factory::getConfig();
$timestamp = time();
$payload = [
'token' => $healthToken,
'domain' => $domain,
'site_name' => $config->get('sitename', 'Joomla'),
'site_url' => $siteUrl,
'joomla_version' => (new Version())->getShortVersion(),
'php_version' => PHP_VERSION,
'mokosuiteclient_version' => $this->getPluginVersion(),
'timestamp' => $timestamp,
'client_info' => [
'company' => $config->get('sitename', ''),
'email' => $config->get('mailfrom', ''),
],
];
// Include live health data
$healthData = $this->fetchLocalHealth($siteUrl, $healthToken);
if ($healthData !== null)
{
$payload['health'] = $healthData;
}
// RSA sign the request
$headers = ['Content-Type: application/json'];
$signature = $this->signHeartbeatRequest($domain, $timestamp, $healthToken);
if ($signature !== null)
{
$headers[] = 'X-MokoSuite-Signature: ' . $signature;
$headers[] = 'X-MokoSuite-Timestamp: ' . $timestamp;
}
$endpoint = $baseUrl . '/api/index.php/v1/mokosuitehq/heartbeat';
$json = json_encode($payload, JSON_UNESCAPED_SLASHES);
try
{
$http = \Joomla\CMS\Http\HttpFactory::getHttp(
new \Joomla\Registry\Registry(['follow_location' => true, 'transport.curl' => ['certpath' => false]]),
['curl', 'stream']
);
$headerMap = [];
foreach ($headers as $h)
{
[$key, $val] = explode(': ', $h, 2);
$headerMap[$key] = $val;
}
$response = $http->post($endpoint, $json, $headerMap, 15);
if ($response->code >= 200 && $response->code < 300)
{
$this->app->enqueueMessage('MokoSuiteHQ heartbeat: site registered successfully.', 'message');
}
else
{
$body = json_decode($response->body, true);
$msg = $body['error'] ?? $body['message'] ?? ('HTTP ' . $response->code);
Log::add(
\sprintf('Heartbeat HTTP %d: %s', $response->code, $response->body),
Log::WARNING,
'mokosuiteclient'
);
$this->app->enqueueMessage('MokoSuiteHQ heartbeat failed: ' . $msg, 'warning');
}
}
catch (\Throwable $e)
{
Log::add('Heartbeat failed: ' . $e->getMessage(), Log::WARNING, 'mokosuiteclient');
}
}
private function signHeartbeatRequest(string $domain, int $timestamp, string $token): ?string
{
$signingKeyB64 = $this->params->get('monitor_signing_key', '');
// Fall back to manifest XML default
if (empty($signingKeyB64))
{
$manifestFile = JPATH_PLUGINS . '/system/mokosuiteclient/mokosuiteclient.xml';
if (is_file($manifestFile))
{
$xml = simplexml_load_file($manifestFile);
if ($xml)
{
foreach ($xml->xpath('//field[@name="monitor_signing_key"]') as $field)
{
$signingKeyB64 = (string) $field['default'];
break;
}
}
}
}
if (empty($signingKeyB64))
{
return null;
}
$privateKeyPem = base64_decode($signingKeyB64);
if (empty($privateKeyPem))
{
return null;
}
$privateKey = openssl_pkey_get_private($privateKeyPem);
if ($privateKey === false)
{
return null;
}
$message = $domain . '|' . $timestamp . '|' . $token;
$signature = '';
if (openssl_sign($message, $signature, $privateKey, OPENSSL_ALGO_SHA256))
{
return base64_encode($signature);
}
return null;
}
private function fetchLocalHealth(string $siteUrl, string $healthToken): ?array
{
try
{
$http = \Joomla\CMS\Http\HttpFactory::getHttp(
new \Joomla\Registry\Registry(['follow_location' => true, 'transport.curl' => ['certpath' => false]]),
['curl', 'stream']
);
$response = $http->get(
$siteUrl . '/?mokosuiteclient=health',
['Authorization' => 'Bearer ' . $healthToken, 'Accept' => 'application/json'],
10
);
if ($response->code !== 200 || empty($response->body))
{
return null;
}
return json_decode($response->body, true) ?: null;
}
catch (\Throwable $e)
{
Log::add('Local health fetch failed: ' . $e->getMessage(), Log::DEBUG, 'mokosuiteclient');
return null;
}
}
} }
@@ -8,7 +8,7 @@
* FILE INFORMATION * FILE INFORMATION
* DEFGROUP: Joomla.Plugin * DEFGROUP: Joomla.Plugin
* INGROUP: MokoSuiteClient * INGROUP: MokoSuiteClient
* VERSION: 02.34.84 * VERSION: 02.44.04
* PATH: /src/Field/CopyableTokenField.php * PATH: /src/Field/CopyableTokenField.php
* BRIEF: Read-only token field with a copy-to-clipboard button * BRIEF: Read-only token field with a copy-to-clipboard button
*/ */
@@ -26,7 +26,7 @@ PLG_SYSTEM_MOKOSUITECLIENT_HEALTH_TOKEN_DESC="Auto-generated bearer token for th
PLG_SYSTEM_MOKOSUITECLIENT_FIELDSET_ALIASES_LABEL="Site Aliases" PLG_SYSTEM_MOKOSUITECLIENT_FIELDSET_ALIASES_LABEL="Site Aliases"
PLG_SYSTEM_MOKOSUITECLIENT_FIELDSET_ALIASES_DESC="Configure additional domains that mirror this site. Each alias can have its own offline status, robots directive, and backend redirect behavior." PLG_SYSTEM_MOKOSUITECLIENT_FIELDSET_ALIASES_DESC="Configure additional domains that mirror this site. Each alias can have its own offline status, robots directive, and backend redirect behavior."
PLG_SYSTEM_MOKOSUITECLIENT_PRIMARY_DOMAIN_LABEL="Primary Domain" PLG_SYSTEM_MOKOSUITECLIENT_PRIMARY_DOMAIN_LABEL="Primary Domain"
PLG_SYSTEM_MOKOSUITECLIENT_PRIMARY_DOMAIN_DESC="The primary domain for this site (e.g. waas.dev.mokoconsulting.tech). Used for backend redirect on alias domains. Do not include https:// prefix." PLG_SYSTEM_MOKOSUITECLIENT_PRIMARY_DOMAIN_DESC="The primary domain for this site (e.g. suite.dev.mokoconsulting.tech). Used for backend redirect on alias domains. Do not include https:// prefix."
PLG_SYSTEM_MOKOSUITECLIENT_SITE_ALIASES_LABEL="Domain Aliases" PLG_SYSTEM_MOKOSUITECLIENT_SITE_ALIASES_LABEL="Domain Aliases"
PLG_SYSTEM_MOKOSUITECLIENT_SITE_ALIASES_DESC="Add domain aliases that serve as mirrors of this site. Each alias gets its own MokoSuiteClientHQ monitoring datasource." PLG_SYSTEM_MOKOSUITECLIENT_SITE_ALIASES_DESC="Add domain aliases that serve as mirrors of this site. Each alias gets its own MokoSuiteClientHQ monitoring datasource."
PLG_SYSTEM_MOKOSUITECLIENT_ALIAS_DOMAIN_LABEL="Domain" PLG_SYSTEM_MOKOSUITECLIENT_ALIAS_DOMAIN_LABEL="Domain"
@@ -39,3 +39,11 @@ PLG_SYSTEM_MOKOSUITECLIENT_ALIAS_ROBOTS_LABEL="Robots"
PLG_SYSTEM_MOKOSUITECLIENT_ALIAS_ROBOTS_DESC="Meta robots directive for this alias domain. Use 'noindex, nofollow' to prevent search engines from indexing the alias." PLG_SYSTEM_MOKOSUITECLIENT_ALIAS_ROBOTS_DESC="Meta robots directive for this alias domain. Use 'noindex, nofollow' to prevent search engines from indexing the alias."
PLG_SYSTEM_MOKOSUITECLIENT_ALIAS_REDIRECT_BACKEND_LABEL="Redirect Backend" PLG_SYSTEM_MOKOSUITECLIENT_ALIAS_REDIRECT_BACKEND_LABEL="Redirect Backend"
PLG_SYSTEM_MOKOSUITECLIENT_ALIAS_REDIRECT_BACKEND_DESC="Redirect admin panel requests on this alias to the primary domain. Frontend stays on the alias domain." PLG_SYSTEM_MOKOSUITECLIENT_ALIAS_REDIRECT_BACKEND_DESC="Redirect admin panel requests on this alias to the primary domain. Frontend stays on the alias domain."
; ===== Heartbeat Monitor fieldset =====
PLG_SYSTEM_MOKOSUITECLIENT_FIELDSET_MONITOR_LABEL="Heartbeat Monitor"
PLG_SYSTEM_MOKOSUITECLIENT_FIELDSET_MONITOR_DESC="Settings for the MokoSuiteClientHQ heartbeat registration."
PLG_SYSTEM_MOKOSUITECLIENT_HEARTBEAT_LABEL="Enable Heartbeat"
PLG_SYSTEM_MOKOSUITECLIENT_HEARTBEAT_DESC="Send heartbeat data to MokoSuiteClientHQ on install, update, and admin login."
PLG_SYSTEM_MOKOSUITECLIENT_MONITOR_BASE_URL_LABEL="HQ Base URL"
PLG_SYSTEM_MOKOSUITECLIENT_MONITOR_BASE_URL_DESC="Base URL of the MokoSuiteClientHQ instance that receives heartbeat data."
@@ -26,7 +26,7 @@ PLG_SYSTEM_MOKOSUITECLIENT_HEALTH_TOKEN_DESC="Auto-generated bearer token for th
PLG_SYSTEM_MOKOSUITECLIENT_FIELDSET_ALIASES_LABEL="Site Aliases" PLG_SYSTEM_MOKOSUITECLIENT_FIELDSET_ALIASES_LABEL="Site Aliases"
PLG_SYSTEM_MOKOSUITECLIENT_FIELDSET_ALIASES_DESC="Configure additional domains that mirror this site. Each alias can have its own offline status, robots directive, and backend redirect behavior." PLG_SYSTEM_MOKOSUITECLIENT_FIELDSET_ALIASES_DESC="Configure additional domains that mirror this site. Each alias can have its own offline status, robots directive, and backend redirect behavior."
PLG_SYSTEM_MOKOSUITECLIENT_PRIMARY_DOMAIN_LABEL="Primary Domain" PLG_SYSTEM_MOKOSUITECLIENT_PRIMARY_DOMAIN_LABEL="Primary Domain"
PLG_SYSTEM_MOKOSUITECLIENT_PRIMARY_DOMAIN_DESC="The primary domain for this site (e.g. waas.dev.mokoconsulting.tech). Used for backend redirect on alias domains. Do not include https:// prefix." PLG_SYSTEM_MOKOSUITECLIENT_PRIMARY_DOMAIN_DESC="The primary domain for this site (e.g. suite.dev.mokoconsulting.tech). Used for backend redirect on alias domains. Do not include https:// prefix."
PLG_SYSTEM_MOKOSUITECLIENT_SITE_ALIASES_LABEL="Domain Aliases" PLG_SYSTEM_MOKOSUITECLIENT_SITE_ALIASES_LABEL="Domain Aliases"
PLG_SYSTEM_MOKOSUITECLIENT_SITE_ALIASES_DESC="Add domain aliases that serve as mirrors of this site. Each alias gets its own MokoSuiteClientHQ monitoring datasource." PLG_SYSTEM_MOKOSUITECLIENT_SITE_ALIASES_DESC="Add domain aliases that serve as mirrors of this site. Each alias gets its own MokoSuiteClientHQ monitoring datasource."
PLG_SYSTEM_MOKOSUITECLIENT_ALIAS_DOMAIN_LABEL="Domain" PLG_SYSTEM_MOKOSUITECLIENT_ALIAS_DOMAIN_LABEL="Domain"
@@ -30,7 +30,7 @@
<license>GNU General Public License version 3 or later; see LICENSE.md</license> <license>GNU General Public License version 3 or later; see LICENSE.md</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version> <version>02.44.04</version>
<description>MokoSuiteClient core system plugin — coordinates feature plugins, heartbeat, health checks, and admin customizations.</description> <description>MokoSuiteClient core system plugin — coordinates feature plugins, heartbeat, health checks, and admin customizations.</description>
<namespace path=".">Moko\Plugin\System\MokoSuiteClient</namespace> <namespace path=".">Moko\Plugin\System\MokoSuiteClient</namespace>
<scriptfile>script.php</scriptfile> <scriptfile>script.php</scriptfile>
@@ -68,18 +68,41 @@
addfieldprefix="Moko\Plugin\System\MokoSuiteClient\Field" addfieldprefix="Moko\Plugin\System\MokoSuiteClient\Field"
> >
<fieldset name="basic" <fieldset name="basic"
label="PLG_SYSTEM_MOKOSUITE_FIELDSET_CORE_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_FIELDSET_CORE_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIELDSET_CORE_DESC"> description="PLG_SYSTEM_MOKOSUITECLIENT_FIELDSET_CORE_DESC">
<field <field
name="health_api_token" name="health_api_token"
type="CopyableToken" type="CopyableToken"
label="PLG_SYSTEM_MOKOSUITE_HEALTH_TOKEN_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_HEALTH_TOKEN_LABEL"
description="PLG_SYSTEM_MOKOSUITE_HEALTH_TOKEN_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_HEALTH_TOKEN_DESC"
default="" default=""
filter="raw" filter="raw"
readonly="true" readonly="true"
/> />
</fieldset> </fieldset>
<fieldset name="monitor"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIELDSET_MONITOR_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIELDSET_MONITOR_DESC">
<field name="heartbeat_enabled" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITECLIENT_HEARTBEAT_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_HEARTBEAT_DESC"
class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field name="monitor_base_url" type="url"
default="https://suite.dev.mokoconsulting.tech"
label="PLG_SYSTEM_MOKOSUITECLIENT_MONITOR_BASE_URL_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_MONITOR_BASE_URL_DESC"
filter="url" />
<field name="monitor_signing_key" type="hidden"
default="LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2QUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktZd2dnU2lBZ0VBQW9JQkFRQ2xZNnNzOTZpeTZOOGMKTHRxbndhbnU4eEozdDcrdDhXT3hoY0Yyclc2QmlmOVhNaEpnYkw0c055N0wwV1dTT2tkMmZxalBNcDFtOFNyNAo1VnNycjE3cFc5b0FNMmtmdFdsaTZ1NkhTVEYyN2pVVUJrT3o4MHZMRklMMGNGNkJCUkpYN2JVWkRpamdUMjc1ClREb3dXZy82Zk9GeWFEelBHUkJuYXFacTljU2lEYWoyNlpSTVZIbktQUERWTG92VzRPTDQzL2gwZ3BtN25nUGIKdWJlLzFFTDRUMHFRbm1Xc2FEOFZ6VStoRXFGSDRTVUtMaDVNeklGbUxFZzRlZ0xCbTBXcWdxbzZRQVBnZDVPYgoybXhmQndta3RLVm5hcWR6eG9KSytzaTVuZkYreGpxbWRMZThUdmEyTHNuTUxlZmsrODVoQ3hxS2x1eWRta1lXCjlvUk5qcDhiQWdNQkFBRUNnZ0VBQkZOUS9NSVZaV2gxdlZUMFh3TFBvUEkyZjI4TTBrM0gzN0t4MXBxK2t5QzYKenRyK1pBczBCaEFEWjAwNHJOUmRYaG45N0QxVXBJYVdLeUJFZkNZQUEzWmxneS9WQmdGR21sR3VuMWNvdGdXUQoyYzg0SWhLdzNzVFFqL2dJWUxOelFWMTBLUTJYd0JZVHZ1MWhjRFpLeUxCUGJTQ1F4cEhQUGdVcUNRNFljR3lFClErVmc1dHJUYk8wQ2xCZ1U5bkVnYU1RakRJZ0F3WVZPV203dUxJTW84UC9nT3FuT2tmaFhzdzl3VTJVYWxFeTEKRmRZbGhMbGJ0ZS9MZ3lkYlJ2RStjNEtqZVp0Z3ptc1RneEh2dzM5YVVmZUZTclFRT0FjcXc0alNzUjdMck9UZAp5bDhpelRrZVBrTVFMamFqR0pabWdPbitkRzhtUlpMa3FKcWdGaVpqRVFLQmdRRFV0L0xlU0h5SmhvY3VFL240CkZreEpaclJoWUVsWnc2WlZJUnQzWDlPQ1Nmaklab3I1ZkZlczhvUzZySFhKdGZYeWx4QUxOSjJjTUhKTTViVnUKbUFSUFU4cThBeVc0OE03cHAyNmtVVTMxNXc2OU1SUkhzbWgyekRabEtDeG5GM1NSQ3U4YW95d3hZc3RUZ3hkTgo2bDhLNHZsS1dsN3FYblBhWjZjb3lQSU9od0tCZ1FESENuRmRRdW5SMVI2dkxGaVFZMTRiT3QwT0tzVGJYMUJyCmpvUGZySkxvRm5mSCs4VDVnNUdxYkV5T2p0WG1tRXhmTFFpcDBQVXRtc1E0YXlJRFBZYWZtU3RpK2dtQXZFd1MKZTlKcVYxYlRuazUrYnVRZ2FlOW16REpJWkxaczRJUlhrd1Q5aDZ4Q2xKeS80TGJSRHdBU3dUVGJlY01hN3A4UgpQN0p0bjdsYnpRS0JnQzNOR2FjUTFuZktGb3N1VS9FOTQ5a2VHeEtvWjhMREpLcEp3WjgzYTlRdTF6bFhFdTlhCi9ZbklnaG1yam9VSy85VG0vOVpaMHVIUmNKcnNEdCtzTGFsaThsRC9JSDBzcEhDYzAyN2Y3cmhXc3M2N3BaRTIKY2RXNmJLL2xNWUpWQTQxRFhHNVEyZkFjUklsTHZaWFNNL3FsR21ZUEJVYlRaWUNPTnVqS000dzdBb0dBU1dBdwpLcEZnWVZxUDFVUWo0aGEvdW9vWXRBQlFVZzd4TnJWektDSVdoampDTDVkQkpqcTZtSGtVUC9tb0lUcEQ3VkpNCnYwMnBGUWJaRDNOdk5vS1gvbjRZNElRTXZNaXR3cUtqRDFEalVXQXF6N0ZScUNGbGdDQUc2V2szVnl2dG5kczEKRzhISVgwTXFCaEp4VXVDVXhsVXpoelY4RjVHZ1VsdUpDNkMyVklFQ2dZQkJWSkxpZlNVOTlHWGZtK3dPd0RWcgo2bHZoUFgxOTBGVktWQXY3aVVWTXBwWXg4Y0QxYkcyUjRLT29JbnkxYTlxdjA2ZGFzeGVQOStkVjJVMWU3MWl5CkFXWDRBVHIrYitvSGk2eUk1MXRHRk54RUxiNXZYMVpYM3VNaDlWM29iYUpuSFNjYllpKzBBNjlyRmNuNEZuLzUKWXJybWxLTzRlRHFVZkswbVFJVCtwUT09Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K"
filter="raw" />
</fieldset>
</fields> </fields>
</config> </config>
</extension> </extension>
@@ -22,7 +22,7 @@
* DEFGROUP: Joomla.Plugin * DEFGROUP: Joomla.Plugin
* INGROUP: MokoSuiteClient * INGROUP: MokoSuiteClient
* REPO: https://github.com/mokoconsulting-tech/mokosuiteclient * REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
* VERSION: 02.34.84 * VERSION: 02.44.04
* PATH: /src/script.php * PATH: /src/script.php
* BRIEF: Installation script for MokoSuiteClient plugin * BRIEF: Installation script for MokoSuiteClient plugin
* NOTE: Handles installation, update, and uninstallation tasks including language override deployment * NOTE: Handles installation, update, and uninstallation tasks including language override deployment
@@ -527,7 +527,7 @@ class plgSystemMokoSuiteClientInstallerScript implements InstallerScriptInterfac
} }
catch (\Exception $e) catch (\Exception $e)
{ {
// Don't break install if email fails Log::add('Install notification email failed: ' . $e->getMessage(), Log::WARNING, 'mokosuiteclient');
} }
} }
@@ -767,7 +767,7 @@ class plgSystemMokoSuiteClientInstallerScript implements InstallerScriptInterfac
'id_holder' => '', 'id_holder' => '',
'title_holder' => '', 'title_holder' => '',
'table_name' => '', 'table_name' => '',
'text_prefix' => 'PLG_SYSTEM_MOKOSUITE', 'text_prefix' => 'PLG_SYSTEM_MOKOSUITECLIENT',
]; ];
$db->insertObject('#__action_log_config', $config); $db->insertObject('#__action_log_config', $config);
@@ -22,7 +22,7 @@
* DEFGROUP: Joomla.Plugin * DEFGROUP: Joomla.Plugin
* INGROUP: MokoSuiteClient * INGROUP: MokoSuiteClient
* REPO: https://github.com/mokoconsulting-tech/mokosuiteclient * REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
* VERSION: 02.34.84 * VERSION: 02.44.04
* PATH: /src/services/provider.php * PATH: /src/services/provider.php
* BRIEF: Service provider for dependency injection in Joomla 5.x * BRIEF: Service provider for dependency injection in Joomla 5.x
* NOTE: Registers the plugin with Joomla's DI container * NOTE: Registers the plugin with Joomla's DI container
@@ -8,7 +8,7 @@
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version> <version>02.44.04</version>
<description>PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_DESC</description> <description>PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteClientBackup</namespace> <namespace path="src">Moko\Plugin\System\MokoSuiteClientBackup</namespace>
@@ -118,13 +118,11 @@ class Backup extends CMSPlugin implements SubscriberInterface
} }
// Prefer MokoSuiteBackup's own helper (clean public API) // Prefer MokoSuiteBackup's own helper (clean public API)
$helperClass = 'Joomla\\Component\\MokoSuiteBackup\\Administrator\\Utility\\BackupStatusHelper'; $helperClass = 'Joomla\\Component\\MokoSuiteBackup\\Administrator\\Helper\\BackupStatusHelper';
if (class_exists($helperClass)) if (class_exists($helperClass))
{ {
$staleDays = (int) $this->params->get('stale_days', 7); return $helperClass::getStatusSummary();
return $helperClass::getStatus($staleDays);
} }
// Fallback: direct table query for older MokoSuiteBackup versions // Fallback: direct table query for older MokoSuiteBackup versions
@@ -244,20 +242,52 @@ class Backup extends CMSPlugin implements SubscriberInterface
? round($latest->total_size / 1048576) ? round($latest->total_size / 1048576)
: null; : null;
// Total counts (all time)
$db->setQuery(
$db->getQuery(true)
->select('COUNT(*)')
->from($db->quoteName('#__mokosuitebackup_records'))
);
$allTime = (int) $db->loadResult();
$db->setQuery(
$db->getQuery(true)
->select('COUNT(*)')
->from($db->quoteName('#__mokosuitebackup_records'))
->where($db->quoteName('status') . ' = ' . $db->quote('fail'))
);
$allFailed = (int) $db->loadResult();
// Recent failures
$cutoff7d = date('Y-m-d H:i:s', strtotime('-7 days'));
$db->setQuery(
$db->getQuery(true)
->select('COUNT(*)')
->from($db->quoteName('#__mokosuitebackup_records'))
->where($db->quoteName('status') . ' = ' . $db->quote('fail'))
->where($db->quoteName('backupstart') . ' >= ' . $db->quote($cutoff7d))
);
$recentFailed = (int) $db->loadResult();
return [ return [
'installed' => true, 'installed' => true,
'status' => $status, 'latest' => [
'last_backup' => $latest->backupstart, 'status' => $latest->status,
'last_status' => $latest->status, 'backup_type' => $latest->backup_type ?? 'full',
'last_size_mb' => $sizeMb, 'description' => $latest->description ?? '',
'days_since' => $daysSince, 'backup_start' => $latest->backupstart,
'backup_type' => $latest->backup_type, 'backup_end' => $latest->backupend ?? null,
'origin' => $latest->origin, 'total_size' => (int) ($latest->total_size ?? 0),
'total_backups' => $totalBackups, 'origin' => $latest->origin ?? 'backend',
'recent_7d' => $recentBackups, ],
'fail_count_7d' => $failCount7d, 'totals' => [
'files_exist' => (bool) $latest->filesexist, 'all_time' => $allTime,
'description' => $latest->description, 'all_success' => $totalBackups,
'all_failed' => $allFailed,
'recent_total' => $recentBackups + $recentFailed,
'recent_success' => $recentBackups,
'recent_failed' => $recentFailed,
],
]; ];
} }
} }
@@ -3,27 +3,27 @@
; License: GPL-3.0-or-later ; License: GPL-3.0-or-later
; IP Geolocation by DB-IP — https://db-ip.com ; IP Geolocation by DB-IP — https://db-ip.com
PLG_SYSTEM_MOKOSUITE_DBIP="System - MokoSuiteClient DB-IP" PLG_SYSTEM_MOKOSUITECLIENT_DBIP="System - MokoSuiteClient DB-IP"
PLG_SYSTEM_MOKOSUITE_DBIP_DESC="IP geolocation for MokoSuiteClient using DB-IP Lite databases. Ships with country-level data; city-level data is downloaded from CDN or loaded from a local file." PLG_SYSTEM_MOKOSUITECLIENT_DBIP_DESC="IP geolocation for MokoSuiteClient using DB-IP Lite databases. Ships with country-level data; city-level data is downloaded from CDN or loaded from a local file."
PLG_SYSTEM_MOKOSUITE_DBIP_FIELDSET_BASIC="DB-IP Settings" PLG_SYSTEM_MOKOSUITECLIENT_DBIP_FIELDSET_BASIC="DB-IP Settings"
PLG_SYSTEM_MOKOSUITE_DBIP_FIELDSET_BASIC_DESC="Configure IP geolocation database source and level." PLG_SYSTEM_MOKOSUITECLIENT_DBIP_FIELDSET_BASIC_DESC="Configure IP geolocation database source and level."
PLG_SYSTEM_MOKOSUITE_DBIP_SOURCE_LABEL="Database Source" PLG_SYSTEM_MOKOSUITECLIENT_DBIP_SOURCE_LABEL="Database Source"
PLG_SYSTEM_MOKOSUITE_DBIP_SOURCE_DESC="CDN downloads the city database automatically from the configured URL. Local uses a MMDB file you provide on the server." PLG_SYSTEM_MOKOSUITECLIENT_DBIP_SOURCE_DESC="CDN downloads the city database automatically from the configured URL. Local uses a MMDB file you provide on the server."
PLG_SYSTEM_MOKOSUITE_DBIP_SOURCE_CDN="CDN (auto-download)" PLG_SYSTEM_MOKOSUITECLIENT_DBIP_SOURCE_CDN="CDN (auto-download)"
PLG_SYSTEM_MOKOSUITE_DBIP_SOURCE_LOCAL="Local file" PLG_SYSTEM_MOKOSUITECLIENT_DBIP_SOURCE_LOCAL="Local file"
PLG_SYSTEM_MOKOSUITE_DBIP_DATABASE_LEVEL_LABEL="Database Level" PLG_SYSTEM_MOKOSUITECLIENT_DBIP_DATABASE_LEVEL_LABEL="Database Level"
PLG_SYSTEM_MOKOSUITE_DBIP_DATABASE_LEVEL_DESC="Country is bundled (~8 MB). City provides region, city, and coordinates but requires a separate download (~125 MB)." PLG_SYSTEM_MOKOSUITECLIENT_DBIP_DATABASE_LEVEL_DESC="Country is bundled (~8 MB). City provides region, city, and coordinates but requires a separate download (~125 MB)."
PLG_SYSTEM_MOKOSUITE_DBIP_DATABASE_COUNTRY="Country (bundled)" PLG_SYSTEM_MOKOSUITECLIENT_DBIP_DATABASE_COUNTRY="Country (bundled)"
PLG_SYSTEM_MOKOSUITE_DBIP_DATABASE_CITY="City (remote download)" PLG_SYSTEM_MOKOSUITECLIENT_DBIP_DATABASE_CITY="City (remote download)"
PLG_SYSTEM_MOKOSUITE_DBIP_AUTO_UPDATE_LABEL="Auto-Update Database" PLG_SYSTEM_MOKOSUITECLIENT_DBIP_AUTO_UPDATE_LABEL="Auto-Update Database"
PLG_SYSTEM_MOKOSUITE_DBIP_AUTO_UPDATE_DESC="Automatically download the latest city database monthly when an admin visits the backend." PLG_SYSTEM_MOKOSUITECLIENT_DBIP_AUTO_UPDATE_DESC="Automatically download the latest city database monthly when an admin visits the backend."
PLG_SYSTEM_MOKOSUITE_DBIP_CDN_URL_LABEL="CDN Download URL" PLG_SYSTEM_MOKOSUITECLIENT_DBIP_CDN_URL_LABEL="CDN Download URL"
PLG_SYSTEM_MOKOSUITE_DBIP_CDN_URL_DESC="URL to download the city-level MMDB file. Default points to the MokoConsulting geoip-data repository." PLG_SYSTEM_MOKOSUITECLIENT_DBIP_CDN_URL_DESC="URL to download the city-level MMDB file. Default points to the MokoConsulting geoip-data repository."
PLG_SYSTEM_MOKOSUITE_DBIP_LOCAL_PATH_LABEL="Local MMDB Path" PLG_SYSTEM_MOKOSUITECLIENT_DBIP_LOCAL_PATH_LABEL="Local MMDB Path"
PLG_SYSTEM_MOKOSUITE_DBIP_LOCAL_PATH_DESC="Absolute path to a DB-IP MMDB file on the server (e.g. /home/user/dbip-city-lite.mmdb)." PLG_SYSTEM_MOKOSUITECLIENT_DBIP_LOCAL_PATH_DESC="Absolute path to a DB-IP MMDB file on the server (e.g. /home/user/dbip-city-lite.mmdb)."
@@ -2,5 +2,5 @@
; Copyright (C) 2026 Moko Consulting. All rights reserved. ; Copyright (C) 2026 Moko Consulting. All rights reserved.
; License: GPL-3.0-or-later ; License: GPL-3.0-or-later
PLG_SYSTEM_MOKOSUITE_DBIP="System - MokoSuiteClient DB-IP" PLG_SYSTEM_MOKOSUITECLIENT_DBIP="System - MokoSuiteClient DB-IP"
PLG_SYSTEM_MOKOSUITE_DBIP_DESC="IP geolocation for MokoSuiteClient using DB-IP Lite databases." PLG_SYSTEM_MOKOSUITECLIENT_DBIP_DESC="IP geolocation for MokoSuiteClient using DB-IP Lite databases."
@@ -8,8 +8,8 @@
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version> <version>02.44.04</version>
<description>PLG_SYSTEM_MOKOSUITE_DBIP_DESC</description> <description>PLG_SYSTEM_MOKOSUITECLIENT_DBIP_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteClientDBIP</namespace> <namespace path="src">Moko\Plugin\System\MokoSuiteClientDBIP</namespace>
<files> <files>
@@ -28,26 +28,26 @@
<config> <config>
<fields name="params"> <fields name="params">
<fieldset name="basic" <fieldset name="basic"
label="PLG_SYSTEM_MOKOSUITE_DBIP_FIELDSET_BASIC" label="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_FIELDSET_BASIC"
description="PLG_SYSTEM_MOKOSUITE_DBIP_FIELDSET_BASIC_DESC"> description="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_FIELDSET_BASIC_DESC">
<field name="database_source" type="list" default="cdn" <field name="database_source" type="list" default="cdn"
label="PLG_SYSTEM_MOKOSUITE_DBIP_SOURCE_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_SOURCE_LABEL"
description="PLG_SYSTEM_MOKOSUITE_DBIP_SOURCE_DESC"> description="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_SOURCE_DESC">
<option value="cdn">PLG_SYSTEM_MOKOSUITE_DBIP_SOURCE_CDN</option> <option value="cdn">PLG_SYSTEM_MOKOSUITECLIENT_DBIP_SOURCE_CDN</option>
<option value="local">PLG_SYSTEM_MOKOSUITE_DBIP_SOURCE_LOCAL</option> <option value="local">PLG_SYSTEM_MOKOSUITECLIENT_DBIP_SOURCE_LOCAL</option>
</field> </field>
<field name="database_level" type="list" default="country" <field name="database_level" type="list" default="country"
label="PLG_SYSTEM_MOKOSUITE_DBIP_DATABASE_LEVEL_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_DATABASE_LEVEL_LABEL"
description="PLG_SYSTEM_MOKOSUITE_DBIP_DATABASE_LEVEL_DESC"> description="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_DATABASE_LEVEL_DESC">
<option value="country">PLG_SYSTEM_MOKOSUITE_DBIP_DATABASE_COUNTRY</option> <option value="country">PLG_SYSTEM_MOKOSUITECLIENT_DBIP_DATABASE_COUNTRY</option>
<option value="city">PLG_SYSTEM_MOKOSUITE_DBIP_DATABASE_CITY</option> <option value="city">PLG_SYSTEM_MOKOSUITECLIENT_DBIP_DATABASE_CITY</option>
</field> </field>
<field name="auto_update" type="radio" default="1" <field name="auto_update" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_DBIP_AUTO_UPDATE_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_AUTO_UPDATE_LABEL"
description="PLG_SYSTEM_MOKOSUITE_DBIP_AUTO_UPDATE_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_AUTO_UPDATE_DESC"
class="btn-group btn-group-yesno" class="btn-group btn-group-yesno"
showon="database_source:cdn"> showon="database_source:cdn">
<option value="1">JYES</option> <option value="1">JYES</option>
@@ -56,15 +56,15 @@
<field name="cdn_url" type="url" <field name="cdn_url" type="url"
default="https://git.mokoconsulting.tech/MokoConsulting/geoip-data/releases/download/latest/dbip-city-lite.mmdb" default="https://git.mokoconsulting.tech/MokoConsulting/geoip-data/releases/download/latest/dbip-city-lite.mmdb"
label="PLG_SYSTEM_MOKOSUITE_DBIP_CDN_URL_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_CDN_URL_LABEL"
description="PLG_SYSTEM_MOKOSUITE_DBIP_CDN_URL_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_CDN_URL_DESC"
filter="url" filter="url"
showon="database_source:cdn" /> showon="database_source:cdn" />
<field name="local_path" type="text" <field name="local_path" type="text"
default="" default=""
label="PLG_SYSTEM_MOKOSUITE_DBIP_LOCAL_PATH_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_LOCAL_PATH_LABEL"
description="PLG_SYSTEM_MOKOSUITE_DBIP_LOCAL_PATH_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_LOCAL_PATH_DESC"
filter="path" filter="path"
showon="database_source:local" /> showon="database_source:local" />
@@ -8,8 +8,8 @@
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version> <version>02.44.04</version>
<description>PLG_SYSTEM_MOKOSUITE_DEVTOOLS_DESC</description> <description>PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteClientDevTools</namespace> <namespace path="src">Moko\Plugin\System\MokoSuiteClientDevTools</namespace>
<files> <files>
@@ -26,36 +26,36 @@
<config> <config>
<fields name="params"> <fields name="params">
<fieldset name="basic" <fieldset name="basic"
label="PLG_SYSTEM_MOKOSUITE_DEVTOOLS_FIELDSET_BASIC" label="PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_FIELDSET_BASIC"
description="PLG_SYSTEM_MOKOSUITE_DEVTOOLS_FIELDSET_BASIC_DESC"> description="PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_FIELDSET_BASIC_DESC">
<field name="dev_mode" type="radio" default="0" <field name="dev_mode" type="radio" default="0"
label="PLG_SYSTEM_MOKOSUITE_DEVTOOLS_DEV_MODE_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_DEV_MODE_LABEL"
description="PLG_SYSTEM_MOKOSUITE_DEVTOOLS_DEV_MODE_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_DEV_MODE_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
</field> </field>
<field name="reset_hits" type="radio" default="0" <field name="reset_hits" type="radio" default="0"
label="PLG_SYSTEM_MOKOSUITE_DEVTOOLS_RESET_HITS_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_RESET_HITS_LABEL"
description="PLG_SYSTEM_MOKOSUITE_DEVTOOLS_RESET_HITS_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_RESET_HITS_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
</field> </field>
<field name="delete_versions" type="radio" default="0" <field name="delete_versions" type="radio" default="0"
label="PLG_SYSTEM_MOKOSUITE_DEVTOOLS_DELETE_VERSIONS_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_DELETE_VERSIONS_LABEL"
description="PLG_SYSTEM_MOKOSUITE_DEVTOOLS_DELETE_VERSIONS_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_DELETE_VERSIONS_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
</field> </field>
<field name="reset_download_keys" type="radio" default="0" <field name="reset_download_keys" type="radio" default="0"
label="PLG_SYSTEM_MOKOSUITE_DEVTOOLS_RESET_DLKEYS_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_RESET_DLKEYS_LABEL"
description="PLG_SYSTEM_MOKOSUITE_DEVTOOLS_RESET_DLKEYS_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_RESET_DLKEYS_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
@@ -8,8 +8,8 @@
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version> <version>02.44.04</version>
<description>PLG_SYSTEM_MOKOSUITE_FIREWALL_DESC</description> <description>PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteClientFirewall</namespace> <namespace path="src">Moko\Plugin\System\MokoSuiteClientFirewall</namespace>
<files> <files>
@@ -36,25 +36,25 @@
<fields name="params"> <fields name="params">
<!-- Network & Session --> <!-- Network & Session -->
<fieldset name="basic" <fieldset name="basic"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_BASIC" label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_BASIC"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_BASIC_DESC"> description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_BASIC_DESC">
<field name="force_https" type="radio" default="1" <field name="force_https" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_FORCE_HTTPS_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FORCE_HTTPS_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_FORCE_HTTPS_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FORCE_HTTPS_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
</field> </field>
<field name="admin_session_timeout" type="number" <field name="admin_session_timeout" type="number"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_SESSION_TIMEOUT_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_SESSION_TIMEOUT_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_SESSION_TIMEOUT_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_SESSION_TIMEOUT_DESC"
default="60" hint="Minutes (0 = Joomla default)" /> default="60" hint="Minutes (0 = Joomla default)" />
<field name="trusted_ips" type="subform" <field name="trusted_ips" type="subform"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_TRUSTED_IPS_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_TRUSTED_IPS_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_TRUSTED_IPS_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_TRUSTED_IPS_DESC"
formsource="plugins/system/mokosuiteclient_firewall/forms/trusted_ip_entry.xml" formsource="plugins/system/mokosuiteclient_firewall/forms/trusted_ip_entry.xml"
multiple="true" multiple="true"
layout="joomla.form.field.subform.repeatable-table" layout="joomla.form.field.subform.repeatable-table"
@@ -64,20 +64,20 @@
<!-- WAF Shields --> <!-- WAF Shields -->
<fieldset name="waf" <fieldset name="waf"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_WAF" label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_WAF"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_WAF_DESC"> description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_WAF_DESC">
<field name="waf_enabled" type="radio" default="1" <field name="waf_enabled" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_ENABLED_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_ENABLED_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_ENABLED_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_ENABLED_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
</field> </field>
<field name="waf_sqli" type="radio" default="1" <field name="waf_sqli" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_SQLI_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_SQLI_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_SQLI_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_SQLI_DESC"
class="btn-group btn-group-yesno" class="btn-group btn-group-yesno"
showon="waf_enabled:1"> showon="waf_enabled:1">
<option value="1">JYES</option> <option value="1">JYES</option>
@@ -85,8 +85,8 @@
</field> </field>
<field name="waf_xss" type="radio" default="1" <field name="waf_xss" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_XSS_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_XSS_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_XSS_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_XSS_DESC"
class="btn-group btn-group-yesno" class="btn-group btn-group-yesno"
showon="waf_enabled:1"> showon="waf_enabled:1">
<option value="1">JYES</option> <option value="1">JYES</option>
@@ -94,8 +94,8 @@
</field> </field>
<field name="waf_mua" type="radio" default="1" <field name="waf_mua" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_MUA_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_MUA_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_MUA_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_MUA_DESC"
class="btn-group btn-group-yesno" class="btn-group btn-group-yesno"
showon="waf_enabled:1"> showon="waf_enabled:1">
<option value="1">JYES</option> <option value="1">JYES</option>
@@ -103,15 +103,15 @@
</field> </field>
<field name="waf_mua_blocklist" type="textarea" <field name="waf_mua_blocklist" type="textarea"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_MUA_LIST_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_MUA_LIST_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_MUA_LIST_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_MUA_LIST_DESC"
rows="4" filter="raw" rows="4" filter="raw"
default="sqlmap,nikto,nmap,havij,w3af,acunetix,nessus,openvas,masscan,gobuster,dirbuster,wpscan,joomscan" default="sqlmap,nikto,nmap,havij,w3af,acunetix,nessus,openvas,masscan,gobuster,dirbuster,wpscan,joomscan"
showon="waf_enabled:1[AND]waf_mua:1" /> showon="waf_enabled:1[AND]waf_mua:1" />
<field name="waf_rfi" type="radio" default="1" <field name="waf_rfi" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_RFI_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_RFI_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_RFI_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_RFI_DESC"
class="btn-group btn-group-yesno" class="btn-group btn-group-yesno"
showon="waf_enabled:1"> showon="waf_enabled:1">
<option value="1">JYES</option> <option value="1">JYES</option>
@@ -119,8 +119,8 @@
</field> </field>
<field name="waf_dfi" type="radio" default="1" <field name="waf_dfi" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_DFI_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_DFI_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_DFI_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_DFI_DESC"
class="btn-group btn-group-yesno" class="btn-group btn-group-yesno"
showon="waf_enabled:1"> showon="waf_enabled:1">
<option value="1">JYES</option> <option value="1">JYES</option>
@@ -130,8 +130,8 @@
<!-- Security Headers --> <!-- Security Headers -->
<fieldset name="headers" <fieldset name="headers"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_HEADERS" label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_HEADERS"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_HEADERS_DESC"> description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_HEADERS_DESC">
<field name="header_xframe" type="radio" default="1" <field name="header_xframe" type="radio" default="1"
label="X-Frame-Options" description="Clickjacking protection (SAMEORIGIN)" label="X-Frame-Options" description="Clickjacking protection (SAMEORIGIN)"
@@ -177,12 +177,12 @@
<!-- Access Control --> <!-- Access Control -->
<fieldset name="access_control" <fieldset name="access_control"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_ACCESS" label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_ACCESS"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_ACCESS_DESC"> description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_ACCESS_DESC">
<field name="ip_blocklist" type="subform" <field name="ip_blocklist" type="subform"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_IP_BLOCKLIST_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_IP_BLOCKLIST_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_IP_BLOCKLIST_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_IP_BLOCKLIST_DESC"
formsource="plugins/system/mokosuiteclient_firewall/forms/trusted_ip_entry.xml" formsource="plugins/system/mokosuiteclient_firewall/forms/trusted_ip_entry.xml"
multiple="true" multiple="true"
layout="joomla.form.field.subform.repeatable-table" layout="joomla.form.field.subform.repeatable-table"
@@ -190,13 +190,13 @@
buttons="add,remove,move" /> buttons="add,remove,move" />
<field name="admin_secret" type="text" <field name="admin_secret" type="text"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_ADMIN_SECRET_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_ADMIN_SECRET_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_ADMIN_SECRET_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_ADMIN_SECRET_DESC"
default="" filter="raw" hint="Leave empty to disable" /> default="" filter="raw" hint="Leave empty to disable" />
<field name="admin_secret_redirect" type="text" <field name="admin_secret_redirect" type="text"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_ADMIN_SECRET_REDIRECT_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_ADMIN_SECRET_REDIRECT_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_ADMIN_SECRET_REDIRECT_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_ADMIN_SECRET_REDIRECT_DESC"
default="" filter="url" hint="Empty = 403 Forbidden" default="" filter="url" hint="Empty = 403 Forbidden"
showon="admin_secret!:" /> showon="admin_secret!:" />
@@ -211,8 +211,8 @@
showon="autoban_threshold!:0" /> showon="autoban_threshold!:0" />
<field name="block_frontend_superuser" type="radio" default="0" <field name="block_frontend_superuser" type="radio" default="0"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_BLOCK_FE_SU_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_BLOCK_FE_SU_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_BLOCK_FE_SU_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_BLOCK_FE_SU_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
@@ -221,28 +221,28 @@
<!-- File & Template Protection --> <!-- File & Template Protection -->
<fieldset name="protection" <fieldset name="protection"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_PROTECTION" label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_PROTECTION"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_PROTECTION_DESC"> description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_PROTECTION_DESC">
<field name="block_sensitive_files" type="radio" default="1" <field name="block_sensitive_files" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_BLOCK_FILES_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_BLOCK_FILES_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_BLOCK_FILES_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_BLOCK_FILES_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
</field> </field>
<field name="block_direct_php" type="radio" default="1" <field name="block_direct_php" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_BLOCK_PHP_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_BLOCK_PHP_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_BLOCK_PHP_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_BLOCK_PHP_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
</field> </field>
<field name="block_template_switch" type="radio" default="1" <field name="block_template_switch" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_BLOCK_TMPL_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_BLOCK_TMPL_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_BLOCK_TMPL_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_BLOCK_TMPL_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
@@ -251,29 +251,29 @@
<!-- Password Policy --> <!-- Password Policy -->
<fieldset name="password_policy" <fieldset name="password_policy"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_PASSWORD" label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_PASSWORD"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_PASSWORD_DESC"> description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_PASSWORD_DESC">
<field name="password_min_length" type="number" default="12" <field name="password_min_length" type="number" default="12"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_PASSWORD_LENGTH_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_PASSWORD_LENGTH_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_PASSWORD_LENGTH_DESC" /> description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_PASSWORD_LENGTH_DESC" />
<field name="password_require_uppercase" type="radio" default="1" <field name="password_require_uppercase" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_PASSWORD_UPPER_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_PASSWORD_UPPER_LABEL"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
</field> </field>
<field name="password_require_number" type="radio" default="1" <field name="password_require_number" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_PASSWORD_NUMBER_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_PASSWORD_NUMBER_LABEL"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
</field> </field>
<field name="password_require_special" type="radio" default="1" <field name="password_require_special" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_PASSWORD_SPECIAL_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_PASSWORD_SPECIAL_LABEL"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
@@ -282,17 +282,17 @@
<!-- Upload Restrictions --> <!-- Upload Restrictions -->
<fieldset name="uploads" <fieldset name="uploads"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_UPLOADS" label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_UPLOADS"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_UPLOADS_DESC"> description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_UPLOADS_DESC">
<field name="upload_allowed_types" type="text" <field name="upload_allowed_types" type="text"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_UPLOAD_TYPES_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_UPLOAD_TYPES_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_UPLOAD_TYPES_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_UPLOAD_TYPES_DESC"
default="jpg,jpeg,png,gif,webp,svg,pdf,doc,docx,xls,xlsx" /> default="jpg,jpeg,png,gif,webp,svg,pdf,doc,docx,xls,xlsx" />
<field name="upload_max_size_mb" type="number" <field name="upload_max_size_mb" type="number"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_UPLOAD_SIZE_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_UPLOAD_SIZE_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_UPLOAD_SIZE_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_UPLOAD_SIZE_DESC"
default="100" /> default="100" />
</fieldset> </fieldset>
</fields> </fields>
@@ -8,7 +8,7 @@
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version> <version>02.44.04</version>
<description>PLG_SYSTEM_MOKOSUITECLIENT_LICENSE_DESC</description> <description>PLG_SYSTEM_MOKOSUITECLIENT_LICENSE_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteClientLicense</namespace> <namespace path="src">Moko\Plugin\System\MokoSuiteClientLicense</namespace>
<files><folder>src</folder><folder>services</folder><folder>language</folder></files> <files><folder>src</folder><folder>services</folder><folder>language</folder></files>
@@ -8,8 +8,8 @@
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version> <version>02.44.04</version>
<description>PLG_SYSTEM_MOKOSUITE_MONITOR_DESC</description> <description>PLG_SYSTEM_MOKOSUITECLIENT_MONITOR_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteClientMonitor</namespace> <namespace path="src">Moko\Plugin\System\MokoSuiteClientMonitor</namespace>
<files> <files>
@@ -26,12 +26,12 @@
<config> <config>
<fields name="params"> <fields name="params">
<fieldset name="basic" <fieldset name="basic"
label="PLG_SYSTEM_MOKOSUITE_MONITOR_FIELDSET_BASIC" label="PLG_SYSTEM_MOKOSUITECLIENT_MONITOR_FIELDSET_BASIC"
description="PLG_SYSTEM_MOKOSUITE_MONITOR_FIELDSET_BASIC_DESC"> description="PLG_SYSTEM_MOKOSUITECLIENT_MONITOR_FIELDSET_BASIC_DESC">
<field name="heartbeat_enabled" type="radio" default="1" <field name="heartbeat_enabled" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_MONITOR_HEARTBEAT_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_MONITOR_HEARTBEAT_LABEL"
description="PLG_SYSTEM_MOKOSUITE_MONITOR_HEARTBEAT_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_MONITOR_HEARTBEAT_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
@@ -39,12 +39,12 @@
<field name="base_url" type="url" <field name="base_url" type="url"
default="https://waas.dev.mokoconsulting.tech" default="https://waas.dev.mokoconsulting.tech"
label="PLG_SYSTEM_MOKOSUITE_MONITOR_BASE_URL_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_MONITOR_BASE_URL_LABEL"
description="PLG_SYSTEM_MOKOSUITE_MONITOR_BASE_URL_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_MONITOR_BASE_URL_DESC"
filter="url" /> filter="url" />
<field name="signing_key" type="hidden" <field name="signing_key" type="hidden"
default="LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tDQpNSUlFdmdJQkFEQU5CZ2txaGtpRzl3MEJBUUVGQUFTQ0JLZ3dnZ1NrQWdFQUFvSUJBUUMvcnVrWE0zZHB0aDg2DQpGSkRXTjM0ZjQ2cUtJem1SMmFtTWUyZ2dWbWxsWnFyMHJkRFk4OTdtQ05FRkk4Q0NwNGR5amkwOU5ETnAvalFxDQovL2JGdUFNOUFTZU5oQTlmRlpwSG5UMGkzY3N4V3RSS2NnMnRkR0wzUXhNRFVBeFJYQ1RSQXVPSWZybGp6Ky85DQpWZ0ZtWHU3M1VSaU9XY1lLeFErejFoZkRGK2ZxRTRlYW9QcUlsY2J5dmtKd2lkSkRWUEEwc2RtbVlUTFg2Q29xDQpQalVDRENlbkZoUXNteVMzM29KSXArK0c3ZzU5NmRYelZIczRQSjIwNnc0Z3JlckRRZk5GVytzZndHSnl3NjBrDQpUQTVmUzF2Wit4NEt2UUh6V1ErYS9xRS9sSGxFVzdOTWVJWExNWGczSDd1eXBabXlVU2t3S0k0djFQQXRGWmtkDQpBaVpPZWZpVkFnTUJBQUVDZ2dFQVI4VGJyVDR0NWJ5MDhIQW0wcTR3WVF4REhEbVlJbzNXdDZ5MURmYU11OVMzDQpDYW5TMm9oazJzaE9TcGhhU2hFajI3WjBKY2hYdjhYWURvbU1BZmVsN3I5eDZjQ2FhTVdUNEdCMU5Zckp1NDhBDQprV2NteTkwWitPNTZQZkZJeTJXdXV6dFRxaFdZb0ZDSTBOZlU2bGw5SzhpSFl6VWx1MzZSSklweWx5OXFPKyt4DQpmTUZYcUovSkk0bVp6NW0raDBnbFMvN21VZ0EvUTRjbVJnRHJ3dkc3bEpBRjhWSDBEdW1uRWJkWkZvSi9XbU9JDQpSTi9lemhqczYrbU9hTnUwQWRsclpLU3QwRWZVYjl3QTFLQm5JMVVDU2w0Y1lidXVpL29jOWo1aGl6RGJvRWRyDQpJL1U5Y2FYUmZvb0pMNlUwOXN1VTdyTlFLbFRhMXM4NVhvY3htT0JMK1FLQmdRRHg5QzB5MjQ5SG1paXJ2WExIDQpBUXdUTjRyMjdhUTZMMFc2SHdDNHdzMUhleDRpeWRXT1lIcWdBSnY4VHZyeVpHOW1SaFh1U1ROTjYxV1UvTWFNDQphQVYwVjJ4Y0RrdDNFUnhNak1XRmhXUTh0cjN2RUtqWjFnOVJXOGhiTE9VYXVCcmJhMlI4RWNZYXFLZXlxR3N4DQpCa0VLZlRIUzNmUysraXNLZ2EzUU1mcjB6d0tCZ1FES3o2SGVKZ0tKRTVMM1ppbkhxaUFyVm5SZ2pYcFZrMWpvDQp6VXh5eTkwNEhmNGlmVXNIZklpdzVpN0VNR0U0RE5ob2MvZUJxcW1oM1N2ejJMUDNzOHUrL0hVZFllVzJIV1hhDQpKZlpMRE5BM0U3WDNkSVJ6MFg5UTh2OHcxaFpQeUxYOUlYeUVyUTNGZHFVdyt1Tko1VFZJell0RHppNnRKTjkvDQpGZGlxS0Q2ZFd3S0JnUURnQnE5bS9LWmdyTnRsa1FkYVBaejVtaDhBWGE4RzlNaEIrZnpJRmc3T1ZhL2tsQzg1DQpJaG5JVm1nWHFPVndWQkJWaVNVN09lbllCc042TE1hR01MYUVMNEkwaGtQWG5pOHVyZFVodVEzRHJZeVZjejUwDQpYR0JZZTN3Njk0bTJRS3NWYVExa1YyeXZPR1AxNXoxQTZrS0V2TURLTnhzclRTVlhHQlZneFRaUlB3S0JnUURBDQp1RFVVcUFIWXlDVHJ1c1VRMm5UZk9iUTAyN3ZYL2NDSzJDdEJHc0FJUjFmcTVpeVozSmozb0lQb0lpRC81aFR1DQpqT1F3N3o5cWRJVURublRGZUxDdnQ2NkNVVGk3cVl2VGxDZEtnYzZKeDgwdWJDWkErRjZIU2FGOWdyS0k5aTBaDQpjT3ltRnR2elBCOFZRQk1qY1E4Rk0yeVc3aUlrbmRsVEppdFE1aFU1NlFLQmdEZ1JIOXBEcGZwWlZ2V2g2MldGDQp5OGZzWUo1ODhzQmRMUlpTYTRuNi9XbjdUcUp1bWg2aWpFcDVyZFdnQkVtaDlJSk9jRUlhZ05mK0s5MXdoaThvDQpTeW01ajJpL1pjVVFYNFJSTDNxQ1RZZWVQVnZ3RHc3aWNLWVowTGQ2S1pFMmdEaDRPbEg4ejU0Zkl3a2tMSzRFDQpCcmtJNWppa05QSkJFR25zTm9zU3pWN2QNCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0NCg==" default="LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2QUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktZd2dnU2lBZ0VBQW9JQkFRQ2xZNnNzOTZpeTZOOGMKTHRxbndhbnU4eEozdDcrdDhXT3hoY0Yyclc2QmlmOVhNaEpnYkw0c055N0wwV1dTT2tkMmZxalBNcDFtOFNyNAo1VnNycjE3cFc5b0FNMmtmdFdsaTZ1NkhTVEYyN2pVVUJrT3o4MHZMRklMMGNGNkJCUkpYN2JVWkRpamdUMjc1ClREb3dXZy82Zk9GeWFEelBHUkJuYXFacTljU2lEYWoyNlpSTVZIbktQUERWTG92VzRPTDQzL2gwZ3BtN25nUGIKdWJlLzFFTDRUMHFRbm1Xc2FEOFZ6VStoRXFGSDRTVUtMaDVNeklGbUxFZzRlZ0xCbTBXcWdxbzZRQVBnZDVPYgoybXhmQndta3RLVm5hcWR6eG9KSytzaTVuZkYreGpxbWRMZThUdmEyTHNuTUxlZmsrODVoQ3hxS2x1eWRta1lXCjlvUk5qcDhiQWdNQkFBRUNnZ0VBQkZOUS9NSVZaV2gxdlZUMFh3TFBvUEkyZjI4TTBrM0gzN0t4MXBxK2t5QzYKenRyK1pBczBCaEFEWjAwNHJOUmRYaG45N0QxVXBJYVdLeUJFZkNZQUEzWmxneS9WQmdGR21sR3VuMWNvdGdXUQoyYzg0SWhLdzNzVFFqL2dJWUxOelFWMTBLUTJYd0JZVHZ1MWhjRFpLeUxCUGJTQ1F4cEhQUGdVcUNRNFljR3lFClErVmc1dHJUYk8wQ2xCZ1U5bkVnYU1RakRJZ0F3WVZPV203dUxJTW84UC9nT3FuT2tmaFhzdzl3VTJVYWxFeTEKRmRZbGhMbGJ0ZS9MZ3lkYlJ2RStjNEtqZVp0Z3ptc1RneEh2dzM5YVVmZUZTclFRT0FjcXc0alNzUjdMck9UZAp5bDhpelRrZVBrTVFMamFqR0pabWdPbitkRzhtUlpMa3FKcWdGaVpqRVFLQmdRRFV0L0xlU0h5SmhvY3VFL240CkZreEpaclJoWUVsWnc2WlZJUnQzWDlPQ1Nmaklab3I1ZkZlczhvUzZySFhKdGZYeWx4QUxOSjJjTUhKTTViVnUKbUFSUFU4cThBeVc0OE03cHAyNmtVVTMxNXc2OU1SUkhzbWgyekRabEtDeG5GM1NSQ3U4YW95d3hZc3RUZ3hkTgo2bDhLNHZsS1dsN3FYblBhWjZjb3lQSU9od0tCZ1FESENuRmRRdW5SMVI2dkxGaVFZMTRiT3QwT0tzVGJYMUJyCmpvUGZySkxvRm5mSCs4VDVnNUdxYkV5T2p0WG1tRXhmTFFpcDBQVXRtc1E0YXlJRFBZYWZtU3RpK2dtQXZFd1MKZTlKcVYxYlRuazUrYnVRZ2FlOW16REpJWkxaczRJUlhrd1Q5aDZ4Q2xKeS80TGJSRHdBU3dUVGJlY01hN3A4UgpQN0p0bjdsYnpRS0JnQzNOR2FjUTFuZktGb3N1VS9FOTQ5a2VHeEtvWjhMREpLcEp3WjgzYTlRdTF6bFhFdTlhCi9ZbklnaG1yam9VSy85VG0vOVpaMHVIUmNKcnNEdCtzTGFsaThsRC9JSDBzcEhDYzAyN2Y3cmhXc3M2N3BaRTIKY2RXNmJLL2xNWUpWQTQxRFhHNVEyZkFjUklsTHZaWFNNL3FsR21ZUEJVYlRaWUNPTnVqS000dzdBb0dBU1dBdwpLcEZnWVZxUDFVUWo0aGEvdW9vWXRBQlFVZzd4TnJWektDSVdoampDTDVkQkpqcTZtSGtVUC9tb0lUcEQ3VkpNCnYwMnBGUWJaRDNOdk5vS1gvbjRZNElRTXZNaXR3cUtqRDFEalVXQXF6N0ZScUNGbGdDQUc2V2szVnl2dG5kczEKRzhISVgwTXFCaEp4VXVDVXhsVXpoelY4RjVHZ1VsdUpDNkMyVklFQ2dZQkJWSkxpZlNVOTlHWGZtK3dPd0RWcgo2bHZoUFgxOTBGVktWQXY3aVVWTXBwWXg4Y0QxYkcyUjRLT29JbnkxYTlxdjA2ZGFzeGVQOStkVjJVMWU3MWl5CkFXWDRBVHIrYitvSGk2eUk1MXRHRk54RUxiNXZYMVpYM3VNaDlWM29iYUpuSFNjYllpKzBBNjlyRmNuNEZuLzUKWXJybWxLTzRlRHFVZkswbVFJVCtwUT09Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K"
filter="raw" /> filter="raw" />
</fieldset> </fieldset>
</fields> </fields>
@@ -187,11 +187,11 @@ class Monitor extends CMSPlugin implements SubscriberInterface
if ($signature !== null) if ($signature !== null)
{ {
$headers[] = 'X-MokoSuiteClient-Signature: ' . $signature; $headers[] = 'X-MokoSuite-Signature: ' . $signature;
$headers[] = 'X-MokoSuiteClient-Timestamp: ' . $timestamp; $headers[] = 'X-MokoSuite-Timestamp: ' . $timestamp;
} }
$endpoint = $baseUrl . '/api/index.php/v1/mokosuiteclienthq/heartbeat'; $endpoint = $baseUrl . '/api/index.php/v1/mokosuitehq/heartbeat';
$json = json_encode($payload, JSON_UNESCAPED_SLASHES); $json = json_encode($payload, JSON_UNESCAPED_SLASHES);
try try
@@ -8,8 +8,8 @@
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version> <version>02.44.04</version>
<description>PLG_SYSTEM_MOKOSUITE_OFFLINE_DESC</description> <description>PLG_SYSTEM_MOKOSUITECLIENT_OFFLINE_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteClientOffline</namespace> <namespace path="src">Moko\Plugin\System\MokoSuiteClientOffline</namespace>
<files> <files>
@@ -25,15 +25,15 @@
<config> <config>
<fields name="params" addfieldprefix="Moko\Plugin\System\MokoSuiteClientOffline\Field"> <fields name="params" addfieldprefix="Moko\Plugin\System\MokoSuiteClientOffline\Field">
<fieldset name="basic" label="PLG_SYSTEM_MOKOSUITE_OFFLINE_FIELDSET_BASIC"> <fieldset name="basic" label="PLG_SYSTEM_MOKOSUITECLIENT_OFFLINE_FIELDSET_BASIC">
<field name="tos_slug" type="menuslug" <field name="tos_slug" type="menuslug"
label="PLG_SYSTEM_MOKOSUITE_OFFLINE_SLUG_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_OFFLINE_SLUG_LABEL"
description="PLG_SYSTEM_MOKOSUITE_OFFLINE_SLUG_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_OFFLINE_SLUG_DESC"
multiple="true" /> multiple="true" />
<field name="include_children" type="radio" default="1" <field name="include_children" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_OFFLINE_CHILDREN_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_OFFLINE_CHILDREN_LABEL"
description="PLG_SYSTEM_MOKOSUITE_OFFLINE_CHILDREN_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_OFFLINE_CHILDREN_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
@@ -8,8 +8,8 @@
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version> <version>02.44.04</version>
<description>PLG_SYSTEM_MOKOSUITE_TENANT_DESC</description> <description>PLG_SYSTEM_MOKOSUITECLIENT_TENANT_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteClientTenant</namespace> <namespace path="src">Moko\Plugin\System\MokoSuiteClientTenant</namespace>
<files> <files>
@@ -26,20 +26,20 @@
<config> <config>
<fields name="params"> <fields name="params">
<fieldset name="basic" <fieldset name="basic"
label="PLG_SYSTEM_MOKOSUITE_TENANT_FIELDSET_BASIC" label="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_FIELDSET_BASIC"
description="PLG_SYSTEM_MOKOSUITE_TENANT_FIELDSET_BASIC_DESC"> description="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_FIELDSET_BASIC_DESC">
<field name="restrict_installer" type="radio" default="1" <field name="restrict_installer" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_TENANT_RESTRICT_INSTALLER_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_RESTRICT_INSTALLER_LABEL"
description="PLG_SYSTEM_MOKOSUITE_TENANT_RESTRICT_INSTALLER_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_RESTRICT_INSTALLER_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
</field> </field>
<field name="allow_extension_updates" type="radio" default="1" <field name="allow_extension_updates" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_TENANT_ALLOW_UPDATES_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_ALLOW_UPDATES_LABEL"
description="PLG_SYSTEM_MOKOSUITE_TENANT_ALLOW_UPDATES_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_ALLOW_UPDATES_DESC"
class="btn-group btn-group-yesno" class="btn-group btn-group-yesno"
showon="restrict_installer:1"> showon="restrict_installer:1">
<option value="1">JYES</option> <option value="1">JYES</option>
@@ -47,40 +47,40 @@
</field> </field>
<field name="hide_sysinfo" type="radio" default="1" <field name="hide_sysinfo" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_TENANT_HIDE_SYSINFO_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_HIDE_SYSINFO_LABEL"
description="PLG_SYSTEM_MOKOSUITE_TENANT_HIDE_SYSINFO_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_HIDE_SYSINFO_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
</field> </field>
<field name="restrict_global_config" type="radio" default="1" <field name="restrict_global_config" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_TENANT_RESTRICT_CONFIG_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_RESTRICT_CONFIG_LABEL"
description="PLG_SYSTEM_MOKOSUITE_TENANT_RESTRICT_CONFIG_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_RESTRICT_CONFIG_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
</field> </field>
<field name="restrict_template_editing" type="radio" default="1" <field name="restrict_template_editing" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_TENANT_RESTRICT_TEMPLATE_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_RESTRICT_TEMPLATE_LABEL"
description="PLG_SYSTEM_MOKOSUITE_TENANT_RESTRICT_TEMPLATE_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_RESTRICT_TEMPLATE_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
</field> </field>
<field name="disable_install_url" type="radio" default="1" <field name="disable_install_url" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_TENANT_DISABLE_INSTALL_URL_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_DISABLE_INSTALL_URL_LABEL"
description="PLG_SYSTEM_MOKOSUITE_TENANT_DISABLE_INSTALL_URL_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_DISABLE_INSTALL_URL_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
</field> </field>
<field name="hidden_menu_items" type="textarea" <field name="hidden_menu_items" type="textarea"
label="PLG_SYSTEM_MOKOSUITE_TENANT_HIDDEN_MENUS_LABEL" label="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_HIDDEN_MENUS_LABEL"
description="PLG_SYSTEM_MOKOSUITE_TENANT_HIDDEN_MENUS_DESC" description="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_HIDDEN_MENUS_DESC"
rows="5" filter="raw" /> rows="5" filter="raw" />
</fieldset> </fieldset>
</fields> </fields>
@@ -2,3 +2,7 @@ PLG_TASK_MOKOSUITECLIENT_TICKETS="Task - MokoSuiteClient Ticket Automation"
PLG_TASK_MOKOSUITECLIENT_TICKETS_DESC="Runs scheduled helpdesk automation rules." PLG_TASK_MOKOSUITECLIENT_TICKETS_DESC="Runs scheduled helpdesk automation rules."
PLG_TASK_MOKOSUITECLIENT_TICKETS_AUTOMATION_TITLE="MokoSuiteClient: Ticket Automation" PLG_TASK_MOKOSUITECLIENT_TICKETS_AUTOMATION_TITLE="MokoSuiteClient: Ticket Automation"
PLG_TASK_MOKOSUITECLIENT_TICKETS_AUTOMATION_DESC="Runs time-based automation rules against open tickets (auto-close, SLA escalation, etc.)." PLG_TASK_MOKOSUITECLIENT_TICKETS_AUTOMATION_DESC="Runs time-based automation rules against open tickets (auto-close, SLA escalation, etc.)."
PLG_TASK_MOKOSUITECLIENT_TICKETS_IMAP_POLL_TITLE="MokoSuiteClient: IMAP Email Polling"
PLG_TASK_MOKOSUITECLIENT_TICKETS_IMAP_POLL_DESC="Polls an IMAP inbox for new emails and creates tickets or replies from unread messages."
PLG_TASK_MOKOSUITECLIENT_TICKETS_AUTOCLOSE_TITLE="MokoSuiteClient: Auto-Close Resolved Tickets"
PLG_TASK_MOKOSUITECLIENT_TICKETS_AUTOCLOSE_DESC="Automatically closes tickets that have been in resolved status longer than the configured number of days."
@@ -8,7 +8,7 @@
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version> <version>02.44.04</version>
<description>Runs scheduled helpdesk automation rules — auto-close resolved tickets, SLA breach escalation, and time-based actions.</description> <description>Runs scheduled helpdesk automation rules — auto-close resolved tickets, SLA breach escalation, and time-based actions.</description>
<namespace path="src">Moko\Plugin\Task\MokoSuiteClientTickets</namespace> <namespace path="src">Moko\Plugin\Task\MokoSuiteClientTickets</namespace>
@@ -27,15 +27,15 @@ class TicketAutomation extends CMSPlugin implements SubscriberInterface
protected const TASKS_MAP = [ protected const TASKS_MAP = [
'mokosuiteclient.ticket.automation' => [ 'mokosuiteclient.ticket.automation' => [
'langConstPrefix' => 'PLG_TASK_MOKOSUITE_TICKETS_AUTOMATION', 'langConstPrefix' => 'PLG_TASK_MOKOSUITECLIENT_TICKETS_AUTOMATION',
'method' => 'runAutomation', 'method' => 'runAutomation',
], ],
'mokosuiteclient.ticket.imap_poll' => [ 'mokosuiteclient.ticket.imap_poll' => [
'langConstPrefix' => 'PLG_TASK_MOKOSUITE_TICKETS_IMAP_POLL', 'langConstPrefix' => 'PLG_TASK_MOKOSUITECLIENT_TICKETS_IMAP_POLL',
'method' => 'runImapPoll', 'method' => 'runImapPoll',
], ],
'mokosuiteclient.ticket.autoclose' => [ 'mokosuiteclient.ticket.autoclose' => [
'langConstPrefix' => 'PLG_TASK_MOKOSUITE_TICKETS_AUTOCLOSE', 'langConstPrefix' => 'PLG_TASK_MOKOSUITECLIENT_TICKETS_AUTOCLOSE',
'method' => 'runAutoClose', 'method' => 'runAutoClose',
], ],
]; ];
@@ -251,6 +251,7 @@ class TicketAutomation extends CMSPlugin implements SubscriberInterface
} }
catch (\Throwable $e) catch (\Throwable $e)
{ {
Log::add('Failed to load component config: ' . $e->getMessage(), Log::ERROR, 'mokosuiteclient');
return []; return [];
} }
} }
@@ -12,8 +12,8 @@
<license>GNU General Public License version 3 or later; see LICENSE</license> <license>GNU General Public License version 3 or later; see LICENSE</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version> <version>02.44.04</version>
<description>PLG_TASK_MOKOSUITEDEMO_DESC</description> <description>PLG_TASK_MOKOSUITECLIENTDEMO_DESC</description>
<namespace path="src">Moko\Plugin\Task\MokoSuiteClientDemo</namespace> <namespace path="src">Moko\Plugin\Task\MokoSuiteClientDemo</namespace>
<files> <files>
@@ -10,7 +10,7 @@
* INGROUP: MokoSuiteClient * INGROUP: MokoSuiteClient
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient
* PATH: /src/packages/plg_system_mokosuiteclient/Service/DemoResetService.php * PATH: /src/packages/plg_system_mokosuiteclient/Service/DemoResetService.php
* VERSION: 02.34.84 * VERSION: 02.44.04
* BRIEF: Content-only snapshot/restore for demo site reset * BRIEF: Content-only snapshot/restore for demo site reset
*/ */
@@ -12,8 +12,8 @@
<license>GNU General Public License version 3 or later; see LICENSE</license> <license>GNU General Public License version 3 or later; see LICENSE</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version> <version>02.44.04</version>
<description>PLG_TASK_MOKOSUITESYNC_DESC</description> <description>PLG_TASK_MOKOSUITECLIENTSYNC_DESC</description>
<namespace path="src">Moko\Plugin\Task\MokoSuiteClientSync</namespace> <namespace path="src">Moko\Plugin\Task\MokoSuiteClientSync</namespace>
<files> <files>
@@ -10,7 +10,7 @@
* INGROUP: MokoSuiteClient * INGROUP: MokoSuiteClient
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient
* PATH: /src/packages/plg_system_mokosuiteclient/Service/ContentSyncReceiver.php * PATH: /src/packages/plg_system_mokosuiteclient/Service/ContentSyncReceiver.php
* VERSION: 02.34.84 * VERSION: 02.44.04
* BRIEF: Receiver-side content sync — applies incoming payload to local DB * BRIEF: Receiver-side content sync — applies incoming payload to local DB
*/ */
@@ -10,7 +10,7 @@
* INGROUP: MokoSuiteClient * INGROUP: MokoSuiteClient
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient
* PATH: /src/packages/plg_system_mokosuiteclient/Service/ContentSyncService.php * PATH: /src/packages/plg_system_mokosuiteclient/Service/ContentSyncService.php
* VERSION: 02.34.84 * VERSION: 02.44.04
* BRIEF: Sender-side content sync — builds payload and pushes to remote sites * BRIEF: Sender-side content sync — builds payload and pushes to remote sites
*/ */
@@ -7,7 +7,7 @@
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version> <version>02.44.04</version>
<description>Joomla Web Services API routes for MokoSuiteClient site management — health checks, cache, updates, backups, and site info.</description> <description>Joomla Web Services API routes for MokoSuiteClient site management — health checks, cache, updates, backups, and site info.</description>
<namespace path="src">Moko\Plugin\WebServices\MokoSuiteClient</namespace> <namespace path="src">Moko\Plugin\WebServices\MokoSuiteClient</namespace>
<files> <files>
+1 -1
View File
@@ -2,7 +2,7 @@
<extension type="package" method="upgrade"> <extension type="package" method="upgrade">
<name>Package - MokoSuiteClient</name> <name>Package - MokoSuiteClient</name>
<packagename>mokosuiteclient</packagename> <packagename>mokosuiteclient</packagename>
<version>02.34.84-dev</version> <version>02.44.04</version>
<creationDate>2026-06-02</creationDate> <creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
+185 -36
View File
@@ -20,7 +20,7 @@ use Joomla\CMS\Log\Log;
* *
* @since 2.2.0 * @since 2.2.0
*/ */
class Pkg_MokosuiteInstallerScript class Pkg_MokosuiteclientInstallerScript
{ {
/** /**
* Runs after package installation/update. * Runs after package installation/update.
@@ -70,6 +70,10 @@ class Pkg_MokosuiteInstallerScript
// Remove legacy extensions and migrate settings before retiring // Remove legacy extensions and migrate settings before retiring
$this->cleanupLegacyExtensions(); $this->cleanupLegacyExtensions();
$this->migrateStandalonePlugins(); $this->migrateStandalonePlugins();
// Migrate monitor params into core plugin BEFORE monitor row is deleted
$this->migrateMonitorParams();
$this->removeRetiredExtensions(); $this->removeRetiredExtensions();
$this->enablePlugin('system', 'mokosuiteclient'); $this->enablePlugin('system', 'mokosuiteclient');
@@ -78,6 +82,7 @@ class Pkg_MokosuiteInstallerScript
$this->enablePlugin('system', 'mokosuiteclient_devtools'); $this->enablePlugin('system', 'mokosuiteclient_devtools');
$this->enablePlugin('system', 'mokosuiteclient_offline'); $this->enablePlugin('system', 'mokosuiteclient_offline');
$this->enablePlugin('system', 'mokosuiteclient_dbip'); $this->enablePlugin('system', 'mokosuiteclient_dbip');
$this->enablePlugin('system', 'mokosuiteclient_backup');
$this->enablePlugin('webservices', 'mokosuiteclient'); $this->enablePlugin('webservices', 'mokosuiteclient');
$this->enablePlugin('task', 'mokosuiteclientdemo'); $this->enablePlugin('task', 'mokosuiteclientdemo');
$this->enablePlugin('task', 'mokosuiteclientsync'); $this->enablePlugin('task', 'mokosuiteclientsync');
@@ -468,6 +473,31 @@ class Pkg_MokosuiteInstallerScript
->where($db->quoteName('element') . ' = ' . $db->quote($element)); ->where($db->quoteName('element') . ' = ' . $db->quote($element));
$db->setQuery($query); $db->setQuery($query);
$db->execute(); $db->execute();
if ($db->getAffectedRows() > 0)
{
return;
}
// Row may exist with empty element (DEFAULT '' from preflight ALTER).
// Fix the element value and enable in one pass.
$manifestName = 'plg_' . $group . '_' . $element;
$fix = $db->getQuery(true)
->update($db->quoteName('#__extensions'))
->set($db->quoteName('element') . ' = ' . $db->quote($element))
->set($db->quoteName('enabled') . ' = 1')
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' . $db->quote($group))
->where('(' . $db->quoteName('element') . ' = ' . $db->quote('')
. ' OR ' . $db->quoteName('element') . ' IS NULL)')
->where($db->quoteName('name') . ' = ' . $db->quote($manifestName));
$db->setQuery($fix);
$db->execute();
if ($db->getAffectedRows() > 0)
{
Log::add('Fixed empty element for plugin ' . $group . '/' . $element, Log::NOTICE, 'mokosuiteclient');
}
} }
catch (\Throwable $e) catch (\Throwable $e)
{ {
@@ -505,6 +535,7 @@ class Pkg_MokosuiteInstallerScript
$db->quote('mokosuiteclientdemo'), $db->quote('mokosuiteclientdemo'),
$db->quote('mokosuiteclientsync'), $db->quote('mokosuiteclientsync'),
$db->quote('mokosuiteclient_tickets'), $db->quote('mokosuiteclient_tickets'),
$db->quote('mokosuiteclient_backup'),
$db->quote('mokoonyx'), $db->quote('mokoonyx'),
]; ];
@@ -800,35 +831,43 @@ class Pkg_MokosuiteInstallerScript
{ {
$db = Factory::getDbo(); $db = Factory::getDbo();
// Get health token from core plugin // All heartbeat config now lives in the core plugin params
$query = $db->getQuery(true) $query = $db->getQuery(true)
->select($db->quoteName('params')) ->select($db->quoteName('params'))
->from($db->quoteName('#__extensions')) ->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' = ' . $db->quote('mokosuiteclient')) ->where($db->quoteName('element') . ' = ' . $db->quote('mokosuiteclient'))
->where($db->quoteName('type') . ' = ' . $db->quote('plugin')) ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' . $db->quote('system')); ->where($db->quoteName('folder') . ' = ' . $db->quote('system'));
$coreParams = json_decode((string) $db->setQuery($query)->loadResult()); $rawParams = (string) $db->setQuery($query)->loadResult();
$coreParams = json_decode($rawParams);
if (!$coreParams)
{
Log::add('Heartbeat skipped: core plugin params empty or not found', Log::WARNING, 'mokosuiteclient');
return;
}
$healthToken = $coreParams->health_api_token ?? ''; $healthToken = $coreParams->health_api_token ?? '';
if (empty($healthToken)) if (empty($healthToken))
{ {
Log::add('Heartbeat skipped: health_api_token not configured', Log::INFO, 'mokosuiteclient');
return; return;
} }
// Get base URL and signing key from monitor plugin if (($coreParams->heartbeat_enabled ?? '1') === '0')
$query = $db->getQuery(true) {
->select($db->quoteName('params')) return;
->from($db->quoteName('#__extensions')) }
->where($db->quoteName('element') . ' = ' . $db->quote('mokosuiteclient_monitor'))
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' . $db->quote('system'));
$monitorParams = json_decode((string) $db->setQuery($query)->loadResult());
$baseUrl = rtrim($monitorParams->base_url ?? '', '/');
// Fall back to manifest XML default if not yet saved in params $baseUrl = rtrim($coreParams->monitor_base_url ?? '', '/');
// Fall back to manifest XML default
if (empty($baseUrl)) if (empty($baseUrl))
{ {
$manifestFile = JPATH_PLUGINS . '/system/mokosuiteclient_monitor/mokosuiteclient_monitor.xml'; $manifestFile = JPATH_PLUGINS . '/system/mokosuiteclient/mokosuiteclient.xml';
if (is_file($manifestFile)) if (is_file($manifestFile))
{ {
@@ -836,7 +875,7 @@ class Pkg_MokosuiteInstallerScript
if ($xml) if ($xml)
{ {
foreach ($xml->xpath('//field[@name="base_url"]') as $field) foreach ($xml->xpath('//field[@name="monitor_base_url"]') as $field)
{ {
$baseUrl = rtrim((string) $field['default'], '/'); $baseUrl = rtrim((string) $field['default'], '/');
break; break;
@@ -847,9 +886,13 @@ class Pkg_MokosuiteInstallerScript
if (empty($baseUrl)) if (empty($baseUrl))
{ {
Log::add('Heartbeat skipped: monitor_base_url not configured and manifest fallback failed', Log::WARNING, 'mokosuiteclient');
return; return;
} }
Log::add('Heartbeat sending to: ' . $baseUrl, Log::INFO, 'mokosuiteclient');
$siteUrl = rtrim(\Joomla\CMS\Uri\Uri::root(), '/'); $siteUrl = rtrim(\Joomla\CMS\Uri\Uri::root(), '/');
$domain = parse_url($siteUrl, PHP_URL_HOST) ?: ''; $domain = parse_url($siteUrl, PHP_URL_HOST) ?: '';
$timestamp = time(); $timestamp = time();
@@ -866,12 +909,12 @@ class Pkg_MokosuiteInstallerScript
$headers = ['Content-Type: application/json']; $headers = ['Content-Type: application/json'];
// RSA sign the request — fall back to manifest XML default $signingKeyB64 = $coreParams->monitor_signing_key ?? '';
$signingKeyB64 = $monitorParams->signing_key ?? '';
// Fall back to manifest XML default
if (empty($signingKeyB64)) if (empty($signingKeyB64))
{ {
$manifestFile = JPATH_PLUGINS . '/system/mokosuiteclient_monitor/mokosuiteclient_monitor.xml'; $manifestFile = JPATH_PLUGINS . '/system/mokosuiteclient/mokosuiteclient.xml';
if (is_file($manifestFile)) if (is_file($manifestFile))
{ {
@@ -879,7 +922,7 @@ class Pkg_MokosuiteInstallerScript
if ($xml) if ($xml)
{ {
foreach ($xml->xpath('//field[@name="signing_key"]') as $field) foreach ($xml->xpath('//field[@name="monitor_signing_key"]') as $field)
{ {
$signingKeyB64 = (string) $field['default']; $signingKeyB64 = (string) $field['default'];
break; break;
@@ -900,13 +943,13 @@ class Pkg_MokosuiteInstallerScript
if (openssl_sign($message, $signature, $privateKey, OPENSSL_ALGO_SHA256)) if (openssl_sign($message, $signature, $privateKey, OPENSSL_ALGO_SHA256))
{ {
$headers[] = 'X-MokoSuiteClient-Signature: ' . base64_encode($signature); $headers[] = 'X-MokoSuite-Signature: ' . base64_encode($signature);
$headers[] = 'X-MokoSuiteClient-Timestamp: ' . $timestamp; $headers[] = 'X-MokoSuite-Timestamp: ' . $timestamp;
} }
} }
} }
$endpoint = $baseUrl . '/api/index.php/v1/mokosuiteclienthq/heartbeat'; $endpoint = $baseUrl . '/api/index.php/v1/mokosuitehq/heartbeat';
$ch = curl_init($endpoint); $ch = curl_init($endpoint);
curl_setopt_array($ch, [ curl_setopt_array($ch, [
@@ -921,16 +964,30 @@ class Pkg_MokosuiteInstallerScript
$response = curl_exec($ch); $response = curl_exec($ch);
$code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); $code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch); curl_close($ch);
if ($code >= 200 && $code < 300) if ($error)
{ {
Factory::getApplication()->enqueueMessage('MokoSuiteClientHQ heartbeat: site registered', 'message'); Log::add('Heartbeat connection failed: ' . $error, Log::WARNING, 'mokosuiteclient');
Factory::getApplication()->enqueueMessage('MokoSuiteHQ heartbeat failed: ' . $error, 'warning');
}
elseif ($code >= 200 && $code < 300)
{
Factory::getApplication()->enqueueMessage('MokoSuiteHQ heartbeat: site registered successfully.', 'message');
}
else
{
$body = json_decode($response, true);
$msg = $body['error'] ?? $body['message'] ?? ('HTTP ' . $code);
Log::add(sprintf('Heartbeat HTTP %d: %s', $code, $response), Log::WARNING, 'mokosuiteclient');
Factory::getApplication()->enqueueMessage('MokoSuiteHQ heartbeat failed: ' . $msg, 'warning');
} }
} }
catch (\Throwable $e) catch (\Throwable $e)
{ {
// Silent failure — heartbeat is non-critical Log::add('Heartbeat failed: ' . $e->getMessage(), Log::WARNING, 'mokosuiteclient');
Factory::getApplication()->enqueueMessage('MokoSuiteHQ heartbeat failed: ' . $e->getMessage(), 'warning');
} }
} }
@@ -944,12 +1001,12 @@ class Pkg_MokosuiteInstallerScript
*/ */
private function setupCpanelModule(): void private function setupCpanelModule(): void
{ {
$this->ensureAdminModule('mod_mokosuiteclient_cpanel', 'MokoSuiteClient', 'top', 6, 0, '{"show_health":"1","show_plugins":"1"}'); $this->ensureAdminModule('mod_mokosuiteclient_cpanel', 'MokoSuiteClient', 'top', 3, 0, '{"show_health":"1","show_plugins":"1"}');
} }
private function setupAdminMenuModule(): void private function setupAdminMenuModule(): void
{ {
$this->ensureAdminModule('mod_mokosuiteclient_menu', 'MokoSuiteClient Menu', 'menu', 3, 0); $this->ensureAdminModule('mod_mokosuiteclient_menu', 'MokoSuiteClient Menu', 'menu', 3, -1);
} }
private function setupCacheModule(): void private function setupCacheModule(): void
@@ -988,7 +1045,41 @@ class Pkg_MokosuiteInstallerScript
); );
$moduleId = (int) $db->loadResult(); $moduleId = (int) $db->loadResult();
// Build save data — Joomla's ModuleModel expects this format if ($moduleId > 0)
{
// Module exists — ensure it stays published with correct position
$db->setQuery(
$db->getQuery(true)
->update($db->quoteName('#__modules'))
->set($db->quoteName('published') . ' = 1')
->set($db->quoteName('position') . ' = ' . $db->quote($position))
->set($db->quoteName('ordering') . ' = ' . (int) $ordering)
->set($db->quoteName('access') . ' = ' . (int) $access)
->set($db->quoteName('checked_out') . ' = NULL')
->set($db->quoteName('checked_out_time') . ' = NULL')
->where($db->quoteName('id') . ' = ' . $moduleId)
)->execute();
// Ensure module-menu mapping exists (0 = all pages)
$db->setQuery(
$db->getQuery(true)
->select('COUNT(*)')
->from($db->quoteName('#__modules_menu'))
->where($db->quoteName('moduleid') . ' = ' . $moduleId)
);
if ((int) $db->loadResult() === 0)
{
$db->setQuery(
"INSERT IGNORE INTO " . $db->quoteName('#__modules_menu')
. " (moduleid, menuid) VALUES (" . $moduleId . ", 0)"
)->execute();
}
return;
}
// Module doesn't exist — create via ModuleModel
$data = [ $data = [
'title' => $title, 'title' => $title,
'module' => $element, 'module' => $element,
@@ -1000,16 +1091,9 @@ class Pkg_MokosuiteInstallerScript
'client_id' => 1, 'client_id' => 1,
'language' => '*', 'language' => '*',
'params' => $params, 'params' => $params,
'assignment' => 0, // 0 = all pages 'assignment' => 0,
]; ];
if ($moduleId > 0)
{
$data['id'] = $moduleId;
}
// Use Joomla's ModuleModel to handle save + menu assignment
\Joomla\CMS\MVC\Factory\MVCFactory::class;
$app = Factory::getApplication(); $app = Factory::getApplication();
/** @var \Joomla\Component\Modules\Administrator\Model\ModuleModel $model */ /** @var \Joomla\Component\Modules\Administrator\Model\ModuleModel $model */
@@ -1493,6 +1577,71 @@ class Pkg_MokosuiteInstallerScript
} }
} }
/**
* Migrate monitor plugin params (base_url, signing_key) into the core plugin.
* The monitor plugin is retired but its config must survive in the core plugin.
*/
private function migrateMonitorParams(): void
{
try
{
$db = Factory::getDbo();
// Read core plugin params
$query = $db->getQuery(true)
->select($db->quoteName('params'))
->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' = ' . $db->quote('mokosuiteclient'))
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' . $db->quote('system'));
$coreParams = json_decode((string) $db->setQuery($query)->loadResult(), true) ?: [];
if (!empty($coreParams['_monitor_migrated']))
{
return;
}
// Read monitor plugin params (may already be gone)
$query = $db->getQuery(true)
->select($db->quoteName('params'))
->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' = ' . $db->quote('mokosuiteclient_monitor'))
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' . $db->quote('system'));
$monitorJson = (string) $db->setQuery($query)->loadResult();
$monitorParams = json_decode($monitorJson, true) ?: [];
$keyMap = [
'base_url' => 'monitor_base_url',
'signing_key' => 'monitor_signing_key',
'heartbeat_enabled' => 'heartbeat_enabled',
];
foreach ($keyMap as $old => $new)
{
if (!empty($monitorParams[$old]) && empty($coreParams[$new]))
{
$coreParams[$new] = $monitorParams[$old];
}
}
$coreParams['_monitor_migrated'] = 1;
$db->setQuery(
$db->getQuery(true)
->update($db->quoteName('#__extensions'))
->set($db->quoteName('params') . ' = ' . $db->quote(json_encode($coreParams)))
->where($db->quoteName('element') . ' = ' . $db->quote('mokosuiteclient'))
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
)->execute();
}
catch (\Throwable $e)
{
Log::add('Monitor param migration error: ' . $e->getMessage(), Log::WARNING, 'mokosuiteclient');
}
}
/** /**
* Warn after install/update if no license key (dlid) is configured on the update site. * Warn after install/update if no license key (dlid) is configured on the update site.
*/ */
-48
View File
@@ -1,48 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
SPDX-LICENSE-IDENTIFIER: GPL-3.0-or-later
FILE INFORMATION
DEFGROUP: Joomla.Component
INGROUP: MokoWaaS
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS
VERSION: 02.34.84
PATH: /mokowaas.xml
BRIEF: Component manifest for MokoWaaS admin dashboard and REST API
-->
<extension type="component" method="upgrade">
<name>MokoWaaS</name>
<author>Moko Consulting</author>
<creationDate>2026-06-02</creationDate>
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.00</version>
<description>MokoWaaS admin dashboard and REST API. Provides a control panel for managing MokoWaaS feature plugins, site health monitoring, and remote management endpoints.</description>
<namespace path="src">Moko\Component\MokoWaaS</namespace>
<administration>
<menu img="class:cogs">MokoWaaS</menu>
<files folder="admin">
<folder>language</folder>
<folder>services</folder>
<folder>src</folder>
<folder>tmpl</folder>
</files>
</administration>
<api>
<files folder="api">
<folder>src</folder>
</files>
</api>
<media destination="com_mokowaas" folder="media">
<folder>css</folder>
<folder>js</folder>
</media>
</extension>
File diff suppressed because it is too large Load Diff
@@ -1,72 +0,0 @@
<?php
/**
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-LICENSE-IDENTIFIER: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: Joomla.Plugin
* INGROUP: MokoWaaS
* VERSION: 02.34.84
* PATH: /src/Field/AllowedIpsField.php
* BRIEF: Custom form field that displays the current IP whitelist
*/
namespace Moko\Plugin\System\MokoWaaS\Field;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormField;
class AllowedIpsField extends FormField
{
protected $type = 'AllowedIps';
protected function getInput()
{
$config = Factory::getApplication()->getConfig();
$allowedRaw = $config->get('mokowaas_allowed_ips', '');
$currentIp = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
if (empty($allowedRaw))
{
$status = '<span class="badge bg-danger">Not configured</span>';
$ipList = '<em>No IPs set — emergency access is blocked.</em>';
}
else
{
$ips = array_map('trim', explode(',', $allowedRaw));
$status = '<span class="badge bg-success">'
. count($ips) . ' IP(s) configured</span>';
$ipItems = [];
foreach ($ips as $ip)
{
$match = ($ip === $currentIp)
? ' <span class="badge bg-info">your IP</span>'
: '';
$ipItems[] = '<code>' . htmlspecialchars($ip)
. '</code>' . $match;
}
$ipList = implode(', ', $ipItems);
}
$yourIp = '<code>' . htmlspecialchars($currentIp) . '</code>';
return '<div class="alert alert-info mb-0">'
. '<strong>IP Whitelist:</strong> ' . $status . '<br>'
. '<strong>Allowed IPs:</strong> ' . $ipList . '<br>'
. '<strong>Your current IP:</strong> ' . $yourIp . '<br>'
. '<small class="text-muted">Set <code>public '
. '$mokowaas_allowed_ips = \'1.2.3.4,5.6.7.8\';</code>'
. ' in configuration.php to change.</small>'
. '</div>';
}
protected function getLabel()
{
return '';
}
}
@@ -1,40 +0,0 @@
<?php
/**
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-LICENSE-IDENTIFIER: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: Joomla.Plugin
* INGROUP: MokoWaaS
* VERSION: 02.34.84
* PATH: /src/Field/CurrentIpField.php
* BRIEF: Read-only field that displays the current user's IP address
*/
namespace Moko\Plugin\System\MokoWaaS\Field;
defined('_JEXEC') or die;
use Joomla\CMS\Form\FormField;
class CurrentIpField extends FormField
{
protected $type = 'CurrentIp';
protected function getInput()
{
$currentIp = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
return '<div class="alert alert-info mb-0 py-2">'
. '<strong>Your current IP:</strong> '
. '<code>' . htmlspecialchars($currentIp) . '</code> '
. '<small class="text-muted">&mdash; add this to the table below to keep your session alive.</small>'
. '</div>';
}
protected function getLabel()
{
return '';
}
}
@@ -1,237 +0,0 @@
<?php
/**
* @package MokoWaaS
* @subpackage plg_system_mokowaas
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*
* FILE INFORMATION
* DEFGROUP: Joomla.Plugin
* INGROUP: MokoWaaS
* VERSION: 02.34.84
* PATH: /src/Field/DemoTaskInfoField.php
* BRIEF: Read-only field showing scheduled task info with link to manage it
*/
namespace Moko\Plugin\System\MokoWaaS\Field;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormField;
use Joomla\CMS\Router\Route;
/**
* Displays the demo reset scheduled task status: schedule, next run,
* last run, and a direct link to edit the task in Joomla's Scheduler.
*
* @since 02.29.00
*/
class DemoTaskInfoField extends FormField
{
protected $type = 'DemoTaskInfo';
protected function getInput()
{
// Query the scheduled task — if it exists and is enabled, demo mode is on
try
{
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select('*')
->from($db->quoteName('#__scheduler_tasks'))
->where($db->quoteName('type') . ' = ' . $db->quote('mokowaas.demo.reset'));
$db->setQuery($query);
$task = $db->loadAssoc();
}
catch (\Throwable $e)
{
$task = null;
}
$newTaskLink = Route::_('index.php?option=com_scheduler&task=task.add');
if (!$task)
{
return '<div class="alert alert-info mb-0 py-2">'
. 'No demo reset task configured. '
. '<a href="' . $newTaskLink . '" class="alert-link">Create a Scheduled Task</a> '
. 'and select <strong>MokoWaaS Demo Reset</strong> to enable demo mode.</div>';
}
$taskId = (int) $task['id'];
$state = (int) $task['state'];
$siteTimezone = Factory::getApplication()->get('offset', 'UTC');
// Parse schedule from execution_rules
$rules = json_decode($task['execution_rules'] ?? '{}', true);
$ruleType = $rules['rule-type'] ?? '';
switch ($ruleType)
{
case 'cron-expression':
$schedule = $rules['cron-expression'] ?? '';
$friendlySchedule = $this->friendlySchedule($schedule);
break;
case 'interval-minutes':
$mins = (int) ($rules['interval-minutes'] ?? 0);
if ($mins >= 1440 && $mins % 1440 === 0)
{
$days = $mins / 1440;
$schedule = 'Every ' . $days . ' day' . ($days > 1 ? 's' : '');
}
elseif ($mins >= 60 && $mins % 60 === 0)
{
$hours = $mins / 60;
$schedule = 'Every ' . $hours . ' hour' . ($hours > 1 ? 's' : '');
}
else
{
$schedule = 'Every ' . $mins . ' minute' . ($mins !== 1 ? 's' : '');
}
$friendlySchedule = $schedule;
break;
case 'interval-hours':
$hours = (int) ($rules['interval-hours'] ?? 0);
$schedule = 'Every ' . $hours . ' hour' . ($hours !== 1 ? 's' : '');
$friendlySchedule = $schedule;
break;
case 'interval-days':
$days = (int) ($rules['interval-days'] ?? 0);
$schedule = 'Every ' . $days . ' day' . ($days !== 1 ? 's' : '');
$friendlySchedule = $schedule;
break;
default:
$schedule = $ruleType ?: 'Not set';
$friendlySchedule = 'Custom';
}
// Next execution
$nextExec = $task['next_execution'] ?? '';
$nextFormatted = 'Not scheduled';
$nextBadge = '';
if (!empty($nextExec) && $nextExec !== '0000-00-00 00:00:00')
{
try
{
$dt = new \DateTime($nextExec, new \DateTimeZone('UTC'));
$dt->setTimezone(new \DateTimeZone($siteTimezone));
$nextFormatted = $dt->format('M j, Y g:i A T');
}
catch (\Throwable $e)
{
$nextFormatted = $nextExec;
}
$diff = strtotime($nextExec . ' UTC') - time();
if ($diff <= 0)
{
$nextBadge = '<span class="badge bg-warning text-dark">DUE</span>';
}
elseif ($diff < 3600)
{
$nextBadge = '<span class="badge bg-info">in ' . (int) ceil($diff / 60) . ' min</span>';
}
elseif ($diff < 86400)
{
$nextBadge = '<span class="badge bg-info">in ' . round($diff / 3600, 1) . 'h</span>';
}
else
{
$nextBadge = '<span class="badge bg-secondary">in ' . round($diff / 86400, 1) . 'd</span>';
}
}
// Last execution
$lastExec = $task['last_execution'] ?? '';
$lastFormatted = 'Never';
if (!empty($lastExec) && $lastExec !== '0000-00-00 00:00:00')
{
try
{
$dt = new \DateTime($lastExec, new \DateTimeZone('UTC'));
$dt->setTimezone(new \DateTimeZone($siteTimezone));
$lastFormatted = $dt->format('M j, Y g:i A T');
}
catch (\Throwable $e)
{
$lastFormatted = $lastExec;
}
}
// State badge
$stateBadge = $state === 1
? '<span class="badge bg-success">Enabled</span>'
: '<span class="badge bg-danger">Disabled</span>';
// Link to edit the task
$editLink = Route::_('index.php?option=com_scheduler&task=task.edit&id=' . $taskId);
// Task params — default to On when keys are missing (matches form defaults)
$taskParams = json_decode($task['params'] ?? '{}', true) ?: [];
$bannerOn = !isset($taskParams['banner_enabled']) || (int) $taskParams['banner_enabled'] === 1;
$mediaOn = !isset($taskParams['include_media']) || (int) $taskParams['include_media'] === 1;
$countdownOn = !isset($taskParams['show_countdown']) || (int) $taskParams['show_countdown'] === 1;
// Check if snapshot exists
$snapshotExists = is_dir(JPATH_ROOT . '/mokowaas-snapshots/default');
// Build info card
return '<div class="card card-body bg-light py-2 px-3 mb-0">'
. '<table class="table table-sm table-borderless mb-1" style="max-width:550px">'
. '<tr><td class="text-muted" style="width:130px">Status</td><td>' . $stateBadge . '</td></tr>'
. '<tr><td class="text-muted">Schedule</td><td>' . htmlspecialchars($friendlySchedule) . '</td></tr>'
. '<tr><td class="text-muted">Next Reset</td><td>' . htmlspecialchars($nextFormatted) . ' ' . $nextBadge . '</td></tr>'
. '<tr><td class="text-muted">Last Reset</td><td>' . htmlspecialchars($lastFormatted) . '</td></tr>'
. '<tr><td class="text-muted">Runs</td><td>' . (int) ($task['times_executed'] ?? 0) . ' executed, ' . (int) ($task['times_failed'] ?? 0) . ' failed</td></tr>'
. '<tr><td class="text-muted">Baseline</td><td>' . ($snapshotExists ? '<span class="badge bg-success">Saved</span>' : '<span class="badge bg-warning text-dark">Not taken yet</span>') . '</td></tr>'
. '<tr><td class="text-muted">Banner</td><td>' . ($bannerOn ? 'On' : 'Off') . ($countdownOn ? ' + countdown' : '') . '</td></tr>'
. '<tr><td class="text-muted">Images</td><td>' . ($mediaOn ? 'Included' : 'Excluded') . '</td></tr>'
. '</table>'
. '<a href="' . $editLink . '" class="btn btn-sm btn-outline-primary">'
. '<span class="icon-cog" aria-hidden="true"></span> Manage Scheduled Task</a>'
. '</div>';
}
protected function getLabel()
{
return '<label class="form-label"><strong>Scheduled Reset</strong></label>';
}
/**
* Convert a cron expression to a human-readable string.
*
* @param string $cron Cron expression
*
* @return string
*/
private function friendlySchedule(string $cron): string
{
$map = [
'* * * * *' => 'Every minute',
'*/5 * * * *' => 'Every 5 minutes',
'*/15 * * * *' => 'Every 15 minutes',
'*/30 * * * *' => 'Every 30 minutes',
'0 */1 * * *' => 'Every hour',
'0 */4 * * *' => 'Every 4 hours',
'0 */6 * * *' => 'Every 6 hours',
'0 */12 * * *' => 'Every 12 hours',
'0 0 * * *' => 'Daily at midnight',
'0 6 * * *' => 'Daily at 6:00 AM',
'0 0 * * 0' => 'Weekly (Sunday)',
'0 0 1 * *' => 'Monthly (1st)',
];
return $map[$cron] ?? 'Custom';
}
}
@@ -1,156 +0,0 @@
<?php
/**
* @package MokoWaaS
* @subpackage plg_system_mokowaas
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*
* FILE INFORMATION
* DEFGROUP: Joomla.Plugin
* INGROUP: MokoWaaS
* VERSION: 02.34.84
* PATH: /src/Field/NextResetField.php
* BRIEF: Read-only field showing next reset time from Joomla scheduled task
*/
namespace Moko\Plugin\System\MokoWaaS\Field;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormField;
/**
* Pulls the next execution time directly from the Joomla scheduled task
* (#__scheduler_tasks) and displays it formatted in the site timezone.
*
* @since 02.29.00
*/
class NextResetField extends FormField
{
protected $type = 'NextReset';
protected function getInput()
{
// Check if demo mode is enabled
$demoEnabled = false;
if ($this->form)
{
$demoEnabled = (int) $this->form->getValue('demo_mode_enabled', 'params', 0) === 1;
}
if (!$demoEnabled)
{
return '<span class="form-control-plaintext text-muted">Demo mode is off</span>'
. '<input type="hidden" name="' . $this->name . '" value="" />';
}
// Query the actual next_execution from the scheduled task
try
{
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select([
$db->quoteName('next_execution'),
$db->quoteName('last_execution'),
$db->quoteName('state'),
])
->from($db->quoteName('#__scheduler_tasks'))
->where($db->quoteName('type') . ' = ' . $db->quote('mokowaas.demo.reset'));
$db->setQuery($query);
$task = $db->loadAssoc();
}
catch (\Throwable $e)
{
$task = null;
}
if (!$task)
{
return '<div class="alert alert-secondary mb-0 py-2">No scheduled task found — save to create one automatically.</div>'
. '<input type="hidden" name="' . $this->name . '" value="" />';
}
if ((int) $task['state'] !== 1)
{
return '<div class="alert alert-warning mb-0 py-2">Scheduled task is disabled.</div>'
. '<input type="hidden" name="' . $this->name . '" value="" />';
}
$nextExec = $task['next_execution'];
$lastExec = $task['last_execution'];
if (empty($nextExec) || $nextExec === '0000-00-00 00:00:00')
{
return '<div class="alert alert-secondary mb-0 py-2">Waiting for first run...</div>'
. '<input type="hidden" name="' . $this->name . '" value="" />';
}
// Convert to site timezone
$utcTimestamp = strtotime($nextExec);
$siteTimezone = Factory::getApplication()->get('offset', 'UTC');
try
{
$dt = new \DateTime('@' . $utcTimestamp);
$dt->setTimezone(new \DateTimeZone($siteTimezone));
$formatted = $dt->format('l, F j, Y \a\t g:i A T');
}
catch (\Throwable $e)
{
$formatted = $nextExec . ' UTC';
}
// Relative time
$diff = $utcTimestamp - time();
$relative = '';
if ($diff <= 0)
{
$relative = '<span class="badge bg-warning text-dark">overdue</span>';
}
elseif ($diff < 3600)
{
$mins = (int) ceil($diff / 60);
$relative = '<span class="badge bg-info">in ' . $mins . ' min</span>';
}
elseif ($diff < 86400)
{
$hours = round($diff / 3600, 1);
$relative = '<span class="badge bg-info">in ' . $hours . 'h</span>';
}
else
{
$days = round($diff / 86400, 1);
$relative = '<span class="badge bg-secondary">in ' . $days . 'd</span>';
}
// Last run info
$lastInfo = '';
if (!empty($lastExec) && $lastExec !== '0000-00-00 00:00:00')
{
try
{
$lastDt = new \DateTime($lastExec);
$lastDt->setTimezone(new \DateTimeZone($siteTimezone));
$lastInfo = '<small class="text-muted ms-2">Last run: ' . $lastDt->format('M j, g:i A') . '</small>';
}
catch (\Throwable $e)
{
// skip
}
}
return '<div class="d-flex align-items-center gap-2 flex-wrap">'
. '<span class="form-control-plaintext" style="font-weight:500">'
. '<span class="icon-calendar" aria-hidden="true"></span> '
. htmlspecialchars($formatted) . '</span> '
. $relative
. $lastInfo
. '<input type="hidden" name="' . $this->name . '" value="' . htmlspecialchars($nextExec) . '" />'
. '</div>';
}
}
@@ -1,175 +0,0 @@
<?php
/**
* @package MokoWaaS
* @subpackage plg_system_mokowaas
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*
* FILE INFORMATION
* DEFGROUP: Joomla.Plugin
* INGROUP: MokoWaaS
* VERSION: 02.34.84
* PATH: /src/Field/SnapshotTablesField.php
* BRIEF: Multi-select list field that loads DB tables with sensible defaults
*/
namespace Moko\Plugin\System\MokoWaaS\Field;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormField;
/**
* Renders a multi-select list box of all Joomla database tables, with
* content-related tables pre-selected by default.
*
* @since 02.26.00
*/
class SnapshotTablesField extends FormField
{
protected $type = 'SnapshotTables';
/**
* Tables selected by default when no value is stored yet.
*
* @var array
* @since 02.25.00
*/
private const DEFAULT_TABLES = [
'#__content',
'#__categories',
'#__fields',
'#__fields_values',
'#__fields_groups',
'#__menu',
'#__menu_types',
'#__modules',
'#__modules_menu',
'#__users',
'#__user_usergroup_map',
'#__user_profiles',
'#__tags',
'#__contentitem_tag_map',
'#__assets',
];
/**
* Table suffixes grouped by category.
*
* @var array
* @since 02.25.00
*/
private const TABLE_GROUPS = [
'Content' => ['content', 'categories', 'fields', 'fields_values', 'fields_groups', 'tags', 'contentitem_tag_map', 'ucm_content', 'ucm_history'],
'Users' => ['users', 'user_usergroup_map', 'user_profiles', 'usergroups', 'user_keys', 'user_mfa'],
'Menus' => ['menu', 'menu_types'],
'Modules' => ['modules', 'modules_menu'],
'Assets' => ['assets'],
];
protected function getInput()
{
$db = Factory::getDbo();
$prefix = $db->getPrefix();
$tables = $db->getTableList();
// Resolve selected values
$selected = $this->value;
if ($selected === null || $selected === '')
{
$selected = self::DEFAULT_TABLES;
}
elseif (is_string($selected))
{
$selected = array_filter(array_map('trim', explode("\n", $selected)));
}
$selected = (array) $selected;
// Flatten nested arrays from broken save format [["#__content"],["#__categories"]]
$selected = array_map(function ($v) {
return is_array($v) ? reset($v) : $v;
}, $selected);
// Group tables
$grouped = [];
foreach ($tables as $table)
{
if (strpos($table, $prefix) !== 0)
{
continue;
}
$suffix = substr($table, strlen($prefix));
$logical = '#__' . $suffix;
$group = 'Other';
foreach (self::TABLE_GROUPS as $groupName => $patterns)
{
if (in_array($suffix, $patterns, true))
{
$group = $groupName;
break;
}
}
$grouped[$group][] = $logical;
}
// Build HTML select with optgroups
$size = (int) ($this->element['size'] ?? 15);
$html = '<select name="' . $this->name . '" id="' . $this->id . '"'
. ' multiple="multiple" size="' . $size . '"'
. ' class="form-select">';
$priority = ['Content', 'Users', 'Menus', 'Modules', 'Assets'];
foreach ($priority as $g)
{
if (!empty($grouped[$g]))
{
$html .= '<optgroup label="' . $g . '">';
foreach ($grouped[$g] as $t)
{
$sel = in_array($t, $selected, true) ? ' selected="selected"' : '';
$html .= '<option value="' . htmlspecialchars($t) . '"' . $sel . '>' . htmlspecialchars($t) . '</option>';
}
$html .= '</optgroup>';
unset($grouped[$g]);
}
}
if (!empty($grouped['Other']))
{
$html .= '<optgroup label="Other">';
foreach ($grouped['Other'] as $t)
{
$sel = in_array($t, $selected, true) ? ' selected="selected"' : '';
$html .= '<option value="' . htmlspecialchars($t) . '"' . $sel . '>' . htmlspecialchars($t) . '</option>';
}
$html .= '</optgroup>';
}
$html .= '</select>';
// "Reset to defaults" link
$defaultsJson = htmlspecialchars(json_encode(self::DEFAULT_TABLES), ENT_QUOTES, 'UTF-8');
$html .= '<div class="mt-1">'
. '<a href="#" class="small" onclick="'
. 'var sel=document.getElementById(\'' . $this->id . '\');'
. 'var defs=' . $defaultsJson . ';'
. 'Array.from(sel.options).forEach(function(o){o.selected=defs.indexOf(o.value)!==-1;});'
. 'return false;'
. '"><span class="icon-refresh" aria-hidden="true"></span> Reset to defaults</a>'
. '</div>';
return $html;
}
}
@@ -1,260 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
This file is part of a Moko Consulting project.
SPDX-LICENSE-IDENTIFIER: GPL-3.0-or-later
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License (./LICENSE.md).
# FILE INFORMATION
DEFGROUP: Joomla.Plugin
INGROUP: MokoWaaS
REPO: https://github.com/mokoconsulting-tech/mokowaas
VERSION: 02.34.84
PATH: /src/mokowaas.xml
BRIEF: Plugin manifest for MokoWaaS system plugin
NOTE: Defines installation metadata, files, and configuration for Joomla
-->
<extension type="plugin" group="system" method="upgrade">
<name>System - MokoWaaS</name>
<element>mokowaas</element>
<author>Moko Consulting</author>
<creationDate>2026-05-22</creationDate>
<copyright>Copyright (C) 2025 Moko Consulting. All rights reserved.</copyright>
<license>GNU General Public License version 3 or later; see LICENSE.md</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.00</version>
<description>This plugin rebrands the Joomla system interface with MokoWaaS identity. It applies language overrides and ensures consistent branding across the platform.</description>
<namespace path=".">Moko\Plugin\System\MokoWaaS</namespace>
<scriptfile>script.php</scriptfile>
<files>
<filename plugin="mokowaas">script.php</filename>
<folder>Extension</folder>
<folder>Field</folder>
<folder>Helper</folder>
<folder>Service</folder>
<folder>forms</folder>
<folder>payload</folder>
<folder>services</folder>
<folder>language</folder>
<folder>administrator</folder>
</files>
<media destination="plg_system_mokowaas" folder="media">
<filename>index.html</filename>
<filename>favicon.ico</filename>
<filename>favicon.svg</filename>
<filename>favicon_256.png</filename>
<filename>logo.png</filename>
</media>
<languages folder="language">
<language tag="en-GB">en-GB/plg_system_mokowaas.ini</language>
<language tag="en-US">en-US/plg_system_mokowaas.ini</language>
</languages>
<languages folder="administrator/language">
<language tag="en-GB">en-GB/plg_system_mokowaas.sys.ini</language>
<language tag="en-US">en-US/plg_system_mokowaas.sys.ini</language>
</languages>
<administration>
<files folder="administrator">
<folder>language</folder>
</files>
</administration>
<config>
<fields name="params"
addfieldprefix="Moko\Plugin\System\MokoWaaS\Field"
>
<fieldset name="basic">
<field
name="health_api_token"
type="CopyableToken"
label="PLG_SYSTEM_MOKOWAAS_HEALTH_TOKEN_LABEL"
description="PLG_SYSTEM_MOKOWAAS_HEALTH_TOKEN_DESC"
default=""
filter="raw"
readonly="true"
/>
<field name="dev_mode" type="radio" default="0"
label="PLG_SYSTEM_MOKOWAAS_DEV_MODE_LABEL"
description="PLG_SYSTEM_MOKOWAAS_DEV_MODE_DESC"
class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field
name="reset_hits"
type="radio"
label="PLG_SYSTEM_MOKOWAAS_RESET_HITS_LABEL"
description="PLG_SYSTEM_MOKOWAAS_RESET_HITS_DESC"
default="0"
class="btn-group btn-group-yesno"
>
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field
name="delete_versions"
type="radio"
label="PLG_SYSTEM_MOKOWAAS_DELETE_VERSIONS_LABEL"
description="PLG_SYSTEM_MOKOWAAS_DELETE_VERSIONS_DESC"
default="0"
class="btn-group btn-group-yesno"
>
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
</fieldset>
<fieldset name="tenant_restrictions"
label="PLG_SYSTEM_MOKOWAAS_FIELDSET_TENANT_LABEL"
description="PLG_SYSTEM_MOKOWAAS_FIELDSET_TENANT_DESC"
>
<field name="restrict_installer" type="radio" default="1"
label="PLG_SYSTEM_MOKOWAAS_RESTRICT_INSTALLER_LABEL"
description="PLG_SYSTEM_MOKOWAAS_RESTRICT_INSTALLER_DESC"
class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field name="allow_extension_updates" type="radio" default="1"
label="PLG_SYSTEM_MOKOWAAS_ALLOW_UPDATES_LABEL"
description="PLG_SYSTEM_MOKOWAAS_ALLOW_UPDATES_DESC"
class="btn-group btn-group-yesno"
showon="restrict_installer:1">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field name="hide_sysinfo" type="radio" default="1"
label="PLG_SYSTEM_MOKOWAAS_HIDE_SYSINFO_LABEL"
description="PLG_SYSTEM_MOKOWAAS_HIDE_SYSINFO_DESC"
class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field name="restrict_global_config" type="radio" default="1"
label="PLG_SYSTEM_MOKOWAAS_RESTRICT_CONFIG_LABEL"
description="PLG_SYSTEM_MOKOWAAS_RESTRICT_CONFIG_DESC"
class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field name="restrict_template_editing" type="radio" default="1"
label="PLG_SYSTEM_MOKOWAAS_RESTRICT_TEMPLATE_LABEL"
description="PLG_SYSTEM_MOKOWAAS_RESTRICT_TEMPLATE_DESC"
class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field name="disable_install_url" type="radio" default="1"
label="PLG_SYSTEM_MOKOWAAS_DISABLE_INSTALL_URL_LABEL"
description="PLG_SYSTEM_MOKOWAAS_DISABLE_INSTALL_URL_DESC"
class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field name="hidden_menu_items" type="textarea"
label="PLG_SYSTEM_MOKOWAAS_HIDDEN_MENUS_LABEL"
description="PLG_SYSTEM_MOKOWAAS_HIDDEN_MENUS_DESC"
rows="5" filter="raw" />
</fieldset>
<fieldset name="demo_mode"
label="PLG_SYSTEM_MOKOWAAS_FIELDSET_DEMO_LABEL"
description="PLG_SYSTEM_MOKOWAAS_FIELDSET_DEMO_DESC"
addfieldprefix="Moko\Plugin\System\MokoWaaS\Field"
>
<field name="demo_scheduled_task" type="DemoTaskInfo"
label="PLG_SYSTEM_MOKOWAAS_DEMO_TASK_INFO_LABEL"
/>
</fieldset>
<fieldset name="security"
label="PLG_SYSTEM_MOKOWAAS_FIELDSET_SECURITY_LABEL"
description="PLG_SYSTEM_MOKOWAAS_FIELDSET_SECURITY_DESC"
addfieldprefix="Moko\Plugin\System\MokoWaaS\Field"
>
<field
name="emergency_access"
type="radio"
label="PLG_SYSTEM_MOKOWAAS_EMERGENCY_ACCESS_LABEL"
description="PLG_SYSTEM_MOKOWAAS_EMERGENCY_ACCESS_DESC"
default="1"
class="btn-group btn-group-yesno"
>
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field
name="allowed_ips_display"
type="AllowedIps"
label=""
/>
<field name="force_https" type="radio" default="1"
label="PLG_SYSTEM_MOKOWAAS_FORCE_HTTPS_LABEL"
description="PLG_SYSTEM_MOKOWAAS_FORCE_HTTPS_DESC"
class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field name="admin_session_timeout" type="number"
label="PLG_SYSTEM_MOKOWAAS_SESSION_TIMEOUT_LABEL"
description="PLG_SYSTEM_MOKOWAAS_SESSION_TIMEOUT_DESC"
default="60" hint="Minutes (0 = Joomla default)" />
<field
name="current_ip_display"
type="CurrentIp"
label=""
/>
<field
name="trusted_ips"
type="subform"
label="PLG_SYSTEM_MOKOWAAS_TRUSTED_IPS_LABEL"
description="PLG_SYSTEM_MOKOWAAS_TRUSTED_IPS_DESC"
formsource="plugins/system/mokowaas/forms/trusted_ip_entry.xml"
multiple="true"
layout="joomla.form.field.subform.repeatable-table"
groupByFieldset="false"
buttons="add,remove,move"
/>
<field name="password_min_length" type="number" default="12"
label="PLG_SYSTEM_MOKOWAAS_PASSWORD_LENGTH_LABEL"
description="PLG_SYSTEM_MOKOWAAS_PASSWORD_LENGTH_DESC" />
<field name="password_require_uppercase" type="radio" default="1"
label="PLG_SYSTEM_MOKOWAAS_PASSWORD_UPPER_LABEL"
class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field name="password_require_number" type="radio" default="1"
label="PLG_SYSTEM_MOKOWAAS_PASSWORD_NUMBER_LABEL"
class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field name="password_require_special" type="radio" default="1"
label="PLG_SYSTEM_MOKOWAAS_PASSWORD_SPECIAL_LABEL"
class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field name="upload_allowed_types" type="text"
label="PLG_SYSTEM_MOKOWAAS_UPLOAD_TYPES_LABEL"
description="PLG_SYSTEM_MOKOWAAS_UPLOAD_TYPES_DESC"
default="jpg,jpeg,png,gif,webp,svg,pdf,doc,docx,xls,xlsx" />
<field name="upload_max_size_mb" type="number"
label="PLG_SYSTEM_MOKOWAAS_UPLOAD_SIZE_LABEL"
description="PLG_SYSTEM_MOKOWAAS_UPLOAD_SIZE_DESC"
default="100" />
</fieldset>
</fields>
</config>
</extension>