Compare commits

..

9 Commits

Author SHA1 Message Date
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 6b1352d82c Merge pull request 'feat: backup bridge plugin to detect MokoSuiteBackup (#208)' (#209) from feature/208-backup-bridge into dev
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
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
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) 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 / Lint & Validate (pull_request) Failing after 5s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 4s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 6s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request_target) Failing after 13s
Generic: Project CI / Lint & Validate (pull_request) Successful in 38s
Generic: Project CI / Lint & Validate (push) Successful in 38s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 39s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 49s
Universal: PR Check / Validate PR (pull_request) Failing after 38s
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
2026-06-18 19:54:34 +00:00
Jonathan Miller 446539844d ci: retrigger after runner restart
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
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 / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 10s
Generic: Project CI / Lint & Validate (pull_request) Successful in 25s
Universal: PR Check / Validate PR (pull_request) Failing after 24s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 29s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Failing after 1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 22s
2026-06-18 12:55:05 -05:00
Jonathan Miller 9b347dd136 fix(backup): send error on failure, use staleDays for cutoff, consistent shape (#208)
Generic: Project CI / Tests (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
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 11s
Generic: Project CI / Lint & Validate (pull_request) Successful in 11s
Universal: PR Check / Validate PR (pull_request) Failing after 24s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 29s
- Send explicit error status when backup data collection fails instead
  of omitting the key (so HQ can distinguish failure from not-installed)
- Add status='ok' to not-installed return for consistent array shape
- Use staleDays param instead of hardcoded 7-day cutoff in fallback query
2026-06-18 11:13:29 -05:00
Jonathan Miller bca879f0d3 feat(backup): use real MokoSuiteBackup schema and prefer BackupStatusHelper (#208)
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 7s
Generic: Project CI / Tests (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 / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Project CI / Lint & Validate (pull_request) Successful in 9s
Universal: PR Check / Validate PR (pull_request) Failing after 24s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 28s
Update bridge to use correct column names (backupstart/backupend,
status values: complete/fail/running/pending). Prefer the
BackupStatusHelper API when available, with direct table query
fallback for older MokoSuiteBackup versions.
2026-06-18 10:41:54 -05:00
Jonathan Miller 0d731eafd0 fix: swap demo/sync plugin manifests — contents were in wrong files
The merge rename accidentally swapped the XML manifest contents between
plg_task_mokosuiteclientdemo and plg_task_mokosuiteclientsync. The demo
manifest contained the sync element/name/namespace and vice versa,
causing JInstaller to look for mokosuiteclientsync.xml inside the demo
plugin zip.
2026-06-18 10:37:22 -05:00
Jonathan Miller 559db324cb feat(backup): scaffold backup bridge plugin (#208)
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 7s
Add plg_system_mokosuiteclient_backup — detects MokoSuiteBackup and
collects backup status for heartbeat payloads to MokoSuiteHQ.

Scaffolding includes manifest, service provider, extension class,
and language files. Table column names are placeholders pending
MokoSuiteBackup schema confirmation (MokoSuiteBackup#47).
2026-06-18 10:24:54 -05:00
Jonathan Miller bd37187f0e ci: add changelog extraction to promote-rc job in auto-release [skip ci] 2026-06-18 10:04:13 -05:00
69 changed files with 7149 additions and 314 deletions
+1 -1
View File
@@ -9,7 +9,7 @@
<display-name>Package - MokoSuiteClient</display-name> <display-name>Package - MokoSuiteClient</display-name>
<org>MokoConsulting</org> <org>MokoConsulting</org>
<description>White-label identity, security hardening, and tenant restriction layer for Suite-managed Joomla environments</description> <description>White-label identity, security hardening, and tenant restriction layer for Suite-managed Joomla environments</description>
<version>02.40.01</version> <version>02.34.84</version>
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license> <license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
</identity> </identity>
<governance> <governance>
+1 -1
View File
@@ -5,7 +5,7 @@
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Automation # INGROUP: moko-platform.Automation
# VERSION: 02.40.01 # VERSION: 02.34.84
# BRIEF: Auto-create feature branch when an issue is opened # BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch" name: "Universal: Issue Branch"
+4 -3
View File
@@ -102,11 +102,12 @@ jobs:
esac esac
# Bump version: minor only on branch elevation, patch for rebuilds # Bump version: minor only on branch elevation, patch for rebuilds
# Check branch name — if already on the target stability branch, it's a rebuild CURRENT=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "00.00.00")
BRANCH="${{ github.ref_name }}"
case "$STABILITY" in case "$STABILITY" in
release-candidate) release-candidate)
if [ "$BRANCH" = "rc" ]; then # If already on RC suffix, this is a rebuild — patch bump
# If not (e.g. coming from dev), this is an elevation — minor bump
if echo "$CURRENT" | grep -q '\-rc$'; then
BUMP="patch" BUMP="patch"
else else
BUMP="minor" BUMP="minor"
+33 -4
View File
@@ -14,16 +14,14 @@
INGROUP: MokoSuiteClient.Documentation INGROUP: MokoSuiteClient.Documentation
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
PATH: ./CHANGELOG.md PATH: ./CHANGELOG.md
VERSION: 02.40.01 VERSION: 02.34.84
BRIEF: Version history using `Keep a Changelog` BRIEF: Version history using `Keep a Changelog`
--> -->
# Changelog # Changelog
## [Unreleased] ## [Unreleased]
## [02.35.00] --- 2026-06-18
### Changed ### 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. - **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.
@@ -178,3 +176,34 @@
- Content Sync config tab (targets now in scheduled tasks) - Content Sync config tab (targets now in scheduled tasks)
- Site Aliases config tab (hardcoded to dev.{primary_domain}) - Site Aliases config tab (hardcoded to dev.{primary_domain})
- File sync (images/, files/, media/) — sync is API/DB content only - File sync (images/, files/, media/) — sync is API/DB content only
## [02.29] - 2026-05-31
### Added
- `allow_extension_updates` param — separate update rights from installer restrictions; tenants can update extensions by default even when the installer is restricted
- Hardcoded master usernames — multiple privileged users supported with identical access
### Fixed
- Emergency access IP whitelist: empty `allowed_ips` now permits all IPs (was blocking everyone)
- Emergency access reads `allowed_ips` from plugin params instead of global config
- `plg_task_mokosuiteclientsync` — Joomla Scheduled Task plugin for automatic content sync to remote sites
- Community Builder tables added to demo reset safe table list
- API endpoint `POST /api/index.php/v1/mokosuiteclient/install` — install extensions from a remote ZIP URL
- Demo Mode with configurable warning banner on frontend when enabled
- Demo banner countdown now shows weeks/days/months for longer intervals instead of raw hours
- `DemoResetService` — baseline snapshot and restore for DB tables + media files
- API endpoints `POST /?mokosuiteclient=reset` and `POST /?mokosuiteclient=snapshot` (query-string)
- REST endpoints `POST /api/v1/mokosuiteclient/reset` and `GET/POST /api/v1/mokosuiteclient/snapshot`
- `plg_task_mokosuiteclientdemo` — Joomla Scheduled Task plugin for automatic demo site reset
- Admin toggles: Take Snapshot Now and Restore Baseline Now in plugin config
- Content Sync: one-way push of articles, categories, menus, and modules to remote MokoSuiteClient sites
- Content Sync: API endpoints `POST /?mokosuiteclient=sync` (sender) and `POST /?mokosuiteclient=sync-receive` (receiver)
- Content Sync: REST endpoints `POST /api/v1/mokosuiteclient/sync` and `POST /api/v1/mokosuiteclient/sync-receive`
- Content Sync: configurable sync targets with URL + API token in plugin settings
- Package installer: protect all MokoSuiteClient extensions (not just system plugin) and ensure update server stays enabled
- Package installer: clean up legacy `mokosuiteclientbrand` extension entries and files on install/update
- API endpoint `GET /?mokosuiteclient=extensions` and `GET /api/v1/mokosuiteclient/extensions` — list installed extensions with version, status, and update server info
## [02.20] --- 2026-05-28
+1 -1
View File
@@ -14,7 +14,7 @@
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Documentation INGROUP: MokoSuiteClient.Documentation
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.40.01 VERSION: 02.34.84
PATH: ./CODE_OF_CONDUCT.md PATH: ./CODE_OF_CONDUCT.md
BRIEF: Reference + packaging repo for Moko Consulting Developer GPT Other Default BRIEF: Reference + packaging repo for Moko Consulting Developer GPT Other Default
--> -->
+1 -1
View File
@@ -19,7 +19,7 @@
DEFGROUP: mokoconsulting-tech.MokoSuiteClientBrand DEFGROUP: mokoconsulting-tech.MokoSuiteClientBrand
INGROUP: MokoStandards.Governance INGROUP: MokoStandards.Governance
REPO: https://github.com/mokoconsulting-tech/MokoSuiteClientBrand REPO: https://github.com/mokoconsulting-tech/MokoSuiteClientBrand
VERSION: 02.40.01 VERSION: 02.34.84
PATH: /GOVERNANCE.md PATH: /GOVERNANCE.md
BRIEF: Project governance rules, roles, and decision process for MokoSuiteClientBrand BRIEF: Project governance rules, roles, and decision process for MokoSuiteClientBrand
--> -->
+1 -1
View File
@@ -15,7 +15,7 @@
INGROUP: MokoSuiteClient.Documentation INGROUP: MokoSuiteClient.Documentation
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
PATH: ./LICENSE.md PATH: ./LICENSE.md
VERSION: 02.40.01 VERSION: 02.34.84
BRIEF: Project license (GPL-3.0-or-later) BRIEF: Project license (GPL-3.0-or-later)
--> -->
GNU GENERAL PUBLIC LICENSE GNU GENERAL PUBLIC LICENSE
+3 -3
View File
@@ -12,10 +12,10 @@
# ============================================================================== # ==============================================================================
# Extension Configuration # Extension Configuration
EXTENSION_NAME := mokosuiteclient EXTENSION_NAME := mokoexample
EXTENSION_TYPE := package EXTENSION_TYPE := module
# Options: module, plugin, component, package, template # Options: module, plugin, component, package, template
EXTENSION_VERSION := 02.35.00 EXTENSION_VERSION := 1.0.0
# Module Configuration (for modules only) # Module Configuration (for modules only)
MODULE_TYPE := site MODULE_TYPE := site
+1 -1
View File
@@ -9,7 +9,7 @@
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient INGROUP: MokoSuiteClient
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient
VERSION: 02.40.01 VERSION: 02.34.84
PATH: /README.md PATH: /README.md
BRIEF: MokoSuiteClient platform plugin for Joomla BRIEF: MokoSuiteClient platform plugin for Joomla
--> -->
+1 -1
View File
@@ -23,7 +23,7 @@ DEFGROUP: [PROJECT_NAME]
INGROUP: [PROJECT_NAME].Documentation INGROUP: [PROJECT_NAME].Documentation
REPO: [REPOSITORY_URL] REPO: [REPOSITORY_URL]
PATH: /SECURITY.md PATH: /SECURITY.md
VERSION: 02.40.01 VERSION: 02.34.84
BRIEF: Security vulnerability reporting and handling policy BRIEF: Security vulnerability reporting and handling policy
--> -->
+2 -2
View File
@@ -11,13 +11,13 @@
INGROUP: MokoSuiteClient.Build INGROUP: MokoSuiteClient.Build
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
FILE: build-guide.md FILE: build-guide.md
VERSION: 02.40.01 VERSION: 02.34.84
PATH: /docs/guides/ PATH: /docs/guides/
BRIEF: Build and packaging guide for the MokoSuiteClient system plugin BRIEF: Build and packaging guide for the MokoSuiteClient system plugin
NOTE: Defines environment setup, repository layout, packaging rules, and release preparation NOTE: Defines environment setup, repository layout, packaging rules, and release preparation
--> -->
# MokoSuiteClient Build Guide (VERSION: 02.40.01) # MokoSuiteClient Build Guide (VERSION: 02.34.84)
## 1. Purpose ## 1. Purpose
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Guides INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.40.01 VERSION: 02.34.84
PATH: /docs/guides/configuration-guide.md PATH: /docs/guides/configuration-guide.md
BRIEF: Configuration guide for the MokoSuiteClient system plugin BRIEF: Configuration guide for the MokoSuiteClient system plugin
NOTE: Defines plugin parameters, expected behaviors, and recommended defaults NOTE: Defines plugin parameters, expected behaviors, and recommended defaults
--> -->
# MokoSuiteClient Configuration Guide (VERSION: 02.40.01) # MokoSuiteClient Configuration Guide (VERSION: 02.34.84)
## 1. Objective ## 1. Objective
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Guides INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.40.01 VERSION: 02.34.84
PATH: /docs/guides/installation-guide.md PATH: /docs/guides/installation-guide.md
BRIEF: Installation guide for the MokoSuiteClient system plugin BRIEF: Installation guide for the MokoSuiteClient system plugin
NOTE: First document in the guide set NOTE: First document in the guide set
--> -->
# MokoSuiteClient Installation Guide (VERSION: 02.40.01) # MokoSuiteClient Installation Guide (VERSION: 02.34.84)
## Introduction ## Introduction
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Guides INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.40.01 VERSION: 02.34.84
PATH: /docs/guides/operations-guide.md PATH: /docs/guides/operations-guide.md
BRIEF: Operational guide for administering and managing the MokoSuiteClient system plugin BRIEF: Operational guide for administering and managing the MokoSuiteClient system plugin
NOTE: Defines lifecycle, responsibilities, and operational behaviors NOTE: Defines lifecycle, responsibilities, and operational behaviors
--> -->
# MokoSuiteClient Operations Guide (VERSION: 02.40.01) # MokoSuiteClient Operations Guide (VERSION: 02.34.84)
## Introduction ## Introduction
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Guides INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.40.01 VERSION: 02.34.84
PATH: /docs/guides/rollback-and-recovery-guide.md PATH: /docs/guides/rollback-and-recovery-guide.md
BRIEF: Rollback and recovery guide for restoring stable operation after plugin related incidents BRIEF: Rollback and recovery guide for restoring stable operation after plugin related incidents
NOTE: Completes the core guide set for Suite plugin governance NOTE: Completes the core guide set for Suite plugin governance
--> -->
# MokoSuiteClient Rollback and Recovery Guide (VERSION: 02.40.01) # MokoSuiteClient Rollback and Recovery Guide (VERSION: 02.34.84)
## Introduction ## Introduction
+2 -2
View File
@@ -7,13 +7,13 @@
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Guides INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.40.01 VERSION: 02.34.84
PATH: /docs/guides/testing-guide.md PATH: /docs/guides/testing-guide.md
BRIEF: Testing guide for MokoSuiteClient v02.01.08 BRIEF: Testing guide for MokoSuiteClient v02.01.08
NOTE: Covers manual test procedures for language overrides, install/uninstall, and configuration NOTE: Covers manual test procedures for language overrides, install/uninstall, and configuration
--> -->
# MokoSuiteClient Testing Guide (VERSION: 02.40.01) # MokoSuiteClient Testing Guide (VERSION: 02.34.84)
## 1. Prerequisites ## 1. Prerequisites
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Guides INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.40.01 VERSION: 02.34.84
PATH: /docs/guides/troubleshooting-guide.md PATH: /docs/guides/troubleshooting-guide.md
BRIEF: Troubleshooting guide for diagnosing and resolving issues related to the MokoSuiteClient plugin BRIEF: Troubleshooting guide for diagnosing and resolving issues related to the MokoSuiteClient plugin
NOTE: Designed for administrators and Suite operations teams NOTE: Designed for administrators and Suite operations teams
--> -->
# MokoSuiteClient Troubleshooting Guide (VERSION: 02.40.01) # MokoSuiteClient Troubleshooting Guide (VERSION: 02.34.84)
## Introduction ## Introduction
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Guides INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.40.01 VERSION: 02.34.84
PATH: /docs/guides/upgrade-and-versioning-guide.md PATH: /docs/guides/upgrade-and-versioning-guide.md
BRIEF: Guide for updating, versioning, and maintaining the MokoSuiteClient plugin BRIEF: Guide for updating, versioning, and maintaining the MokoSuiteClient plugin
NOTE: Defines release flow, version rules, and upgrade validation NOTE: Defines release flow, version rules, and upgrade validation
--> -->
# MokoSuiteClient Upgrade and Versioning Guide (VERSION: 02.40.01) # MokoSuiteClient Upgrade and Versioning Guide (VERSION: 02.34.84)
## Introduction ## Introduction
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Documentation INGROUP: MokoSuiteClient.Documentation
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.40.01 VERSION: 02.34.84
PATH: /docs/index.md PATH: /docs/index.md
BRIEF: Master index of all documentation for the MokoSuiteClient plugin BRIEF: Master index of all documentation for the MokoSuiteClient plugin
NOTE: Automatically maintained index for all guide canvases NOTE: Automatically maintained index for all guide canvases
--> -->
# MokoSuiteClient Documentation Index (VERSION: 02.40.01) # MokoSuiteClient Documentation Index (VERSION: 02.34.84)
## Introduction ## Introduction
+2 -2
View File
@@ -11,12 +11,12 @@
INGROUP: MokoSuiteClient INGROUP: MokoSuiteClient
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
PATH: /docs/plugin-basic.md PATH: /docs/plugin-basic.md
VERSION: 02.40.01 VERSION: 02.34.84
BRIEF: Baseline documentation for the MokoSuiteClient system plugin BRIEF: Baseline documentation for the MokoSuiteClient system plugin
NOTE: Foundational reference for internal and external stakeholders NOTE: Foundational reference for internal and external stakeholders
--> -->
# MokoSuiteClient Plugin Overview (VERSION: 02.40.01) # MokoSuiteClient Plugin Overview (VERSION: 02.34.84)
## Introduction ## Introduction
+1 -1
View File
@@ -10,7 +10,7 @@ DEFGROUP: MokoSuiteClient.Documentation
INGROUP: MokoStandards.Templates INGROUP: MokoStandards.Templates
REPO: https://github.com/mokoconsulting-tech/MokoSuiteClient REPO: https://github.com/mokoconsulting-tech/MokoSuiteClient
PATH: /docs/update-server.md PATH: /docs/update-server.md
VERSION: 02.40.01 VERSION: 02.34.84
BRIEF: How this extension's Joomla update server file (update.xml) is managed BRIEF: How this extension's Joomla update server file (update.xml) is managed
--> -->
@@ -500,7 +500,6 @@ class DisplayController extends BaseController
if (strlen($query) < 3) if (strlen($query) < 3)
{ {
$this->jsonResponse(['results' => []]); $this->jsonResponse(['results' => []]);
return;
} }
try try
@@ -528,8 +527,7 @@ class DisplayController extends BaseController
} }
catch (\Throwable $e) catch (\Throwable $e)
{ {
Log::add('KB search failed: ' . $e->getMessage(), Log::ERROR, 'mokosuiteclient'); $this->jsonResponse(['results' => []]);
$this->jsonResponse(['results' => [], 'error' => 'Search unavailable']);
} }
} }
@@ -577,7 +575,7 @@ class DisplayController extends BaseController
public function saveCategory() public function saveCategory()
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); return; } if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); }
$input = Factory::getApplication()->getInput(); $input = Factory::getApplication()->getInput();
$db = Factory::getDbo(); $db = Factory::getDbo();
$id = $input->getInt('id', 0); $id = $input->getInt('id', 0);
@@ -602,7 +600,7 @@ class DisplayController extends BaseController
public function deleteCategory() public function deleteCategory()
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); return; } if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); }
$db = Factory::getDbo(); $db = Factory::getDbo();
$db->setQuery($db->getQuery(true)->delete('#__mokosuiteclient_ticket_categories')->where('id = ' . Factory::getApplication()->getInput()->getInt('id', 0)))->execute(); $db->setQuery($db->getQuery(true)->delete('#__mokosuiteclient_ticket_categories')->where('id = ' . Factory::getApplication()->getInput()->getInt('id', 0)))->execute();
$this->jsonResponse(['success' => true, 'message' => 'Category deleted.']); $this->jsonResponse(['success' => true, 'message' => 'Category deleted.']);
@@ -611,7 +609,7 @@ class DisplayController extends BaseController
public function reorderCategory() public function reorderCategory()
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); return; } if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); }
$order = json_decode(Factory::getApplication()->getInput()->getRaw('order', '[]'), true); $order = json_decode(Factory::getApplication()->getInput()->getRaw('order', '[]'), true);
if (!is_array($order)) { $this->jsonResponse(['success' => false, 'message' => 'Invalid order']); return; } if (!is_array($order)) { $this->jsonResponse(['success' => false, 'message' => 'Invalid order']); return; }
$db = Factory::getDbo(); $db = Factory::getDbo();
@@ -624,7 +622,7 @@ class DisplayController extends BaseController
public function saveCanned() public function saveCanned()
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); return; } if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); }
$input = Factory::getApplication()->getInput(); $input = Factory::getApplication()->getInput();
$db = Factory::getDbo(); $db = Factory::getDbo();
$data = (object) [ $data = (object) [
@@ -642,7 +640,7 @@ class DisplayController extends BaseController
public function deleteCanned() public function deleteCanned()
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); return; } if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); }
$db = Factory::getDbo(); $db = Factory::getDbo();
$db->setQuery($db->getQuery(true)->delete('#__mokosuiteclient_ticket_canned')->where('id = ' . Factory::getApplication()->getInput()->getInt('id', 0)))->execute(); $db->setQuery($db->getQuery(true)->delete('#__mokosuiteclient_ticket_canned')->where('id = ' . Factory::getApplication()->getInput()->getInt('id', 0)))->execute();
$this->jsonResponse(['success' => true, 'message' => 'Canned response deleted.']); $this->jsonResponse(['success' => true, 'message' => 'Canned response deleted.']);
@@ -651,7 +649,7 @@ class DisplayController extends BaseController
public function reorderCanned() public function reorderCanned()
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); return; } if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); }
$order = json_decode(Factory::getApplication()->getInput()->getRaw('order', '[]'), true); $order = json_decode(Factory::getApplication()->getInput()->getRaw('order', '[]'), true);
if (!is_array($order)) { $this->jsonResponse(['success' => false, 'message' => 'Invalid order']); return; } if (!is_array($order)) { $this->jsonResponse(['success' => false, 'message' => 'Invalid order']); return; }
$db = Factory::getDbo(); $db = Factory::getDbo();
@@ -664,7 +662,7 @@ class DisplayController extends BaseController
public function uploadAttachment() public function uploadAttachment()
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); return; } if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); }
$input = Factory::getApplication()->getInput(); $input = Factory::getApplication()->getInput();
$ticketId = $input->getInt('ticket_id', 0); $ticketId = $input->getInt('ticket_id', 0);
$replyId = $input->getInt('reply_id', 0) ?: null; $replyId = $input->getInt('reply_id', 0) ?: null;
@@ -677,7 +675,7 @@ class DisplayController extends BaseController
public function downloadAttachment() public function downloadAttachment()
{ {
if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); return; } if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); }
$id = Factory::getApplication()->getInput()->getInt('id', 0); $id = Factory::getApplication()->getInput()->getInt('id', 0);
$db = Factory::getDbo(); $db = Factory::getDbo();
$db->setQuery($db->getQuery(true)->select('*')->from('#__mokosuiteclient_ticket_attachments')->where('id = ' . $id)); $db->setQuery($db->getQuery(true)->select('*')->from('#__mokosuiteclient_ticket_attachments')->where('id = ' . $id));
@@ -698,7 +696,7 @@ class DisplayController extends BaseController
public function deleteAttachment() public function deleteAttachment()
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); return; } if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); }
$id = Factory::getApplication()->getInput()->getInt('id', 0); $id = Factory::getApplication()->getInput()->getInt('id', 0);
$ok = \Moko\Component\MokoSuiteClient\Administrator\Service\AttachmentService::delete($id); $ok = \Moko\Component\MokoSuiteClient\Administrator\Service\AttachmentService::delete($id);
$this->jsonResponse(['success' => $ok, 'message' => $ok ? 'Attachment deleted' : 'Not found']); $this->jsonResponse(['success' => $ok, 'message' => $ok ? 'Attachment deleted' : 'Not found']);
@@ -707,7 +705,7 @@ class DisplayController extends BaseController
public function rateTicket() public function rateTicket()
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); return; } if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); }
$input = Factory::getApplication()->getInput(); $input = Factory::getApplication()->getInput();
$ticketId = $input->getInt('ticket_id', 0); $ticketId = $input->getInt('ticket_id', 0);
$rating = $input->getInt('rating', 0); $rating = $input->getInt('rating', 0);
@@ -730,7 +728,7 @@ class DisplayController extends BaseController
public function saveAutomation() public function saveAutomation()
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('core.admin')) { $this->jsonForbidden(); return; } if (!$this->checkAcl('core.admin')) { $this->jsonForbidden(); }
$input = Factory::getApplication()->getInput(); $input = Factory::getApplication()->getInput();
$db = Factory::getDbo(); $db = Factory::getDbo();
$data = (object) [ $data = (object) [
@@ -751,7 +749,7 @@ class DisplayController extends BaseController
public function deleteAutomation() public function deleteAutomation()
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('core.admin')) { $this->jsonForbidden(); return; } if (!$this->checkAcl('core.admin')) { $this->jsonForbidden(); }
$db = Factory::getDbo(); $db = Factory::getDbo();
$db->setQuery($db->getQuery(true)->delete('#__mokosuiteclient_ticket_automation')->where('id = ' . Factory::getApplication()->getInput()->getInt('id', 0)))->execute(); $db->setQuery($db->getQuery(true)->delete('#__mokosuiteclient_ticket_automation')->where('id = ' . Factory::getApplication()->getInput()->getInt('id', 0)))->execute();
$this->jsonResponse(['success' => true, 'message' => 'Rule deleted.']); $this->jsonResponse(['success' => true, 'message' => 'Rule deleted.']);
@@ -760,7 +758,7 @@ class DisplayController extends BaseController
public function toggleAutomation() public function toggleAutomation()
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('core.admin')) { $this->jsonForbidden(); return; } if (!$this->checkAcl('core.admin')) { $this->jsonForbidden(); }
$input = Factory::getApplication()->getInput(); $input = Factory::getApplication()->getInput();
$db = Factory::getDbo(); $db = Factory::getDbo();
$db->setQuery($db->getQuery(true)->update('#__mokosuiteclient_ticket_automation') $db->setQuery($db->getQuery(true)->update('#__mokosuiteclient_ticket_automation')
@@ -772,7 +770,7 @@ class DisplayController extends BaseController
public function reorderAutomation() public function reorderAutomation()
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('core.admin')) { $this->jsonForbidden(); return; } if (!$this->checkAcl('core.admin')) { $this->jsonForbidden(); }
$order = json_decode(Factory::getApplication()->getInput()->getRaw('order', '[]'), true); $order = json_decode(Factory::getApplication()->getInput()->getRaw('order', '[]'), true);
if (!is_array($order)) { $this->jsonResponse(['success' => false, 'message' => 'Invalid order']); return; } if (!is_array($order)) { $this->jsonResponse(['success' => false, 'message' => 'Invalid order']); return; }
$db = Factory::getDbo(); $db = Factory::getDbo();
@@ -52,9 +52,8 @@ class AttachmentService
$ticketDir = self::STORAGE_DIR . '/' . $ticketId; $ticketDir = self::STORAGE_DIR . '/' . $ticketId;
if (!is_dir($ticketDir) && !Folder::create($ticketDir)) { if (!is_dir($ticketDir)) {
Log::add("Failed to create attachment directory: {$ticketDir}", Log::ERROR, 'mokosuiteclient'); Folder::create($ticketDir);
return [];
} }
$userId = (int) Factory::getUser()->id; $userId = (int) Factory::getUser()->id;
@@ -63,7 +62,6 @@ class AttachmentService
for ($i = 0, $count = count($files['name']); $i < $count; $i++) for ($i = 0, $count = count($files['name']); $i < $count; $i++)
{ {
if ($files['error'][$i] !== UPLOAD_ERR_OK) { if ($files['error'][$i] !== UPLOAD_ERR_OK) {
Log::add("Attachment upload error for '{$files['name'][$i]}': PHP error code {$files['error'][$i]}", Log::WARNING, 'mokosuiteclient');
continue; continue;
} }
@@ -129,13 +127,9 @@ class AttachmentService
/** /**
* Get the absolute filesystem path for an attachment. * Get the absolute filesystem path for an attachment.
*/ */
public static function getAbsolutePath(object $attachment): ?string public static function getAbsolutePath(object $attachment): string
{ {
$path = realpath(self::STORAGE_DIR . '/' . $attachment->filepath); return self::STORAGE_DIR . '/' . $attachment->filepath;
if ($path === false || !str_starts_with($path, realpath(self::STORAGE_DIR))) {
return null;
}
return $path;
} }
/** /**
@@ -137,9 +137,8 @@ class AutomationEngine
break; break;
case 'assign': case 'assign':
$assignId = (int) $value; if ($ticketId) {
if ($ticketId && $assignId > 0) { $db->setQuery("UPDATE {$db->quoteName('#__mokosuiteclient_tickets')} SET assigned_to = {$db->quote($value)}, modified = {$db->quote(Factory::getDate()->toSql())} WHERE id = {$ticketId}")->execute();
$db->setQuery("UPDATE {$db->quoteName('#__mokosuiteclient_tickets')} SET assigned_to = {$assignId}, modified = {$db->quote(Factory::getDate()->toSql())} WHERE id = {$ticketId}")->execute();
} }
break; break;
@@ -188,7 +187,7 @@ class AutomationEngine
} }
catch (\Throwable $e) catch (\Throwable $e)
{ {
Log::add("Automation action '{$type}' failed for rule #{$rule->id}: " . $e->getMessage(), Log::ERROR, 'mokosuiteclient'); Log::add("Automation action {$type} failed: " . $e->getMessage(), Log::WARNING, 'mokosuiteclient');
} }
} }
} }
@@ -305,7 +305,6 @@ class NotificationService
} }
catch (\Throwable $e) catch (\Throwable $e)
{ {
Log::add('Failed to look up email for user ID ' . $userId . ': ' . $e->getMessage(), Log::WARNING, 'mokosuiteclient');
return null; return null;
} }
} }
@@ -332,7 +331,6 @@ class NotificationService
} }
catch (\Throwable $e) catch (\Throwable $e)
{ {
Log::add('Failed to load notification config: ' . $e->getMessage(), Log::ERROR, 'mokosuiteclient');
return []; return [];
} }
} }
@@ -399,16 +397,12 @@ class NotificationService
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 5); curl_setopt($ch, CURLOPT_TIMEOUT, 5);
$response = curl_exec($ch); curl_exec($ch);
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch); curl_close($ch);
if ($response === false) if ($httpCode < 200 || $httpCode >= 300)
{
Log::add("Ntfy push connection failed for event {$event}: " . $curlError, Log::WARNING, 'mokosuiteclient');
}
elseif ($httpCode < 200 || $httpCode >= 300)
{ {
Log::add("Ntfy push failed (HTTP {$httpCode}) for event {$event}", Log::WARNING, 'mokosuiteclient'); Log::add("Ntfy push failed (HTTP {$httpCode}) for event {$event}", Log::WARNING, 'mokosuiteclient');
} }
@@ -35,7 +35,7 @@ class HtmlView extends BaseHtmlView
protected function addToolbar(): void protected function addToolbar(): void
{ {
ToolbarHelper::title(Text::_('COM_MOKOSUITECLIENT_TICKET_SETTINGS'), 'cog'); ToolbarHelper::title(Text::_('COM_MOKOSUITE_TICKET_SETTINGS'), 'cog');
ToolbarHelper::back('JTOOLBAR_BACK', 'index.php?option=com_mokosuiteclient&view=tickets'); ToolbarHelper::back('JTOOLBAR_BACK', 'index.php?option=com_mokosuiteclient&view=tickets');
} }
} }
@@ -68,9 +68,8 @@ class TicketsController extends BaseController
$tickets = $db->loadObjectList() ?: []; $tickets = $db->loadObjectList() ?: [];
// Total count (with same filters applied) // Total count
$countQuery = clone $query; $countQuery = $db->getQuery(true)->select('COUNT(*)')->from('#__mokosuiteclient_tickets');
$countQuery->clear('select')->clear('order')->select('COUNT(*)');
$db->setQuery($countQuery); $db->setQuery($countQuery);
$total = (int) $db->loadResult(); $total = (int) $db->loadResult();
@@ -194,18 +193,14 @@ class TicketsController extends BaseController
$id = $input->getInt('id', 0); $id = $input->getInt('id', 0);
$db = Factory::getDbo(); $db = Factory::getDbo();
// Type-safe input extraction
$fields = []; $fields = [];
$intFields = ['status_id', 'priority_id', 'category_id', 'assigned_to']; $updatable = ['status', 'status_id', 'priority', 'priority_id', 'category_id', 'assigned_to'];
$strFields = ['status', 'priority'];
foreach ($intFields as $field) { foreach ($updatable as $field) {
$value = $input->getInt($field, 0); $value = $input->get($field, null, 'raw');
if ($value > 0) { $fields[$field] = $value; } if ($value !== null) {
} $fields[$field] = $value;
foreach ($strFields as $field) { }
$value = $input->getString($field, '');
if ($value !== '') { $fields[$field] = $value; }
} }
if (empty($fields)) { if (empty($fields)) {
@@ -213,43 +208,14 @@ class TicketsController extends BaseController
return; return;
} }
// Sync status/status_id if only one is provided
if (isset($fields['status']) && !isset($fields['status_id'])) {
$q = $db->getQuery(true)->select('id')->from('#__mokosuiteclient_ticket_statuses')
->where($db->quoteName('alias') . ' = ' . $db->quote($fields['status']));
$resolved = (int) $db->setQuery($q, 0, 1)->loadResult();
if ($resolved) { $fields['status_id'] = $resolved; }
} elseif (isset($fields['status_id']) && !isset($fields['status'])) {
$q = $db->getQuery(true)->select('alias')->from('#__mokosuiteclient_ticket_statuses')
->where('id = ' . (int) $fields['status_id']);
$alias = $db->setQuery($q, 0, 1)->loadResult();
if ($alias) { $fields['status'] = $alias; }
}
if (isset($fields['priority']) && !isset($fields['priority_id'])) {
$q = $db->getQuery(true)->select('id')->from('#__mokosuiteclient_ticket_priorities')
->where($db->quoteName('alias') . ' = ' . $db->quote($fields['priority']));
$resolved = (int) $db->setQuery($q, 0, 1)->loadResult();
if ($resolved) { $fields['priority_id'] = $resolved; }
} elseif (isset($fields['priority_id']) && !isset($fields['priority'])) {
$q = $db->getQuery(true)->select('alias')->from('#__mokosuiteclient_ticket_priorities')
->where('id = ' . (int) $fields['priority_id']);
$alias = $db->setQuery($q, 0, 1)->loadResult();
if ($alias) { $fields['priority'] = $alias; }
}
$sets = []; $sets = [];
foreach ($fields as $k => $v) { foreach ($fields as $k => $v) {
$sets[] = $db->quoteName($k) . ' = ' . (is_int($v) ? $v : $db->quote($v)); $sets[] = $db->quoteName($k) . ' = ' . $db->quote($v);
} }
$sets[] = 'modified = ' . $db->quote(Factory::getDate()->toSql()); $sets[] = 'modified = ' . $db->quote(Factory::getDate()->toSql());
$db->setQuery('UPDATE ' . $db->quoteName('#__mokosuiteclient_tickets') . ' SET ' . implode(', ', $sets) . ' WHERE id = ' . $id)->execute(); $db->setQuery('UPDATE ' . $db->quoteName('#__mokosuiteclient_tickets') . ' SET ' . implode(', ', $sets) . ' WHERE id = ' . $id)->execute();
if ($db->getAffectedRows() === 0) {
$this->sendJson(404, ['error' => 'Ticket not found']);
return;
}
$this->sendJson(200, ['id' => $id, 'message' => 'Ticket updated', 'updated' => array_keys($fields)]); $this->sendJson(200, ['id' => $id, 'message' => 'Ticket updated', 'updated' => array_keys($fields)]);
} }
@@ -298,7 +264,6 @@ class TicketsController extends BaseController
$user = Factory::getUser(); $user = Factory::getUser();
if (!$user->authorise($action, $asset)) { if (!$user->authorise($action, $asset)) {
$this->sendJson(403, ['error' => 'Not authorized']); $this->sendJson(403, ['error' => 'Not authorized']);
throw new \RuntimeException('Not authorized', 403);
} }
} }
@@ -20,7 +20,7 @@
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.40.01-rc</version> <version>02.34.84-dev</version>
<description>MokoSuiteClient admin dashboard and REST API. Provides a control panel for managing MokoSuiteClient feature plugins, site health monitoring, and remote management endpoints.</description> <description>MokoSuiteClient admin dashboard and REST API. Provides a control panel for managing MokoSuiteClient feature plugins, site health monitoring, and remote management endpoints.</description>
<namespace path="src">Moko\Component\MokoSuiteClient</namespace> <namespace path="src">Moko\Component\MokoSuiteClient</namespace>
@@ -7,8 +7,8 @@
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.40.01-rc</version> <version>02.34.84-dev</version>
<description>MOD_MOKOSUITECLIENT_CACHE_DESC</description> <description>MOD_MOKOSUITE_CACHE_DESC</description>
<namespace path="src">Moko\Module\MokoSuiteCache</namespace> <namespace path="src">Moko\Module\MokoSuiteCache</namespace>
<files> <files>
@@ -7,7 +7,7 @@
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.40.01-rc</version> <version>02.34.84-dev</version>
<description>MOD_MOKOSUITECLIENT_CATEGORIES_DESC</description> <description>MOD_MOKOSUITECLIENT_CATEGORIES_DESC</description>
<namespace path="src">Moko\Module\MokoSuiteClientCategories</namespace> <namespace path="src">Moko\Module\MokoSuiteClientCategories</namespace>
@@ -7,8 +7,8 @@
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.40.01-rc</version> <version>02.34.84-dev</version>
<description>MOD_MOKOSUITECLIENT_CPANEL_DESC</description> <description>MOD_MOKOSUITE_CPANEL_DESC</description>
<namespace path="src">Moko\Module\MokoSuiteCpanel</namespace> <namespace path="src">Moko\Module\MokoSuiteCpanel</namespace>
<files> <files>
@@ -25,64 +25,64 @@
<config> <config>
<fields name="params"> <fields name="params">
<fieldset name="basic" <fieldset name="basic"
label="MOD_MOKOSUITECLIENT_CPANEL_FIELDSET_DISPLAY" label="MOD_MOKOSUITE_CPANEL_FIELDSET_DISPLAY"
description="MOD_MOKOSUITECLIENT_CPANEL_FIELDSET_DISPLAY_DESC"> description="MOD_MOKOSUITE_CPANEL_FIELDSET_DISPLAY_DESC">
<field name="collapsed" type="radio" default="1" <field name="collapsed" type="radio" default="1"
label="MOD_MOKOSUITECLIENT_CPANEL_COLLAPSED_LABEL" label="MOD_MOKOSUITE_CPANEL_COLLAPSED_LABEL"
description="MOD_MOKOSUITECLIENT_CPANEL_COLLAPSED_DESC" description="MOD_MOKOSUITE_CPANEL_COLLAPSED_DESC"
layout="joomla.form.field.radio.switcher"> layout="joomla.form.field.radio.switcher">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
</field> </field>
<field name="show_health" type="radio" default="1" <field name="show_health" type="radio" default="1"
label="MOD_MOKOSUITECLIENT_CPANEL_SHOW_HEALTH_LABEL" label="MOD_MOKOSUITE_CPANEL_SHOW_HEALTH_LABEL"
layout="joomla.form.field.radio.switcher"> layout="joomla.form.field.radio.switcher">
<option value="0">JHIDE</option> <option value="0">JHIDE</option>
<option value="1">JSHOW</option> <option value="1">JSHOW</option>
</field> </field>
<field name="show_stats" type="radio" default="1" <field name="show_stats" type="radio" default="1"
label="MOD_MOKOSUITECLIENT_CPANEL_SHOW_STATS_LABEL" label="MOD_MOKOSUITE_CPANEL_SHOW_STATS_LABEL"
description="MOD_MOKOSUITECLIENT_CPANEL_SHOW_STATS_DESC" description="MOD_MOKOSUITE_CPANEL_SHOW_STATS_DESC"
layout="joomla.form.field.radio.switcher"> layout="joomla.form.field.radio.switcher">
<option value="0">JHIDE</option> <option value="0">JHIDE</option>
<option value="1">JSHOW</option> <option value="1">JSHOW</option>
</field> </field>
<field name="show_disk" type="radio" default="1" <field name="show_disk" type="radio" default="1"
label="MOD_MOKOSUITECLIENT_CPANEL_SHOW_DISK_LABEL" label="MOD_MOKOSUITE_CPANEL_SHOW_DISK_LABEL"
layout="joomla.form.field.radio.switcher"> layout="joomla.form.field.radio.switcher">
<option value="0">JHIDE</option> <option value="0">JHIDE</option>
<option value="1">JSHOW</option> <option value="1">JSHOW</option>
</field> </field>
<field name="show_ip" type="radio" default="1" <field name="show_ip" type="radio" default="1"
label="MOD_MOKOSUITECLIENT_CPANEL_SHOW_IP_LABEL" label="MOD_MOKOSUITE_CPANEL_SHOW_IP_LABEL"
layout="joomla.form.field.radio.switcher"> layout="joomla.form.field.radio.switcher">
<option value="0">JHIDE</option> <option value="0">JHIDE</option>
<option value="1">JSHOW</option> <option value="1">JSHOW</option>
</field> </field>
<field name="show_plugins" type="radio" default="1" <field name="show_plugins" type="radio" default="1"
label="MOD_MOKOSUITECLIENT_CPANEL_SHOW_PLUGINS_LABEL" label="MOD_MOKOSUITE_CPANEL_SHOW_PLUGINS_LABEL"
layout="joomla.form.field.radio.switcher"> layout="joomla.form.field.radio.switcher">
<option value="0">JHIDE</option> <option value="0">JHIDE</option>
<option value="1">JSHOW</option> <option value="1">JSHOW</option>
</field> </field>
<field name="show_actions" type="radio" default="1" <field name="show_actions" type="radio" default="1"
label="MOD_MOKOSUITECLIENT_CPANEL_SHOW_ACTIONS_LABEL" label="MOD_MOKOSUITE_CPANEL_SHOW_ACTIONS_LABEL"
description="MOD_MOKOSUITECLIENT_CPANEL_SHOW_ACTIONS_DESC" description="MOD_MOKOSUITE_CPANEL_SHOW_ACTIONS_DESC"
layout="joomla.form.field.radio.switcher"> layout="joomla.form.field.radio.switcher">
<option value="0">JHIDE</option> <option value="0">JHIDE</option>
<option value="1">JSHOW</option> <option value="1">JSHOW</option>
</field> </field>
<field name="show_versions" type="radio" default="1" <field name="show_versions" type="radio" default="1"
label="MOD_MOKOSUITECLIENT_CPANEL_SHOW_VERSIONS_LABEL" label="MOD_MOKOSUITE_CPANEL_SHOW_VERSIONS_LABEL"
description="MOD_MOKOSUITECLIENT_CPANEL_SHOW_VERSIONS_DESC" description="MOD_MOKOSUITE_CPANEL_SHOW_VERSIONS_DESC"
layout="joomla.form.field.radio.switcher"> layout="joomla.form.field.radio.switcher">
<option value="0">JHIDE</option> <option value="0">JHIDE</option>
<option value="1">JSHOW</option> <option value="1">JSHOW</option>
@@ -7,7 +7,7 @@
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.40.01-rc</version> <version>02.34.84-dev</version>
<description>MokoSuiteClient admin sidebar menu — renders a dedicated MokoSuiteClient section in the admin menu before Joomla's default menu.</description> <description>MokoSuiteClient admin sidebar menu — renders a dedicated MokoSuiteClient section in the admin menu before Joomla's default menu.</description>
<namespace path="src">Moko\Module\MokoSuiteClientMenu</namespace> <namespace path="src">Moko\Module\MokoSuiteClientMenu</namespace>
@@ -22,7 +22,7 @@
* DEFGROUP: Joomla.Plugin * DEFGROUP: Joomla.Plugin
* INGROUP: MokoSuiteClient * INGROUP: MokoSuiteClient
* REPO: https://github.com/mokoconsulting-tech/mokosuiteclient * REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
* VERSION: 02.40.01 * VERSION: 02.34.84
* PATH: /src/Extension/MokoSuiteClient.php * PATH: /src/Extension/MokoSuiteClient.php
* NOTE: Core system plugin for MokoSuiteClient admin tools suite * NOTE: Core system plugin for MokoSuiteClient admin tools suite
*/ */
@@ -8,7 +8,7 @@
* FILE INFORMATION * FILE INFORMATION
* DEFGROUP: Joomla.Plugin * DEFGROUP: Joomla.Plugin
* INGROUP: MokoSuiteClient * INGROUP: MokoSuiteClient
* VERSION: 02.40.01 * VERSION: 02.34.84
* PATH: /src/Field/CopyableTokenField.php * PATH: /src/Field/CopyableTokenField.php
* BRIEF: Read-only token field with a copy-to-clipboard button * BRIEF: Read-only token field with a copy-to-clipboard button
*/ */
@@ -30,7 +30,7 @@
<license>GNU General Public License version 3 or later; see LICENSE.md</license> <license>GNU General Public License version 3 or later; see LICENSE.md</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.40.01-rc</version> <version>02.34.84-dev</version>
<description>MokoSuiteClient core system plugin — coordinates feature plugins, heartbeat, health checks, and admin customizations.</description> <description>MokoSuiteClient core system plugin — coordinates feature plugins, heartbeat, health checks, and admin customizations.</description>
<namespace path=".">Moko\Plugin\System\MokoSuiteClient</namespace> <namespace path=".">Moko\Plugin\System\MokoSuiteClient</namespace>
<scriptfile>script.php</scriptfile> <scriptfile>script.php</scriptfile>
@@ -68,13 +68,13 @@
addfieldprefix="Moko\Plugin\System\MokoSuiteClient\Field" addfieldprefix="Moko\Plugin\System\MokoSuiteClient\Field"
> >
<fieldset name="basic" <fieldset name="basic"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIELDSET_CORE_LABEL" label="PLG_SYSTEM_MOKOSUITE_FIELDSET_CORE_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIELDSET_CORE_DESC"> description="PLG_SYSTEM_MOKOSUITE_FIELDSET_CORE_DESC">
<field <field
name="health_api_token" name="health_api_token"
type="CopyableToken" type="CopyableToken"
label="PLG_SYSTEM_MOKOSUITECLIENT_HEALTH_TOKEN_LABEL" label="PLG_SYSTEM_MOKOSUITE_HEALTH_TOKEN_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_HEALTH_TOKEN_DESC" description="PLG_SYSTEM_MOKOSUITE_HEALTH_TOKEN_DESC"
default="" default=""
filter="raw" filter="raw"
readonly="true" readonly="true"
@@ -22,7 +22,7 @@
* DEFGROUP: Joomla.Plugin * DEFGROUP: Joomla.Plugin
* INGROUP: MokoSuiteClient * INGROUP: MokoSuiteClient
* REPO: https://github.com/mokoconsulting-tech/mokosuiteclient * REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
* VERSION: 02.40.01 * VERSION: 02.34.84
* PATH: /src/script.php * PATH: /src/script.php
* BRIEF: Installation script for MokoSuiteClient plugin * BRIEF: Installation script for MokoSuiteClient plugin
* NOTE: Handles installation, update, and uninstallation tasks including language override deployment * NOTE: Handles installation, update, and uninstallation tasks including language override deployment
@@ -527,7 +527,7 @@ class plgSystemMokoSuiteClientInstallerScript implements InstallerScriptInterfac
} }
catch (\Exception $e) catch (\Exception $e)
{ {
Log::add('Install notification email failed: ' . $e->getMessage(), Log::WARNING, 'mokosuiteclient'); // Don't break install if email fails
} }
} }
@@ -767,7 +767,7 @@ class plgSystemMokoSuiteClientInstallerScript implements InstallerScriptInterfac
'id_holder' => '', 'id_holder' => '',
'title_holder' => '', 'title_holder' => '',
'table_name' => '', 'table_name' => '',
'text_prefix' => 'PLG_SYSTEM_MOKOSUITECLIENT', 'text_prefix' => 'PLG_SYSTEM_MOKOSUITE',
]; ];
$db->insertObject('#__action_log_config', $config); $db->insertObject('#__action_log_config', $config);
@@ -22,7 +22,7 @@
* DEFGROUP: Joomla.Plugin * DEFGROUP: Joomla.Plugin
* INGROUP: MokoSuiteClient * INGROUP: MokoSuiteClient
* REPO: https://github.com/mokoconsulting-tech/mokosuiteclient * REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
* VERSION: 02.40.01 * VERSION: 02.34.84
* PATH: /src/services/provider.php * PATH: /src/services/provider.php
* BRIEF: Service provider for dependency injection in Joomla 5.x * BRIEF: Service provider for dependency injection in Joomla 5.x
* NOTE: Registers the plugin with Joomla's DI container * NOTE: Registers the plugin with Joomla's DI container
@@ -0,0 +1,13 @@
; MokoSuiteClient Backup Bridge Plugin
; Copyright (C) 2026 Moko Consulting. All rights reserved.
; License: GPL-3.0-or-later
PLG_SYSTEM_MOKOSUITECLIENT_BACKUP="System - MokoSuiteClient Backup"
PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_DESC="Detects MokoSuiteBackup and includes backup status in heartbeat payloads sent to MokoSuiteHQ."
PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_FIELDSET_BASIC="Backup Monitoring"
PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_FIELDSET_BASIC_DESC="Configure backup status collection for heartbeat reporting."
PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_HEARTBEAT_LABEL="Include in Heartbeat"
PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_HEARTBEAT_DESC="Include MokoSuiteBackup status data in heartbeat payloads sent to MokoSuiteHQ."
PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_STALE_DAYS_LABEL="Stale Backup Threshold (days)"
PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_STALE_DAYS_DESC="Number of days without a backup before status is marked as degraded. Default: 7."
@@ -0,0 +1,3 @@
; MokoSuiteClient Backup Bridge Plugin - System strings
PLG_SYSTEM_MOKOSUITECLIENT_BACKUP="System - MokoSuiteClient Backup"
PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_DESC="MokoSuiteBackup detection and heartbeat integration."
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<extension type="plugin" group="system" method="upgrade">
<name>System - MokoSuiteClient Backup</name>
<element>mokosuiteclient_backup</element>
<author>Moko Consulting</author>
<creationDate>2026-06-18</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.84-dev</version>
<description>PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteClientBackup</namespace>
<files>
<folder>src</folder>
<folder>services</folder>
<folder>language</folder>
</files>
<languages folder="language">
<language tag="en-GB">en-GB/plg_system_mokosuiteclient_backup.ini</language>
<language tag="en-GB">en-GB/plg_system_mokosuiteclient_backup.sys.ini</language>
</languages>
<config>
<fields name="params">
<fieldset name="basic"
label="PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_FIELDSET_BASIC"
description="PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_FIELDSET_BASIC_DESC">
<field name="heartbeat_enabled" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_HEARTBEAT_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_HEARTBEAT_DESC"
class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field name="stale_days" type="number" default="7"
label="PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_STALE_DAYS_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_STALE_DAYS_DESC"
min="1" max="90" step="1" />
</fieldset>
</fields>
</config>
</extension>
@@ -0,0 +1,34 @@
<?php
/**
* @package MokoSuiteClient
* @subpackage plg_system_mokosuiteclient_backup
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*/
defined('_JEXEC') or die;
use Joomla\CMS\Extension\PluginInterface;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Joomla\Event\DispatcherInterface;
use Moko\Plugin\System\MokoSuiteClientBackup\Extension\Backup;
return new class implements ServiceProviderInterface
{
public function register(Container $container): void
{
$container->set(
PluginInterface::class,
function (Container $container) {
$dispatcher = $container->get(DispatcherInterface::class);
$plugin = new Backup($dispatcher, (array) PluginHelper::getPlugin('system', 'mokosuiteclient_backup'));
$plugin->setApplication(Factory::getApplication());
return $plugin;
}
);
}
};
@@ -0,0 +1,263 @@
<?php
/**
* @package MokoSuiteClient
* @subpackage plg_system_mokosuiteclient_backup
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*/
namespace Moko\Plugin\System\MokoSuiteClientBackup\Extension;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Database\DatabaseInterface;
use Joomla\Event\SubscriberInterface;
/**
* MokoSuiteClient Backup Bridge Plugin
*
* Detects whether MokoSuiteBackup is installed and collects backup
* status data for inclusion in heartbeat payloads to MokoSuiteHQ.
*
* Prefers MokoSuiteBackup's own BackupStatusHelper when available,
* falling back to a direct table query if the helper class is missing
* (e.g. older versions of MokoSuiteBackup).
*
* @since 02.34.84
*/
class Backup extends CMSPlugin implements SubscriberInterface
{
protected $autoloadLanguage = true;
public static function getSubscribedEvents(): array
{
return [
'onMokoSuiteClientCollectHeartbeat' => 'onCollectHeartbeat',
];
}
/**
* Collect backup status data for the heartbeat payload.
*
* Triggered by the monitor plugin before sending a heartbeat.
* Appends a 'backup' key to the heartbeat data array.
*/
public function onCollectHeartbeat($event): void
{
if (!$this->params->get('heartbeat_enabled', 1))
{
return;
}
try
{
$data = $this->getBackupStatus();
$event->addResult('backup', $data);
}
catch (\Throwable $e)
{
Log::add('Backup bridge: ' . $e->getMessage(), Log::WARNING, 'mokosuiteclient');
// Send explicit error so HQ knows collection failed,
// rather than interpreting absence as "not installed"
$event->addResult('backup', [
'installed' => true,
'status' => 'error',
'message' => 'Failed to collect backup status',
]);
}
}
/**
* Check if MokoSuiteBackup is installed.
*
* Queries the extensions table for the component, which is more
* reliable than checking for database tables alone.
*/
public function isBackupInstalled(): bool
{
try
{
$db = Factory::getContainer()->get(DatabaseInterface::class);
$query = $db->getQuery(true)
->select('COUNT(*)')
->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuitebackup'))
->where($db->quoteName('type') . ' = ' . $db->quote('component'));
$db->setQuery($query);
return (int) $db->loadResult() > 0;
}
catch (\Throwable $e)
{
return false;
}
}
/**
* Get backup status summary from MokoSuiteBackup.
*
* Prefers the BackupStatusHelper API when available. Falls back
* to a direct database query for compatibility with older versions.
*
* @return array Backup status data for heartbeat inclusion.
*/
public function getBackupStatus(): array
{
if (!$this->isBackupInstalled())
{
return [
'installed' => false,
'status' => 'ok',
];
}
// Prefer MokoSuiteBackup's own helper (clean public API)
$helperClass = 'Joomla\\Component\\MokoSuiteBackup\\Administrator\\Utility\\BackupStatusHelper';
if (class_exists($helperClass))
{
$staleDays = (int) $this->params->get('stale_days', 7);
return $helperClass::getStatus($staleDays);
}
// Fallback: direct table query for older MokoSuiteBackup versions
$db = Factory::getContainer()->get(DatabaseInterface::class);
$tables = $db->getTableList();
$prefix = $db->getPrefix();
if (!in_array($prefix . 'mokosuitebackup_records', $tables, true))
{
return [
'installed' => true,
'status' => 'degraded',
'message' => 'Backup tables not found',
];
}
return $this->queryBackupRecords($db);
}
/**
* Query MokoSuiteBackup records for the latest backup summary.
*
* Column names match the MokoSuiteBackup schema:
* - backupstart/backupend (not created/modified)
* - status: pending, running, complete, fail
* - total_size in bytes
*
* @param DatabaseInterface $db Database driver.
*
* @return array Backup status array.
*/
private function queryBackupRecords(DatabaseInterface $db): array
{
$staleDays = (int) $this->params->get('stale_days', 7);
// Most recent backup record
$query = $db->getQuery(true)
->select([
$db->quoteName('id'),
$db->quoteName('description'),
$db->quoteName('status'),
$db->quoteName('backup_type'),
$db->quoteName('total_size'),
$db->quoteName('backupstart'),
$db->quoteName('backupend'),
$db->quoteName('origin'),
$db->quoteName('filesexist'),
])
->from($db->quoteName('#__mokosuitebackup_records'))
->order($db->quoteName('id') . ' DESC');
$db->setQuery($query, 0, 1);
$latest = $db->loadObject();
if (!$latest)
{
return [
'installed' => true,
'status' => 'degraded',
'message' => 'No backups found',
];
}
// Count completed backups
$db->setQuery(
$db->getQuery(true)
->select('COUNT(*)')
->from($db->quoteName('#__mokosuitebackup_records'))
->where($db->quoteName('status') . ' = ' . $db->quote('complete'))
);
$totalBackups = (int) $db->loadResult();
$cutoff = date('Y-m-d H:i:s', strtotime("-{$staleDays} days"));
$db->setQuery(
$db->getQuery(true)
->select('COUNT(*)')
->from($db->quoteName('#__mokosuitebackup_records'))
->where($db->quoteName('status') . ' = ' . $db->quote('complete'))
->where($db->quoteName('backupstart') . ' >= ' . $db->quote($cutoff))
);
$recentBackups = (int) $db->loadResult();
// Failures in last 7 days
$db->setQuery(
$db->getQuery(true)
->select('COUNT(*)')
->from($db->quoteName('#__mokosuitebackup_records'))
->where($db->quoteName('status') . ' = ' . $db->quote('fail'))
->where($db->quoteName('backupstart') . ' >= ' . $db->quote($cutoff))
);
$failCount7d = (int) $db->loadResult();
// Determine status
$daysSince = 999;
if (!empty($latest->backupstart) && $latest->backupstart !== '0000-00-00 00:00:00')
{
$daysSince = (int) ((time() - strtotime($latest->backupstart)) / 86400);
}
$status = 'ok';
if ($latest->status === 'fail')
{
$status = 'degraded';
}
elseif ($latest->status !== 'complete')
{
$status = ($latest->status === 'running') ? 'ok' : 'degraded';
}
elseif ($daysSince > $staleDays)
{
$status = 'degraded';
}
$sizeMb = $latest->total_size
? round($latest->total_size / 1048576)
: null;
return [
'installed' => true,
'status' => $status,
'last_backup' => $latest->backupstart,
'last_status' => $latest->status,
'last_size_mb' => $sizeMb,
'days_since' => $daysSince,
'backup_type' => $latest->backup_type,
'origin' => $latest->origin,
'total_backups' => $totalBackups,
'recent_7d' => $recentBackups,
'fail_count_7d' => $failCount7d,
'files_exist' => (bool) $latest->filesexist,
'description' => $latest->description,
];
}
}
@@ -3,27 +3,27 @@
; License: GPL-3.0-or-later ; License: GPL-3.0-or-later
; IP Geolocation by DB-IP — https://db-ip.com ; IP Geolocation by DB-IP — https://db-ip.com
PLG_SYSTEM_MOKOSUITECLIENT_DBIP="System - MokoSuiteClient DB-IP" PLG_SYSTEM_MOKOSUITE_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_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_FIELDSET_BASIC="DB-IP Settings" PLG_SYSTEM_MOKOSUITE_DBIP_FIELDSET_BASIC="DB-IP Settings"
PLG_SYSTEM_MOKOSUITECLIENT_DBIP_FIELDSET_BASIC_DESC="Configure IP geolocation database source and level." PLG_SYSTEM_MOKOSUITE_DBIP_FIELDSET_BASIC_DESC="Configure IP geolocation database source and level."
PLG_SYSTEM_MOKOSUITECLIENT_DBIP_SOURCE_LABEL="Database Source" PLG_SYSTEM_MOKOSUITE_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_MOKOSUITE_DBIP_SOURCE_DESC="CDN downloads the city database automatically from the configured URL. Local uses a MMDB file you provide on the server."
PLG_SYSTEM_MOKOSUITECLIENT_DBIP_SOURCE_CDN="CDN (auto-download)" PLG_SYSTEM_MOKOSUITE_DBIP_SOURCE_CDN="CDN (auto-download)"
PLG_SYSTEM_MOKOSUITECLIENT_DBIP_SOURCE_LOCAL="Local file" PLG_SYSTEM_MOKOSUITE_DBIP_SOURCE_LOCAL="Local file"
PLG_SYSTEM_MOKOSUITECLIENT_DBIP_DATABASE_LEVEL_LABEL="Database Level" PLG_SYSTEM_MOKOSUITE_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_MOKOSUITE_DBIP_DATABASE_LEVEL_DESC="Country is bundled (~8 MB). City provides region, city, and coordinates but requires a separate download (~125 MB)."
PLG_SYSTEM_MOKOSUITECLIENT_DBIP_DATABASE_COUNTRY="Country (bundled)" PLG_SYSTEM_MOKOSUITE_DBIP_DATABASE_COUNTRY="Country (bundled)"
PLG_SYSTEM_MOKOSUITECLIENT_DBIP_DATABASE_CITY="City (remote download)" PLG_SYSTEM_MOKOSUITE_DBIP_DATABASE_CITY="City (remote download)"
PLG_SYSTEM_MOKOSUITECLIENT_DBIP_AUTO_UPDATE_LABEL="Auto-Update Database" PLG_SYSTEM_MOKOSUITE_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_AUTO_UPDATE_DESC="Automatically download the latest city database monthly when an admin visits the backend."
PLG_SYSTEM_MOKOSUITECLIENT_DBIP_CDN_URL_LABEL="CDN Download URL" PLG_SYSTEM_MOKOSUITE_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_CDN_URL_DESC="URL to download the city-level MMDB file. Default points to the MokoConsulting geoip-data repository."
PLG_SYSTEM_MOKOSUITECLIENT_DBIP_LOCAL_PATH_LABEL="Local MMDB Path" PLG_SYSTEM_MOKOSUITE_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)." 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)."
@@ -2,5 +2,5 @@
; Copyright (C) 2026 Moko Consulting. All rights reserved. ; Copyright (C) 2026 Moko Consulting. All rights reserved.
; License: GPL-3.0-or-later ; License: GPL-3.0-or-later
PLG_SYSTEM_MOKOSUITECLIENT_DBIP="System - MokoSuiteClient DB-IP" PLG_SYSTEM_MOKOSUITE_DBIP="System - MokoSuiteClient DB-IP"
PLG_SYSTEM_MOKOSUITECLIENT_DBIP_DESC="IP geolocation for MokoSuiteClient using DB-IP Lite databases." PLG_SYSTEM_MOKOSUITE_DBIP_DESC="IP geolocation for MokoSuiteClient using DB-IP Lite databases."
@@ -8,8 +8,8 @@
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.40.01-rc</version> <version>02.34.84-dev</version>
<description>PLG_SYSTEM_MOKOSUITECLIENT_DBIP_DESC</description> <description>PLG_SYSTEM_MOKOSUITE_DBIP_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteClientDBIP</namespace> <namespace path="src">Moko\Plugin\System\MokoSuiteClientDBIP</namespace>
<files> <files>
@@ -28,26 +28,26 @@
<config> <config>
<fields name="params"> <fields name="params">
<fieldset name="basic" <fieldset name="basic"
label="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_FIELDSET_BASIC" label="PLG_SYSTEM_MOKOSUITE_DBIP_FIELDSET_BASIC"
description="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_FIELDSET_BASIC_DESC"> description="PLG_SYSTEM_MOKOSUITE_DBIP_FIELDSET_BASIC_DESC">
<field name="database_source" type="list" default="cdn" <field name="database_source" type="list" default="cdn"
label="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_SOURCE_LABEL" label="PLG_SYSTEM_MOKOSUITE_DBIP_SOURCE_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_SOURCE_DESC"> description="PLG_SYSTEM_MOKOSUITE_DBIP_SOURCE_DESC">
<option value="cdn">PLG_SYSTEM_MOKOSUITECLIENT_DBIP_SOURCE_CDN</option> <option value="cdn">PLG_SYSTEM_MOKOSUITE_DBIP_SOURCE_CDN</option>
<option value="local">PLG_SYSTEM_MOKOSUITECLIENT_DBIP_SOURCE_LOCAL</option> <option value="local">PLG_SYSTEM_MOKOSUITE_DBIP_SOURCE_LOCAL</option>
</field> </field>
<field name="database_level" type="list" default="country" <field name="database_level" type="list" default="country"
label="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_DATABASE_LEVEL_LABEL" label="PLG_SYSTEM_MOKOSUITE_DBIP_DATABASE_LEVEL_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_DATABASE_LEVEL_DESC"> description="PLG_SYSTEM_MOKOSUITE_DBIP_DATABASE_LEVEL_DESC">
<option value="country">PLG_SYSTEM_MOKOSUITECLIENT_DBIP_DATABASE_COUNTRY</option> <option value="country">PLG_SYSTEM_MOKOSUITE_DBIP_DATABASE_COUNTRY</option>
<option value="city">PLG_SYSTEM_MOKOSUITECLIENT_DBIP_DATABASE_CITY</option> <option value="city">PLG_SYSTEM_MOKOSUITE_DBIP_DATABASE_CITY</option>
</field> </field>
<field name="auto_update" type="radio" default="1" <field name="auto_update" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_AUTO_UPDATE_LABEL" label="PLG_SYSTEM_MOKOSUITE_DBIP_AUTO_UPDATE_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_AUTO_UPDATE_DESC" description="PLG_SYSTEM_MOKOSUITE_DBIP_AUTO_UPDATE_DESC"
class="btn-group btn-group-yesno" class="btn-group btn-group-yesno"
showon="database_source:cdn"> showon="database_source:cdn">
<option value="1">JYES</option> <option value="1">JYES</option>
@@ -56,15 +56,15 @@
<field name="cdn_url" type="url" <field name="cdn_url" type="url"
default="https://git.mokoconsulting.tech/MokoConsulting/geoip-data/releases/download/latest/dbip-city-lite.mmdb" default="https://git.mokoconsulting.tech/MokoConsulting/geoip-data/releases/download/latest/dbip-city-lite.mmdb"
label="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_CDN_URL_LABEL" label="PLG_SYSTEM_MOKOSUITE_DBIP_CDN_URL_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_CDN_URL_DESC" description="PLG_SYSTEM_MOKOSUITE_DBIP_CDN_URL_DESC"
filter="url" filter="url"
showon="database_source:cdn" /> showon="database_source:cdn" />
<field name="local_path" type="text" <field name="local_path" type="text"
default="" default=""
label="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_LOCAL_PATH_LABEL" label="PLG_SYSTEM_MOKOSUITE_DBIP_LOCAL_PATH_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_DBIP_LOCAL_PATH_DESC" description="PLG_SYSTEM_MOKOSUITE_DBIP_LOCAL_PATH_DESC"
filter="path" filter="path"
showon="database_source:local" /> showon="database_source:local" />
@@ -8,8 +8,8 @@
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.40.01-rc</version> <version>02.34.84-dev</version>
<description>PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_DESC</description> <description>PLG_SYSTEM_MOKOSUITE_DEVTOOLS_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteClientDevTools</namespace> <namespace path="src">Moko\Plugin\System\MokoSuiteClientDevTools</namespace>
<files> <files>
@@ -26,36 +26,36 @@
<config> <config>
<fields name="params"> <fields name="params">
<fieldset name="basic" <fieldset name="basic"
label="PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_FIELDSET_BASIC" label="PLG_SYSTEM_MOKOSUITE_DEVTOOLS_FIELDSET_BASIC"
description="PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_FIELDSET_BASIC_DESC"> description="PLG_SYSTEM_MOKOSUITE_DEVTOOLS_FIELDSET_BASIC_DESC">
<field name="dev_mode" type="radio" default="0" <field name="dev_mode" type="radio" default="0"
label="PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_DEV_MODE_LABEL" label="PLG_SYSTEM_MOKOSUITE_DEVTOOLS_DEV_MODE_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_DEV_MODE_DESC" description="PLG_SYSTEM_MOKOSUITE_DEVTOOLS_DEV_MODE_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
</field> </field>
<field name="reset_hits" type="radio" default="0" <field name="reset_hits" type="radio" default="0"
label="PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_RESET_HITS_LABEL" label="PLG_SYSTEM_MOKOSUITE_DEVTOOLS_RESET_HITS_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_RESET_HITS_DESC" description="PLG_SYSTEM_MOKOSUITE_DEVTOOLS_RESET_HITS_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
</field> </field>
<field name="delete_versions" type="radio" default="0" <field name="delete_versions" type="radio" default="0"
label="PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_DELETE_VERSIONS_LABEL" label="PLG_SYSTEM_MOKOSUITE_DEVTOOLS_DELETE_VERSIONS_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_DELETE_VERSIONS_DESC" description="PLG_SYSTEM_MOKOSUITE_DEVTOOLS_DELETE_VERSIONS_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
</field> </field>
<field name="reset_download_keys" type="radio" default="0" <field name="reset_download_keys" type="radio" default="0"
label="PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_RESET_DLKEYS_LABEL" label="PLG_SYSTEM_MOKOSUITE_DEVTOOLS_RESET_DLKEYS_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_RESET_DLKEYS_DESC" description="PLG_SYSTEM_MOKOSUITE_DEVTOOLS_RESET_DLKEYS_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
@@ -8,8 +8,8 @@
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.40.01-rc</version> <version>02.34.84-dev</version>
<description>PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_DESC</description> <description>PLG_SYSTEM_MOKOSUITE_FIREWALL_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteClientFirewall</namespace> <namespace path="src">Moko\Plugin\System\MokoSuiteClientFirewall</namespace>
<files> <files>
@@ -36,25 +36,25 @@
<fields name="params"> <fields name="params">
<!-- Network & Session --> <!-- Network & Session -->
<fieldset name="basic" <fieldset name="basic"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_BASIC" label="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_BASIC"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_BASIC_DESC"> description="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_BASIC_DESC">
<field name="force_https" type="radio" default="1" <field name="force_https" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FORCE_HTTPS_LABEL" label="PLG_SYSTEM_MOKOSUITE_FIREWALL_FORCE_HTTPS_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FORCE_HTTPS_DESC" description="PLG_SYSTEM_MOKOSUITE_FIREWALL_FORCE_HTTPS_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
</field> </field>
<field name="admin_session_timeout" type="number" <field name="admin_session_timeout" type="number"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_SESSION_TIMEOUT_LABEL" label="PLG_SYSTEM_MOKOSUITE_FIREWALL_SESSION_TIMEOUT_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_SESSION_TIMEOUT_DESC" description="PLG_SYSTEM_MOKOSUITE_FIREWALL_SESSION_TIMEOUT_DESC"
default="60" hint="Minutes (0 = Joomla default)" /> default="60" hint="Minutes (0 = Joomla default)" />
<field name="trusted_ips" type="subform" <field name="trusted_ips" type="subform"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_TRUSTED_IPS_LABEL" label="PLG_SYSTEM_MOKOSUITE_FIREWALL_TRUSTED_IPS_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_TRUSTED_IPS_DESC" description="PLG_SYSTEM_MOKOSUITE_FIREWALL_TRUSTED_IPS_DESC"
formsource="plugins/system/mokosuiteclient_firewall/forms/trusted_ip_entry.xml" formsource="plugins/system/mokosuiteclient_firewall/forms/trusted_ip_entry.xml"
multiple="true" multiple="true"
layout="joomla.form.field.subform.repeatable-table" layout="joomla.form.field.subform.repeatable-table"
@@ -64,20 +64,20 @@
<!-- WAF Shields --> <!-- WAF Shields -->
<fieldset name="waf" <fieldset name="waf"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_WAF" label="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_WAF"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_WAF_DESC"> description="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_WAF_DESC">
<field name="waf_enabled" type="radio" default="1" <field name="waf_enabled" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_ENABLED_LABEL" label="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_ENABLED_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_ENABLED_DESC" description="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_ENABLED_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
</field> </field>
<field name="waf_sqli" type="radio" default="1" <field name="waf_sqli" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_SQLI_LABEL" label="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_SQLI_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_SQLI_DESC" description="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_SQLI_DESC"
class="btn-group btn-group-yesno" class="btn-group btn-group-yesno"
showon="waf_enabled:1"> showon="waf_enabled:1">
<option value="1">JYES</option> <option value="1">JYES</option>
@@ -85,8 +85,8 @@
</field> </field>
<field name="waf_xss" type="radio" default="1" <field name="waf_xss" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_XSS_LABEL" label="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_XSS_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_XSS_DESC" description="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_XSS_DESC"
class="btn-group btn-group-yesno" class="btn-group btn-group-yesno"
showon="waf_enabled:1"> showon="waf_enabled:1">
<option value="1">JYES</option> <option value="1">JYES</option>
@@ -94,8 +94,8 @@
</field> </field>
<field name="waf_mua" type="radio" default="1" <field name="waf_mua" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_MUA_LABEL" label="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_MUA_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_MUA_DESC" description="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_MUA_DESC"
class="btn-group btn-group-yesno" class="btn-group btn-group-yesno"
showon="waf_enabled:1"> showon="waf_enabled:1">
<option value="1">JYES</option> <option value="1">JYES</option>
@@ -103,15 +103,15 @@
</field> </field>
<field name="waf_mua_blocklist" type="textarea" <field name="waf_mua_blocklist" type="textarea"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_MUA_LIST_LABEL" label="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_MUA_LIST_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_MUA_LIST_DESC" description="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_MUA_LIST_DESC"
rows="4" filter="raw" rows="4" filter="raw"
default="sqlmap,nikto,nmap,havij,w3af,acunetix,nessus,openvas,masscan,gobuster,dirbuster,wpscan,joomscan" default="sqlmap,nikto,nmap,havij,w3af,acunetix,nessus,openvas,masscan,gobuster,dirbuster,wpscan,joomscan"
showon="waf_enabled:1[AND]waf_mua:1" /> showon="waf_enabled:1[AND]waf_mua:1" />
<field name="waf_rfi" type="radio" default="1" <field name="waf_rfi" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_RFI_LABEL" label="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_RFI_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_RFI_DESC" description="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_RFI_DESC"
class="btn-group btn-group-yesno" class="btn-group btn-group-yesno"
showon="waf_enabled:1"> showon="waf_enabled:1">
<option value="1">JYES</option> <option value="1">JYES</option>
@@ -119,8 +119,8 @@
</field> </field>
<field name="waf_dfi" type="radio" default="1" <field name="waf_dfi" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_DFI_LABEL" label="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_DFI_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_WAF_DFI_DESC" description="PLG_SYSTEM_MOKOSUITE_FIREWALL_WAF_DFI_DESC"
class="btn-group btn-group-yesno" class="btn-group btn-group-yesno"
showon="waf_enabled:1"> showon="waf_enabled:1">
<option value="1">JYES</option> <option value="1">JYES</option>
@@ -130,8 +130,8 @@
<!-- Security Headers --> <!-- Security Headers -->
<fieldset name="headers" <fieldset name="headers"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_HEADERS" label="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_HEADERS"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_HEADERS_DESC"> description="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_HEADERS_DESC">
<field name="header_xframe" type="radio" default="1" <field name="header_xframe" type="radio" default="1"
label="X-Frame-Options" description="Clickjacking protection (SAMEORIGIN)" label="X-Frame-Options" description="Clickjacking protection (SAMEORIGIN)"
@@ -177,12 +177,12 @@
<!-- Access Control --> <!-- Access Control -->
<fieldset name="access_control" <fieldset name="access_control"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_ACCESS" label="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_ACCESS"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_ACCESS_DESC"> description="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_ACCESS_DESC">
<field name="ip_blocklist" type="subform" <field name="ip_blocklist" type="subform"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_IP_BLOCKLIST_LABEL" label="PLG_SYSTEM_MOKOSUITE_FIREWALL_IP_BLOCKLIST_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_IP_BLOCKLIST_DESC" description="PLG_SYSTEM_MOKOSUITE_FIREWALL_IP_BLOCKLIST_DESC"
formsource="plugins/system/mokosuiteclient_firewall/forms/trusted_ip_entry.xml" formsource="plugins/system/mokosuiteclient_firewall/forms/trusted_ip_entry.xml"
multiple="true" multiple="true"
layout="joomla.form.field.subform.repeatable-table" layout="joomla.form.field.subform.repeatable-table"
@@ -190,13 +190,13 @@
buttons="add,remove,move" /> buttons="add,remove,move" />
<field name="admin_secret" type="text" <field name="admin_secret" type="text"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_ADMIN_SECRET_LABEL" label="PLG_SYSTEM_MOKOSUITE_FIREWALL_ADMIN_SECRET_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_ADMIN_SECRET_DESC" description="PLG_SYSTEM_MOKOSUITE_FIREWALL_ADMIN_SECRET_DESC"
default="" filter="raw" hint="Leave empty to disable" /> default="" filter="raw" hint="Leave empty to disable" />
<field name="admin_secret_redirect" type="text" <field name="admin_secret_redirect" type="text"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_ADMIN_SECRET_REDIRECT_LABEL" label="PLG_SYSTEM_MOKOSUITE_FIREWALL_ADMIN_SECRET_REDIRECT_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_ADMIN_SECRET_REDIRECT_DESC" description="PLG_SYSTEM_MOKOSUITE_FIREWALL_ADMIN_SECRET_REDIRECT_DESC"
default="" filter="url" hint="Empty = 403 Forbidden" default="" filter="url" hint="Empty = 403 Forbidden"
showon="admin_secret!:" /> showon="admin_secret!:" />
@@ -211,8 +211,8 @@
showon="autoban_threshold!:0" /> showon="autoban_threshold!:0" />
<field name="block_frontend_superuser" type="radio" default="0" <field name="block_frontend_superuser" type="radio" default="0"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_BLOCK_FE_SU_LABEL" label="PLG_SYSTEM_MOKOSUITE_FIREWALL_BLOCK_FE_SU_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_BLOCK_FE_SU_DESC" description="PLG_SYSTEM_MOKOSUITE_FIREWALL_BLOCK_FE_SU_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
@@ -221,28 +221,28 @@
<!-- File & Template Protection --> <!-- File & Template Protection -->
<fieldset name="protection" <fieldset name="protection"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_PROTECTION" label="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_PROTECTION"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_PROTECTION_DESC"> description="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_PROTECTION_DESC">
<field name="block_sensitive_files" type="radio" default="1" <field name="block_sensitive_files" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_BLOCK_FILES_LABEL" label="PLG_SYSTEM_MOKOSUITE_FIREWALL_BLOCK_FILES_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_BLOCK_FILES_DESC" description="PLG_SYSTEM_MOKOSUITE_FIREWALL_BLOCK_FILES_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
</field> </field>
<field name="block_direct_php" type="radio" default="1" <field name="block_direct_php" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_BLOCK_PHP_LABEL" label="PLG_SYSTEM_MOKOSUITE_FIREWALL_BLOCK_PHP_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_BLOCK_PHP_DESC" description="PLG_SYSTEM_MOKOSUITE_FIREWALL_BLOCK_PHP_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
</field> </field>
<field name="block_template_switch" type="radio" default="1" <field name="block_template_switch" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_BLOCK_TMPL_LABEL" label="PLG_SYSTEM_MOKOSUITE_FIREWALL_BLOCK_TMPL_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_BLOCK_TMPL_DESC" description="PLG_SYSTEM_MOKOSUITE_FIREWALL_BLOCK_TMPL_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
@@ -251,29 +251,29 @@
<!-- Password Policy --> <!-- Password Policy -->
<fieldset name="password_policy" <fieldset name="password_policy"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_PASSWORD" label="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_PASSWORD"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_PASSWORD_DESC"> description="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_PASSWORD_DESC">
<field name="password_min_length" type="number" default="12" <field name="password_min_length" type="number" default="12"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_PASSWORD_LENGTH_LABEL" label="PLG_SYSTEM_MOKOSUITE_FIREWALL_PASSWORD_LENGTH_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_PASSWORD_LENGTH_DESC" /> description="PLG_SYSTEM_MOKOSUITE_FIREWALL_PASSWORD_LENGTH_DESC" />
<field name="password_require_uppercase" type="radio" default="1" <field name="password_require_uppercase" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_PASSWORD_UPPER_LABEL" label="PLG_SYSTEM_MOKOSUITE_FIREWALL_PASSWORD_UPPER_LABEL"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
</field> </field>
<field name="password_require_number" type="radio" default="1" <field name="password_require_number" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_PASSWORD_NUMBER_LABEL" label="PLG_SYSTEM_MOKOSUITE_FIREWALL_PASSWORD_NUMBER_LABEL"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
</field> </field>
<field name="password_require_special" type="radio" default="1" <field name="password_require_special" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_PASSWORD_SPECIAL_LABEL" label="PLG_SYSTEM_MOKOSUITE_FIREWALL_PASSWORD_SPECIAL_LABEL"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
@@ -282,17 +282,17 @@
<!-- Upload Restrictions --> <!-- Upload Restrictions -->
<fieldset name="uploads" <fieldset name="uploads"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_UPLOADS" label="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_UPLOADS"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_FIELDSET_UPLOADS_DESC"> description="PLG_SYSTEM_MOKOSUITE_FIREWALL_FIELDSET_UPLOADS_DESC">
<field name="upload_allowed_types" type="text" <field name="upload_allowed_types" type="text"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_UPLOAD_TYPES_LABEL" label="PLG_SYSTEM_MOKOSUITE_FIREWALL_UPLOAD_TYPES_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_UPLOAD_TYPES_DESC" description="PLG_SYSTEM_MOKOSUITE_FIREWALL_UPLOAD_TYPES_DESC"
default="jpg,jpeg,png,gif,webp,svg,pdf,doc,docx,xls,xlsx" /> default="jpg,jpeg,png,gif,webp,svg,pdf,doc,docx,xls,xlsx" />
<field name="upload_max_size_mb" type="number" <field name="upload_max_size_mb" type="number"
label="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_UPLOAD_SIZE_LABEL" label="PLG_SYSTEM_MOKOSUITE_FIREWALL_UPLOAD_SIZE_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_UPLOAD_SIZE_DESC" description="PLG_SYSTEM_MOKOSUITE_FIREWALL_UPLOAD_SIZE_DESC"
default="100" /> default="100" />
</fieldset> </fieldset>
</fields> </fields>
@@ -8,7 +8,7 @@
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.40.01-rc</version> <version>02.34.84-dev</version>
<description>PLG_SYSTEM_MOKOSUITECLIENT_LICENSE_DESC</description> <description>PLG_SYSTEM_MOKOSUITECLIENT_LICENSE_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteClientLicense</namespace> <namespace path="src">Moko\Plugin\System\MokoSuiteClientLicense</namespace>
<files><folder>src</folder><folder>services</folder><folder>language</folder></files> <files><folder>src</folder><folder>services</folder><folder>language</folder></files>
@@ -8,8 +8,8 @@
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.40.01-rc</version> <version>02.34.84-dev</version>
<description>PLG_SYSTEM_MOKOSUITECLIENT_MONITOR_DESC</description> <description>PLG_SYSTEM_MOKOSUITE_MONITOR_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteClientMonitor</namespace> <namespace path="src">Moko\Plugin\System\MokoSuiteClientMonitor</namespace>
<files> <files>
@@ -26,12 +26,12 @@
<config> <config>
<fields name="params"> <fields name="params">
<fieldset name="basic" <fieldset name="basic"
label="PLG_SYSTEM_MOKOSUITECLIENT_MONITOR_FIELDSET_BASIC" label="PLG_SYSTEM_MOKOSUITE_MONITOR_FIELDSET_BASIC"
description="PLG_SYSTEM_MOKOSUITECLIENT_MONITOR_FIELDSET_BASIC_DESC"> description="PLG_SYSTEM_MOKOSUITE_MONITOR_FIELDSET_BASIC_DESC">
<field name="heartbeat_enabled" type="radio" default="1" <field name="heartbeat_enabled" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITECLIENT_MONITOR_HEARTBEAT_LABEL" label="PLG_SYSTEM_MOKOSUITE_MONITOR_HEARTBEAT_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_MONITOR_HEARTBEAT_DESC" description="PLG_SYSTEM_MOKOSUITE_MONITOR_HEARTBEAT_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
@@ -39,8 +39,8 @@
<field name="base_url" type="url" <field name="base_url" type="url"
default="https://waas.dev.mokoconsulting.tech" default="https://waas.dev.mokoconsulting.tech"
label="PLG_SYSTEM_MOKOSUITECLIENT_MONITOR_BASE_URL_LABEL" label="PLG_SYSTEM_MOKOSUITE_MONITOR_BASE_URL_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_MONITOR_BASE_URL_DESC" description="PLG_SYSTEM_MOKOSUITE_MONITOR_BASE_URL_DESC"
filter="url" /> filter="url" />
<field name="signing_key" type="hidden" <field name="signing_key" type="hidden"
@@ -8,8 +8,8 @@
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.40.01-rc</version> <version>02.34.84-dev</version>
<description>PLG_SYSTEM_MOKOSUITECLIENT_OFFLINE_DESC</description> <description>PLG_SYSTEM_MOKOSUITE_OFFLINE_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteClientOffline</namespace> <namespace path="src">Moko\Plugin\System\MokoSuiteClientOffline</namespace>
<files> <files>
@@ -25,15 +25,15 @@
<config> <config>
<fields name="params" addfieldprefix="Moko\Plugin\System\MokoSuiteClientOffline\Field"> <fields name="params" addfieldprefix="Moko\Plugin\System\MokoSuiteClientOffline\Field">
<fieldset name="basic" label="PLG_SYSTEM_MOKOSUITECLIENT_OFFLINE_FIELDSET_BASIC"> <fieldset name="basic" label="PLG_SYSTEM_MOKOSUITE_OFFLINE_FIELDSET_BASIC">
<field name="tos_slug" type="menuslug" <field name="tos_slug" type="menuslug"
label="PLG_SYSTEM_MOKOSUITECLIENT_OFFLINE_SLUG_LABEL" label="PLG_SYSTEM_MOKOSUITE_OFFLINE_SLUG_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_OFFLINE_SLUG_DESC" description="PLG_SYSTEM_MOKOSUITE_OFFLINE_SLUG_DESC"
multiple="true" /> multiple="true" />
<field name="include_children" type="radio" default="1" <field name="include_children" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITECLIENT_OFFLINE_CHILDREN_LABEL" label="PLG_SYSTEM_MOKOSUITE_OFFLINE_CHILDREN_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_OFFLINE_CHILDREN_DESC" description="PLG_SYSTEM_MOKOSUITE_OFFLINE_CHILDREN_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
@@ -8,8 +8,8 @@
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.40.01-rc</version> <version>02.34.84-dev</version>
<description>PLG_SYSTEM_MOKOSUITECLIENT_TENANT_DESC</description> <description>PLG_SYSTEM_MOKOSUITE_TENANT_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteClientTenant</namespace> <namespace path="src">Moko\Plugin\System\MokoSuiteClientTenant</namespace>
<files> <files>
@@ -26,20 +26,20 @@
<config> <config>
<fields name="params"> <fields name="params">
<fieldset name="basic" <fieldset name="basic"
label="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_FIELDSET_BASIC" label="PLG_SYSTEM_MOKOSUITE_TENANT_FIELDSET_BASIC"
description="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_FIELDSET_BASIC_DESC"> description="PLG_SYSTEM_MOKOSUITE_TENANT_FIELDSET_BASIC_DESC">
<field name="restrict_installer" type="radio" default="1" <field name="restrict_installer" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_RESTRICT_INSTALLER_LABEL" label="PLG_SYSTEM_MOKOSUITE_TENANT_RESTRICT_INSTALLER_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_RESTRICT_INSTALLER_DESC" description="PLG_SYSTEM_MOKOSUITE_TENANT_RESTRICT_INSTALLER_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
</field> </field>
<field name="allow_extension_updates" type="radio" default="1" <field name="allow_extension_updates" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_ALLOW_UPDATES_LABEL" label="PLG_SYSTEM_MOKOSUITE_TENANT_ALLOW_UPDATES_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_ALLOW_UPDATES_DESC" description="PLG_SYSTEM_MOKOSUITE_TENANT_ALLOW_UPDATES_DESC"
class="btn-group btn-group-yesno" class="btn-group btn-group-yesno"
showon="restrict_installer:1"> showon="restrict_installer:1">
<option value="1">JYES</option> <option value="1">JYES</option>
@@ -47,40 +47,40 @@
</field> </field>
<field name="hide_sysinfo" type="radio" default="1" <field name="hide_sysinfo" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_HIDE_SYSINFO_LABEL" label="PLG_SYSTEM_MOKOSUITE_TENANT_HIDE_SYSINFO_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_HIDE_SYSINFO_DESC" description="PLG_SYSTEM_MOKOSUITE_TENANT_HIDE_SYSINFO_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
</field> </field>
<field name="restrict_global_config" type="radio" default="1" <field name="restrict_global_config" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_RESTRICT_CONFIG_LABEL" label="PLG_SYSTEM_MOKOSUITE_TENANT_RESTRICT_CONFIG_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_RESTRICT_CONFIG_DESC" description="PLG_SYSTEM_MOKOSUITE_TENANT_RESTRICT_CONFIG_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
</field> </field>
<field name="restrict_template_editing" type="radio" default="1" <field name="restrict_template_editing" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_RESTRICT_TEMPLATE_LABEL" label="PLG_SYSTEM_MOKOSUITE_TENANT_RESTRICT_TEMPLATE_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_RESTRICT_TEMPLATE_DESC" description="PLG_SYSTEM_MOKOSUITE_TENANT_RESTRICT_TEMPLATE_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
</field> </field>
<field name="disable_install_url" type="radio" default="1" <field name="disable_install_url" type="radio" default="1"
label="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_DISABLE_INSTALL_URL_LABEL" label="PLG_SYSTEM_MOKOSUITE_TENANT_DISABLE_INSTALL_URL_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_DISABLE_INSTALL_URL_DESC" description="PLG_SYSTEM_MOKOSUITE_TENANT_DISABLE_INSTALL_URL_DESC"
class="btn-group btn-group-yesno"> class="btn-group btn-group-yesno">
<option value="1">JYES</option> <option value="1">JYES</option>
<option value="0">JNO</option> <option value="0">JNO</option>
</field> </field>
<field name="hidden_menu_items" type="textarea" <field name="hidden_menu_items" type="textarea"
label="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_HIDDEN_MENUS_LABEL" label="PLG_SYSTEM_MOKOSUITE_TENANT_HIDDEN_MENUS_LABEL"
description="PLG_SYSTEM_MOKOSUITECLIENT_TENANT_HIDDEN_MENUS_DESC" description="PLG_SYSTEM_MOKOSUITE_TENANT_HIDDEN_MENUS_DESC"
rows="5" filter="raw" /> rows="5" filter="raw" />
</fieldset> </fieldset>
</fields> </fields>
@@ -8,7 +8,7 @@
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.40.01-rc</version> <version>02.34.84-dev</version>
<description>Runs scheduled helpdesk automation rules — auto-close resolved tickets, SLA breach escalation, and time-based actions.</description> <description>Runs scheduled helpdesk automation rules — auto-close resolved tickets, SLA breach escalation, and time-based actions.</description>
<namespace path="src">Moko\Plugin\Task\MokoSuiteClientTickets</namespace> <namespace path="src">Moko\Plugin\Task\MokoSuiteClientTickets</namespace>
@@ -27,15 +27,15 @@ class TicketAutomation extends CMSPlugin implements SubscriberInterface
protected const TASKS_MAP = [ protected const TASKS_MAP = [
'mokosuiteclient.ticket.automation' => [ 'mokosuiteclient.ticket.automation' => [
'langConstPrefix' => 'PLG_TASK_MOKOSUITECLIENT_TICKETS_AUTOMATION', 'langConstPrefix' => 'PLG_TASK_MOKOSUITE_TICKETS_AUTOMATION',
'method' => 'runAutomation', 'method' => 'runAutomation',
], ],
'mokosuiteclient.ticket.imap_poll' => [ 'mokosuiteclient.ticket.imap_poll' => [
'langConstPrefix' => 'PLG_TASK_MOKOSUITECLIENT_TICKETS_IMAP_POLL', 'langConstPrefix' => 'PLG_TASK_MOKOSUITE_TICKETS_IMAP_POLL',
'method' => 'runImapPoll', 'method' => 'runImapPoll',
], ],
'mokosuiteclient.ticket.autoclose' => [ 'mokosuiteclient.ticket.autoclose' => [
'langConstPrefix' => 'PLG_TASK_MOKOSUITECLIENT_TICKETS_AUTOCLOSE', 'langConstPrefix' => 'PLG_TASK_MOKOSUITE_TICKETS_AUTOCLOSE',
'method' => 'runAutoClose', 'method' => 'runAutoClose',
], ],
]; ];
@@ -251,7 +251,6 @@ class TicketAutomation extends CMSPlugin implements SubscriberInterface
} }
catch (\Throwable $e) catch (\Throwable $e)
{ {
Log::add('Failed to load component config: ' . $e->getMessage(), Log::ERROR, 'mokosuiteclient');
return []; return [];
} }
} }
@@ -12,8 +12,8 @@
<license>GNU General Public License version 3 or later; see LICENSE</license> <license>GNU General Public License version 3 or later; see LICENSE</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.40.01-rc</version> <version>02.34.84-dev</version>
<description>PLG_TASK_MOKOSUITECLIENTDEMO_DESC</description> <description>PLG_TASK_MOKOSUITEDEMO_DESC</description>
<namespace path="src">Moko\Plugin\Task\MokoSuiteClientDemo</namespace> <namespace path="src">Moko\Plugin\Task\MokoSuiteClientDemo</namespace>
<files> <files>
@@ -10,7 +10,7 @@
* INGROUP: MokoSuiteClient * INGROUP: MokoSuiteClient
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient
* PATH: /src/packages/plg_system_mokosuiteclient/Service/DemoResetService.php * PATH: /src/packages/plg_system_mokosuiteclient/Service/DemoResetService.php
* VERSION: 02.40.01 * VERSION: 02.34.84
* BRIEF: Content-only snapshot/restore for demo site reset * BRIEF: Content-only snapshot/restore for demo site reset
*/ */
@@ -12,8 +12,8 @@
<license>GNU General Public License version 3 or later; see LICENSE</license> <license>GNU General Public License version 3 or later; see LICENSE</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.40.01-rc</version> <version>02.34.84-dev</version>
<description>PLG_TASK_MOKOSUITECLIENTSYNC_DESC</description> <description>PLG_TASK_MOKOSUITESYNC_DESC</description>
<namespace path="src">Moko\Plugin\Task\MokoSuiteClientSync</namespace> <namespace path="src">Moko\Plugin\Task\MokoSuiteClientSync</namespace>
<files> <files>
@@ -10,7 +10,7 @@
* INGROUP: MokoSuiteClient * INGROUP: MokoSuiteClient
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient
* PATH: /src/packages/plg_system_mokosuiteclient/Service/ContentSyncReceiver.php * PATH: /src/packages/plg_system_mokosuiteclient/Service/ContentSyncReceiver.php
* VERSION: 02.40.01 * VERSION: 02.34.84
* BRIEF: Receiver-side content sync — applies incoming payload to local DB * BRIEF: Receiver-side content sync — applies incoming payload to local DB
*/ */
@@ -10,7 +10,7 @@
* INGROUP: MokoSuiteClient * INGROUP: MokoSuiteClient
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient
* PATH: /src/packages/plg_system_mokosuiteclient/Service/ContentSyncService.php * PATH: /src/packages/plg_system_mokosuiteclient/Service/ContentSyncService.php
* VERSION: 02.40.01 * VERSION: 02.34.84
* BRIEF: Sender-side content sync — builds payload and pushes to remote sites * BRIEF: Sender-side content sync — builds payload and pushes to remote sites
*/ */
@@ -7,7 +7,7 @@
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.40.01-rc</version> <version>02.34.84-dev</version>
<description>Joomla Web Services API routes for MokoSuiteClient site management — health checks, cache, updates, backups, and site info.</description> <description>Joomla Web Services API routes for MokoSuiteClient site management — health checks, cache, updates, backups, and site info.</description>
<namespace path="src">Moko\Plugin\WebServices\MokoSuiteClient</namespace> <namespace path="src">Moko\Plugin\WebServices\MokoSuiteClient</namespace>
<files> <files>
+2 -1
View File
@@ -2,7 +2,7 @@
<extension type="package" method="upgrade"> <extension type="package" method="upgrade">
<name>Package - MokoSuiteClient</name> <name>Package - MokoSuiteClient</name>
<packagename>mokosuiteclient</packagename> <packagename>mokosuiteclient</packagename>
<version>02.40.01-rc</version> <version>02.34.84-dev</version>
<creationDate>2026-06-02</creationDate> <creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -25,6 +25,7 @@
<file type="module" id="mod_mokosuiteclient_menu" client="administrator">mod_mokosuiteclient_menu.zip</file> <file type="module" id="mod_mokosuiteclient_menu" client="administrator">mod_mokosuiteclient_menu.zip</file>
<file type="module" id="mod_mokosuiteclient_cache" client="administrator">mod_mokosuiteclient_cache.zip</file> <file type="module" id="mod_mokosuiteclient_cache" client="administrator">mod_mokosuiteclient_cache.zip</file>
<file type="module" id="mod_mokosuiteclient_categories" client="administrator">mod_mokosuiteclient_categories.zip</file> <file type="module" id="mod_mokosuiteclient_categories" client="administrator">mod_mokosuiteclient_categories.zip</file>
<file type="plugin" id="plg_system_mokosuiteclient_backup" group="system">plg_system_mokosuiteclient_backup.zip</file>
<file type="plugin" id="plg_webservices_mokosuiteclient" group="webservices">plg_webservices_mokosuiteclient.zip</file> <file type="plugin" id="plg_webservices_mokosuiteclient" group="webservices">plg_webservices_mokosuiteclient.zip</file>
<file type="plugin" id="plg_task_mokosuiteclientdemo" group="task">plg_task_mokosuiteclientdemo.zip</file> <file type="plugin" id="plg_task_mokosuiteclientdemo" group="task">plg_task_mokosuiteclientdemo.zip</file>
<file type="plugin" id="plg_task_mokosuiteclientsync" group="task">plg_task_mokosuiteclientsync.zip</file> <file type="plugin" id="plg_task_mokosuiteclientsync" group="task">plg_task_mokosuiteclientsync.zip</file>
+1 -2
View File
@@ -20,7 +20,7 @@ use Joomla\CMS\Log\Log;
* *
* @since 2.2.0 * @since 2.2.0
*/ */
class Pkg_MokosuiteclientInstallerScript class Pkg_MokosuiteInstallerScript
{ {
/** /**
* Runs after package installation/update. * Runs after package installation/update.
@@ -78,7 +78,6 @@ class Pkg_MokosuiteclientInstallerScript
$this->enablePlugin('system', 'mokosuiteclient_devtools'); $this->enablePlugin('system', 'mokosuiteclient_devtools');
$this->enablePlugin('system', 'mokosuiteclient_offline'); $this->enablePlugin('system', 'mokosuiteclient_offline');
$this->enablePlugin('system', 'mokosuiteclient_dbip'); $this->enablePlugin('system', 'mokosuiteclient_dbip');
$this->enablePlugin('system', 'mokosuiteclient_monitor');
$this->enablePlugin('webservices', 'mokosuiteclient'); $this->enablePlugin('webservices', 'mokosuiteclient');
$this->enablePlugin('task', 'mokosuiteclientdemo'); $this->enablePlugin('task', 'mokosuiteclientdemo');
$this->enablePlugin('task', 'mokosuiteclientsync'); $this->enablePlugin('task', 'mokosuiteclientsync');
+48
View File
@@ -0,0 +1,48 @@
<?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
@@ -0,0 +1,72 @@
<?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 '';
}
}
@@ -0,0 +1,40 @@
<?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 '';
}
}
@@ -0,0 +1,237 @@
<?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';
}
}
@@ -0,0 +1,156 @@
<?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>';
}
}
@@ -0,0 +1,175 @@
<?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;
}
}
@@ -0,0 +1,260 @@
<?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>