Compare commits

..

43 Commits

Author SHA1 Message Date
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
71 changed files with 931 additions and 6961 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>
+56 -28
View File
@@ -4,8 +4,8 @@
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Release
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
# INGROUP: mokocli.Release
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli
# PATH: /templates/workflows/universal/auto-release.yml.template
# VERSION: 05.00.00
# BRIEF: Universal build & release detects platform from manifest.xml
@@ -66,25 +66,25 @@ jobs:
token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 1
- name: Setup moko-platform tools
- name: Setup mokocli tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: |
if [ -f /opt/moko-platform/cli/version_bump.php ] && [ -f /opt/moko-platform/vendor/autoload.php ]; then
echo Using pre-installed /opt/moko-platform
echo MOKO_CLI=/opt/moko-platform/cli >> $GITHUB_ENV
if [ -f /opt/mokocli/cli/version_bump.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/moko-platform-api
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/moko-platform-api
cd /tmp/moko-platform-api
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/moko-platform-api/cli >> $GITHUB_ENV
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
fi
- name: Rename branch to rc
@@ -180,26 +180,26 @@ jobs:
fi
echo "No conflict markers found"
- name: Setup moko-platform tools
- name: Setup mokocli tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}'
run: |
if [ -f /opt/moko-platform/cli/version_bump.php ] && [ -f /opt/moko-platform/vendor/autoload.php ]; then
echo Using pre-installed /opt/moko-platform
echo MOKO_CLI=/opt/moko-platform/cli >> $GITHUB_ENV
if [ -f /opt/mokocli/cli/version_bump.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/moko-platform-api
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/moko-platform-api
cd /tmp/moko-platform-api
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/moko-platform-api/cli >> $GITHUB_ENV
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
fi
- name: "Determine version bump level"
@@ -225,22 +225,32 @@ jobs:
--path . --stability stable ${BUMP_FLAG} --branch main \
--token "${{ secrets.MOKOGITEA_TOKEN }}"
- name: Update release notes from CHANGELOG.md
- name: Update release notes and promote changelog
run: |
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
NOTES=""
if [ -f "CHANGELOG.md" ]; then
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
[ -z "$NOTES" ] && NOTES="Stable release"
else
NOTES="Stable release"
fi
[ -z "$NOTES" ] && NOTES="Stable release"
# 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
python3 -c "
import json, urllib.request
@@ -250,7 +260,7 @@ jobs:
'${API_BASE}/releases/${RELEASE_ID}',
data=payload, method='PATCH',
headers={
'Authorization': 'token ${{ secrets.MOKOGITEA_TOKEN }}',
'Authorization': 'token ${TOKEN}',
'Content-Type': 'application/json'
})
urllib.request.urlopen(req)
@@ -258,6 +268,24 @@ jobs:
echo "Release notes updated from CHANGELOG.md"
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) --------------------------------
- name: "Step 9: Mirror release to GitHub"
if: >-
+1 -1
View File
@@ -5,7 +5,7 @@
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/branch-cleanup.yml
# VERSION: 01.00.00
# BRIEF: Delete feature branches after PR merge
+1 -1
View File
@@ -5,7 +5,7 @@
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Automation
# VERSION: 02.34.84
# VERSION: 02.41.00
# BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch"
+2 -2
View File
@@ -4,8 +4,8 @@
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.CI
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
# INGROUP: mokocli.CI
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli
# PATH: /templates/workflows/universal/pr-check.yml.template
# VERSION: 09.23.00
# BRIEF: PR gate — branch policy + code validation before merge
+36 -27
View File
@@ -4,23 +4,26 @@
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: mokoplatform.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
# INGROUP: mokocli.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /templates/workflows/universal/pre-release.yml.template
# 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"
on:
pull_request:
types: [closed]
push:
branches:
- dev
pull_request_target:
types: [synchronize, opened, reopened]
branches:
- main
- 'fix/**'
- 'patch/**'
- 'hotfix/**'
- 'bugfix/**'
- 'chore/**'
- alpha
- beta
- rc
workflow_dispatch:
inputs:
stability:
@@ -43,12 +46,11 @@ env:
jobs:
build:
name: "Build Pre-Release (${{ inputs.stability || 'development' }})"
name: "Build Pre-Release (${{ inputs.stability || github.ref_name }})"
runs-on: release
if: >-
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 == 'pull_request_target' && github.event.pull_request.base.ref == 'main')
github.event_name == 'push'
steps:
- name: Checkout
@@ -56,40 +58,47 @@ jobs:
with:
fetch-depth: 0
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:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: |
# Use pre-installed /opt/mokoplatform 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
echo Using pre-installed /opt/mokoplatform
echo MOKO_CLI=/opt/mokoplatform/cli >> $GITHUB_ENV
# Use pre-installed /opt/mokocli if available (updated by cron every 6h)
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/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/mokoplatform-api
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokoplatform.git
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokoplatform-api
cd /tmp/mokoplatform-api && composer install --no-dev --no-interaction --quiet
echo MOKO_CLI=/tmp/mokoplatform-api/cli >> $GITHUB_ENV
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: Detect platform
id: platform
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
- name: Resolve metadata and bump version
id: meta
run: |
# Auto-detect stability: RC for PRs targeting main, else use input or default to development
if [ "${{ github.event_name }}" = "pull_request_target" ] && [ "${{ github.event.pull_request.base.ref }}" = "main" ]; then
STABILITY="release-candidate"
# Auto-detect stability from branch name on push, or use input on dispatch
if [ "${{ github.event_name }}" = "push" ]; then
case "${{ github.ref_name }}" in
rc) STABILITY="release-candidate" ;;
alpha) STABILITY="alpha" ;;
beta) STABILITY="beta" ;;
*) STABILITY="development" ;;
esac
else
STABILITY="${{ inputs.stability || 'development' }}"
fi
@@ -164,7 +173,7 @@ jobs:
php ${MOKO_CLI}/release_create.php \
--path . --version "$VERSION" --tag "$TAG" \
--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
run: |
+4 -3
View File
@@ -7,8 +7,8 @@
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Validation
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
# INGROUP: mokocli.Validation
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli
# PATH: /templates/workflows/joomla/repo_health.yml.template
# VERSION: 09.23.00
# BRIEF: Enforces repository guardrails by validating scripts governance, tooling availability, and core repository health artifacts.
@@ -33,7 +33,8 @@ on:
- scripts
- repo
pull_request:
push:
branches:
- main
permissions:
contents: read
+24 -67
View File
@@ -14,14 +14,36 @@
INGROUP: MokoSuiteClient.Documentation
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
PATH: ./CHANGELOG.md
VERSION: 02.34.84
VERSION: 02.41.00
BRIEF: Version history using `Keep a Changelog`
-->
# Changelog
## [Unreleased]
## [02.41.00] --- 2026-06-20
### Fixed
- Ticket automation and offline bypass plugins not enabling on install/update — `enablePlugin()` now handles empty element columns with a fallback match by manifest name
- Heartbeat silently failing — `sendHeartbeat()` was reading config from the retired monitor plugin; now reads from core plugin params
- Cpanel module not publishing on update — `ensureAdminModule()` now does a direct DB update for existing modules (bypasses ModuleModel checked_out issues)
- Cpanel module access level changed from 6 (may not exist) to 3 (Special)
- Admin menu module ordering set to -1 to ensure it appears at the top of the menu position
### Changed
- Retired monitor plugin config (base_url, signing_key, heartbeat_enabled) consolidated into core plugin params with one-time migration
- Runtime heartbeat moved from retired `plg_system_mokosuiteclient_monitor` into core plugin (`checkHeartbeat` on admin page load after version change)
- `DisplayController::sendHeartbeat()` reads from core plugin instead of retired monitor plugin
- Removed monitor plugin from cpanel dashboard plugin grid
### Added
- Missing language strings for IMAP poll and auto-close ticket automation task routines
- Monitor fieldset in core plugin XML with heartbeat_enabled, monitor_base_url, and monitor_signing_key fields
- Language strings for monitor fieldset (PLG_SYSTEM_MOKOSUITECLIENT_FIELDSET_MONITOR_*)
## [02.35.00] --- 2026-06-18
### Changed
- **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.
@@ -142,68 +164,3 @@
### 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
INGROUP: MokoSuiteClient.Documentation
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.34.84
VERSION: 02.41.00
PATH: ./CODE_OF_CONDUCT.md
BRIEF: Reference + packaging repo for Moko Consulting Developer GPT Other Default
-->
+1 -1
View File
@@ -19,7 +19,7 @@
DEFGROUP: mokoconsulting-tech.MokoSuiteClientBrand
INGROUP: MokoStandards.Governance
REPO: https://github.com/mokoconsulting-tech/MokoSuiteClientBrand
VERSION: 02.34.84
VERSION: 02.41.00
PATH: /GOVERNANCE.md
BRIEF: Project governance rules, roles, and decision process for MokoSuiteClientBrand
-->
+1 -1
View File
@@ -15,7 +15,7 @@
INGROUP: MokoSuiteClient.Documentation
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
PATH: ./LICENSE.md
VERSION: 02.34.84
VERSION: 02.41.00
BRIEF: Project license (GPL-3.0-or-later)
-->
GNU GENERAL PUBLIC LICENSE
+3 -3
View File
@@ -12,10 +12,10 @@
# ==============================================================================
# Extension Configuration
EXTENSION_NAME := mokoexample
EXTENSION_TYPE := module
EXTENSION_NAME := mokosuiteclient
EXTENSION_TYPE := package
# Options: module, plugin, component, package, template
EXTENSION_VERSION := 1.0.0
EXTENSION_VERSION := 02.35.00
# Module Configuration (for modules only)
MODULE_TYPE := site
+1 -1
View File
@@ -9,7 +9,7 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient
VERSION: 02.34.84
VERSION: 02.41.00
PATH: /README.md
BRIEF: MokoSuiteClient platform plugin for Joomla
-->
+1 -1
View File
@@ -23,7 +23,7 @@ DEFGROUP: [PROJECT_NAME]
INGROUP: [PROJECT_NAME].Documentation
REPO: [REPOSITORY_URL]
PATH: /SECURITY.md
VERSION: 02.34.84
VERSION: 02.41.00
BRIEF: Security vulnerability reporting and handling policy
-->
+2 -2
View File
@@ -11,13 +11,13 @@
INGROUP: MokoSuiteClient.Build
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
FILE: build-guide.md
VERSION: 02.34.84
VERSION: 02.41.00
PATH: /docs/guides/
BRIEF: Build and packaging guide for the MokoSuiteClient system plugin
NOTE: Defines environment setup, repository layout, packaging rules, and release preparation
-->
# MokoSuiteClient Build Guide (VERSION: 02.34.84)
# MokoSuiteClient Build Guide (VERSION: 02.41.00)
## 1. Purpose
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.34.84
VERSION: 02.41.00
PATH: /docs/guides/configuration-guide.md
BRIEF: Configuration guide for the MokoSuiteClient system plugin
NOTE: Defines plugin parameters, expected behaviors, and recommended defaults
-->
# MokoSuiteClient Configuration Guide (VERSION: 02.34.84)
# MokoSuiteClient Configuration Guide (VERSION: 02.41.00)
## 1. Objective
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.34.84
VERSION: 02.41.00
PATH: /docs/guides/installation-guide.md
BRIEF: Installation guide for the MokoSuiteClient system plugin
NOTE: First document in the guide set
-->
# MokoSuiteClient Installation Guide (VERSION: 02.34.84)
# MokoSuiteClient Installation Guide (VERSION: 02.41.00)
## Introduction
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.34.84
VERSION: 02.41.00
PATH: /docs/guides/operations-guide.md
BRIEF: Operational guide for administering and managing the MokoSuiteClient system plugin
NOTE: Defines lifecycle, responsibilities, and operational behaviors
-->
# MokoSuiteClient Operations Guide (VERSION: 02.34.84)
# MokoSuiteClient Operations Guide (VERSION: 02.41.00)
## Introduction
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.34.84
VERSION: 02.41.00
PATH: /docs/guides/rollback-and-recovery-guide.md
BRIEF: Rollback and recovery guide for restoring stable operation after plugin related incidents
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.41.00)
## Introduction
+2 -2
View File
@@ -7,13 +7,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.34.84
VERSION: 02.41.00
PATH: /docs/guides/testing-guide.md
BRIEF: Testing guide for MokoSuiteClient v02.01.08
NOTE: Covers manual test procedures for language overrides, install/uninstall, and configuration
-->
# MokoSuiteClient Testing Guide (VERSION: 02.34.84)
# MokoSuiteClient Testing Guide (VERSION: 02.41.00)
## 1. Prerequisites
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.34.84
VERSION: 02.41.00
PATH: /docs/guides/troubleshooting-guide.md
BRIEF: Troubleshooting guide for diagnosing and resolving issues related to the MokoSuiteClient plugin
NOTE: Designed for administrators and Suite operations teams
-->
# MokoSuiteClient Troubleshooting Guide (VERSION: 02.34.84)
# MokoSuiteClient Troubleshooting Guide (VERSION: 02.41.00)
## Introduction
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.34.84
VERSION: 02.41.00
PATH: /docs/guides/upgrade-and-versioning-guide.md
BRIEF: Guide for updating, versioning, and maintaining the MokoSuiteClient plugin
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.41.00)
## Introduction
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Documentation
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.34.84
VERSION: 02.41.00
PATH: /docs/index.md
BRIEF: Master index of all documentation for the MokoSuiteClient plugin
NOTE: Automatically maintained index for all guide canvases
-->
# MokoSuiteClient Documentation Index (VERSION: 02.34.84)
# MokoSuiteClient Documentation Index (VERSION: 02.41.00)
## Introduction
+2 -2
View File
@@ -11,12 +11,12 @@
INGROUP: MokoSuiteClient
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
PATH: /docs/plugin-basic.md
VERSION: 02.34.84
VERSION: 02.41.00
BRIEF: Baseline documentation for the MokoSuiteClient system plugin
NOTE: Foundational reference for internal and external stakeholders
-->
# MokoSuiteClient Plugin Overview (VERSION: 02.34.84)
# MokoSuiteClient Plugin Overview (VERSION: 02.41.00)
## Introduction
+1 -1
View File
@@ -10,7 +10,7 @@ DEFGROUP: MokoSuiteClient.Documentation
INGROUP: MokoStandards.Templates
REPO: https://github.com/mokoconsulting-tech/MokoSuiteClient
PATH: /docs/update-server.md
VERSION: 02.34.84
VERSION: 02.41.00
BRIEF: How this extension's Joomla update server file (update.xml) is managed
-->
@@ -90,22 +90,22 @@ class DisplayController extends BaseController
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;
}
$params = new \Joomla\Registry\Registry($monitorPlugin->params);
$baseUrl = rtrim($params->get('base_url', ''), '/');
$params = new \Joomla\Registry\Registry($corePlugin->params);
$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))
{
$manifestFile = JPATH_PLUGINS . '/system/mokosuiteclient_monitor/mokosuiteclient_monitor.xml';
$manifestFile = JPATH_PLUGINS . '/system/mokosuiteclient/mokosuiteclient.xml';
if (is_file($manifestFile))
{
@@ -113,7 +113,7 @@ class DisplayController extends BaseController
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'], '/');
break;
@@ -124,14 +124,12 @@ class DisplayController extends BaseController
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;
}
$corePlugin = \Joomla\CMS\Plugin\PluginHelper::getPlugin('system', 'mokosuiteclient');
$coreParams = new \Joomla\Registry\Registry($corePlugin ? $corePlugin->params : '{}');
$healthToken = $coreParams->get('health_api_token', '');
$healthToken = $params->get('health_api_token', '');
if (empty($healthToken))
{
@@ -156,12 +154,12 @@ class DisplayController extends BaseController
// RSA sign the request
$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))
{
$manifestFile = JPATH_PLUGINS . '/system/mokosuiteclient_monitor/mokosuiteclient_monitor.xml';
$manifestFile = JPATH_PLUGINS . '/system/mokosuiteclient/mokosuiteclient.xml';
if (is_file($manifestFile))
{
@@ -169,7 +167,7 @@ class DisplayController extends BaseController
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'];
break;
@@ -500,6 +498,7 @@ class DisplayController extends BaseController
if (strlen($query) < 3)
{
$this->jsonResponse(['results' => []]);
return;
}
try
@@ -527,7 +526,8 @@ class DisplayController extends BaseController
}
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()
{
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();
$db = Factory::getDbo();
$id = $input->getInt('id', 0);
@@ -600,7 +600,7 @@ class DisplayController extends BaseController
public function deleteCategory()
{
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->setQuery($db->getQuery(true)->delete('#__mokosuiteclient_ticket_categories')->where('id = ' . Factory::getApplication()->getInput()->getInt('id', 0)))->execute();
$this->jsonResponse(['success' => true, 'message' => 'Category deleted.']);
@@ -609,7 +609,7 @@ class DisplayController extends BaseController
public function reorderCategory()
{
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);
if (!is_array($order)) { $this->jsonResponse(['success' => false, 'message' => 'Invalid order']); return; }
$db = Factory::getDbo();
@@ -622,7 +622,7 @@ class DisplayController extends BaseController
public function saveCanned()
{
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();
$db = Factory::getDbo();
$data = (object) [
@@ -640,7 +640,7 @@ class DisplayController extends BaseController
public function deleteCanned()
{
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->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.']);
@@ -649,7 +649,7 @@ class DisplayController extends BaseController
public function reorderCanned()
{
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);
if (!is_array($order)) { $this->jsonResponse(['success' => false, 'message' => 'Invalid order']); return; }
$db = Factory::getDbo();
@@ -662,7 +662,7 @@ class DisplayController extends BaseController
public function uploadAttachment()
{
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();
$ticketId = $input->getInt('ticket_id', 0);
$replyId = $input->getInt('reply_id', 0) ?: null;
@@ -675,7 +675,7 @@ class DisplayController extends BaseController
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);
$db = Factory::getDbo();
$db->setQuery($db->getQuery(true)->select('*')->from('#__mokosuiteclient_ticket_attachments')->where('id = ' . $id));
@@ -696,7 +696,7 @@ class DisplayController extends BaseController
public function deleteAttachment()
{
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);
$ok = \Moko\Component\MokoSuiteClient\Administrator\Service\AttachmentService::delete($id);
$this->jsonResponse(['success' => $ok, 'message' => $ok ? 'Attachment deleted' : 'Not found']);
@@ -705,7 +705,7 @@ class DisplayController extends BaseController
public function rateTicket()
{
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();
$ticketId = $input->getInt('ticket_id', 0);
$rating = $input->getInt('rating', 0);
@@ -728,7 +728,7 @@ class DisplayController extends BaseController
public function saveAutomation()
{
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();
$db = Factory::getDbo();
$data = (object) [
@@ -749,7 +749,7 @@ class DisplayController extends BaseController
public function deleteAutomation()
{
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->setQuery($db->getQuery(true)->delete('#__mokosuiteclient_ticket_automation')->where('id = ' . Factory::getApplication()->getInput()->getInt('id', 0)))->execute();
$this->jsonResponse(['success' => true, 'message' => 'Rule deleted.']);
@@ -758,7 +758,7 @@ class DisplayController extends BaseController
public function toggleAutomation()
{
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();
$db = Factory::getDbo();
$db->setQuery($db->getQuery(true)->update('#__mokosuiteclient_ticket_automation')
@@ -770,7 +770,7 @@ class DisplayController extends BaseController
public function reorderAutomation()
{
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);
if (!is_array($order)) { $this->jsonResponse(['success' => false, 'message' => 'Invalid order']); return; }
$db = Factory::getDbo();
@@ -52,8 +52,9 @@ class AttachmentService
$ticketDir = self::STORAGE_DIR . '/' . $ticketId;
if (!is_dir($ticketDir)) {
Folder::create($ticketDir);
if (!is_dir($ticketDir) && !Folder::create($ticketDir)) {
Log::add("Failed to create attachment directory: {$ticketDir}", Log::ERROR, 'mokosuiteclient');
return [];
}
$userId = (int) Factory::getUser()->id;
@@ -62,6 +63,7 @@ class AttachmentService
for ($i = 0, $count = count($files['name']); $i < $count; $i++)
{
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;
}
@@ -127,9 +129,13 @@ class AttachmentService
/**
* 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;
case 'assign':
if ($ticketId) {
$db->setQuery("UPDATE {$db->quoteName('#__mokosuiteclient_tickets')} SET assigned_to = {$db->quote($value)}, modified = {$db->quote(Factory::getDate()->toSql())} WHERE id = {$ticketId}")->execute();
$assignId = (int) $value;
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;
@@ -187,7 +188,7 @@ class AutomationEngine
}
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)
{
Log::add('Failed to look up email for user ID ' . $userId . ': ' . $e->getMessage(), Log::WARNING, 'mokosuiteclient');
return null;
}
}
@@ -331,6 +332,7 @@ class NotificationService
}
catch (\Throwable $e)
{
Log::add('Failed to load notification config: ' . $e->getMessage(), Log::ERROR, 'mokosuiteclient');
return [];
}
}
@@ -397,12 +399,16 @@ class NotificationService
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
curl_exec($ch);
$response = curl_exec($ch);
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($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');
}
@@ -35,7 +35,7 @@ class HtmlView extends BaseHtmlView
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');
}
}
@@ -68,8 +68,9 @@ class TicketsController extends BaseController
$tickets = $db->loadObjectList() ?: [];
// Total count
$countQuery = $db->getQuery(true)->select('COUNT(*)')->from('#__mokosuiteclient_tickets');
// Total count (with same filters applied)
$countQuery = clone $query;
$countQuery->clear('select')->clear('order')->select('COUNT(*)');
$db->setQuery($countQuery);
$total = (int) $db->loadResult();
@@ -193,14 +194,18 @@ class TicketsController extends BaseController
$id = $input->getInt('id', 0);
$db = Factory::getDbo();
// Type-safe input extraction
$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) {
$value = $input->get($field, null, 'raw');
if ($value !== null) {
$fields[$field] = $value;
}
foreach ($intFields as $field) {
$value = $input->getInt($field, 0);
if ($value > 0) { $fields[$field] = $value; }
}
foreach ($strFields as $field) {
$value = $input->getString($field, '');
if ($value !== '') { $fields[$field] = $value; }
}
if (empty($fields)) {
@@ -208,14 +213,43 @@ class TicketsController extends BaseController
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 = [];
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());
$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)]);
}
@@ -264,6 +298,7 @@ class TicketsController extends BaseController
$user = Factory::getUser();
if (!$user->authorise($action, $asset)) {
$this->sendJson(403, ['error' => 'Not authorized']);
throw new \RuntimeException('Not authorized', 403);
}
}
@@ -20,7 +20,7 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version>
<version>02.41.00</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>
<namespace path="src">Moko\Component\MokoSuiteClient</namespace>
@@ -7,8 +7,8 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version>
<description>MOD_MOKOSUITE_CACHE_DESC</description>
<version>02.41.00</version>
<description>MOD_MOKOSUITECLIENT_CACHE_DESC</description>
<namespace path="src">Moko\Module\MokoSuiteCache</namespace>
<files>
@@ -7,7 +7,7 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version>
<version>02.41.00</version>
<description>MOD_MOKOSUITECLIENT_CATEGORIES_DESC</description>
<namespace path="src">Moko\Module\MokoSuiteClientCategories</namespace>
@@ -7,8 +7,8 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version>
<description>MOD_MOKOSUITE_CPANEL_DESC</description>
<version>02.41.00</version>
<description>MOD_MOKOSUITECLIENT_CPANEL_DESC</description>
<namespace path="src">Moko\Module\MokoSuiteCpanel</namespace>
<files>
@@ -25,64 +25,64 @@
<config>
<fields name="params">
<fieldset name="basic"
label="MOD_MOKOSUITE_CPANEL_FIELDSET_DISPLAY"
description="MOD_MOKOSUITE_CPANEL_FIELDSET_DISPLAY_DESC">
label="MOD_MOKOSUITECLIENT_CPANEL_FIELDSET_DISPLAY"
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"
label="MOD_MOKOSUITECLIENT_CPANEL_COLLAPSED_LABEL"
description="MOD_MOKOSUITECLIENT_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"
label="MOD_MOKOSUITE_CPANEL_SHOW_HEALTH_LABEL"
label="MOD_MOKOSUITECLIENT_CPANEL_SHOW_HEALTH_LABEL"
layout="joomla.form.field.radio.switcher">
<option value="0">JHIDE</option>
<option value="1">JSHOW</option>
</field>
<field name="show_stats" type="radio" default="1"
label="MOD_MOKOSUITE_CPANEL_SHOW_STATS_LABEL"
description="MOD_MOKOSUITE_CPANEL_SHOW_STATS_DESC"
label="MOD_MOKOSUITECLIENT_CPANEL_SHOW_STATS_LABEL"
description="MOD_MOKOSUITECLIENT_CPANEL_SHOW_STATS_DESC"
layout="joomla.form.field.radio.switcher">
<option value="0">JHIDE</option>
<option value="1">JSHOW</option>
</field>
<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">
<option value="0">JHIDE</option>
<option value="1">JSHOW</option>
</field>
<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">
<option value="0">JHIDE</option>
<option value="1">JSHOW</option>
</field>
<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">
<option value="0">JHIDE</option>
<option value="1">JSHOW</option>
</field>
<field name="show_actions" type="radio" default="1"
label="MOD_MOKOSUITE_CPANEL_SHOW_ACTIONS_LABEL"
description="MOD_MOKOSUITE_CPANEL_SHOW_ACTIONS_DESC"
label="MOD_MOKOSUITECLIENT_CPANEL_SHOW_ACTIONS_LABEL"
description="MOD_MOKOSUITECLIENT_CPANEL_SHOW_ACTIONS_DESC"
layout="joomla.form.field.radio.switcher">
<option value="0">JHIDE</option>
<option value="1">JSHOW</option>
</field>
<field name="show_versions" type="radio" default="1"
label="MOD_MOKOSUITE_CPANEL_SHOW_VERSIONS_LABEL"
description="MOD_MOKOSUITE_CPANEL_SHOW_VERSIONS_DESC"
label="MOD_MOKOSUITECLIENT_CPANEL_SHOW_VERSIONS_LABEL"
description="MOD_MOKOSUITECLIENT_CPANEL_SHOW_VERSIONS_DESC"
layout="joomla.form.field.radio.switcher">
<option value="0">JHIDE</option>
<option value="1">JSHOW</option>
@@ -48,7 +48,6 @@ $labels = [
'mokosuiteclient_firewall' => 'Firewall',
'mokosuiteclient_tenant' => 'Tenant',
'mokosuiteclient_devtools' => 'DevTools',
'mokosuiteclient_monitor' => 'Monitor',
];
$diskPct = ($disk->total_mb && $disk->total_mb > 0)
@@ -7,7 +7,7 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version>
<version>02.41.00</version>
<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>
@@ -22,7 +22,7 @@
* DEFGROUP: Joomla.Plugin
* INGROUP: MokoSuiteClient
* REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
* VERSION: 02.34.84
* VERSION: 02.41.00
* PATH: /src/Extension/MokoSuiteClient.php
* 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\Plugin\CMSPlugin;
use Joomla\CMS\Uri\Uri;
use Joomla\CMS\Version;
use Psr\Container\ContainerInterface;
/**
@@ -164,6 +165,7 @@ class MokoSuiteClient extends CMSPlugin implements BootableExtensionInterface
$this->handleOneTimeLogin();
$this->checkSetupRequired();
$this->ensureAdminModulesActive();
$this->checkHeartbeat();
}
}
@@ -2392,4 +2394,271 @@ 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-MokoSuiteClient-Signature: ' . $signature;
$headers[] = 'X-MokoSuiteClient-Timestamp: ' . $timestamp;
}
$endpoint = $baseUrl . '/api/index.php/v1/mokosuiteclienthq/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('MokoSuiteClientHQ heartbeat: site registered', 'message');
}
else
{
Log::add(
\sprintf('Heartbeat HTTP %d: %s', $response->code, $response->body),
Log::WARNING,
'mokosuiteclient'
);
}
}
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
* DEFGROUP: Joomla.Plugin
* INGROUP: MokoSuiteClient
* VERSION: 02.34.84
* VERSION: 02.41.00
* PATH: /src/Field/CopyableTokenField.php
* BRIEF: Read-only token field with a copy-to-clipboard button
*/
@@ -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_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."
; ===== 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."
@@ -30,7 +30,7 @@
<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.84-dev</version>
<version>02.41.00</version>
<description>MokoSuiteClient core system plugin — coordinates feature plugins, heartbeat, health checks, and admin customizations.</description>
<namespace path=".">Moko\Plugin\System\MokoSuiteClient</namespace>
<scriptfile>script.php</scriptfile>
@@ -68,18 +68,41 @@
addfieldprefix="Moko\Plugin\System\MokoSuiteClient\Field"
>
<fieldset name="basic"
label="PLG_SYSTEM_MOKOSUITE_FIELDSET_CORE_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIELDSET_CORE_DESC">
label="PLG_SYSTEM_MOKOSUITECLIENT_FIELDSET_CORE_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIELDSET_CORE_DESC">
<field
name="health_api_token"
type="CopyableToken"
label="PLG_SYSTEM_MOKOSUITE_HEALTH_TOKEN_LABEL"
description="PLG_SYSTEM_MOKOSUITE_HEALTH_TOKEN_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_HEALTH_TOKEN_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_HEALTH_TOKEN_DESC"
default=""
filter="raw"
readonly="true"
/>
</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://waas.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="LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tDQpNSUlFdmdJQkFEQU5CZ2txaGtpRzl3MEJBUUVGQUFTQ0JLZ3dnZ1NrQWdFQUFvSUJBUUMvcnVrWE0zZHB0aDg2DQpGSkRXTjM0ZjQ2cUtJem1SMmFtTWUyZ2dWbWxsWnFyMHJkRFk4OTdtQ05FRkk4Q0NwNGR5amkwOU5ETnAvalFxDQovL2JGdUFNOUFTZU5oQTlmRlpwSG5UMGkzY3N4V3RSS2NnMnRkR0wzUXhNRFVBeFJYQ1RSQXVPSWZybGp6Ky85DQpWZ0ZtWHU3M1VSaU9XY1lLeFErejFoZkRGK2ZxRTRlYW9QcUlsY2J5dmtKd2lkSkRWUEEwc2RtbVlUTFg2Q29xDQpQalVDRENlbkZoUXNteVMzM29KSXArK0c3ZzU5NmRYelZIczRQSjIwNnc0Z3JlckRRZk5GVytzZndHSnl3NjBrDQpUQTVmUzF2Wit4NEt2UUh6V1ErYS9xRS9sSGxFVzdOTWVJWExNWGczSDd1eXBabXlVU2t3S0k0djFQQXRGWmtkDQpBaVpPZWZpVkFnTUJBQUVDZ2dFQVI0VGJyVDR0NWJ5MDhIQW0wcTR3WVF4REhEbVlJbzNXdDZ5MURmYU11OVMzDQpDYW5TMm9oazJzaE9TcGhhU2hFajI3WjBKY2hYdjhYWURvbU1BZmVsN3I5eDZjQ2FhTVdUNEdCMU5Zckp1NDhBDQprV2NteTkwWitPNTZQZkZJeTJXdXV6dFRxaFdZb0ZDSTBOZlU2bGw5SzhpSFl6VWx1MzZSSklweWx5OXFPKyt4DQpmTUZYcUovSkk0bVp6NW0raDBnbFMvN21VZ0EvUTRjbVJnRHJ3dkc3bEpBRjhWSDBEdW1uRWJkWkZvSi9XbU9JDQpSTi9lemhqczYrbU9hTnUwQWRsclpLU3QwRWZVYjl3QTFLQm5JMVVDU2w0Y1lidXVpL29jOWo1aGl6RGJvRWRyDQpJL1U5Y2FYUmZvb0pMNlUwOXN1VTdyTlFLbFRhMXM4NVhvY3htT0JMK1FLQmdRRHg5QzB5MjQ5SG1paXJ2WExIDQpBUXdUTjRyMjdhUTZMMFc2SHdDNHdzMUhleDRpeWRXT1lIcWdBSnY4VHZyeVpHOW1SaFh1U1ROTjYxV1UvTWFNDQphQVYwVjJ4Y0RrdDNFUnhNak1XRmhXUTh0cjN2RUtqWjFnOVJXOGhiTE9VYXVCcmJhMlI4RWNZYXFLZXlxR3N4DQpCa0VLZlRIUzNmUysraXNLZ2EzUU1mcjB6d0tCZ1FES3o2SGVKZ0tKRTVMM1ppbkhxaUFyVm5SZ2pYcFZrMWpvDQp6VXh5eTkwNEhmNGlmVXNIZklpdzVpN0VNR0U0RE5ob2MvZUJxcW1oM1N2ejJMUDNzOHUrL0hVZFllVzJIV1hhDQpKZlpMRE5BM0U3WDNkSVJ6MFg5UTh2OHcxaFpQeUxYOUlYeUVyUTNGZHFVdyt1Tko1VFZJell0RHppNnRKTjkvDQpGZGlxS0Q2ZFd3S0JnUURnQnE5bS9LWmdyTnRsa1FkYVBaejVtaDhBWGE4RzlNaEIrZnpJRmc3T1ZhL2tsQzg1DQpJaG5JVm1nWHFPVndWQkJWaVNVN09lbllCc042TE1hR01MYUVMNEkwaGtQWG5pOHVyZFVodVEzRHJZeVZjejUwDQpYR0JZZTN3Njk0bTJRS3NWYVExa1YyeXZPR1AxNXoxQTZrS0V2TURLTnhzclRTVlhHQlZneFRaUlB3S0JnUURBDQp1RFVVcUFIWXlDVHJ1c1VRMm5UZk9iUTAyN3ZYL2NDSzJDdEJHc0FJUjFmcTVpeVozSmozb0lQb0lpRC81aFR1DQpqT1F3N3o5cWRJVURublRGZUxDdnQ2NkNVVGk3cVl2VGxDZEtnYzZKeDgwdWJDWkErRjZIU2FGOWdyS0k5aTBaDQpjT3ltRnR2elBCOFZRQk1qY1E0Rk0yeVc3aUlrbmRsVEppdFE1aFU1NlFLQmdEZ1JIOXBEcGZwWlZ2V2g2MldGDQp5OGZzWUo1ODhzQmRMUlpTYTRuNi9XbjdUcUp1bWg2aWpFcDVyZFdnQkVtaDlJSk9jRUlhZ05mK0s5MXdoaThvDQpTeW01ajJpL1pjVVFYNFJSTDNxQ1RZZWVQVnZ3RHc3aWNLWVowTGQ2S1pFMmdEaDRPbEg4ejU0Zkl3a2tMSzRFDQpCcmtJNWppa05QSkJFR25zTm9zU3pWN2QNCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0NCg=="
filter="raw" />
</fieldset>
</fields>
</config>
</extension>
@@ -22,7 +22,7 @@
* DEFGROUP: Joomla.Plugin
* INGROUP: MokoSuiteClient
* REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
* VERSION: 02.34.84
* VERSION: 02.41.00
* PATH: /src/script.php
* BRIEF: Installation script for MokoSuiteClient plugin
* NOTE: Handles installation, update, and uninstallation tasks including language override deployment
@@ -527,7 +527,7 @@ class plgSystemMokoSuiteClientInstallerScript implements InstallerScriptInterfac
}
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' => '',
'title_holder' => '',
'table_name' => '',
'text_prefix' => 'PLG_SYSTEM_MOKOSUITE',
'text_prefix' => 'PLG_SYSTEM_MOKOSUITECLIENT',
];
$db->insertObject('#__action_log_config', $config);
@@ -22,7 +22,7 @@
* DEFGROUP: Joomla.Plugin
* INGROUP: MokoSuiteClient
* REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
* VERSION: 02.34.84
* VERSION: 02.41.00
* PATH: /src/services/provider.php
* BRIEF: Service provider for dependency injection in Joomla 5.x
* NOTE: Registers the plugin with Joomla's DI container
@@ -3,27 +3,27 @@
; License: GPL-3.0-or-later
; IP Geolocation by DB-IP — https://db-ip.com
PLG_SYSTEM_MOKOSUITE_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="System - MokoSuiteClient DB-IP"
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_MOKOSUITE_DBIP_FIELDSET_BASIC_DESC="Configure IP geolocation database source and level."
PLG_SYSTEM_MOKOSUITECLIENT_DBIP_FIELDSET_BASIC="DB-IP Settings"
PLG_SYSTEM_MOKOSUITECLIENT_DBIP_FIELDSET_BASIC_DESC="Configure IP geolocation database source and level."
PLG_SYSTEM_MOKOSUITE_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_MOKOSUITE_DBIP_SOURCE_CDN="CDN (auto-download)"
PLG_SYSTEM_MOKOSUITE_DBIP_SOURCE_LOCAL="Local file"
PLG_SYSTEM_MOKOSUITECLIENT_DBIP_SOURCE_LABEL="Database Source"
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_MOKOSUITECLIENT_DBIP_SOURCE_CDN="CDN (auto-download)"
PLG_SYSTEM_MOKOSUITECLIENT_DBIP_SOURCE_LOCAL="Local file"
PLG_SYSTEM_MOKOSUITE_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_MOKOSUITE_DBIP_DATABASE_COUNTRY="Country (bundled)"
PLG_SYSTEM_MOKOSUITE_DBIP_DATABASE_CITY="City (remote download)"
PLG_SYSTEM_MOKOSUITECLIENT_DBIP_DATABASE_LEVEL_LABEL="Database Level"
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_MOKOSUITECLIENT_DBIP_DATABASE_COUNTRY="Country (bundled)"
PLG_SYSTEM_MOKOSUITECLIENT_DBIP_DATABASE_CITY="City (remote download)"
PLG_SYSTEM_MOKOSUITE_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_LABEL="Auto-Update Database"
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_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_LABEL="CDN Download URL"
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_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_LABEL="Local MMDB Path"
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.
; License: GPL-3.0-or-later
PLG_SYSTEM_MOKOSUITE_DBIP="System - MokoSuiteClient DB-IP"
PLG_SYSTEM_MOKOSUITE_DBIP_DESC="IP geolocation for MokoSuiteClient using DB-IP Lite databases."
PLG_SYSTEM_MOKOSUITECLIENT_DBIP="System - MokoSuiteClient DB-IP"
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>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version>
<description>PLG_SYSTEM_MOKOSUITE_DBIP_DESC</description>
<version>02.41.00</version>
<description>PLG_SYSTEM_MOKOSUITECLIENT_DBIP_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteClientDBIP</namespace>
<files>
@@ -28,26 +28,26 @@
<config>
<fields name="params">
<fieldset name="basic"
label="PLG_SYSTEM_MOKOSUITE_DBIP_FIELDSET_BASIC"
description="PLG_SYSTEM_MOKOSUITE_DBIP_FIELDSET_BASIC_DESC">
label="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_FIELDSET_BASIC"
description="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_FIELDSET_BASIC_DESC">
<field name="database_source" type="list" default="cdn"
label="PLG_SYSTEM_MOKOSUITE_DBIP_SOURCE_LABEL"
description="PLG_SYSTEM_MOKOSUITE_DBIP_SOURCE_DESC">
<option value="cdn">PLG_SYSTEM_MOKOSUITE_DBIP_SOURCE_CDN</option>
<option value="local">PLG_SYSTEM_MOKOSUITE_DBIP_SOURCE_LOCAL</option>
label="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_SOURCE_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_SOURCE_DESC">
<option value="cdn">PLG_SYSTEM_MOKOSUITECLIENT_DBIP_SOURCE_CDN</option>
<option value="local">PLG_SYSTEM_MOKOSUITECLIENT_DBIP_SOURCE_LOCAL</option>
</field>
<field name="database_level" type="list" default="country"
label="PLG_SYSTEM_MOKOSUITE_DBIP_DATABASE_LEVEL_LABEL"
description="PLG_SYSTEM_MOKOSUITE_DBIP_DATABASE_LEVEL_DESC">
<option value="country">PLG_SYSTEM_MOKOSUITE_DBIP_DATABASE_COUNTRY</option>
<option value="city">PLG_SYSTEM_MOKOSUITE_DBIP_DATABASE_CITY</option>
label="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_DATABASE_LEVEL_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_DATABASE_LEVEL_DESC">
<option value="country">PLG_SYSTEM_MOKOSUITECLIENT_DBIP_DATABASE_COUNTRY</option>
<option value="city">PLG_SYSTEM_MOKOSUITECLIENT_DBIP_DATABASE_CITY</option>
</field>
<field name="auto_update" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_DBIP_AUTO_UPDATE_LABEL"
description="PLG_SYSTEM_MOKOSUITE_DBIP_AUTO_UPDATE_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_AUTO_UPDATE_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_AUTO_UPDATE_DESC"
class="btn-group btn-group-yesno"
showon="database_source:cdn">
<option value="1">JYES</option>
@@ -56,15 +56,15 @@
<field name="cdn_url" type="url"
default="https://git.mokoconsulting.tech/MokoConsulting/geoip-data/releases/download/latest/dbip-city-lite.mmdb"
label="PLG_SYSTEM_MOKOSUITE_DBIP_CDN_URL_LABEL"
description="PLG_SYSTEM_MOKOSUITE_DBIP_CDN_URL_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_CDN_URL_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_CDN_URL_DESC"
filter="url"
showon="database_source:cdn" />
<field name="local_path" type="text"
default=""
label="PLG_SYSTEM_MOKOSUITE_DBIP_LOCAL_PATH_LABEL"
description="PLG_SYSTEM_MOKOSUITE_DBIP_LOCAL_PATH_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_LOCAL_PATH_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_LOCAL_PATH_DESC"
filter="path"
showon="database_source:local" />
@@ -8,8 +8,8 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version>
<description>PLG_SYSTEM_MOKOSUITE_DEVTOOLS_DESC</description>
<version>02.41.00</version>
<description>PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteClientDevTools</namespace>
<files>
@@ -26,36 +26,36 @@
<config>
<fields name="params">
<fieldset name="basic"
label="PLG_SYSTEM_MOKOSUITE_DEVTOOLS_FIELDSET_BASIC"
description="PLG_SYSTEM_MOKOSUITE_DEVTOOLS_FIELDSET_BASIC_DESC">
label="PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_FIELDSET_BASIC"
description="PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_FIELDSET_BASIC_DESC">
<field name="dev_mode" type="radio" default="0"
label="PLG_SYSTEM_MOKOSUITE_DEVTOOLS_DEV_MODE_LABEL"
description="PLG_SYSTEM_MOKOSUITE_DEVTOOLS_DEV_MODE_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_DEV_MODE_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_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" default="0"
label="PLG_SYSTEM_MOKOSUITE_DEVTOOLS_RESET_HITS_LABEL"
description="PLG_SYSTEM_MOKOSUITE_DEVTOOLS_RESET_HITS_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_RESET_HITS_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_RESET_HITS_DESC"
class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field name="delete_versions" type="radio" default="0"
label="PLG_SYSTEM_MOKOSUITE_DEVTOOLS_DELETE_VERSIONS_LABEL"
description="PLG_SYSTEM_MOKOSUITE_DEVTOOLS_DELETE_VERSIONS_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_DELETE_VERSIONS_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_DELETE_VERSIONS_DESC"
class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field name="reset_download_keys" type="radio" default="0"
label="PLG_SYSTEM_MOKOSUITE_DEVTOOLS_RESET_DLKEYS_LABEL"
description="PLG_SYSTEM_MOKOSUITE_DEVTOOLS_RESET_DLKEYS_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_RESET_DLKEYS_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_RESET_DLKEYS_DESC"
class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
@@ -8,8 +8,8 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version>
<description>PLG_SYSTEM_MOKOSUITE_FIREWALL_DESC</description>
<version>02.41.00</version>
<description>PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteClientFirewall</namespace>
<files>
@@ -36,25 +36,25 @@
<fields name="params">
<!-- Network & Session -->
<fieldset name="basic"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_BASIC"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_BASIC_DESC">
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_BASIC"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_BASIC_DESC">
<field name="force_https" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_FORCE_HTTPS_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_FORCE_HTTPS_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FORCE_HTTPS_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_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_MOKOSUITE_FIREWALL_SESSION_TIMEOUT_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_SESSION_TIMEOUT_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_SESSION_TIMEOUT_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_SESSION_TIMEOUT_DESC"
default="60" hint="Minutes (0 = Joomla default)" />
<field name="trusted_ips" type="subform"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_TRUSTED_IPS_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_TRUSTED_IPS_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_TRUSTED_IPS_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_TRUSTED_IPS_DESC"
formsource="plugins/system/mokosuiteclient_firewall/forms/trusted_ip_entry.xml"
multiple="true"
layout="joomla.form.field.subform.repeatable-table"
@@ -64,20 +64,20 @@
<!-- WAF Shields -->
<fieldset name="waf"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_WAF"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_WAF_DESC">
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_WAF"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_WAF_DESC">
<field name="waf_enabled" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_ENABLED_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_ENABLED_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_ENABLED_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_ENABLED_DESC"
class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field name="waf_sqli" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_SQLI_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_SQLI_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_SQLI_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_SQLI_DESC"
class="btn-group btn-group-yesno"
showon="waf_enabled:1">
<option value="1">JYES</option>
@@ -85,8 +85,8 @@
</field>
<field name="waf_xss" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_XSS_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_XSS_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_XSS_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_XSS_DESC"
class="btn-group btn-group-yesno"
showon="waf_enabled:1">
<option value="1">JYES</option>
@@ -94,8 +94,8 @@
</field>
<field name="waf_mua" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_MUA_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_MUA_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_MUA_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_MUA_DESC"
class="btn-group btn-group-yesno"
showon="waf_enabled:1">
<option value="1">JYES</option>
@@ -103,15 +103,15 @@
</field>
<field name="waf_mua_blocklist" type="textarea"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_MUA_LIST_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_MUA_LIST_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_MUA_LIST_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_MUA_LIST_DESC"
rows="4" filter="raw"
default="sqlmap,nikto,nmap,havij,w3af,acunetix,nessus,openvas,masscan,gobuster,dirbuster,wpscan,joomscan"
showon="waf_enabled:1[AND]waf_mua:1" />
<field name="waf_rfi" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_RFI_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_RFI_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_RFI_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_RFI_DESC"
class="btn-group btn-group-yesno"
showon="waf_enabled:1">
<option value="1">JYES</option>
@@ -119,8 +119,8 @@
</field>
<field name="waf_dfi" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_DFI_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_DFI_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_DFI_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_DFI_DESC"
class="btn-group btn-group-yesno"
showon="waf_enabled:1">
<option value="1">JYES</option>
@@ -130,8 +130,8 @@
<!-- Security Headers -->
<fieldset name="headers"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_HEADERS"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_HEADERS_DESC">
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_HEADERS"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_HEADERS_DESC">
<field name="header_xframe" type="radio" default="1"
label="X-Frame-Options" description="Clickjacking protection (SAMEORIGIN)"
@@ -177,12 +177,12 @@
<!-- Access Control -->
<fieldset name="access_control"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_ACCESS"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_ACCESS_DESC">
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_ACCESS"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_ACCESS_DESC">
<field name="ip_blocklist" type="subform"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_IP_BLOCKLIST_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_IP_BLOCKLIST_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_IP_BLOCKLIST_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_IP_BLOCKLIST_DESC"
formsource="plugins/system/mokosuiteclient_firewall/forms/trusted_ip_entry.xml"
multiple="true"
layout="joomla.form.field.subform.repeatable-table"
@@ -190,13 +190,13 @@
buttons="add,remove,move" />
<field name="admin_secret" type="text"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_ADMIN_SECRET_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_ADMIN_SECRET_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_ADMIN_SECRET_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_ADMIN_SECRET_DESC"
default="" filter="raw" hint="Leave empty to disable" />
<field name="admin_secret_redirect" type="text"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_ADMIN_SECRET_REDIRECT_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_ADMIN_SECRET_REDIRECT_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_ADMIN_SECRET_REDIRECT_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_ADMIN_SECRET_REDIRECT_DESC"
default="" filter="url" hint="Empty = 403 Forbidden"
showon="admin_secret!:" />
@@ -211,8 +211,8 @@
showon="autoban_threshold!:0" />
<field name="block_frontend_superuser" type="radio" default="0"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_BLOCK_FE_SU_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_BLOCK_FE_SU_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_BLOCK_FE_SU_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_BLOCK_FE_SU_DESC"
class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
@@ -221,28 +221,28 @@
<!-- File & Template Protection -->
<fieldset name="protection"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_PROTECTION"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_PROTECTION_DESC">
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_PROTECTION"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_PROTECTION_DESC">
<field name="block_sensitive_files" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_BLOCK_FILES_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_BLOCK_FILES_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_BLOCK_FILES_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_BLOCK_FILES_DESC"
class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field name="block_direct_php" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_BLOCK_PHP_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_BLOCK_PHP_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_BLOCK_PHP_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_BLOCK_PHP_DESC"
class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field name="block_template_switch" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_BLOCK_TMPL_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_BLOCK_TMPL_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_BLOCK_TMPL_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_BLOCK_TMPL_DESC"
class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
@@ -251,29 +251,29 @@
<!-- Password Policy -->
<fieldset name="password_policy"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_PASSWORD"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_PASSWORD_DESC">
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_PASSWORD"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_PASSWORD_DESC">
<field name="password_min_length" type="number" default="12"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_PASSWORD_LENGTH_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_PASSWORD_LENGTH_DESC" />
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_PASSWORD_LENGTH_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_PASSWORD_LENGTH_DESC" />
<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">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<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">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<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">
<option value="1">JYES</option>
<option value="0">JNO</option>
@@ -282,17 +282,17 @@
<!-- Upload Restrictions -->
<fieldset name="uploads"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_UPLOADS"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_UPLOADS_DESC">
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_UPLOADS"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_UPLOADS_DESC">
<field name="upload_allowed_types" type="text"
label="PLG_SYSTEM_MOKOSUITE_FIREWALL_UPLOAD_TYPES_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_UPLOAD_TYPES_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_UPLOAD_TYPES_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_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_MOKOSUITE_FIREWALL_UPLOAD_SIZE_LABEL"
description="PLG_SYSTEM_MOKOSUITE_FIREWALL_UPLOAD_SIZE_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_UPLOAD_SIZE_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_UPLOAD_SIZE_DESC"
default="100" />
</fieldset>
</fields>
@@ -8,7 +8,7 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version>
<version>02.41.00</version>
<description>PLG_SYSTEM_MOKOSUITECLIENT_LICENSE_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteClientLicense</namespace>
<files><folder>src</folder><folder>services</folder><folder>language</folder></files>
@@ -8,8 +8,8 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version>
<description>PLG_SYSTEM_MOKOSUITE_MONITOR_DESC</description>
<version>02.41.00</version>
<description>PLG_SYSTEM_MOKOSUITECLIENT_MONITOR_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteClientMonitor</namespace>
<files>
@@ -26,12 +26,12 @@
<config>
<fields name="params">
<fieldset name="basic"
label="PLG_SYSTEM_MOKOSUITE_MONITOR_FIELDSET_BASIC"
description="PLG_SYSTEM_MOKOSUITE_MONITOR_FIELDSET_BASIC_DESC">
label="PLG_SYSTEM_MOKOSUITECLIENT_MONITOR_FIELDSET_BASIC"
description="PLG_SYSTEM_MOKOSUITECLIENT_MONITOR_FIELDSET_BASIC_DESC">
<field name="heartbeat_enabled" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_MONITOR_HEARTBEAT_LABEL"
description="PLG_SYSTEM_MOKOSUITE_MONITOR_HEARTBEAT_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_MONITOR_HEARTBEAT_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_MONITOR_HEARTBEAT_DESC"
class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
@@ -39,8 +39,8 @@
<field name="base_url" type="url"
default="https://waas.dev.mokoconsulting.tech"
label="PLG_SYSTEM_MOKOSUITE_MONITOR_BASE_URL_LABEL"
description="PLG_SYSTEM_MOKOSUITE_MONITOR_BASE_URL_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_MONITOR_BASE_URL_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_MONITOR_BASE_URL_DESC"
filter="url" />
<field name="signing_key" type="hidden"
@@ -8,8 +8,8 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version>
<description>PLG_SYSTEM_MOKOSUITE_OFFLINE_DESC</description>
<version>02.41.00</version>
<description>PLG_SYSTEM_MOKOSUITECLIENT_OFFLINE_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteClientOffline</namespace>
<files>
@@ -25,15 +25,15 @@
<config>
<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"
label="PLG_SYSTEM_MOKOSUITE_OFFLINE_SLUG_LABEL"
description="PLG_SYSTEM_MOKOSUITE_OFFLINE_SLUG_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_OFFLINE_SLUG_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_OFFLINE_SLUG_DESC"
multiple="true" />
<field name="include_children" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_OFFLINE_CHILDREN_LABEL"
description="PLG_SYSTEM_MOKOSUITE_OFFLINE_CHILDREN_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_OFFLINE_CHILDREN_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_OFFLINE_CHILDREN_DESC"
class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
@@ -8,8 +8,8 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version>
<description>PLG_SYSTEM_MOKOSUITE_TENANT_DESC</description>
<version>02.41.00</version>
<description>PLG_SYSTEM_MOKOSUITECLIENT_TENANT_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteClientTenant</namespace>
<files>
@@ -26,20 +26,20 @@
<config>
<fields name="params">
<fieldset name="basic"
label="PLG_SYSTEM_MOKOSUITE_TENANT_FIELDSET_BASIC"
description="PLG_SYSTEM_MOKOSUITE_TENANT_FIELDSET_BASIC_DESC">
label="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_FIELDSET_BASIC"
description="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_FIELDSET_BASIC_DESC">
<field name="restrict_installer" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_TENANT_RESTRICT_INSTALLER_LABEL"
description="PLG_SYSTEM_MOKOSUITE_TENANT_RESTRICT_INSTALLER_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_RESTRICT_INSTALLER_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_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_MOKOSUITE_TENANT_ALLOW_UPDATES_LABEL"
description="PLG_SYSTEM_MOKOSUITE_TENANT_ALLOW_UPDATES_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_ALLOW_UPDATES_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_ALLOW_UPDATES_DESC"
class="btn-group btn-group-yesno"
showon="restrict_installer:1">
<option value="1">JYES</option>
@@ -47,40 +47,40 @@
</field>
<field name="hide_sysinfo" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITE_TENANT_HIDE_SYSINFO_LABEL"
description="PLG_SYSTEM_MOKOSUITE_TENANT_HIDE_SYSINFO_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_HIDE_SYSINFO_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_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_MOKOSUITE_TENANT_RESTRICT_CONFIG_LABEL"
description="PLG_SYSTEM_MOKOSUITE_TENANT_RESTRICT_CONFIG_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_RESTRICT_CONFIG_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_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_MOKOSUITE_TENANT_RESTRICT_TEMPLATE_LABEL"
description="PLG_SYSTEM_MOKOSUITE_TENANT_RESTRICT_TEMPLATE_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_RESTRICT_TEMPLATE_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_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_MOKOSUITE_TENANT_DISABLE_INSTALL_URL_LABEL"
description="PLG_SYSTEM_MOKOSUITE_TENANT_DISABLE_INSTALL_URL_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_DISABLE_INSTALL_URL_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_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_MOKOSUITE_TENANT_HIDDEN_MENUS_LABEL"
description="PLG_SYSTEM_MOKOSUITE_TENANT_HIDDEN_MENUS_DESC"
label="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_HIDDEN_MENUS_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_HIDDEN_MENUS_DESC"
rows="5" filter="raw" />
</fieldset>
</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_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_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>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version>
<version>02.41.00</version>
<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>
@@ -27,15 +27,15 @@ class TicketAutomation extends CMSPlugin implements SubscriberInterface
protected const TASKS_MAP = [
'mokosuiteclient.ticket.automation' => [
'langConstPrefix' => 'PLG_TASK_MOKOSUITE_TICKETS_AUTOMATION',
'langConstPrefix' => 'PLG_TASK_MOKOSUITECLIENT_TICKETS_AUTOMATION',
'method' => 'runAutomation',
],
'mokosuiteclient.ticket.imap_poll' => [
'langConstPrefix' => 'PLG_TASK_MOKOSUITE_TICKETS_IMAP_POLL',
'langConstPrefix' => 'PLG_TASK_MOKOSUITECLIENT_TICKETS_IMAP_POLL',
'method' => 'runImapPoll',
],
'mokosuiteclient.ticket.autoclose' => [
'langConstPrefix' => 'PLG_TASK_MOKOSUITE_TICKETS_AUTOCLOSE',
'langConstPrefix' => 'PLG_TASK_MOKOSUITECLIENT_TICKETS_AUTOCLOSE',
'method' => 'runAutoClose',
],
];
@@ -251,6 +251,7 @@ class TicketAutomation extends CMSPlugin implements SubscriberInterface
}
catch (\Throwable $e)
{
Log::add('Failed to load component config: ' . $e->getMessage(), Log::ERROR, 'mokosuiteclient');
return [];
}
}
@@ -12,8 +12,8 @@
<license>GNU General Public License version 3 or later; see LICENSE</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version>
<description>PLG_TASK_MOKOSUITEDEMO_DESC</description>
<version>02.41.00</version>
<description>PLG_TASK_MOKOSUITECLIENTDEMO_DESC</description>
<namespace path="src">Moko\Plugin\Task\MokoSuiteClientDemo</namespace>
<files>
@@ -10,7 +10,7 @@
* INGROUP: MokoSuiteClient
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient
* PATH: /src/packages/plg_system_mokosuiteclient/Service/DemoResetService.php
* VERSION: 02.34.84
* VERSION: 02.41.00
* 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>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version>
<description>PLG_TASK_MOKOSUITESYNC_DESC</description>
<version>02.41.00</version>
<description>PLG_TASK_MOKOSUITECLIENTSYNC_DESC</description>
<namespace path="src">Moko\Plugin\Task\MokoSuiteClientSync</namespace>
<files>
@@ -10,7 +10,7 @@
* INGROUP: MokoSuiteClient
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient
* PATH: /src/packages/plg_system_mokosuiteclient/Service/ContentSyncReceiver.php
* VERSION: 02.34.84
* VERSION: 02.41.00
* BRIEF: Receiver-side content sync — applies incoming payload to local DB
*/
@@ -10,7 +10,7 @@
* INGROUP: MokoSuiteClient
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient
* PATH: /src/packages/plg_system_mokosuiteclient/Service/ContentSyncService.php
* VERSION: 02.34.84
* VERSION: 02.41.00
* BRIEF: Sender-side content sync — builds payload and pushes to remote sites
*/
@@ -7,7 +7,7 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.84-dev</version>
<version>02.41.00</version>
<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>
<files>
+1 -1
View File
@@ -2,7 +2,7 @@
<extension type="package" method="upgrade">
<name>Package - MokoSuiteClient</name>
<packagename>mokosuiteclient</packagename>
<version>02.34.84-dev</version>
<version>02.41.00</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
+186 -42
View File
@@ -20,7 +20,7 @@ use Joomla\CMS\Log\Log;
*
* @since 2.2.0
*/
class Pkg_MokosuiteInstallerScript
class Pkg_MokosuiteclientInstallerScript
{
/**
* Runs after package installation/update.
@@ -70,6 +70,10 @@ class Pkg_MokosuiteInstallerScript
// Remove legacy extensions and migrate settings before retiring
$this->cleanupLegacyExtensions();
$this->migrateStandalonePlugins();
// Migrate monitor params into core plugin BEFORE monitor row is deleted
$this->migrateMonitorParams();
$this->removeRetiredExtensions();
$this->enablePlugin('system', 'mokosuiteclient');
@@ -78,6 +82,7 @@ class Pkg_MokosuiteInstallerScript
$this->enablePlugin('system', 'mokosuiteclient_devtools');
$this->enablePlugin('system', 'mokosuiteclient_offline');
$this->enablePlugin('system', 'mokosuiteclient_dbip');
$this->enablePlugin('system', 'mokosuiteclient_backup');
$this->enablePlugin('webservices', 'mokosuiteclient');
$this->enablePlugin('task', 'mokosuiteclientdemo');
$this->enablePlugin('task', 'mokosuiteclientsync');
@@ -468,6 +473,31 @@ class Pkg_MokosuiteInstallerScript
->where($db->quoteName('element') . ' = ' . $db->quote($element));
$db->setQuery($query);
$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)
{
@@ -505,7 +535,8 @@ class Pkg_MokosuiteInstallerScript
$db->quote('mokosuiteclientdemo'),
$db->quote('mokosuiteclientsync'),
$db->quote('mokosuiteclient_tickets'),
$db->quote('mokoonyx'),
$db->quote('mokosuiteclient_backup'),
$db->quote('mokoonyx'),
];
$query = $db->getQuery(true)
@@ -800,35 +831,43 @@ class Pkg_MokosuiteInstallerScript
{
$db = Factory::getDbo();
// Get health token from core plugin
// All heartbeat config now lives in the 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());
$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 ?? '';
if (empty($healthToken))
{
Log::add('Heartbeat skipped: health_api_token not configured', Log::INFO, 'mokosuiteclient');
return;
}
// Get base URL and signing key from monitor plugin
$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'));
$monitorParams = json_decode((string) $db->setQuery($query)->loadResult());
$baseUrl = rtrim($monitorParams->base_url ?? '', '/');
if (($coreParams->heartbeat_enabled ?? '1') === '0')
{
return;
}
// 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))
{
$manifestFile = JPATH_PLUGINS . '/system/mokosuiteclient_monitor/mokosuiteclient_monitor.xml';
$manifestFile = JPATH_PLUGINS . '/system/mokosuiteclient/mokosuiteclient.xml';
if (is_file($manifestFile))
{
@@ -836,7 +875,7 @@ class Pkg_MokosuiteInstallerScript
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'], '/');
break;
@@ -847,9 +886,13 @@ class Pkg_MokosuiteInstallerScript
if (empty($baseUrl))
{
Log::add('Heartbeat skipped: monitor_base_url not configured and manifest fallback failed', Log::WARNING, 'mokosuiteclient');
return;
}
Log::add('Heartbeat sending to: ' . $baseUrl, Log::INFO, 'mokosuiteclient');
$siteUrl = rtrim(\Joomla\CMS\Uri\Uri::root(), '/');
$domain = parse_url($siteUrl, PHP_URL_HOST) ?: '';
$timestamp = time();
@@ -866,12 +909,12 @@ class Pkg_MokosuiteInstallerScript
$headers = ['Content-Type: application/json'];
// RSA sign the request — fall back to manifest XML default
$signingKeyB64 = $monitorParams->signing_key ?? '';
$signingKeyB64 = $coreParams->monitor_signing_key ?? '';
// Fall back to manifest XML default
if (empty($signingKeyB64))
{
$manifestFile = JPATH_PLUGINS . '/system/mokosuiteclient_monitor/mokosuiteclient_monitor.xml';
$manifestFile = JPATH_PLUGINS . '/system/mokosuiteclient/mokosuiteclient.xml';
if (is_file($manifestFile))
{
@@ -879,7 +922,7 @@ class Pkg_MokosuiteInstallerScript
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'];
break;
@@ -921,16 +964,25 @@ class Pkg_MokosuiteInstallerScript
$response = curl_exec($ch);
$code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($code >= 200 && $code < 300)
if ($error)
{
Log::add('Heartbeat connection failed: ' . $error, Log::WARNING, 'mokosuiteclient');
}
elseif ($code >= 200 && $code < 300)
{
Factory::getApplication()->enqueueMessage('MokoSuiteClientHQ heartbeat: site registered', 'message');
}
else
{
Log::add(sprintf('Heartbeat HTTP %d: %s', $code, $response), Log::WARNING, 'mokosuiteclient');
}
}
catch (\Throwable $e)
{
// Silent failure — heartbeat is non-critical
Log::add('Heartbeat failed: ' . $e->getMessage(), Log::WARNING, 'mokosuiteclient');
}
}
@@ -944,12 +996,12 @@ class Pkg_MokosuiteInstallerScript
*/
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
{
$this->ensureAdminModule('mod_mokosuiteclient_menu', 'MokoSuiteClient Menu', 'menu', 3, 0);
$this->ensureAdminModule('mod_mokosuiteclient_menu', 'MokoSuiteClient Menu', 'menu', 3, -1);
}
private function setupCacheModule(): void
@@ -988,28 +1040,55 @@ class Pkg_MokosuiteInstallerScript
);
$moduleId = (int) $db->loadResult();
// Build save data — Joomla's ModuleModel expects this format
$data = [
'title' => $title,
'module' => $element,
'position' => $position,
'published' => 1,
'access' => $access,
'ordering' => $ordering,
'showtitle' => 0,
'client_id' => 1,
'language' => '*',
'params' => $params,
'assignment' => 0, // 0 = all pages
];
if ($moduleId > 0)
{
$data['id'] = $moduleId;
// 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;
}
// Use Joomla's ModuleModel to handle save + menu assignment
\Joomla\CMS\MVC\Factory\MVCFactory::class;
// Module doesn't exist — create via ModuleModel
$data = [
'title' => $title,
'module' => $element,
'position' => $position,
'published' => 1,
'access' => $access,
'ordering' => $ordering,
'showtitle' => 0,
'client_id' => 1,
'language' => '*',
'params' => $params,
'assignment' => 0,
];
$app = Factory::getApplication();
/** @var \Joomla\Component\Modules\Administrator\Model\ModuleModel $model */
@@ -1493,6 +1572,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.
*/
-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>