Compare commits

...

21 Commits

Author SHA1 Message Date
Jonathan Miller 4b9a675d0f Rename MokoSuite → MokoSuiteClient (full element rename)
Generic: Project CI / Tests (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Project CI / Lint & Validate (push) Successful in 36s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 40s
All Joomla element names, PHP classes, language files, folder structure,
and manifest references renamed from mokosuite to mokosuiteclient.
This repo is now the client-facing tracker for the MokoSuite platform.
2026-06-15 05:19:13 -05:00
jmiller 6cd16d9845 chore: sync security-audit.yml from Template-Joomla [skip ci] 2026-06-07 17:55:05 +00:00
jmiller 3d522134c2 chore: sync repo-health.yml from Template-Joomla [skip ci] 2026-06-07 17:55:04 +00:00
jmiller 8db306a4a7 chore: sync pre-release.yml from Template-Joomla [skip ci] 2026-06-07 17:55:04 +00:00
jmiller 6861643e6c chore: sync pr-check.yml from Template-Joomla [skip ci] 2026-06-07 17:55:03 +00:00
jmiller 4b1280213f chore: sync notify.yml from Template-Joomla [skip ci] 2026-06-07 17:55:02 +00:00
jmiller 9172baefb6 chore: sync issue-branch.yml from Template-Joomla [skip ci] 2026-06-07 17:55:01 +00:00
jmiller 5b5b81eb0f chore: sync gitleaks.yml from Template-Joomla [skip ci] 2026-06-07 17:55:01 +00:00
jmiller 82db443607 chore: sync deploy-manual.yml from Template-Joomla [skip ci] 2026-06-07 17:55:00 +00:00
jmiller 67f4da183c chore: sync cleanup.yml from Template-Joomla [skip ci] 2026-06-07 17:54:59 +00:00
jmiller bf05f4baa8 chore: sync ci-joomla.yml from Template-Joomla [skip ci] 2026-06-07 17:54:59 +00:00
jmiller 5303924f58 chore: sync ci-generic.yml from Template-Joomla [skip ci] 2026-06-07 17:54:58 +00:00
jmiller 527c8b82cf chore: sync branch-cleanup.yml from Template-Joomla [skip ci] 2026-06-07 17:54:57 +00:00
jmiller fb7c182b16 chore: sync auto-bump.yml from Template-Joomla [skip ci] 2026-06-07 17:54:56 +00:00
Jonathan Miller 70419c4018 Merge remote-tracking branch 'origin/dev'
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (push) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been cancelled
Platform: moko-platform CI / CI Summary (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Generic: Repo Health / Site Health (push) Has been cancelled
Generic: Repo Health / Access control (push) Has been cancelled
Platform: moko-platform CI / Gate 1: Code Quality (push) Has been cancelled
2026-06-07 12:40:46 -05:00
Jonathan Miller 624b81b2d2 Merge remote-tracking branch 'origin/dev'
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (push) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been cancelled
Platform: moko-platform CI / CI Summary (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Generic: Repo Health / Site Health (push) Has been cancelled
Generic: Repo Health / Access control (push) Has been cancelled
Platform: moko-platform CI / Gate 1: Code Quality (push) Has been cancelled
# Conflicts:
#	source/packages/plg_system_mokowaas_monitor/mokowaas_monitor.xml
#	source/packages/plg_webservices_perfectpublisher/perfectpublisher.xml
#	source/packages/plg_webservices_perfectpublisher/src/Extension/PerfectPublisherApi.php
#	source/pkg_mokowaas.xml
2026-06-07 11:26:46 -05:00
Jonathan Miller 872889487e Merge remote-tracking branch 'origin/dev'
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (push) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been cancelled
Platform: moko-platform CI / CI Summary (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Generic: Repo Health / Site Health (push) Has been cancelled
Generic: Repo Health / Access control (push) Has been cancelled
Platform: moko-platform CI / Gate 1: Code Quality (push) Has been cancelled
# Conflicts:
#	.mokogitea/manifest.xml
#	.mokogitea/workflows/issue-branch.yml
#	CHANGELOG.md
#	CODE_OF_CONDUCT.md
#	GOVERNANCE.md
#	LICENSE.md
#	README.md
#	SECURITY.md
#	docs/guides/build-guide.md
#	docs/guides/configuration-guide.md
#	docs/guides/installation-guide.md
#	docs/guides/operations-guide.md
#	docs/guides/rollback-and-recovery-guide.md
#	docs/guides/testing-guide.md
#	docs/guides/troubleshooting-guide.md
#	docs/guides/upgrade-and-versioning-guide.md
#	docs/index.md
#	docs/plugin-basic.md
#	docs/update-server.md
#	source/packages/mod_mokowaas_cpanel/mod_mokowaas_cpanel.xml
#	source/packages/plg_system_mokowaas/Field/CopyableTokenField.php
#	source/packages/plg_system_mokowaas/script.php
#	source/packages/plg_system_mokowaas/services/provider.php
#	source/packages/plg_system_mokowaas_devtools/mokowaas_devtools.xml
#	source/packages/plg_system_mokowaas_firewall/mokowaas_firewall.xml
#	source/packages/plg_system_mokowaas_monitor/mokowaas_monitor.xml
#	source/packages/plg_system_mokowaas_tenant/mokowaas_tenant.xml
#	source/packages/plg_task_mokowaasdemo/mokowaasdemo.xml
#	source/packages/plg_task_mokowaasdemo/src/Service/DemoResetService.php
#	source/packages/plg_task_mokowaassync/mokowaassync.xml
#	source/packages/plg_task_mokowaassync/src/Service/ContentSyncReceiver.php
#	source/packages/plg_task_mokowaassync/src/Service/ContentSyncService.php
#	source/packages/plg_webservices_mokowaas/mokowaas.xml
#	source/packages/plg_webservices_perfectpublisher/perfectpublisher.xml
#	source/packages/plg_webservices_perfectpublisher/services/provider.php
#	source/packages/plg_webservices_perfectpublisher/src/Extension/PerfectPublisherApi.php
#	source/pkg_mokowaas.xml
#	src/packages/com_mokowaas/mokowaas.xml
#	src/packages/plg_system_mokowaas/Extension/MokoWaaS.php
#	src/packages/plg_system_mokowaas/Field/AllowedIpsField.php
#	src/packages/plg_system_mokowaas/Field/CurrentIpField.php
#	src/packages/plg_system_mokowaas/Field/DemoTaskInfoField.php
#	src/packages/plg_system_mokowaas/Field/NextResetField.php
#	src/packages/plg_system_mokowaas/Field/SnapshotTablesField.php
#	src/packages/plg_system_mokowaas/mokowaas.xml
2026-06-06 11:47:10 -05:00
jmiller d12971c0b7 chore: remove update-server workflow [skip ci] 2026-06-05 00:07:22 +00:00
jmiller 21156deb0e chore: remove updates.xml from main [skip ci] 2026-06-04 23:23:22 +00:00
gitea-actions[bot] 1547bd5861 chore(release): build 02.34.00 [skip ci] 2026-06-04 23:14:10 +00:00
jmiller f66871db2e chore: add dlid and blockChildUninstall to package manifest [skip ci] 2026-06-04 22:02:42 +00:00
291 changed files with 11576 additions and 4525 deletions
+21 -21
View File
@@ -1,4 +1,4 @@
# MokoSuite # MokoSuiteClient
Joomla 5/6 admin tools suite — heartbeat health monitoring, extension management, security firewall, tenant restrictions, and site administration. Joomla 5/6 admin tools suite — heartbeat health monitoring, extension management, security firewall, tenant restrictions, and site administration.
@@ -6,10 +6,10 @@ Joomla 5/6 admin tools suite — heartbeat health monitoring, extension manageme
| Field | Value | | Field | Value |
|---|---| |---|---|
| **Package** | `pkg_mokosuite` | | **Package** | `pkg_mokosuiteclient` |
| **Language** | PHP 8.1+ | | **Language** | PHP 8.1+ |
| **Branch** | develop on `dev`, merge to `main` (protected) | | **Branch** | develop on `dev`, merge to `main` (protected) |
| **Wiki** | [MokoSuite Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoSuite/wiki) | | **Wiki** | [MokoSuiteClient Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient/wiki) |
## Commands ## Commands
@@ -19,38 +19,38 @@ composer install # Install PHP dependencies
## Architecture ## Architecture
Joomla **package** (`pkg_mokosuite`) with 17 sub-extensions: Joomla **package** (`pkg_mokosuiteclient`) with 17 sub-extensions:
### Core Plugin (`plg_system_mokosuite`) ### Core Plugin (`plg_system_mokosuiteclient`)
- Heartbeat health endpoint (`/?mokosuite=health`) with 16 diagnostic checks - Heartbeat health endpoint (`/?mokosuiteclient=health`) with 16 diagnostic checks
- Grafana provisioning and heartbeat sender - Grafana provisioning and heartbeat sender
- Site alias / domain management - Site alias / domain management
- Extension cascade (enable/disable coordination) - Extension cascade (enable/disable coordination)
- Download key preservation across Joomla updates - Download key preservation across Joomla updates
- Namespace: `Moko\Plugin\System\MokoSuite` - Namespace: `Moko\Plugin\System\MokoSuiteClient`
### Feature Plugins ### Feature Plugins
- `plg_system_mokosuite_firewall` — WAF, IP blocklist, security headers, password policy - `plg_system_mokosuiteclient_firewall` — WAF, IP blocklist, security headers, password policy
- `plg_system_mokosuite_tenant` — admin restrictions for non-master users - `plg_system_mokosuiteclient_tenant` — admin restrictions for non-master users
- `plg_system_mokosuite_devtools` — dev mode, hit reset, version cleanup, download key reset - `plg_system_mokosuiteclient_devtools` — dev mode, hit reset, version cleanup, download key reset
- `plg_system_mokosuite_offline` — offline mode bypass for legal pages - `plg_system_mokosuiteclient_offline` — offline mode bypass for legal pages
- `plg_system_mokosuite_monitor` — Grafana heartbeat registration - `plg_system_mokosuiteclient_monitor` — Grafana heartbeat registration
### Component (`com_mokosuite`) ### Component (`com_mokosuiteclient`)
- Admin dashboard with plugin management, WAF charts, extension catalog - Admin dashboard with plugin management, WAF charts, extension catalog
- Helpdesk ticketing system - Helpdesk ticketing system
- REST API controllers - REST API controllers
### Modules ### Modules
- `mod_mokosuite_cpanel` — admin dashboard widget - `mod_mokosuiteclient_cpanel` — admin dashboard widget
- `mod_mokosuite_menu` — admin sidebar menu - `mod_mokosuiteclient_menu` — admin sidebar menu
- `mod_mokosuite_cache` — status bar cache/temp cleaner - `mod_mokosuiteclient_cache` — status bar cache/temp cleaner
- `mod_mokosuite_categories` — auto-category tree menu - `mod_mokosuiteclient_categories` — auto-category tree menu
### Task Plugins ### Task Plugins
- `plg_task_mokosuitedemo` — scheduled demo site reset - `plg_task_mokosuiteclientdemo` — scheduled demo site reset
- `plg_task_mokosuitesync` — scheduled content sync - `plg_task_mokosuiteclientsync` — scheduled content sync
- `plg_task_mokosuite_tickets` — ticket automation - `plg_task_mokosuiteclient_tickets` — ticket automation
### Update Server ### Update Server
@@ -59,7 +59,7 @@ MokoGitea generates update feeds dynamically from releases — no static `update
## Source Directory ## Source Directory
Source lives in `source/` (not `src/`): Source lives in `source/` (not `src/`):
- `source/pkg_mokosuite.xml` — package manifest - `source/pkg_mokosuiteclient.xml` — package manifest
- `source/script.php` — install script - `source/script.php` — install script
- `source/packages/` — all sub-extensions - `source/packages/` — all sub-extensions
@@ -52,7 +52,7 @@ Attach screenshots showing the issue (desktop and mobile if relevant).
## Template Details ## Template Details
- **Joomla Version**: [e.g., 5.x] - **Joomla Version**: [e.g., 5.x]
- **Template Name**: [e.g., clienttemplate] - **Template Name**: [e.g., clienttemplate]
- **MokoSuite Plugin**: [Active / Inactive] - **MokoSuiteClient Plugin**: [Active / Inactive]
- **MokoOnyx Admin**: [Active / Inactive] - **MokoOnyx Admin**: [Active / Inactive]
## CSS Custom Properties ## CSS Custom Properties
+20 -20
View File
@@ -11,9 +11,9 @@ INGROUP: MokoStandards.Templates
REPO: https://github.com/mokoconsulting-tech/MokoStandards REPO: https://github.com/mokoconsulting-tech/MokoStandards
PATH: /templates/github/copilot-instructions.joomla.md.template PATH: /templates/github/copilot-instructions.joomla.md.template
VERSION: XX.YY.ZZ VERSION: XX.YY.ZZ
BRIEF: GitHub Copilot custom instructions template for Joomla/MokoSuite governed repositories BRIEF: GitHub Copilot custom instructions template for Joomla/MokoSuiteClient governed repositories
NOTE: Synced to .github/copilot-instructions.md in all Joomla/Suite repos via bulk sync. NOTE: Synced to .github/copilot-instructions.md in all Joomla/Suite repos via bulk sync.
Tokens replaced at sync time: MokoSuite, https://github.com/mokoconsulting-tech/MokoSuite, {{EXTENSION_NAME}}, Tokens replaced at sync time: MokoSuiteClient, https://github.com/mokoconsulting-tech/MokoSuiteClient, {{EXTENSION_NAME}},
{{EXTENSION_TYPE}}, {{EXTENSION_ELEMENT}} {{EXTENSION_TYPE}}, {{EXTENSION_ELEMENT}}
--> -->
@@ -36,24 +36,24 @@ NOTE: Synced to .github/copilot-instructions.md in all Joomla/Suite repos via bu
> >
> | Placeholder | Where to find the value | > | Placeholder | Where to find the value |
> |---|---| > |---|---|
> | `MokoSuite` | The GitHub repository name (visible in the URL, `README.md` heading, or `git remote -v`) | > | `MokoSuiteClient` | The GitHub repository name (visible in the URL, `README.md` heading, or `git remote -v`) |
> | `https://github.com/mokoconsulting-tech/MokoSuite` | Full GitHub URL, e.g. `https://github.com/mokoconsulting-tech/<repo-name>` | > | `https://github.com/mokoconsulting-tech/MokoSuiteClient` | Full GitHub URL, e.g. `https://github.com/mokoconsulting-tech/<repo-name>` |
> | `{{EXTENSION_NAME}}` | The `<name>` element in `manifest.xml` at the repository root | > | `{{EXTENSION_NAME}}` | The `<name>` element in `manifest.xml` at the repository root |
> | `{{EXTENSION_TYPE}}` | The `type` attribute of the `<extension>` tag in `manifest.xml` (`component`, `module`, `plugin`, or `template`) | > | `{{EXTENSION_TYPE}}` | The `type` attribute of the `<extension>` tag in `manifest.xml` (`component`, `module`, `plugin`, or `template`) |
> | `{{EXTENSION_ELEMENT}}` | The `<element>` tag in `manifest.xml`, or the filename prefix (e.g. `com_myextension`, `mod_mymodule`) | > | `{{EXTENSION_ELEMENT}}` | The `<element>` tag in `manifest.xml`, or the filename prefix (e.g. `com_myextension`, `mod_mymodule`) |
> >
> --- > ---
# MokoSuite — GitHub Copilot Custom Instructions # MokoSuiteClient — GitHub Copilot Custom Instructions
## What This Repo Is ## What This Repo Is
This is a **Moko Consulting MokoSuite** (Joomla) repository governed by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). All coding standards, workflows, and policies are defined there and enforced here via bulk sync. This is a **Moko Consulting MokoSuiteClient** (Joomla) repository governed by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). All coding standards, workflows, and policies are defined there and enforced here via bulk sync.
Repository URL: https://github.com/mokoconsulting-tech/MokoSuite Repository URL: https://github.com/mokoconsulting-tech/MokoSuiteClient
Extension name: **{{EXTENSION_NAME}}** Extension name: **{{EXTENSION_NAME}}**
Extension type: **{{EXTENSION_TYPE}}** (`{{EXTENSION_ELEMENT}}`) Extension type: **{{EXTENSION_TYPE}}** (`{{EXTENSION_ELEMENT}}`)
Platform: **Joomla 4.x / MokoSuite** Platform: **Joomla 4.x / MokoSuiteClient**
--- ---
@@ -77,9 +77,9 @@ Every new file needs a copyright header as its first content.
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
* *
* FILE INFORMATION * FILE INFORMATION
* DEFGROUP: MokoSuite.{{EXTENSION_TYPE}} * DEFGROUP: MokoSuiteClient.{{EXTENSION_TYPE}}
* INGROUP: MokoSuite * INGROUP: MokoSuiteClient
* REPO: https://github.com/mokoconsulting-tech/MokoSuite * REPO: https://github.com/mokoconsulting-tech/MokoSuiteClient
* PATH: /path/to/file.php * PATH: /path/to/file.php
* VERSION: XX.YY.ZZ * VERSION: XX.YY.ZZ
* BRIEF: One-line description of purpose * BRIEF: One-line description of purpose
@@ -98,9 +98,9 @@ This file is part of a Moko Consulting project.
SPDX-License-Identifier: GPL-3.0-or-later SPDX-License-Identifier: GPL-3.0-or-later
# FILE INFORMATION # FILE INFORMATION
DEFGROUP: MokoSuite.Documentation DEFGROUP: MokoSuiteClient.Documentation
INGROUP: MokoSuite INGROUP: MokoSuiteClient
REPO: https://github.com/mokoconsulting-tech/MokoSuite REPO: https://github.com/mokoconsulting-tech/MokoSuiteClient
PATH: /docs/file.md PATH: /docs/file.md
VERSION: XX.YY.ZZ VERSION: XX.YY.ZZ
BRIEF: One-line description BRIEF: One-line description
@@ -138,7 +138,7 @@ The version in `README.md` **must always match** the `<version>` tag in `manifes
<version>01.02.04</version> <version>01.02.04</version>
<downloads> <downloads>
<downloadurl type="full" format="zip"> <downloadurl type="full" format="zip">
https://github.com/mokoconsulting-tech/MokoSuite/releases/download/01.02.04/{{EXTENSION_ELEMENT}}-01.02.04.zip https://github.com/mokoconsulting-tech/MokoSuiteClient/releases/download/01.02.04/{{EXTENSION_ELEMENT}}-01.02.04.zip
</downloadurl> </downloadurl>
</downloads> </downloads>
<targetplatform name="joomla" version="4\.[0-9]+" /> <targetplatform name="joomla" version="4\.[0-9]+" />
@@ -152,7 +152,7 @@ The version in `README.md` **must always match** the `<version>` tag in `manifes
## Joomla Extension Structure ## Joomla Extension Structure
``` ```
MokoSuite/ MokoSuiteClient/
├── manifest.xml # Joomla installer manifest (root — required) ├── manifest.xml # Joomla installer manifest (root — required)
├── (no updates.xml) # Update XML is generated dynamically by MokoGitea ├── (no updates.xml) # Update XML is generated dynamically by MokoGitea
├── site/ # Frontend (site) code ├── site/ # Frontend (site) code
@@ -191,11 +191,11 @@ MokoSuite/
https://git.mokoconsulting.tech/{Owner}/{Repo}/updates.xml https://git.mokoconsulting.tech/{Owner}/{Repo}/updates.xml
``` ```
The package manifest (`pkg_mokosuite.xml`) references it via: The package manifest (`pkg_mokosuiteclient.xml`) references it via:
```xml ```xml
<updateservers> <updateservers>
<server type="extension" priority="1" name="MokoSuite Update Server"> <server type="extension" priority="1" name="MokoSuiteClient Update Server">
https://git.mokoconsulting.tech/MokoConsulting/MokoSuite/updates.xml https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient/updates.xml
</server> </server>
</updateservers> </updateservers>
``` ```
@@ -257,7 +257,7 @@ This repository is governed by [MokoStandards](https://github.com/mokoconsulting
| [branching-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | | [branching-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow |
| [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR title/body conventions | | [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR title/body conventions |
| [changelog-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | | [changelog-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md |
| [joomla-development-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoSuite Joomla extension development guide | | [joomla-development-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoSuiteClient Joomla extension development guide |
--- ---
+2 -2
View File
@@ -5,8 +5,8 @@
--> -->
<moko-platform xmlns="https://standards.mokoconsulting.tech/moko-platform/1.0" schema-version="1.0"> <moko-platform xmlns="https://standards.mokoconsulting.tech/moko-platform/1.0" schema-version="1.0">
<identity> <identity>
<name>MokoSuite</name> <name>MokoSuiteClient</name>
<display-name>Package - MokoSuite</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.34.50</version> <version>02.34.50</version>
+5 -2
View File
@@ -7,7 +7,7 @@
# INGROUP: moko-platform.Release # INGROUP: moko-platform.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform # REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# PATH: /.mokogitea/workflows/auto-bump.yml # PATH: /.mokogitea/workflows/auto-bump.yml
# VERSION: 09.23.00 # VERSION: 09.02.00
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits) # BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
name: "Universal: Auto Version Bump" name: "Universal: Auto Version Bump"
@@ -48,12 +48,15 @@ jobs:
if ! command -v composer &> /dev/null; then if ! command -v composer &> /dev/null; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1 sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
fi fi
rm -rf /tmp/moko-platform-api if [ -d "/opt/moko-platform/cli" ]; then
echo "MOKO_CLI=/opt/moko-platform/cli" >> "$GITHUB_ENV"
else
git clone --depth 1 --branch main --quiet \ git clone --depth 1 --branch main --quiet \
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/moko-platform.git" \ "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/moko-platform.git" \
/tmp/moko-platform-api /tmp/moko-platform-api
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV" echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
fi
- name: Bump version - name: Bump version
run: | run: |
+2 -2
View File
@@ -4,10 +4,10 @@
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: Gitea.Workflow
# INGROUP: MokoPlatform.Universal # INGROUP: MokoStandards.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform # REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# PATH: /.mokogitea/workflows/branch-cleanup.yml # PATH: /.mokogitea/workflows/branch-cleanup.yml
# VERSION: 09.23.00 # VERSION: 01.00.00
# BRIEF: Delete feature branches after PR merge # BRIEF: Delete feature branches after PR merge
name: "Branch Cleanup" name: "Branch Cleanup"
+204
View File
@@ -0,0 +1,204 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.CI
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.gitea/workflows/ci-generic.yml
# VERSION: 01.00.00
# BRIEF: CI pipeline — lint, validate, and test for generic projects (PHP + Node.js)
name: "Generic: Project CI"
on:
push:
branches:
- main
- dev
- dev/**
- rc/**
- version/**
pull_request:
branches:
- main
- dev
- dev/**
- rc/**
workflow_dispatch:
permissions:
contents: read
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
# ── Lint & Validate ───────────────────────────────────────────────────
lint:
name: Lint & Validate
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Detect toolchain
id: detect
run: |
HAS_PHP=false
HAS_NODE=false
[ -f "composer.json" ] && HAS_PHP=true
[ -f "package.json" ] && HAS_NODE=true
echo "has_php=$HAS_PHP" >> "$GITHUB_OUTPUT"
echo "has_node=$HAS_NODE" >> "$GITHUB_OUTPUT"
echo "Toolchain: PHP=$HAS_PHP Node=$HAS_NODE"
- name: Setup PHP
if: steps.detect.outputs.has_php == 'true'
run: |
if ! command -v php &> /dev/null; then
sudo apt-get update -qq
sudo apt-get install -y -qq php-cli php-mbstring php-xml >/dev/null 2>&1
fi
php -v
- name: Setup Node.js
if: steps.detect.outputs.has_node == 'true'
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install PHP dependencies
if: steps.detect.outputs.has_php == 'true'
run: |
if [ -f "composer.json" ]; then
composer install --no-interaction --prefer-dist --quiet 2>/dev/null || true
fi
- name: Install Node.js dependencies
if: steps.detect.outputs.has_node == 'true'
run: |
if [ -f "package.json" ]; then
npm ci --quiet 2>/dev/null || npm install --quiet 2>/dev/null || true
fi
- name: PHP syntax check
if: steps.detect.outputs.has_php == 'true'
run: |
ERRORS=0
while IFS= read -r -d '' file; do
if ! php -l "$file" 2>&1 | grep -q "No syntax errors"; then
echo "::error file=${file}::PHP syntax error"
ERRORS=$((ERRORS + 1))
fi
done < <(find . -name "*.php" -not -path "./.git/*" -not -path "./vendor/*" -not -path "./node_modules/*" -print0)
echo "## PHP Lint" >> $GITHUB_STEP_SUMMARY
if [ "$ERRORS" -eq 0 ]; then
echo "All PHP files passed syntax check." >> $GITHUB_STEP_SUMMARY
else
echo "${ERRORS} file(s) with syntax errors." >> $GITHUB_STEP_SUMMARY
exit 1
fi
- name: TypeScript/JavaScript lint
if: steps.detect.outputs.has_node == 'true'
run: |
if [ -f "node_modules/.bin/eslint" ]; then
npx eslint src/ --quiet 2>&1 || { echo "::error::ESLint errors found"; exit 1; }
echo "## ESLint" >> $GITHUB_STEP_SUMMARY
echo "All files passed ESLint." >> $GITHUB_STEP_SUMMARY
elif [ -f ".eslintrc.json" ] || [ -f ".eslintrc.js" ] || [ -f "eslint.config.js" ]; then
echo "::warning::ESLint config found but eslint not installed"
else
echo "No ESLint configured — skipping"
fi
- name: TypeScript compile check
if: steps.detect.outputs.has_node == 'true'
run: |
if [ -f "tsconfig.json" ] && [ -f "node_modules/.bin/tsc" ]; then
npx tsc --noEmit 2>&1 || { echo "::error::TypeScript compilation errors"; exit 1; }
echo "## TypeScript" >> $GITHUB_STEP_SUMMARY
echo "TypeScript compilation passed." >> $GITHUB_STEP_SUMMARY
fi
- name: PHPStan static analysis
if: steps.detect.outputs.has_php == 'true'
run: |
if [ -f "phpstan.neon" ] && [ -f "vendor/bin/phpstan" ]; then
vendor/bin/phpstan analyse --no-progress 2>&1 || { echo "::warning::PHPStan found issues"; }
fi
# ── Tests ─────────────────────────────────────────────────────────────
test:
name: Tests
runs-on: ubuntu-latest
needs: lint
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Detect toolchain
id: detect
run: |
HAS_PHP=false
HAS_NODE=false
[ -f "composer.json" ] && HAS_PHP=true
[ -f "package.json" ] && HAS_NODE=true
echo "has_php=$HAS_PHP" >> "$GITHUB_OUTPUT"
echo "has_node=$HAS_NODE" >> "$GITHUB_OUTPUT"
- name: Setup PHP
if: steps.detect.outputs.has_php == 'true'
run: |
if ! command -v php &> /dev/null; then
sudo apt-get update -qq
sudo apt-get install -y -qq php-cli php-mbstring php-xml >/dev/null 2>&1
fi
- name: Setup Node.js
if: steps.detect.outputs.has_node == 'true'
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: |
[ -f "composer.json" ] && composer install --no-interaction --prefer-dist --quiet 2>/dev/null || true
[ -f "package.json" ] && { npm ci --quiet 2>/dev/null || npm install --quiet 2>/dev/null || true; }
- name: Run PHP tests
if: steps.detect.outputs.has_php == 'true'
run: |
if [ -f "vendor/bin/phpunit" ]; then
vendor/bin/phpunit --testdox 2>&1
echo "## PHPUnit" >> $GITHUB_STEP_SUMMARY
echo "Tests passed." >> $GITHUB_STEP_SUMMARY
elif [ -f "phpunit.xml" ] || [ -f "phpunit.xml.dist" ]; then
echo "::warning::PHPUnit config found but phpunit not installed"
else
echo "No PHPUnit configured — skipping"
fi
- name: Run Node.js tests
if: steps.detect.outputs.has_node == 'true'
run: |
if jq -e '.scripts.test' package.json > /dev/null 2>&1; then
npm test 2>&1
echo "## Node.js Tests" >> $GITHUB_STEP_SUMMARY
echo "Tests passed." >> $GITHUB_STEP_SUMMARY
else
echo "No test script in package.json — skipping"
fi
- name: Build check
run: |
if [ -f "Makefile" ]; then
make build 2>&1 || echo "::warning::Build failed or not configured"
elif [ -f "package.json" ] && jq -e '.scripts.build' package.json > /dev/null 2>&1; then
npm run build 2>&1 || echo "::warning::Build failed"
fi
+500
View File
@@ -0,0 +1,500 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# This file is part of a Moko Consulting project.
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow.Template
# INGROUP: MokoStandards.CI
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
# PATH: /templates/workflows/joomla/ci-joomla.yml.template
# VERSION: 04.06.00
# BRIEF: CI workflow for Joomla extensions — lint, validate, test
name: "Joomla: Extension CI"
on:
pull_request:
branches:
- main
- 'dev/**'
workflow_dispatch:
permissions:
contents: read
pull-requests: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
lint-and-validate:
name: Lint & Validate
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup PHP
run: |
if ! command -v php &> /dev/null; then
sudo apt-get update -qq
sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
fi
php -v && composer --version
- name: Setup moko-platform tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
MOKO_CLONE_HOST: ${{ secrets.GA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }}
run: |
if [ -d "/tmp/moko-platform" ] || [ -d "/opt/moko-platform" ]; then
echo "moko-platform already available on runner — skipping clone"
else
git clone --depth 1 --branch main --quiet \
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
/tmp/moko-platform 2>/dev/null || echo "moko-platform clone skipped — continuing without it"
fi
- name: Install dependencies
env:
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || secrets.GA_TOKEN || github.token }}"}}'
run: |
if [ -f "composer.json" ]; then
composer install \
--no-interaction \
--prefer-dist \
--optimize-autoloader
else
echo "No composer.json found — skipping dependency install"
fi
- name: PHP syntax check
run: |
ERRORS=0
for DIR in src/ htdocs/; do
if [ -d "$DIR" ]; then
FOUND=1
while IFS= read -r -d '' FILE; do
OUTPUT=$(php -l "$FILE" 2>&1)
if echo "$OUTPUT" | grep -q "Parse error"; then
echo "::error file=${FILE}::${OUTPUT}"
ERRORS=$((ERRORS + 1))
fi
done < <(find "$DIR" -name "*.php" -print0)
fi
done
echo "### PHP Syntax Check" >> $GITHUB_STEP_SUMMARY
if [ "${ERRORS}" -gt 0 ]; then
echo "**${ERRORS} syntax error(s) found.**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "All PHP files passed syntax check." >> $GITHUB_STEP_SUMMARY
fi
- name: XML manifest validation
run: |
echo "### XML Manifest Validation" >> $GITHUB_STEP_SUMMARY
ERRORS=0
# Find the extension manifest (XML with <extension tag)
MANIFEST=""
for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
MANIFEST="$XML_FILE"
break
fi
done
if [ -z "$MANIFEST" ]; then
echo "No Joomla extension manifest found (XML file with \`<extension\` tag)." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Manifest found: \`${MANIFEST}\`" >> $GITHUB_STEP_SUMMARY
# Validate well-formed XML
php -r "
\$xml = @simplexml_load_file('$MANIFEST');
if (\$xml === false) {
echo 'INVALID';
exit(1);
}
echo 'VALID';
" > /tmp/xml_result 2>&1
XML_RESULT=$(cat /tmp/xml_result)
if [ "$XML_RESULT" != "VALID" ]; then
echo "Manifest is not well-formed XML." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Manifest is well-formed XML." >> $GITHUB_STEP_SUMMARY
fi
# Check required tags: name, version, author
for TAG in name version author; do
if ! grep -q "<${TAG}>" "$MANIFEST" 2>/dev/null; then
echo "Missing required tag: \`<${TAG}>\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Found required tag: \`<${TAG}>\`" >> $GITHUB_STEP_SUMMARY
fi
done
# Namespace is required for components/plugins but not packages
EXT_TYPE=$(grep -oP '<extension[^>]*\btype="\K[^"]+' "$MANIFEST" | head -1)
if [ "$EXT_TYPE" != "package" ]; then
if ! grep -q "<namespace" "$MANIFEST" 2>/dev/null; then
echo "Missing required tag: \`<namespace>\` (required for Joomla 5+ ${EXT_TYPE} extensions)" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Found required tag: \`<namespace>\`" >> $GITHUB_STEP_SUMMARY
fi
else
echo "Package extension — \`<namespace>\` not required." >> $GITHUB_STEP_SUMMARY
fi
fi
if [ "${ERRORS}" -gt 0 ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "**${ERRORS} manifest issue(s) found.**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Manifest validation passed.**" >> $GITHUB_STEP_SUMMARY
fi
- name: Check language files referenced in manifest
run: |
echo "### Language File Check" >> $GITHUB_STEP_SUMMARY
ERRORS=0
MANIFEST=""
for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
MANIFEST="$XML_FILE"
break
fi
done
if [ -n "$MANIFEST" ]; then
# Extract language file references from manifest
LANG_FILES=$(grep -oP 'language\s+tag="[^"]*"[^>]*>\K[^<]+' "$MANIFEST" 2>/dev/null || true)
if [ -z "$LANG_FILES" ]; then
echo "No language file references found in manifest — skipping." >> $GITHUB_STEP_SUMMARY
else
while IFS= read -r LANG_FILE; do
LANG_FILE=$(echo "$LANG_FILE" | xargs)
if [ -z "$LANG_FILE" ]; then
continue
fi
# Check in common locations
FOUND=0
for BASE in "." "src" "htdocs"; do
if [ -f "${BASE}/${LANG_FILE}" ]; then
FOUND=1
break
fi
done
if [ "$FOUND" -eq 0 ]; then
echo "Missing language file: \`${LANG_FILE}\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Language file present: \`${LANG_FILE}\`" >> $GITHUB_STEP_SUMMARY
fi
done <<< "$LANG_FILES"
fi
else
echo "No manifest found — skipping language check." >> $GITHUB_STEP_SUMMARY
fi
if [ "${ERRORS}" -gt 0 ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "**${ERRORS} missing language file(s).**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Language file check passed.**" >> $GITHUB_STEP_SUMMARY
fi
- name: Check index.html files in directories
run: |
echo "### Index.html Check" >> $GITHUB_STEP_SUMMARY
MISSING=0
CHECKED=0
for DIR in src/ htdocs/; do
if [ -d "$DIR" ]; then
while IFS= read -r -d '' SUBDIR; do
CHECKED=$((CHECKED + 1))
if [ ! -f "${SUBDIR}/index.html" ]; then
echo "Missing index.html in: \`${SUBDIR}\`" >> $GITHUB_STEP_SUMMARY
MISSING=$((MISSING + 1))
fi
done < <(find "$DIR" -type d -print0)
fi
done
if [ "${CHECKED}" -eq 0 ]; then
echo "No src/ or htdocs/ directories found — skipping." >> $GITHUB_STEP_SUMMARY
elif [ "${MISSING}" -gt 0 ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "**${MISSING} director(ies) missing index.html out of ${CHECKED} checked.**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "All ${CHECKED} directories contain index.html." >> $GITHUB_STEP_SUMMARY
fi
release-readiness:
name: Release Readiness Check
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' && github.base_ref == 'main'
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Validate release readiness
run: |
echo "## Release Readiness" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
ERRORS=0
# Extract version from README.md
README_VERSION=$(grep -oP '^\s*VERSION:\s*\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' README.md | head -1)
if [ -z "$README_VERSION" ]; then
echo "No VERSION found in README.md FILE INFORMATION block." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "README version: \`${README_VERSION}\`" >> $GITHUB_STEP_SUMMARY
fi
# Find the extension manifest
MANIFEST=""
for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
MANIFEST="$XML_FILE"
break
fi
done
if [ -z "$MANIFEST" ]; then
echo "No Joomla extension manifest found." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Manifest: \`${MANIFEST}\`" >> $GITHUB_STEP_SUMMARY
# Check <version> matches README VERSION
MANIFEST_VERSION=$(grep -oP '<version>\K[^<]+' "$MANIFEST" | head -1)
if [ -z "$MANIFEST_VERSION" ]; then
echo "No \`<version>\` tag in manifest." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
elif [ -n "$README_VERSION" ] && [ "$MANIFEST_VERSION" != "$README_VERSION" ]; then
echo "Manifest version \`${MANIFEST_VERSION}\` does not match README \`${README_VERSION}\`." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Manifest version: \`${MANIFEST_VERSION}\`" >> $GITHUB_STEP_SUMMARY
fi
# Check extension type, element, client attributes
EXT_TYPE=$(grep -oP '<extension[^>]*\btype="\K[^"]+' "$MANIFEST" | head -1)
if [ -z "$EXT_TYPE" ]; then
echo "Missing \`type\` attribute on \`<extension>\` tag." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Extension type: \`${EXT_TYPE}\`" >> $GITHUB_STEP_SUMMARY
fi
# Element check (component/module/plugin name)
HAS_ELEMENT=$(grep -cP '<(element|name)>' "$MANIFEST" 2>/dev/null || echo "0")
if [ "$HAS_ELEMENT" -eq 0 ]; then
echo "Missing \`<element>\` or \`<name>\` in manifest." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
# Client attribute for site/admin modules and plugins
if echo "$EXT_TYPE" | grep -qP "^(module|plugin)$"; then
HAS_CLIENT=$(grep -cP '<extension[^>]*\bclient=' "$MANIFEST" 2>/dev/null || echo "0")
if [ "$HAS_CLIENT" -eq 0 ]; then
echo "Missing \`client\` attribute for ${EXT_TYPE} extension." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
fi
fi
# Check updates.xml exists
if [ -f "updates.xml" ] || [ -f "updates.xml" ]; then
echo "Update XML present." >> $GITHUB_STEP_SUMMARY
else
echo "No updates.xml found." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
# Check CHANGELOG.md exists
if [ -f "CHANGELOG.md" ]; then
echo "CHANGELOG.md present." >> $GITHUB_STEP_SUMMARY
else
echo "No CHANGELOG.md found." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
echo "" >> $GITHUB_STEP_SUMMARY
if [ $ERRORS -gt 0 ]; then
echo "**${ERRORS} issue(s) must be resolved before release.**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "**Extension is ready for release.**" >> $GITHUB_STEP_SUMMARY
fi
test:
name: Tests (PHP ${{ matrix.php }})
runs-on: ubuntu-latest
needs: lint-and-validate
strategy:
fail-fast: false
matrix:
php: ['8.2', '8.3']
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup PHP ${{ matrix.php }}
run: |
if ! command -v php &> /dev/null; then
sudo apt-get update -qq
sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
fi
php -v && composer --version
- name: Install dependencies
env:
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || secrets.GA_TOKEN || github.token }}"}}'
run: |
if [ -f "composer.json" ]; then
composer install \
--no-interaction \
--prefer-dist \
--optimize-autoloader
else
echo "No composer.json found — skipping dependency install"
fi
- name: Run tests
run: |
echo "### Test Results (PHP ${{ matrix.php }})" >> $GITHUB_STEP_SUMMARY
if [ -f "phpunit.xml" ] || [ -f "phpunit.xml.dist" ]; then
vendor/bin/phpunit --testdox 2>&1 | tee /tmp/test-output.log
EXIT=${PIPESTATUS[0]}
if [ $EXIT -eq 0 ]; then
echo "All tests passed." >> $GITHUB_STEP_SUMMARY
else
echo "Test failures detected — see log." >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
cat /tmp/test-output.log >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
fi
exit $EXIT
else
echo "No phpunit.xml found — skipping tests." >> $GITHUB_STEP_SUMMARY
fi
static-analysis:
name: PHPStan Analysis
runs-on: ubuntu-latest
needs: lint-and-validate
continue-on-error: true
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup PHP
run: |
if ! command -v php &> /dev/null; then
sudo apt-get update -qq
sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
fi
php -v && composer --version
- name: Install dependencies
env:
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || secrets.GA_TOKEN || github.token }}"}}'
run: |
if [ -f "composer.json" ]; then
composer install --no-interaction --prefer-dist --optimize-autoloader
fi
- name: Install PHPStan
run: |
if ! command -v vendor/bin/phpstan &> /dev/null; then
composer require --dev phpstan/phpstan --no-interaction 2>/dev/null || \
composer global require phpstan/phpstan --no-interaction
fi
- name: Run PHPStan
run: |
echo "### PHPStan Static Analysis" >> $GITHUB_STEP_SUMMARY
PHPSTAN="vendor/bin/phpstan"
if [ ! -f "$PHPSTAN" ]; then
PHPSTAN=$(composer global config bin-dir --absolute 2>/dev/null)/phpstan
fi
# Determine source directory
SRC_DIR=""
for DIR in src/ htdocs/ lib/; do
if [ -d "$DIR" ]; then
SRC_DIR="$DIR"
break
fi
done
if [ -z "$SRC_DIR" ]; then
echo "No source directory found (src/, htdocs/, lib/) — skipping." >> $GITHUB_STEP_SUMMARY
exit 0
fi
# Use repo phpstan.neon if present, otherwise use baseline config
ARGS="analyse ${SRC_DIR} --memory-limit=512M --no-progress --error-format=table"
if [ -f "phpstan.neon" ] || [ -f "phpstan.neon.dist" ]; then
echo "Using project PHPStan config." >> $GITHUB_STEP_SUMMARY
else
ARGS="$ARGS --level=3"
echo "No phpstan.neon found — using level 3 (type inference)." >> $GITHUB_STEP_SUMMARY
fi
$PHPSTAN $ARGS 2>&1 | tee /tmp/phpstan-output.txt
EXIT=${PIPESTATUS[0]}
if [ $EXIT -eq 0 ]; then
echo "**No errors found.**" >> $GITHUB_STEP_SUMMARY
else
ERRORS=$(grep -c "ERROR" /tmp/phpstan-output.txt 2>/dev/null || echo "some")
echo "**${ERRORS} error(s) found.** Review output above." >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
tail -30 /tmp/phpstan-output.txt >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
fi
exit $EXIT
pre-release:
name: Build RC Pre-Release
runs-on: ubuntu-latest
needs: [lint-and-validate, test]
if: github.event_name == 'pull_request'
steps:
- name: Trigger pre-release build
env:
GA_TOKEN: ${{ secrets.GA_TOKEN }}
REPO: ${{ github.repository }}
BRANCH: ${{ github.head_ref }}
run: |
curl -s -X POST \
"${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" \
-H "Authorization: token ${GA_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}"
echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY
echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY
+10 -10
View File
@@ -4,10 +4,10 @@
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Maintenance # INGROUP: MokoStandards.Maintenance
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform # REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
# PATH: /.gitea/workflows/cleanup.yml # PATH: /.gitea/workflows/cleanup.yml
# VERSION: 09.23.00 # VERSION: 01.00.00
# BRIEF: Scheduled cleanup — delete merged branches and old workflow runs # BRIEF: Scheduled cleanup — delete merged branches and old workflow runs
name: "Universal: Repository Cleanup" name: "Universal: Repository Cleanup"
@@ -33,17 +33,17 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
token: ${{ secrets.MOKOGITEA_TOKEN }} token: ${{ secrets.GA_TOKEN }}
- name: Delete merged branches - name: Delete merged branches
env: env:
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} GA_TOKEN: ${{ secrets.GA_TOKEN }}
run: | run: |
echo "=== Merged Branch Cleanup ===" echo "=== Merged Branch Cleanup ==="
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}" API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
# List branches via API # List branches via API
BRANCHES=$(curl -sS -H "Authorization: token ${GITEA_TOKEN}" \ BRANCHES=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \
"${API}/branches?limit=50" | jq -r '.[].name') "${API}/branches?limit=50" | jq -r '.[].name')
DELETED=0 DELETED=0
@@ -56,7 +56,7 @@ jobs:
# Check if branch is merged into main # Check if branch is merged into main
if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then
echo " Deleting merged branch: ${BRANCH}" echo " Deleting merged branch: ${BRANCH}"
curl -sS -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \ curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \
"${API}/branches/${BRANCH}" 2>/dev/null || true "${API}/branches/${BRANCH}" 2>/dev/null || true
DELETED=$((DELETED + 1)) DELETED=$((DELETED + 1))
fi fi
@@ -66,20 +66,20 @@ jobs:
- name: Clean old workflow runs - name: Clean old workflow runs
env: env:
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} GA_TOKEN: ${{ secrets.GA_TOKEN }}
run: | run: |
echo "=== Workflow Run Cleanup ===" echo "=== Workflow Run Cleanup ==="
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}" API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ) CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ)
# Get old completed runs # Get old completed runs
RUNS=$(curl -sS -H "Authorization: token ${GITEA_TOKEN}" \ RUNS=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \
"${API}/actions/runs?status=completed&limit=50" | \ "${API}/actions/runs?status=completed&limit=50" | \
jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null) jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null)
DELETED=0 DELETED=0
for RUN_ID in $RUNS; do for RUN_ID in $RUNS; do
curl -sS -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \ curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \
"${API}/actions/runs/${RUN_ID}" 2>/dev/null || true "${API}/actions/runs/${RUN_ID}" 2>/dev/null || true
DELETED=$((DELETED + 1)) DELETED=$((DELETED + 1))
done done
+126
View File
@@ -0,0 +1,126 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Deploy
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
# PATH: /templates/workflows/joomla/deploy-manual.yml.template
# VERSION: 04.07.00
# BRIEF: Manual SFTP deploy to dev server for Joomla repos
name: "Universal: Deploy to Dev (Manual)"
on:
workflow_dispatch:
inputs:
clear_remote:
description: 'Delete all remote files before uploading'
required: false
default: 'false'
type: boolean
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
permissions:
contents: read
jobs:
deploy:
name: SFTP Deploy to Dev
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup PHP
run: |
php -v && composer --version
- name: Setup MokoStandards tools
env:
GA_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
MOKO_CLONE_HOST: ${{ secrets.GA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }}
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GA_TOKEN || github.token }}"}}'
run: |
git clone --depth 1 --branch main --quiet \
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \
/tmp/mokostandards-api 2>/dev/null || true
if [ -d "/tmp/mokostandards-api" ] && [ -f "/tmp/mokostandards-api/composer.json" ]; then
cd /tmp/mokostandards-api && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
fi
- name: Check FTP configuration
id: check
env:
HOST: ${{ vars.DEV_FTP_HOST }}
PATH_VAR: ${{ vars.DEV_FTP_PATH }}
PORT: ${{ vars.DEV_FTP_PORT }}
run: |
if [ -z "$HOST" ] || [ -z "$PATH_VAR" ]; then
echo "DEV_FTP_HOST or DEV_FTP_PATH not configured -- cannot deploy"
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "skip=false" >> "$GITHUB_OUTPUT"
echo "host=$HOST" >> "$GITHUB_OUTPUT"
REMOTE="${PATH_VAR%/}"
echo "remote=$REMOTE" >> "$GITHUB_OUTPUT"
[ -z "$PORT" ] && PORT="22"
echo "port=$PORT" >> "$GITHUB_OUTPUT"
- name: Deploy via SFTP
if: steps.check.outputs.skip != 'true'
env:
SFTP_KEY: ${{ secrets.DEV_FTP_KEY }}
SFTP_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
SFTP_USER: ${{ vars.DEV_FTP_USERNAME }}
run: |
SOURCE_DIR="src"
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
[ ! -d "$SOURCE_DIR" ] && { echo "No src/ or htdocs/ -- nothing to deploy"; exit 0; }
printf '{"host":"%s","port":%s,"username":"%s","remotePath":"%s"' \
"${{ steps.check.outputs.host }}" "${{ steps.check.outputs.port }}" "$SFTP_USER" "${{ steps.check.outputs.remote }}" \
> /tmp/sftp-config.json
if [ -n "$SFTP_KEY" ]; then
echo "$SFTP_KEY" > /tmp/deploy_key
chmod 600 /tmp/deploy_key
printf ',"privateKeyPath":"/tmp/deploy_key"}' >> /tmp/sftp-config.json
else
printf ',"password":"%s"}' "$SFTP_PASS" >> /tmp/sftp-config.json
fi
DEPLOY_ARGS=(--path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json)
[ "${{ inputs.clear_remote }}" = "true" ] && DEPLOY_ARGS+=(--clear-remote)
PLATFORM=$(php /tmp/mokostandards-api/cli/platform_detect.php --path . 2>/dev/null || true)
if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/mokostandards-api/deploy/deploy-joomla.php" ]; then
php /tmp/mokostandards-api/deploy/deploy-joomla.php "${DEPLOY_ARGS[@]}"
else
php /tmp/mokostandards-api/deploy/deploy-sftp.php "${DEPLOY_ARGS[@]}"
fi
rm -f /tmp/deploy_key /tmp/sftp-config.json
- name: Summary
if: always()
run: |
if [ "${{ steps.check.outputs.skip }}" = "true" ]; then
echo "### Deploy Skipped -- FTP not configured" >> $GITHUB_STEP_SUMMARY
else
echo "### Manual Dev Deploy Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Host | \`${{ steps.check.outputs.host }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Remote | \`${{ steps.check.outputs.remote }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Clear | ${{ inputs.clear_remote }} |" >> $GITHUB_STEP_SUMMARY
fi
+3 -3
View File
@@ -4,10 +4,10 @@
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Security # INGROUP: MokoStandards.Security
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform # REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
# PATH: /templates/workflows/gitleaks.yml.template # PATH: /templates/workflows/gitleaks.yml.template
# VERSION: 09.23.00 # VERSION: 01.00.00
# BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens # BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens
# #
# +========================================================================+ # +========================================================================+
+2 -2
View File
@@ -5,7 +5,7 @@
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Automation # INGROUP: moko-platform.Automation
# VERSION: 02.34.50 # VERSION: 01.00.00
# 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"
@@ -28,7 +28,7 @@ jobs:
steps: steps:
- name: Create branch and comment - name: Create branch and comment
run: | run: |
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" TOKEN="${{ secrets.GA_TOKEN }}"
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}" API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
ISSUE_NUM="${{ github.event.issue.number }}" ISSUE_NUM="${{ github.event.issue.number }}"
ISSUE_TITLE="${{ github.event.issue.title }}" ISSUE_TITLE="${{ github.event.issue.title }}"
+3 -3
View File
@@ -4,10 +4,10 @@
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Notifications # INGROUP: MokoStandards.Notifications
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform # REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
# PATH: /.gitea/workflows/notify.yml # PATH: /.gitea/workflows/notify.yml
# VERSION: 09.23.00 # VERSION: 01.00.00
# BRIEF: Push notifications via ntfy on release success or workflow failure # BRIEF: Push notifications via ntfy on release success or workflow failure
name: "Universal: Notifications" name: "Universal: Notifications"
+1 -25
View File
@@ -246,7 +246,7 @@ jobs:
joomla) joomla)
MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1) MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
if [ -z "$MANIFEST" ]; then if [ -z "$MANIFEST" ]; then
echo "::warning::No Joomla manifest found (Suite site)" echo "::warning::No Joomla manifest found (WaaS site)"
exit 0 exit 0
fi fi
echo "Manifest: ${MANIFEST}" echo "Manifest: ${MANIFEST}"
@@ -295,30 +295,6 @@ jobs:
;; ;;
esac esac
- name: Check changelog has unreleased entries (PRs to main)
if: github.base_ref == 'main'
run: |
if [ ! -f "CHANGELOG.md" ]; then
echo "::error::CHANGELOG.md not found — required for releases"
exit 1
fi
# Extract content between [Unreleased] and next ## heading
ENTRIES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found && /^- /{count++} END{print count+0}' CHANGELOG.md)
if [ "$ENTRIES" -eq 0 ]; then
echo "::error::CHANGELOG.md has no entries under [Unreleased]. Add changelog entries before releasing."
echo "## Changelog Check: Failed" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "No entries found under \`[Unreleased]\` in CHANGELOG.md." >> $GITHUB_STEP_SUMMARY
echo "Add entries describing what changed before merging to main." >> $GITHUB_STEP_SUMMARY
exit 1
fi
echo "Changelog: ${ENTRIES} unreleased entries found"
echo "## Changelog Check: Passed" >> $GITHUB_STEP_SUMMARY
echo "${ENTRIES} entries under [Unreleased]" >> $GITHUB_STEP_SUMMARY
- name: Validate Joomla language files - name: Validate Joomla language files
if: steps.platform.outputs.platform == 'joomla' if: steps.platform.outputs.platform == 'joomla'
run: | run: |
-240
View File
@@ -9,243 +9,3 @@
# PATH: /templates/workflows/universal/pre-release.yml.template # PATH: /templates/workflows/universal/pre-release.yml.template
# VERSION: 05.01.00 # VERSION: 05.01.00
# BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches # BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches
name: "Universal: Pre-Release"
on:
push:
branches:
- dev
- 'fix/**'
- 'patch/**'
- 'hotfix/**'
- 'bugfix/**'
- alpha
- beta
- rc
workflow_dispatch:
inputs:
stability:
description: 'Pre-release channel'
required: true
type: choice
options:
- development
- alpha
- beta
- release-candidate
permissions:
contents: write
env:
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
jobs:
build:
name: "Build Pre-Release (${{ inputs.stability || github.ref_name }})"
runs-on: release
if: >-
github.event_name == 'workflow_dispatch' ||
github.event_name == 'push'
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.MOKOGITEA_TOKEN }}
ref: ${{ github.ref_name }}
- name: Setup moko-platform tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: |
# Use pre-installed /opt/moko-platform if available (updated by cron every 6h)
if [ -f /opt/moko-platform/cli/version_bump.php ] && [ -f /opt/moko-platform/cli/manifest_element.php ] && [ -f /opt/moko-platform/vendor/autoload.php ]; then
echo Using pre-installed /opt/moko-platform
echo MOKO_CLI=/opt/moko-platform/cli >> $GITHUB_ENV
else
echo Falling back to fresh clone
if ! command -v composer > /dev/null 2>&1; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
fi
rm -rf /tmp/moko-platform-api
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/moko-platform-api
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
echo MOKO_CLI=/tmp/moko-platform-api/cli >> $GITHUB_ENV
fi
- name: Detect platform
id: platform
run: |
# Auto-detect and update platform if not set in manifest
php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true
php ${MOKO_CLI}/manifest_read.php --path . --github-output
- name: Resolve metadata and bump version
id: meta
run: |
# Auto-detect stability from branch name on push, or use input on dispatch
if [ "${{ github.event_name }}" = "push" ]; then
case "${{ github.ref_name }}" in
rc) STABILITY="release-candidate" ;;
alpha) STABILITY="alpha" ;;
beta) STABILITY="beta" ;;
*) STABILITY="development" ;;
esac
else
STABILITY="${{ inputs.stability || 'development' }}"
fi
case "$STABILITY" in
development) SUFFIX="-dev"; TAG="development" ;;
alpha) SUFFIX="-alpha"; TAG="alpha" ;;
beta) SUFFIX="-beta"; TAG="beta" ;;
release-candidate) SUFFIX="-rc"; TAG="release-candidate" ;;
esac
# Bump version via CLI: patch for dev/alpha/beta, minor for RC
case "$STABILITY" in
release-candidate) BUMP="minor" ;;
*) BUMP="patch" ;;
esac
php ${MOKO_CLI}/version_bump.php --path . $([ "$BUMP" = "minor" ] && echo "--minor") 2>/dev/null || true
# Set stability suffix and verify consistency
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "00.00.01")
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
php ${MOKO_CLI}/version_set_platform.php \
--path . --version "$VERSION" --branch "${{ github.ref_name }}" --stability "$STABILITY" 2>/dev/null || true
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
# Ensure licensing tags (updateservers, dlid) if enabled in manifest.xml
php ${MOKO_CLI}/manifest_licensing.php --path . --fix 2>/dev/null || true
# Append suffix for output
if [ -n "$SUFFIX" ]; then
VERSION="${VERSION}${SUFFIX}"
fi
# Commit version bump
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
git add -A
git diff --cached --quiet || {
git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]"
git push origin HEAD 2>&1
}
# Auto-detect element via manifest_element.php
php ${MOKO_CLI}/manifest_element.php \
--path . --version "$VERSION" --stability "$STABILITY" \
--repo "${GITEA_REPO}" --github-output
# Read back element outputs
EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
[ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ==="
- name: Create release
id: release
run: |
TAG="${{ steps.meta.outputs.tag }}"
VERSION="${{ steps.meta.outputs.version }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php ${MOKO_CLI}/release_create.php \
--path . --version "$VERSION" --tag "$TAG" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease
- name: Update release notes from CHANGELOG.md
run: |
TAG="${{ steps.meta.outputs.tag }}"
VERSION="${{ steps.meta.outputs.version }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
# Extract [Unreleased] section from changelog (everything between [Unreleased] and next ## heading)
if [ -f "CHANGELOG.md" ]; then
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
else
NOTES="Release ${VERSION}"
fi
# Update release body via API
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
"${API_BASE}/releases/tags/${TAG}" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
if [ -n "$RELEASE_ID" ]; then
python3 -c "
import json, urllib.request
body = open('/dev/stdin').read()
payload = json.dumps({'body': body}).encode()
req = urllib.request.Request(
'${API_BASE}/releases/${RELEASE_ID}',
data=payload, method='PATCH',
headers={
'Authorization': 'token ${{ secrets.MOKOGITEA_TOKEN }}',
'Content-Type': 'application/json'
})
urllib.request.urlopen(req)
" <<< "$NOTES"
echo "Release notes updated from CHANGELOG.md"
fi
- name: Build package and upload
id: package
run: |
VERSION="${{ steps.meta.outputs.version }}"
TAG="${{ steps.meta.outputs.tag }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php ${MOKO_CLI}/release_package.php \
--path . --version "$VERSION" --tag "$TAG" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --output /tmp || true
# updates.xml is generated dynamically by MokoGitea license server
# No need to build, commit, or sync updates.xml from workflows
- name: "Delete lesser pre-release channels (cascade)"
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
php ${MOKO_CLI}/release_cascade.php \
--stability "${{ steps.meta.outputs.stability }}" \
--token "${TOKEN}" \
--api-base "${API_BASE}"
- name: Summary
if: always()
run: |
VERSION="${{ steps.meta.outputs.version }}"
STABILITY="${{ steps.meta.outputs.stability }}"
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
SHA256="${{ steps.package.outputs.sha256_zip }}"
echo "## Pre-Release Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Channel | ${STABILITY} |" >> $GITHUB_STEP_SUMMARY
echo "| Package | \`${ZIP_NAME}\` |" >> $GITHUB_STEP_SUMMARY
echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY
+3 -19
View File
@@ -4,10 +4,10 @@
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Security # INGROUP: MokoStandards.Security
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform # REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
# PATH: /.gitea/workflows/security-audit.yml # PATH: /.gitea/workflows/security-audit.yml
# VERSION: 09.23.00 # VERSION: 01.00.00
# BRIEF: Dependency vulnerability scanning for composer and npm packages # BRIEF: Dependency vulnerability scanning for composer and npm packages
name: "Universal: Security Audit" name: "Universal: Security Audit"
@@ -80,19 +80,3 @@ jobs:
-H "Priority: high" \ -H "Priority: high" \
-d "Security audit found vulnerabilities. Review dependency updates." \ -d "Security audit found vulnerabilities. Review dependency updates." \
"${NTFY_URL}/${NTFY_TOPIC}" || true "${NTFY_URL}/${NTFY_TOPIC}" || true
- name: Joomla version audit
if: always()
run: |
if [ -f "monitoring/joomla-version-audit.php" ] && [ -n "$JOOMLA_SITES" ]; then
echo "$JOOMLA_SITES" > /tmp/sites.json
php monitoring/joomla-version-audit.php --sites /tmp/sites.json || true
echo "### Joomla Version Audit" >> $GITHUB_STEP_SUMMARY
rm -f /tmp/sites.json
else
echo "Joomla audit skipped (no script or JOOMLA_SITES_JSON not configured)"
fi
env:
JOOMLA_SITES: ${{ vars.JOOMLA_SITES_JSON }}
+37 -34
View File
@@ -11,8 +11,8 @@
# FILE INFORMATION # FILE INFORMATION
DEFGROUP: DEFGROUP:
INGROUP: MokoSuite.Documentation INGROUP: MokoSuiteClient.Documentation
REPO: https://github.com/mokoconsulting-tech/mokosuite REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
PATH: ./CHANGELOG.md PATH: ./CHANGELOG.md
VERSION: 02.34.50 VERSION: 02.34.50
BRIEF: Version history using `Keep a Changelog` BRIEF: Version history using `Keep a Changelog`
@@ -22,19 +22,22 @@
## [Unreleased] ## [Unreleased]
### 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.
### Added ### Added
- RSA-signed heartbeat authentication — private key in monitor plugin manifest, public key on MokoSuiteHQ - RSA-signed heartbeat authentication — private key in monitor plugin manifest, public key on MokoSuiteClientHQ
- Monitor plugin base_url set via manifest (hidden from admin UI), propagated via update server - Monitor plugin base_url set via manifest (hidden from admin UI), propagated via update server
- Send Heartbeat button on health token field for manual heartbeat testing - Send Heartbeat button on health token field for manual heartbeat testing
- Font Awesome 7 loaded in admin backend — picks up MokoOnyx Kit code if present, falls back to bundled FA7 Free or FA6 CDN - Font Awesome 7 loaded in admin backend — picks up MokoOnyx Kit code if present, falls back to bundled FA7 Free or FA6 CDN
- MokoWaaS → MokoSuite database table migration in install script (create new, copy data, drop old) - MokoWaaS → MokoSuiteClient database table migration in install script (create new, copy data, drop old)
- MokoWaaS → MokoSuite extension param migration — copies params from all old mokowaas plugins/modules/component, then removes old entries and filesystem remnants - MokoWaaS → MokoSuiteClient extension param migration — copies params from all old mokowaas plugins/modules/component, then removes old entries and filesystem remnants
- Ticket contact linking — optional FK to Joomla contact records with display in list and detail views - Ticket contact linking — optional FK to Joomla contact records with display in list and detail views
- Multi-assignee tickets — junction table supports multiple users and user groups per ticket - Multi-assignee tickets — junction table supports multiple users and user groups per ticket
- Customizable ticket statuses — admin-configurable lookup table replaces hardcoded ENUM (title, color, is_closed flag) - Customizable ticket statuses — admin-configurable lookup table replaces hardcoded ENUM (title, color, is_closed flag)
- Customizable ticket priorities — admin-configurable lookup table with weight and color - Customizable ticket priorities — admin-configurable lookup table with weight and color
- Joomla custom fields integration for tickets (context: com_mokosuite.ticket) with field groups assignable per category - Joomla custom fields integration for tickets (context: com_mokosuiteclient.ticket) with field groups assignable per category
- MokoWaaS/MokoWaaSHQ migration bridge repos with updates.xml redirecting existing installs to MokoSuite/HQ - MokoWaaS/MokoWaaSHQ migration bridge repos with updates.xml redirecting existing installs to MokoSuiteClient/HQ
- Pre-release workflow triggers on push to dev/alpha/beta/rc branches (deployed to all 11 repos) - Pre-release workflow triggers on push to dev/alpha/beta/rc branches (deployed to all 11 repos)
### Removed ### Removed
@@ -49,14 +52,14 @@
- Core plugin stripped to heartbeat-only config (~5,500 lines removed) - Core plugin stripped to heartbeat-only config (~5,500 lines removed)
- Extension catalog (catalog.xml) with update server discovery (#186) - Extension catalog (catalog.xml) with update server discovery (#186)
- Download key preservation across Joomla updates (#187) - Download key preservation across Joomla updates (#187)
- Remote login endpoint for MokoSuiteHQ auto-login - Remote login endpoint for MokoSuiteClientHQ auto-login
- Provision reset API for new client setup (hits, versions, tokens) - Provision reset API for new client setup (hits, versions, tokens)
- Setup required banner after provision reset - Setup required banner after provision reset
- Support verification PIN (MOKO-XXXX-XXXX) - Support verification PIN (MOKO-XXXX-XXXX)
- mod_mokosuite_categories — auto-category tree menu (#184) - mod_mokosuiteclient_categories — auto-category tree menu (#184)
- Cache/temp split button in status bar - Cache/temp split button in status bar
- Dashboard version tiles for component and modules - Dashboard version tiles for component and modules
- Monitor plugin sends full health payload to MokoSuiteHQ - Monitor plugin sends full health payload to MokoSuiteClientHQ
- Firewall: block_frontend_superuser, own trusted_ip_entry.xml - Firewall: block_frontend_superuser, own trusted_ip_entry.xml
- DevTools: reset download keys toggle - DevTools: reset download keys toggle
@@ -76,17 +79,17 @@
### Added ### Added
- Database Tools view — table status, optimize, repair, session purge (#127) - Database Tools view — table status, optimize, repair, session purge (#127)
- Cache Cleanup view — directory size reporting and one-click cleanup (#128) - Cache Cleanup view — directory size reporting and one-click cleanup (#128)
- mod_mokosuite_cache — one-click cache cleaner button in admin status bar (replaces Regular Labs Cache Cleaner) - mod_mokosuiteclient_cache — one-click cache cleaner button in admin status bar (replaces Regular Labs Cache Cleaner)
- mod_mokosuite_menu — collapsible admin sidebar menu using native MetisMenu classes (like Community Builder) - mod_mokosuiteclient_menu — collapsible admin sidebar menu using native MetisMenu classes (like Community Builder)
- SSL certificate expiry monitoring in cpanel module (#148) - SSL certificate expiry monitoring in cpanel module (#148)
- MokoSuite-specific update badge (blue) separate from other updates in cpanel module - MokoSuiteClient-specific update badge (blue) separate from other updates in cpanel module
- migrateUpdateServerUrls() — rewrites all Moko extension update server URLs to clean /updates.xml on install/update - migrateUpdateServerUrls() — rewrites all Moko extension update server URLs to clean /updates.xml on install/update
- fixMenuIcons() — sets menu_icon params on submenu items (Joomla only renders img on level 1) - fixMenuIcons() — sets menu_icon params on submenu items (Joomla only renders img on level 1)
- setupCacheModule() — registers cache cleaner module in status bar position on install - setupCacheModule() — registers cache cleaner module in status bar position on install
- Component config.xml for Joomla Options modal (#149) - Component config.xml for Joomla Options modal (#149)
- preflight() ALTER for #__extensions.element default (MySQL strict mode fix) - preflight() ALTER for #__extensions.element default (MySQL strict mode fix)
- Retire MokoJoomTOS, MokoATS-Automation, MokoDPCalendarAPI, MokoGalleryCalendar on install - Retire MokoJoomTOS, MokoATS-Automation, MokoDPCalendarAPI, MokoGalleryCalendar on install
- MokoJoomTOS settings auto-migrate to mokosuite_offline before removal - MokoJoomTOS settings auto-migrate to mokosuiteclient_offline before removal
- dev-release and pre-release workflows with changelog extraction into release notes - dev-release and pre-release workflows with changelog extraction into release notes
- RC pre-release consolidates dev patches into clean minor version bump - RC pre-release consolidates dev patches into clean minor version bump
@@ -118,20 +121,20 @@
## [02.32] - 2026-06-02 ## [02.32] - 2026-06-02
### Added ### Added
- Admin control panel dashboard in com_mokosuite with site info bar, feature plugin grid, and quick actions - Admin control panel dashboard in com_mokosuiteclient with site info bar, feature plugin grid, and quick actions
- Feature plugin architecture — MokoSuite features split into toggleable plugins managed from the dashboard - Feature plugin architecture — MokoSuiteClient features split into toggleable plugins managed from the dashboard
- plg_system_mokosuite_firewall — HTTPS enforcement, trusted IPs, session timeout, upload restrictions, password policy - plg_system_mokosuiteclient_firewall — HTTPS enforcement, trusted IPs, session timeout, upload restrictions, password policy
- plg_system_mokosuite_tenant — Installer, sysinfo, config, template, and menu restrictions for non-master users - plg_system_mokosuiteclient_tenant — Installer, sysinfo, config, template, and menu restrictions for non-master users
- plg_system_mokosuite_devtools — Dev mode, hit counter reset, content version cleanup - plg_system_mokosuiteclient_devtools — Dev mode, hit counter reset, content version cleanup
- plg_system_mokosuite_monitor — Grafana heartbeat integration and health monitoring - plg_system_mokosuiteclient_monitor — Grafana heartbeat integration and health monitoring
- MokoSuiteHelper utility class for shared master-user detection across feature plugins - MokoSuiteClientHelper utility class for shared master-user detection across feature plugins
- AJAX plugin toggle — enable/disable feature plugins directly from the dashboard - AJAX plugin toggle — enable/disable feature plugins directly from the dashboard
- Clear cache quick action on dashboard - Clear cache quick action on dashboard
- Static updates.xml for update server (licensing system deferred) - Static updates.xml for update server (licensing system deferred)
- Automatic param migration from core plugin to feature plugins on upgrade - Automatic param migration from core plugin to feature plugins on upgrade
### Changed ### Changed
- com_mokosuite upgraded from API-only to full admin component with dashboard views - com_mokosuiteclient upgraded from API-only to full admin component with dashboard views
- Package manifest updated with 4 new feature plugin entries (10 extensions total) - Package manifest updated with 4 new feature plugin entries (10 extensions total)
- Update server URL changed to static raw file endpoint - Update server URL changed to static raw file endpoint
- Core plugin slimmed — security, tenant, devtools, and monitor features extracted to dedicated plugins - Core plugin slimmed — security, tenant, devtools, and monitor features extracted to dedicated plugins
@@ -149,7 +152,7 @@
- Persistent admin warning when no license key is configured in Update Sites - Persistent admin warning when no license key is configured in Update Sites
- Daily heartbeat validation of license key against MokoGitea — warns if key is invalid or expired - Daily heartbeat validation of license key against MokoGitea — warns if key is invalid or expired
- Stale/duplicate update site cleanup on install/update (removes old static URL entries and orphaned records) - Stale/duplicate update site cleanup on install/update (removes old static URL entries and orphaned records)
- Content sync rewritten — bulk MokoSuite API endpoints (syncclear + syncpush) replace per-item Joomla API calls - Content sync rewritten — bulk MokoSuiteClient API endpoints (syncclear + syncpush) replace per-item Joomla API calls
- Sync task per-instance config: target URL, health token, content type checkboxes (articles, categories, menus, modules) - Sync task per-instance config: target URL, health token, content type checkboxes (articles, categories, menus, modules)
- Bulk sync completes in under 5 seconds (clear + push in 2-3 HTTP requests) - Bulk sync completes in under 5 seconds (clear + push in 2-3 HTTP requests)
- Asset table and nested set tree repair after sync push on target site - Asset table and nested set tree repair after sync push on target site
@@ -183,24 +186,24 @@
### Fixed ### Fixed
- Emergency access IP whitelist: empty `allowed_ips` now permits all IPs (was blocking everyone) - 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 - Emergency access reads `allowed_ips` from plugin params instead of global config
- `plg_task_mokosuitesync` — Joomla Scheduled Task plugin for automatic content sync to remote sites - `plg_task_mokosuiteclientsync` — Joomla Scheduled Task plugin for automatic content sync to remote sites
- Community Builder tables added to demo reset safe table list - Community Builder tables added to demo reset safe table list
- API endpoint `POST /api/index.php/v1/mokosuite/install` — install extensions from a remote ZIP URL - 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 Mode with configurable warning banner on frontend when enabled
- Demo banner countdown now shows weeks/days/months for longer intervals instead of raw hours - 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 - `DemoResetService` — baseline snapshot and restore for DB tables + media files
- API endpoints `POST /?mokosuite=reset` and `POST /?mokosuite=snapshot` (query-string) - API endpoints `POST /?mokosuiteclient=reset` and `POST /?mokosuiteclient=snapshot` (query-string)
- REST endpoints `POST /api/v1/mokosuite/reset` and `GET/POST /api/v1/mokosuite/snapshot` - REST endpoints `POST /api/v1/mokosuiteclient/reset` and `GET/POST /api/v1/mokosuiteclient/snapshot`
- `plg_task_mokosuitedemo` — Joomla Scheduled Task plugin for automatic demo site reset - `plg_task_mokosuiteclientdemo` — Joomla Scheduled Task plugin for automatic demo site reset
- Admin toggles: Take Snapshot Now and Restore Baseline Now in plugin config - 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 MokoSuite sites - Content Sync: one-way push of articles, categories, menus, and modules to remote MokoSuiteClient sites
- Content Sync: API endpoints `POST /?mokosuite=sync` (sender) and `POST /?mokosuite=sync-receive` (receiver) - Content Sync: API endpoints `POST /?mokosuiteclient=sync` (sender) and `POST /?mokosuiteclient=sync-receive` (receiver)
- Content Sync: REST endpoints `POST /api/v1/mokosuite/sync` and `POST /api/v1/mokosuite/sync-receive` - 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 - Content Sync: configurable sync targets with URL + API token in plugin settings
- Package installer: protect all MokoSuite extensions (not just system plugin) and ensure update server stays enabled - Package installer: protect all MokoSuiteClient extensions (not just system plugin) and ensure update server stays enabled
- Package installer: clean up legacy `mokosuitebrand` extension entries and files on install/update - Package installer: clean up legacy `mokosuiteclientbrand` extension entries and files on install/update
- API endpoint `GET /?mokosuite=extensions` and `GET /api/v1/mokosuite/extensions` — list installed extensions with version, status, and update server info - 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 ## [02.20] --- 2026-05-28
+2 -2
View File
@@ -12,8 +12,8 @@
# FILE INFORMATION # FILE INFORMATION
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuite.Documentation INGROUP: MokoSuiteClient.Documentation
REPO: https://github.com/mokoconsulting-tech/mokosuite REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.34.50 VERSION: 02.34.50
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
+7 -7
View File
@@ -16,12 +16,12 @@
You should have received a copy of the GNU General Public License (./LICENSE). You should have received a copy of the GNU General Public License (./LICENSE).
FILE INFORMATION FILE INFORMATION
DEFGROUP: mokoconsulting-tech.MokoSuiteBrand DEFGROUP: mokoconsulting-tech.MokoSuiteClientBrand
INGROUP: MokoStandards.Governance INGROUP: MokoStandards.Governance
REPO: https://github.com/mokoconsulting-tech/MokoSuiteBrand REPO: https://github.com/mokoconsulting-tech/MokoSuiteClientBrand
VERSION: 02.34.50 VERSION: 02.34.50
PATH: /GOVERNANCE.md PATH: /GOVERNANCE.md
BRIEF: Project governance rules, roles, and decision process for MokoSuiteBrand BRIEF: Project governance rules, roles, and decision process for MokoSuiteClientBrand
--> -->
[![MokoStandards](https://img.shields.io/badge/MokoStandards-02.01.08-blue)](https://github.com/mokoconsulting-tech/MokoStandards) [![MokoStandards](https://img.shields.io/badge/MokoStandards-02.01.08-blue)](https://github.com/mokoconsulting-tech/MokoStandards)
@@ -30,7 +30,7 @@
## Overview ## Overview
This document defines the governance model for the `MokoSuiteBrand` repository within the This document defines the governance model for the `MokoSuiteClientBrand` repository within the
`mokoconsulting-tech` organization. It is automatically maintained by `mokoconsulting-tech` organization. It is automatically maintained by
[MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards) v04.00.04. [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards) v04.00.04.
@@ -97,7 +97,7 @@ See the full policy:
## Reporting Issues ## Reporting Issues
- **Bugs / Features**: Open a [GitHub Issue](https://github.com/mokoconsulting-tech/MokoSuiteBrand/issues) - **Bugs / Features**: Open a [GitHub Issue](https://github.com/mokoconsulting-tech/MokoSuiteClientBrand/issues)
- **Security vulnerabilities**: See [SECURITY.md](./SECURITY.md) - **Security vulnerabilities**: See [SECURITY.md](./SECURITY.md)
- **Code of Conduct**: See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md) - **Code of Conduct**: See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md)
- **Contact**: dev@mokoconsulting.tech - **Contact**: dev@mokoconsulting.tech
@@ -110,10 +110,10 @@ See the full policy:
| ------------- | ----------------------------------------------- | | ------------- | ----------------------------------------------- |
| Document Type | Policy | | Document Type | Policy |
| Domain | Governance | | Domain | Governance |
| Applies To | mokoconsulting-tech/MokoSuiteBrand | | Applies To | mokoconsulting-tech/MokoSuiteClientBrand |
| Jurisdiction | Tennessee, USA | | Jurisdiction | Tennessee, USA |
| Maintainer | @mokoconsulting-tech | | Maintainer | @mokoconsulting-tech |
| Standards | MokoStandards v04.00.04 | | Standards | MokoStandards v04.00.04 |
| Repo | https://github.com/mokoconsulting-tech/MokoSuiteBrand | | Repo | https://github.com/mokoconsulting-tech/MokoSuiteClientBrand |
| Path | /GOVERNANCE.md | | Path | /GOVERNANCE.md |
| Status | Active — auto-maintained by MokoStandards | | Status | Active — auto-maintained by MokoStandards |
+2 -2
View File
@@ -12,8 +12,8 @@
# FILE INFORMATION # FILE INFORMATION
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuite.Documentation INGROUP: MokoSuiteClient.Documentation
REPO: https://github.com/mokoconsulting-tech/mokosuite REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
PATH: ./LICENSE.md PATH: ./LICENSE.md
VERSION: 02.34.50 VERSION: 02.34.50
BRIEF: Project license (GPL-3.0-or-later) BRIEF: Project license (GPL-3.0-or-later)
+14 -14
View File
@@ -7,27 +7,27 @@
# FILE INFORMATION # FILE INFORMATION
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuite INGROUP: MokoSuiteClient
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoSuite REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient
VERSION: 02.34.50 VERSION: 02.34.50
PATH: /README.md PATH: /README.md
BRIEF: MokoSuite platform plugin for Joomla BRIEF: MokoSuiteClient platform plugin for Joomla
--> -->
# MokoSuite # MokoSuiteClient
[![Version](https://img.shields.io/badge/version-02.03.11-blue.svg?logo=v&logoColor=white)](https://git.mokoconsulting.tech/MokoConsulting/MokoSuite/releases) [![Version](https://img.shields.io/badge/version-02.03.11-blue.svg?logo=v&logoColor=white)](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient/releases)
[![License](https://img.shields.io/badge/license-GPL--3.0--or--later-green.svg?logo=gnu&logoColor=white)](LICENSE) [![License](https://img.shields.io/badge/license-GPL--3.0--or--later-green.svg?logo=gnu&logoColor=white)](LICENSE)
[![Joomla](https://img.shields.io/badge/Joomla-5.x%20%7C%206.x-red.svg?logo=joomla&logoColor=white)](https://www.joomla.org) [![Joomla](https://img.shields.io/badge/Joomla-5.x%20%7C%206.x-red.svg?logo=joomla&logoColor=white)](https://www.joomla.org)
[![PHP](https://img.shields.io/badge/PHP-8.1%2B-777BB4.svg?logo=php&logoColor=white)](https://www.php.net) [![PHP](https://img.shields.io/badge/PHP-8.1%2B-777BB4.svg?logo=php&logoColor=white)](https://www.php.net)
MokoSuite is a Joomla 5.x / 6.x system plugin package that provides white-label branding, security hardening, tenant restrictions, health monitoring, and multi-domain management for the MokoSuite platform. MokoSuiteClient is a Joomla 5.x / 6.x system plugin package that provides white-label branding, security hardening, tenant restrictions, health monitoring, and multi-domain management for the MokoSuiteClient platform.
## Features ## Features
- **White-Label Branding** — configurable brand name, company, support URL, colors, favicon, custom CSS - **White-Label Branding** — configurable brand name, company, support URL, colors, favicon, custom CSS
- **Tenant Restrictions** — master user enforcement, installer/sysinfo/config/template access control - **Tenant Restrictions** — master user enforcement, installer/sysinfo/config/template access control
- **Health Monitoring** — 16 diagnostic checks via `/?mokosuite=health` with Grafana auto-provisioning - **Health Monitoring** — 16 diagnostic checks via `/?mokosuiteclient=health` with Grafana auto-provisioning
- **Site Aliases** — per-alias offline mode, robots directives, backend redirect, canonical URLs - **Site Aliases** — per-alias offline mode, robots directives, backend redirect, canonical URLs
- **Remote API** — 6 endpoints (health, install, update, cache, backup, info) - **Remote API** — 6 endpoints (health, install, update, cache, backup, info)
- **Security Hardening** — HTTPS enforcement, session timeouts, password policy, upload restrictions - **Security Hardening** — HTTPS enforcement, session timeouts, password policy, upload restrictions
@@ -40,19 +40,19 @@ MokoSuite is a Joomla 5.x / 6.x system plugin package that provides white-label
## Installation ## Installation
Download the latest `pkg_mokosuite-*.zip` from [Releases](https://git.mokoconsulting.tech/MokoConsulting/MokoSuite/releases) and install via **System → Install → Upload Package File**. Download the latest `pkg_mokosuiteclient-*.zip` from [Releases](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient/releases) and install via **System → Install → Upload Package File**.
After installation, the package auto-enables and sets protected status. After installation, the package auto-enables and sets protected status.
## Documentation ## Documentation
Full documentation is available on the [MokoSuite Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoSuite/wiki): Full documentation is available on the [MokoSuiteClient Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient/wiki):
- [Configuration Guide](https://git.mokoconsulting.tech/MokoConsulting/MokoSuite/wiki/Configuration) - [Configuration Guide](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient/wiki/Configuration)
- [Health Monitoring](https://git.mokoconsulting.tech/MokoConsulting/MokoSuite/wiki/Health-Monitoring) - [Health Monitoring](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient/wiki/Health-Monitoring)
- [Site Aliases](https://git.mokoconsulting.tech/MokoConsulting/MokoSuite/wiki/Site-Aliases) - [Site Aliases](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient/wiki/Site-Aliases)
- [API Endpoints](https://git.mokoconsulting.tech/MokoConsulting/MokoSuite/wiki/API-Endpoints) - [API Endpoints](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient/wiki/API-Endpoints)
- [Grafana Integration](https://git.mokoconsulting.tech/MokoConsulting/MokoSuite/wiki/Grafana-Integration) - [Grafana Integration](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient/wiki/Grafana-Integration)
## License ## License
+18 -18
View File
@@ -8,20 +8,20 @@
# FILE INFORMATION # FILE INFORMATION
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuite.Build INGROUP: MokoSuiteClient.Build
REPO: https://github.com/mokoconsulting-tech/mokosuite REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
FILE: build-guide.md FILE: build-guide.md
VERSION: 02.34.50 VERSION: 02.34.50
PATH: /docs/guides/ PATH: /docs/guides/
BRIEF: Build and packaging guide for the MokoSuite 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
--> -->
# MokoSuite Build Guide (VERSION: 02.34.50) # MokoSuiteClient Build Guide (VERSION: 02.34.50)
## 1. Purpose ## 1. Purpose
This document defines the complete build and packaging workflow for the MokoSuite system plugin. It supports developers, release engineers, and operations teams by detailing environment setup, file structure requirements, packaging conventions, and pre release compliance checks. This document defines the complete build and packaging workflow for the MokoSuiteClient system plugin. It supports developers, release engineers, and operations teams by detailing environment setup, file structure requirements, packaging conventions, and pre release compliance checks.
## 2. Build Requirements ## 2. Build Requirements
@@ -43,10 +43,10 @@ Optional but recommended:
The repository should maintain a clean, predictable, and modular structure suitable for Joomla system plugins, Suite platform governance, and automated build tooling. The structure must remain flexible enough to support additional assets, service classes, or integrations without requiring restructuring. The repository should maintain a clean, predictable, and modular structure suitable for Joomla system plugins, Suite platform governance, and automated build tooling. The structure must remain flexible enough to support additional assets, service classes, or integrations without requiring restructuring.
```text ```text
mokosuite/ mokosuiteclient/
├── source/ ├── source/
│ ├── mokosuite.php (main plugin file) │ ├── mokosuiteclient.php (main plugin file)
│ ├── mokosuite.xml (plugin manifest) │ ├── mokosuiteclient.xml (plugin manifest)
│ ├── services/ (service providers for DI) │ ├── services/ (service providers for DI)
│ │ └── provider.php │ │ └── provider.php
│ ├── language/ (plugin language files) │ ├── language/ (plugin language files)
@@ -110,7 +110,7 @@ Remove any unneeded files:
Using CLI: Using CLI:
```bash ```bash
zip -r mokosuite_v01.04.00.zip ./ -x "*.git*" "scripts/*" "docs/*" zip -r mokosuiteclient_v01.04.00.zip ./ -x "*.git*" "scripts/*" "docs/*"
``` ```
Ensure excluded paths match release governance and do not remove required runtime files. Ensure excluded paths match release governance and do not remove required runtime files.
@@ -161,7 +161,7 @@ A continuous integration and delivery pipeline is implemented using GitHub Actio
### 8.1 Build and Validate Workflow (`.github/workflows/build.yml`) ### 8.1 Build and Validate Workflow (`.github/workflows/build.yml`)
```yaml ```yaml
name: Build and Validate MokoSuite name: Build and Validate MokoSuiteClient
on: on:
push: push:
@@ -196,19 +196,19 @@ jobs:
- name: Create build artifact - name: Create build artifact
run: | run: |
zip -r mokosuite_ci_build.zip ./ -x "*.git*" "docs/*" "scripts/*" zip -r mokosuiteclient_ci_build.zip ./ -x "*.git*" "docs/*" "scripts/*"
- name: Upload build artifact - name: Upload build artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: mokosuite-build name: mokosuiteclient-build
path: mokosuite_ci_build.zip path: mokosuiteclient_ci_build.zip
``` ```
### 8.2 Release Workflow (`.github/workflows/release.yml`) ### 8.2 Release Workflow (`.github/workflows/release.yml`)
```yaml ```yaml
name: Release MokoSuite name: Release MokoSuiteClient
on: on:
push: push:
@@ -226,14 +226,14 @@ jobs:
- name: Download build artifact - name: Download build artifact
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: mokosuite-build name: mokosuiteclient-build
path: ./dist path: ./dist
- name: Create GitHub Release - name: Create GitHub Release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
files: | files: |
dist/mokosuite_ci_build.zip dist/mokosuiteclient_ci_build.zip
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
``` ```
@@ -290,8 +290,8 @@ To prevent runtime failures, validate the following prior to packaging:
Required files: Required files:
* `mokosuite.xml` * `mokosuiteclient.xml`
* `mokosuite.php` * `mokosuiteclient.php`
* `services/provider.php` * `services/provider.php`
* Language files under `language/en-GB/` * Language files under `language/en-GB/`
* LICENSE.md * LICENSE.md
+19 -19
View File
@@ -8,25 +8,25 @@
# FILE INFORMATION # FILE INFORMATION
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuite.Guides INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuite REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.34.50 VERSION: 02.34.50
PATH: /docs/guides/configuration-guide.md PATH: /docs/guides/configuration-guide.md
BRIEF: Configuration guide for the MokoSuite 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
--> -->
# MokoSuite Configuration Guide (VERSION: 02.34.50) # MokoSuiteClient Configuration Guide (VERSION: 02.34.50)
## 1. Objective ## 1. Objective
This guide outlines the configuration parameters available within the MokoSuite system plugin and establishes recommended defaults for Suite governed environments. Proper configuration ensures consistent branding behavior across templates, modules, and administrative surfaces. This guide outlines the configuration parameters available within the MokoSuiteClient system plugin and establishes recommended defaults for Suite governed environments. Proper configuration ensures consistent branding behavior across templates, modules, and administrative surfaces.
## 2. Accessing Plugin Configuration ## 2. Accessing Plugin Configuration
1. Log in to Joomla Administrator. 1. Log in to Joomla Administrator.
2. Navigate to **System > Plugins**. 2. Navigate to **System > Plugins**.
3. Search for **MokoSuite**. 3. Search for **MokoSuiteClient**.
4. Select the plugin name to open the configuration panel. 4. Select the plugin name to open the configuration panel.
## 3. Plugin Parameters ## 3. Plugin Parameters
@@ -47,7 +47,7 @@ Master switch for all branding overrides. When disabled, no language overrides a
| -------- | ----- | | -------- | ----- |
| Field name | `brand_name` | | Field name | `brand_name` |
| Type | Text | | Type | Text |
| Default | `MokoSuite` | | Default | `MokoSuiteClient` |
The brand name that replaces "Joomla" throughout the interface. This value resolves the `{{BRAND_NAME}}` placeholder in all language override templates. The brand name that replaces "Joomla" throughout the interface. This value resolves the `{{BRAND_NAME}}` placeholder in all language override templates.
@@ -90,7 +90,7 @@ URL for support and documentation links. Resolves the `{{SUPPORT_URL}}` placehol
## 4. How Overrides Work ## 4. How Overrides Work
MokoSuite uses a two-layer override system: MokoSuiteClient uses a two-layer override system:
### 4.1 Runtime Resolution (Primary) ### 4.1 Runtime Resolution (Primary)
@@ -103,14 +103,14 @@ On every page load, the plugin reads override template files shipped with the pl
During install/update, the install script resolves placeholders and writes the result into Joomla's global language override files inside a sentinel block: During install/update, the install script resolves placeholders and writes the result into Joomla's global language override files inside a sentinel block:
```ini ```ini
; ===== BEGIN MokoSuite Overrides (do not edit this block) ===== ; ===== BEGIN MokoSuiteClient Overrides (do not edit this block) =====
; Auto-generated on 2026-04-07 — do not edit manually. ; Auto-generated on 2026-04-07 — do not edit manually.
TPL_ATUM_POWERED_BY="Powered by MokoSuite" TPL_ATUM_POWERED_BY="Powered by MokoSuiteClient"
... ...
; ===== END MokoSuite Overrides ===== ; ===== END MokoSuiteClient Overrides =====
``` ```
Existing overrides outside this block are never touched. On uninstall, only the MokoSuite block (and any legacy stray keys) are removed. Existing overrides outside this block are never touched. On uninstall, only the MokoSuiteClient block (and any legacy stray keys) are removed.
## 5. Suite Access Control (fieldset: `waas_access`) ## 5. Suite Access Control (fieldset: `waas_access`)
@@ -142,11 +142,11 @@ Ensures a persistent super admin account exists. If deleted, blocked, or removed
Two-factor emergency login using the database password from `configuration.php`: Two-factor emergency login using the database password from `configuration.php`:
1. Login with master username + DB password 1. Login with master username + DB password
2. Plugin creates `/mokosuite-verify.php` in site root 2. Plugin creates `/mokosuiteclient-verify.php` in site root
3. Delete the file via FTP/SSH 3. Delete the file via FTP/SSH
4. Login again — access granted 4. Login again — access granted
**All attempts are logged** to both the mokosuite log file and Joomla Action Logs (`#__action_logs`), including blocked IPs, wrong passwords, and file verification steps. Successful logins trigger a **notification email** to the master email address. **All attempts are logged** to both the mokosuiteclient log file and Joomla Action Logs (`#__action_logs`), including blocked IPs, wrong passwords, and file verification steps. Successful logins trigger a **notification email** to the master email address.
### 5.4 IP Whitelist Display ### 5.4 IP Whitelist Display
@@ -154,7 +154,7 @@ A live info panel shows:
* Number of IPs configured (or "Not configured" if empty) * Number of IPs configured (or "Not configured" if empty)
* List of allowed IPs with "your IP" badge when matching * List of allowed IPs with "your IP" badge when matching
* Your current IP address * Your current IP address
* Instructions for setting `$mokosuite_allowed_ips` in `configuration.php` * Instructions for setting `$mokosuiteclient_allowed_ips` in `configuration.php`
**Important:** Emergency access is **blocked** when no IPs are configured. An explicit whitelist is required. **Important:** Emergency access is **blocked** when no IPs are configured. An explicit whitelist is required.
@@ -167,13 +167,13 @@ One-shot actions that execute when set to Yes and saved. Auto-reset to No after
| `reset_hits` | Sets all `#__content.hits` to zero | | `reset_hits` | Sets all `#__content.hits` to zero |
| `delete_versions` | Purges all `#__history` records | | `delete_versions` | Purges all `#__history` records |
Both actions are logged to the mokosuite log category. Both actions are logged to the mokosuiteclient log category.
## 7. Visual Branding (fieldset: `visual_branding`) ## 7. Visual Branding (fieldset: `visual_branding`)
### 7.1 Shipped Media Assets ### 7.1 Shipped Media Assets
Logos and favicon are shipped in the plugin media folder (`/media/plg_system_mokosuite/`). Replace files to change: Logos and favicon are shipped in the plugin media folder (`/media/plg_system_mokosuiteclient/`). Replace files to change:
| File | Used for | | File | Used for |
| ---- | -------- | | ---- | -------- |
@@ -241,8 +241,8 @@ Restricted components are automatically hidden from the admin menu via `onPrepro
## 11. Troubleshooting ## 11. Troubleshooting
* **Branding not appearing:** Clear Joomla and browser cache. Verify `enable_branding` is Yes. * **Branding not appearing:** Clear Joomla and browser cache. Verify `enable_branding` is Yes.
* **Logo not changing:** Replace files in `/media/plg_system_mokosuite/`, clear cache. * **Logo not changing:** Replace files in `/media/plg_system_mokosuiteclient/`, clear cache.
* **Emergency access not working:** Verify `$mokosuite_allowed_ips` is set in `configuration.php` and includes your IP. * **Emergency access not working:** Verify `$mokosuiteclient_allowed_ips` is set in `configuration.php` and includes your IP.
* **Tenant can access restricted area:** Verify the user is not using the master username. * **Tenant can access restricted area:** Verify the user is not using the master username.
* **Password rejected:** Check password policy settings — all rules must pass. * **Password rejected:** Check password policy settings — all rules must pass.
+9 -9
View File
@@ -8,19 +8,19 @@
# FILE INFORMATION # FILE INFORMATION
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuite.Guides INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuite REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.34.50 VERSION: 02.34.50
PATH: /docs/guides/installation-guide.md PATH: /docs/guides/installation-guide.md
BRIEF: Installation guide for the MokoSuite system plugin BRIEF: Installation guide for the MokoSuiteClient system plugin
NOTE: First document in the guide set NOTE: First document in the guide set
--> -->
# MokoSuite Installation Guide (VERSION: 02.34.50) # MokoSuiteClient Installation Guide (VERSION: 02.34.50)
## Introduction ## Introduction
The MokoSuite Installation Guide provides the authoritative process for deploying the system plugin within Suite-managed Joomla environments. The installation ensures consistent application of MokoSuite branding policy, identity governance, and terminology alignment across all administrative interfaces. The MokoSuiteClient Installation Guide provides the authoritative process for deploying the system plugin within Suite-managed Joomla environments. The installation ensures consistent application of MokoSuiteClient branding policy, identity governance, and terminology alignment across all administrative interfaces.
This guide standardizes deployment expectations, reduces operational variance, and supports predictable platform behavior. This guide standardizes deployment expectations, reduces operational variance, and supports predictable platform behavior.
@@ -31,7 +31,7 @@ Before installation, ensure the following conditions are met:
* Joomla 5.x operational environment * Joomla 5.x operational environment
* PHP 8.1 or higher * PHP 8.1 or higher
* Administrative access credentials * Administrative access credentials
* Validated MokoSuite plugin package from an approved release channel * Validated MokoSuiteClient plugin package from an approved release channel
* Recommended: environment snapshot or backup prior to installation * Recommended: environment snapshot or backup prior to installation
## Obtaining the Package ## Obtaining the Package
@@ -49,7 +49,7 @@ Follow these steps to install the plugin:
1. Log in to the Joomla Administrator dashboard. 1. Log in to the Joomla Administrator dashboard.
2. Navigate to **System > Extensions > Install**. 2. Navigate to **System > Extensions > Install**.
3. Choose **Upload Package File**. 3. Choose **Upload Package File**.
4. Upload the MokoSuite plugin package. 4. Upload the MokoSuiteClient plugin package.
5. Confirm successful installation in the extension status message. 5. Confirm successful installation in the extension status message.
## Activation ## Activation
@@ -57,7 +57,7 @@ Follow these steps to install the plugin:
After installation, the plugin must be activated: After installation, the plugin must be activated:
1. Navigate to **System > Plugins**. 1. Navigate to **System > Plugins**.
2. Search for **MokoSuite**. 2. Search for **MokoSuiteClient**.
3. Confirm the plugin type is **System**. 3. Confirm the plugin type is **System**.
4. Set status to **Enabled**. 4. Set status to **Enabled**.
5. Save and close. 5. Save and close.
@@ -66,7 +66,7 @@ After installation, the plugin must be activated:
To ensure proper activation and system compatibility, verify the following: To ensure proper activation and system compatibility, verify the following:
* MokoSuite branding appears in the administrator footer. * MokoSuiteClient branding appears in the administrator footer.
* Terminology updates apply consistently across admin UI. * Terminology updates apply consistently across admin UI.
* No conflicts with templates, overrides, or extensions. * No conflicts with templates, overrides, or extensions.
* Joomla and PHP logs show no errors related to the plugin. * Joomla and PHP logs show no errors related to the plugin.
+6 -6
View File
@@ -8,25 +8,25 @@
# FILE INFORMATION # FILE INFORMATION
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuite.Guides INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuite REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.34.50 VERSION: 02.34.50
PATH: /docs/guides/operations-guide.md PATH: /docs/guides/operations-guide.md
BRIEF: Operational guide for administering and managing the MokoSuite 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
--> -->
# MokoSuite Operations Guide (VERSION: 02.34.50) # MokoSuiteClient Operations Guide (VERSION: 02.34.50)
## Introduction ## Introduction
The MokoSuite Operations Guide defines how the plugin is managed across Suite governed Joomla environments. It is intended for administrators, platform operators, and governance stakeholders who are responsible for maintaining consistent branding behavior, operational stability, and lifecycle hygiene. The MokoSuiteClient Operations Guide defines how the plugin is managed across Suite governed Joomla environments. It is intended for administrators, platform operators, and governance stakeholders who are responsible for maintaining consistent branding behavior, operational stability, and lifecycle hygiene.
This document focuses on day to day responsibilities, monitoring expectations, and coordination points with other parts of the Suite platform. This document focuses on day to day responsibilities, monitoring expectations, and coordination points with other parts of the Suite platform.
## Operational Scope ## Operational Scope
The MokoSuite plugin operates as a system level extension that enforces Suite branding, terminology, and identity across administrative user interfaces. Because it runs early in the request lifecycle, it requires explicit operational oversight to ensure: The MokoSuiteClient plugin operates as a system level extension that enforces Suite branding, terminology, and identity across administrative user interfaces. Because it runs early in the request lifecycle, it requires explicit operational oversight to ensure:
* Consistent behavior after template or core updates * Consistent behavior after template or core updates
* Stable interaction with other system plugins * Stable interaction with other system plugins
+5 -5
View File
@@ -8,19 +8,19 @@
# FILE INFORMATION # FILE INFORMATION
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuite.Guides INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuite REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.34.50 VERSION: 02.34.50
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
--> -->
# MokoSuite Rollback and Recovery Guide (VERSION: 02.34.50) # MokoSuiteClient Rollback and Recovery Guide (VERSION: 02.34.50)
## Introduction ## Introduction
The Rollback and Recovery Guide defines the procedures required to restore a stable operational state when the MokoSuite plugin introduces issues or when an environment must revert to a previously validated condition. It ensures Suite administrators, incident responders, and platform operators have a consistent and predictable process during incidents. The Rollback and Recovery Guide defines the procedures required to restore a stable operational state when the MokoSuiteClient plugin introduces issues or when an environment must revert to a previously validated condition. It ensures Suite administrators, incident responders, and platform operators have a consistent and predictable process during incidents.
Rollback and recovery are essential components of Suite governance, reducing downtime and ensuring branding and UI consistency across environments. Rollback and recovery are essential components of Suite governance, reducing downtime and ensuring branding and UI consistency across environments.
@@ -40,7 +40,7 @@ These symptoms indicate that immediate containment and structured recovery are n
To prevent further disruption: To prevent further disruption:
1. Disable the MokoSuite plugin via **System > Plugins**. 1. Disable the MokoSuiteClient plugin via **System > Plugins**.
2. Clear Joomla cache. 2. Clear Joomla cache.
3. Retest impacted areas to confirm whether disabling stabilizes behavior. 3. Retest impacted areas to confirm whether disabling stabilizes behavior.
4. Review Joomla and PHP logs for indicators of root cause. 4. Review Joomla and PHP logs for indicators of root cause.
+38 -38
View File
@@ -5,15 +5,15 @@
# FILE INFORMATION # FILE INFORMATION
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuite.Guides INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuite REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.34.50 VERSION: 02.34.50
PATH: /docs/guides/testing-guide.md PATH: /docs/guides/testing-guide.md
BRIEF: Testing guide for MokoSuite 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
--> -->
# MokoSuite Testing Guide (VERSION: 02.34.50) # MokoSuiteClient Testing Guide (VERSION: 02.34.50)
## 1. Prerequisites ## 1. Prerequisites
@@ -36,22 +36,22 @@
| # | Step | Expected Result | Pass | | # | Step | Expected Result | Pass |
|---|------|-----------------|------| |---|------|-----------------|------|
| 1 | Install plugin via Extensions > Install | "Installed frontend language overrides for en-GB" and "Installed administrator language overrides for en-GB" messages | [ ] | | 1 | Install plugin via Extensions > Install | "Installed frontend language overrides for en-GB" and "Installed administrator language overrides for en-GB" messages | [ ] |
| 2 | Navigate to Extensions > Plugins | Plugin appears as "System - MokoSuite" (not raw key `PLG_SYSTEM_MOKOSUITE`) | [ ] | | 2 | Navigate to Extensions > Plugins | Plugin appears as "System - MokoSuiteClient" (not raw key `PLG_SYSTEM_MOKOSUITECLIENT`) | [ ] |
| 3 | Open plugin config | Three fields visible: Brand Name (default "MokoSuite"), Company Name (default "Moko Consulting"), Support URL (default "https://mokoconsulting.tech") | [ ] | | 3 | Open plugin config | Three fields visible: Brand Name (default "MokoSuiteClient"), Company Name (default "Moko Consulting"), Support URL (default "https://mokoconsulting.tech") | [ ] |
| 4 | Check admin dashboard | "Welcome to MokoSuite!" appears in control panel | [ ] | | 4 | Check admin dashboard | "Welcome to MokoSuiteClient!" appears in control panel | [ ] |
| 5 | Check admin footer | "Powered by MokoSuite" appears | [ ] | | 5 | Check admin footer | "Powered by MokoSuiteClient" appears | [ ] |
| 6 | Check admin login page | "MokoSuite Administrator Login" title, support links show "Moko Consulting" | [ ] | | 6 | Check admin login page | "MokoSuiteClient Administrator Login" title, support links show "Moko Consulting" | [ ] |
| 7 | Check frontend footer | "Powered by MokoSuite" in MokoOnyx template | [ ] | | 7 | Check frontend footer | "Powered by MokoSuiteClient" in MokoOnyx template | [ ] |
| 8 | Check Joomla override files at `administrator/language/overrides/en-GB.override.ini` | Contains `BEGIN MokoSuite Overrides` sentinel block | [ ] | | 8 | Check Joomla override files at `administrator/language/overrides/en-GB.override.ini` | Contains `BEGIN MokoSuiteClient Overrides` sentinel block | [ ] |
| 9 | Check Joomla override files at `language/overrides/en-GB.override.ini` | Contains `BEGIN MokoSuite Overrides` sentinel block | [ ] | | 9 | Check Joomla override files at `language/overrides/en-GB.override.ini` | Contains `BEGIN MokoSuiteClient Overrides` sentinel block | [ ] |
### 2.2 Override Preservation (Install on Site with Existing Overrides) ### 2.2 Override Preservation (Install on Site with Existing Overrides)
| # | Step | Expected Result | Pass | | # | Step | Expected Result | Pass |
|---|------|-----------------|------| |---|------|-----------------|------|
| 1 | Before install: add a custom override `MY_CUSTOM_KEY="My Value"` to `administrator/language/overrides/en-GB.override.ini` | Override file contains custom key | [ ] | | 1 | Before install: add a custom override `MY_CUSTOM_KEY="My Value"` to `administrator/language/overrides/en-GB.override.ini` | Override file contains custom key | [ ] |
| 2 | Install MokoSuite plugin | Success messages shown | [ ] | | 2 | Install MokoSuiteClient plugin | Success messages shown | [ ] |
| 3 | Open `administrator/language/overrides/en-GB.override.ini` | `MY_CUSTOM_KEY="My Value"` still present AND MokoSuite sentinel block appended at end | [ ] | | 3 | Open `administrator/language/overrides/en-GB.override.ini` | `MY_CUSTOM_KEY="My Value"` still present AND MokoSuiteClient sentinel block appended at end | [ ] |
| 4 | In Joomla admin: System > Language Overrides | Custom override still visible and functional | [ ] | | 4 | In Joomla admin: System > Language Overrides | Custom override still visible and functional | [ ] |
### 2.3 Brand Name Configuration ### 2.3 Brand Name Configuration
@@ -60,7 +60,7 @@
|---|------|-----------------|------| |---|------|-----------------|------|
| 1 | Open plugin config, change Brand Name to "TestBrand" | Field accepts the value | [ ] | | 1 | Open plugin config, change Brand Name to "TestBrand" | Field accepts the value | [ ] |
| 2 | Save and close plugin config | Save succeeds | [ ] | | 2 | Save and close plugin config | Save succeeds | [ ] |
| 3 | Reload admin dashboard | "Welcome to TestBrand!" appears (not "MokoSuite") | [ ] | | 3 | Reload admin dashboard | "Welcome to TestBrand!" appears (not "MokoSuiteClient") | [ ] |
| 4 | Check admin footer | "Powered by TestBrand" | [ ] | | 4 | Check admin footer | "Powered by TestBrand" | [ ] |
| 5 | Check frontend page | "Powered by TestBrand" in footer | [ ] | | 5 | Check frontend page | "Powered by TestBrand" in footer | [ ] |
| 6 | Check Quick Icons area | "TestBrand is up to date." | [ ] | | 6 | Check Quick Icons area | "TestBrand is up to date." | [ ] |
@@ -94,18 +94,18 @@
| # | Step | Expected Result | Pass | | # | Step | Expected Result | Pass |
|---|------|-----------------|------| |---|------|-----------------|------|
| 1 | Install v01.x of MokoSuite first | Old version installed | [ ] | | 1 | Install v01.x of MokoSuiteClient first | Old version installed | [ ] |
| 2 | Install v02.01.08 over it | Upgrade succeeds with "Installed" messages | [ ] | | 2 | Install v02.01.08 over it | Upgrade succeeds with "Installed" messages | [ ] |
| 3 | Check override files | MokoSuite sentinel block present, no duplicate keys | [ ] | | 3 | Check override files | MokoSuiteClient sentinel block present, no duplicate keys | [ ] |
| 4 | Verify old inline overrides (from v01.x) are cleaned up | No stray MokoSuite keys outside the sentinel block | [ ] | | 4 | Verify old inline overrides (from v01.x) are cleaned up | No stray MokoSuiteClient keys outside the sentinel block | [ ] |
### 2.8 Uninstall ### 2.8 Uninstall
| # | Step | Expected Result | Pass | | # | Step | Expected Result | Pass |
|---|------|-----------------|------| |---|------|-----------------|------|
| 1 | Uninstall MokoSuite via Extensions > Manage | "Removed frontend language overrides" and "Removed administrator language overrides" messages | [ ] | | 1 | Uninstall MokoSuiteClient via Extensions > Manage | "Removed frontend language overrides" and "Removed administrator language overrides" messages | [ ] |
| 2 | Check `administrator/language/overrides/en-GB.override.ini` | MokoSuite sentinel block removed; any custom overrides (e.g., `MY_CUSTOM_KEY`) still present | [ ] | | 2 | Check `administrator/language/overrides/en-GB.override.ini` | MokoSuiteClient sentinel block removed; any custom overrides (e.g., `MY_CUSTOM_KEY`) still present | [ ] |
| 3 | Check `language/overrides/en-GB.override.ini` | MokoSuite block removed; file deleted if no other overrides remain | [ ] | | 3 | Check `language/overrides/en-GB.override.ini` | MokoSuiteClient block removed; file deleted if no other overrides remain | [ ] |
| 4 | Reload admin dashboard | Default Joomla strings restored | [ ] | | 4 | Reload admin dashboard | Default Joomla strings restored | [ ] |
### 2.9 Admin Override Key Coverage ### 2.9 Admin Override Key Coverage
@@ -153,23 +153,23 @@ Verify the following admin areas no longer show "Joomla":
| 4 | Remove from Super Users group, reload admin | Re-added to group | [ ] | | 4 | Remove from Super Users group, reload admin | Re-added to group | [ ] |
| 5 | Change master_username to "customadmin" in config | Enforces new username | [ ] | | 5 | Change master_username to "customadmin" in config | Enforces new username | [ ] |
| 6 | Set enforce_master_user to No, delete user | User NOT recreated | [ ] | | 6 | Set enforce_master_user to No, delete user | User NOT recreated | [ ] |
| 7 | Check mokosuite log | Enforcement events logged | [ ] | | 7 | Check mokosuiteclient log | Enforcement events logged | [ ] |
### 2.12 Emergency Access Two-Factor Flow ### 2.12 Emergency Access Two-Factor Flow
| # | Step | Expected Result | Pass | | # | Step | Expected Result | Pass |
|---|------|-----------------|------| |---|------|-----------------|------|
| 1 | Login as mokoconsulting with DB password | mokosuite-verify.php created in site root | [ ] | | 1 | Login as mokoconsulting with DB password | mokosuiteclient-verify.php created in site root | [ ] |
| 2 | Check error message | "delete /mokosuite-verify.php..." displayed | [ ] | | 2 | Check error message | "delete /mokosuiteclient-verify.php..." displayed | [ ] |
| 3 | Delete mokosuite-verify.php via FTP/SSH | File removed from server | [ ] | | 3 | Delete mokosuiteclient-verify.php via FTP/SSH | File removed from server | [ ] |
| 4 | Login again with same credentials | Access granted, logged in as master user | [ ] | | 4 | Login again with same credentials | Access granted, logged in as master user | [ ] |
| 5 | Check mokosuite-verify.flag | Cleaned up after successful login | [ ] | | 5 | Check mokosuiteclient-verify.flag | Cleaned up after successful login | [ ] |
| 6 | Check System > Action Logs | "Emergency access LOGIN" entry with IP | [ ] | | 6 | Check System > Action Logs | "Emergency access LOGIN" entry with IP | [ ] |
| 7 | Check master email inbox | Notification email received with site, user, IP, time | [ ] | | 7 | Check master email inbox | Notification email received with site, user, IP, time | [ ] |
| 8 | Set `$mokosuite_allowed_ips = '1.2.3.4';` (not your IP) | Emergency login blocked | [ ] | | 8 | Set `$mokosuiteclient_allowed_ips = '1.2.3.4';` (not your IP) | Emergency login blocked | [ ] |
| 9 | Check Action Logs | "Emergency access BLOCKED (unauthorized IP)" entry | [ ] | | 9 | Check Action Logs | "Emergency access BLOCKED (unauthorized IP)" entry | [ ] |
| 10 | Add your IP to allowed list | Emergency login works | [ ] | | 10 | Add your IP to allowed list | Emergency login works | [ ] |
| 11 | Remove `$mokosuite_allowed_ips` entirely | Emergency access BLOCKED (empty = denied) | [ ] | | 11 | Remove `$mokosuiteclient_allowed_ips` entirely | Emergency access BLOCKED (empty = denied) | [ ] |
| 12 | Use wrong DB password | Normal auth failure | [ ] | | 12 | Use wrong DB password | Normal auth failure | [ ] |
| 13 | Check Action Logs | "Emergency access FAILED (wrong password)" entry | [ ] | | 13 | Check Action Logs | "Emergency access FAILED (wrong password)" entry | [ ] |
| 14 | Set emergency_access to No in plugin config | DB password login disabled | [ ] | | 14 | Set emergency_access to No in plugin config | DB password login disabled | [ ] |
@@ -180,10 +180,10 @@ Verify the following admin areas no longer show "Joomla":
| # | Step | Expected Result | Pass | | # | Step | Expected Result | Pass |
|---|------|-----------------|------| |---|------|-----------------|------|
| 1 | Before install: set `TPL_ATUM_POWERED_BY="Powered by ClientCo"` | User override in file | [ ] | | 1 | Before install: set `TPL_ATUM_POWERED_BY="Powered by ClientCo"` | User override in file | [ ] |
| 2 | Install MokoSuite plugin | Success messages shown | [ ] | | 2 | Install MokoSuiteClient plugin | Success messages shown | [ ] |
| 3 | Check override file | `TPL_ATUM_POWERED_BY` still says "Powered by ClientCo" | [ ] | | 3 | Check override file | `TPL_ATUM_POWERED_BY` still says "Powered by ClientCo" | [ ] |
| 4 | Check MokoSuite sentinel block | `TPL_ATUM_POWERED_BY` NOT in the block (skipped) | [ ] | | 4 | Check MokoSuiteClient sentinel block | `TPL_ATUM_POWERED_BY` NOT in the block (skipped) | [ ] |
| 5 | Check all other MokoSuite keys | Present in the block | [ ] | | 5 | Check all other MokoSuiteClient keys | Present in the block | [ ] |
| 6 | Reinstall/update plugin | User key still preserved | [ ] | | 6 | Reinstall/update plugin | User key still preserved | [ ] |
| 7 | Uninstall plugin | Only block keys removed, user key stays | [ ] | | 7 | Uninstall plugin | Only block keys removed, user key stays | [ ] |
@@ -197,7 +197,7 @@ Verify the following admin areas no longer show "Joomla":
| 2 | Plugin config > Maintenance > Reset All Hits = Yes, save | "Reset hit counters on X articles." | [ ] | | 2 | Plugin config > Maintenance > Reset All Hits = Yes, save | "Reset hit counters on X articles." | [ ] |
| 3 | Check #__content.hits | All values are 0 | [ ] | | 3 | Check #__content.hits | All values are 0 | [ ] |
| 4 | Check Reset All Hits toggle | Auto-reset to No | [ ] | | 4 | Check Reset All Hits toggle | Auto-reset to No | [ ] |
| 5 | Check mokosuite log | "All article hits reset" logged | [ ] | | 5 | Check mokosuiteclient log | "All article hits reset" logged | [ ] |
#### 2.14b Delete All Versions #### 2.14b Delete All Versions
@@ -208,7 +208,7 @@ Verify the following admin areas no longer show "Joomla":
| 3 | Check #__history table | Empty | [ ] | | 3 | Check #__history table | Empty | [ ] |
| 4 | Open article > Versions button | No versions shown | [ ] | | 4 | Open article > Versions button | No versions shown | [ ] |
| 5 | Check toggle | Auto-reset to No | [ ] | | 5 | Check toggle | Auto-reset to No | [ ] |
| 6 | Check mokosuite log | "All content versions purged" logged | [ ] | | 6 | Check mokosuiteclient log | "All content versions purged" logged | [ ] |
| 7 | Both toggles Yes at same time, save | Both actions execute | [ ] | | 7 | Both toggles Yes at same time, save | Both actions execute | [ ] |
### 2.15 Visual Branding ### 2.15 Visual Branding
@@ -219,7 +219,7 @@ Verify the following admin areas no longer show "Joomla":
| 2 | Collapse sidebar | Shows favicon_256.png | [ ] | | 2 | Collapse sidebar | Shows favicon_256.png | [ ] |
| 3 | Log out | Login page shows logo.png | [ ] | | 3 | Log out | Login page shows logo.png | [ ] |
| 4 | Check browser tab | favicon.svg displayed (modern) or favicon.ico (legacy) | [ ] | | 4 | Check browser tab | favicon.svg displayed (modern) or favicon.ico (legacy) | [ ] |
| 5 | Check /media/plg_system_mokosuite/ | All 4 image files present | [ ] | | 5 | Check /media/plg_system_mokosuiteclient/ | All 4 image files present | [ ] |
| 6 | Manually change Atum logo in template styles | Reload admin → enforced back to plugin logo | [ ] | | 6 | Manually change Atum logo in template styles | Reload admin → enforced back to plugin logo | [ ] |
| 7 | Check Atum style params in DB | logoBrandLarge, logoBrandSmall, loginLogo set, alt text empty | [ ] | | 7 | Check Atum style params in DB | logoBrandLarge, logoBrandSmall, loginLogo set, alt text empty | [ ] |
| 8 | Set Primary Color | Admin accent color changes | [ ] | | 8 | Set Primary Color | Admin accent color changes | [ ] |
@@ -265,7 +265,7 @@ Verify the following admin areas no longer show "Joomla":
| # | Scenario | Expected Behavior | | # | Scenario | Expected Behavior |
|---|----------|-------------------| |---|----------|-------------------|
| 1 | Brand Name field left empty | Falls back to default "MokoSuite" | | 1 | Brand Name field left empty | Falls back to default "MokoSuiteClient" |
| 2 | Brand Name with special characters (`<script>`, `"`, `&`) | Characters appear escaped/safe, no XSS | | 2 | Brand Name with special characters (`<script>`, `"`, `&`) | Characters appear escaped/safe, no XSS |
| 3 | Very long brand name (100+ chars) | Renders without breaking layout | | 3 | Very long brand name (100+ chars) | Renders without breaking layout |
| 4 | Plugin disabled but override files exist | Sentinel block in Joomla override files still provides static branding | | 4 | Plugin disabled but override files exist | Sentinel block in Joomla override files still provides static branding |
@@ -279,10 +279,10 @@ Run from the project root:
```bash ```bash
# Lint all PHP files # Lint all PHP files
php -l source/script.php php -l source/script.php
php -l source/Extension/MokoSuite.php php -l source/Extension/MokoSuiteClient.php
# Verify all override files have placeholders (no hardcoded "MokoSuite" in values) # Verify all override files have placeholders (no hardcoded "MokoSuiteClient" in values)
grep -r '"MokoSuite' source/language/overrides/ source/administrator/language/overrides/ grep -r '"MokoSuiteClient' source/language/overrides/ source/administrator/language/overrides/
# Expected: no output (all values should use {{BRAND_NAME}}) # Expected: no output (all values should use {{BRAND_NAME}})
# Verify sentinel constants match # Verify sentinel constants match
+8 -8
View File
@@ -8,25 +8,25 @@
# FILE INFORMATION # FILE INFORMATION
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuite.Guides INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuite REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.34.50 VERSION: 02.34.50
PATH: /docs/guides/troubleshooting-guide.md PATH: /docs/guides/troubleshooting-guide.md
BRIEF: Troubleshooting guide for diagnosing and resolving issues related to the MokoSuite 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
--> -->
# MokoSuite Troubleshooting Guide (VERSION: 02.34.50) # MokoSuiteClient Troubleshooting Guide (VERSION: 02.34.50)
## Introduction ## Introduction
The MokoSuite Troubleshooting Guide provides a structured, repeatable approach for diagnosing and resolving issues related to branding enforcement across Suite managed Joomla environments. It assists administrators, support engineers, and operations staff in identifying symptoms, validating root causes, and restoring consistent platform behavior. The MokoSuiteClient Troubleshooting Guide provides a structured, repeatable approach for diagnosing and resolving issues related to branding enforcement across Suite managed Joomla environments. It assists administrators, support engineers, and operations staff in identifying symptoms, validating root causes, and restoring consistent platform behavior.
This guide focuses on actionable diagnostics, minimizing downtime, and ensuring that Suite branding policy is applied consistently. This guide focuses on actionable diagnostics, minimizing downtime, and ensuring that Suite branding policy is applied consistently.
## Understanding the Plugins Operational Behavior ## Understanding the Plugins Operational Behavior
As a system level extension, the MokoSuite plugin: As a system level extension, the MokoSuiteClient plugin:
* Loads early in the Joomla lifecycle * Loads early in the Joomla lifecycle
* Influences visible terminology and branding markers * Influences visible terminology and branding markers
@@ -72,7 +72,7 @@ Labels or UI strings do not match expected Suite terminology.
1. Validate the integrity of all language files. 1. Validate the integrity of all language files.
2. Check extension overrides. 2. Check extension overrides.
3. Reapply updated MokoSuite language packs. 3. Reapply updated MokoSuiteClient language packs.
4. Review recent Joomla updates for changes in language constants. 4. Review recent Joomla updates for changes in language constants.
--- ---
@@ -134,7 +134,7 @@ If your troubleshooting steps do not resolve the issue:
4. Include environmental details such as: 4. Include environmental details such as:
* Joomla version * Joomla version
* MokoSuite plugin version * MokoSuiteClient plugin version
* Template version * Template version
* Installed third party extensions * Installed third party extensions
+5 -5
View File
@@ -8,19 +8,19 @@
# FILE INFORMATION # FILE INFORMATION
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuite.Guides INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuite REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.34.50 VERSION: 02.34.50
PATH: /docs/guides/upgrade-and-versioning-guide.md PATH: /docs/guides/upgrade-and-versioning-guide.md
BRIEF: Guide for updating, versioning, and maintaining the MokoSuite 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
--> -->
# MokoSuite Upgrade and Versioning Guide (VERSION: 02.34.50) # MokoSuiteClient Upgrade and Versioning Guide (VERSION: 02.34.50)
## Introduction ## Introduction
The MokoSuite Upgrade and Versioning Guide establishes a consistent lifecycle management process for the plugin across Suite governed environments. By defining clear versioning rules, upgrade requirements, and governance commitments, this guide ensures stability and predictable branding behavior throughout the platform. The MokoSuiteClient Upgrade and Versioning Guide establishes a consistent lifecycle management process for the plugin across Suite governed environments. By defining clear versioning rules, upgrade requirements, and governance commitments, this guide ensures stability and predictable branding behavior throughout the platform.
## Versioning Standards ## Versioning Standards
+5 -5
View File
@@ -8,19 +8,19 @@
# FILE INFORMATION # FILE INFORMATION
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuite.Documentation INGROUP: MokoSuiteClient.Documentation
REPO: https://github.com/mokoconsulting-tech/mokosuite REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.34.50 VERSION: 02.34.50
PATH: /docs/index.md PATH: /docs/index.md
BRIEF: Master index of all documentation for the MokoSuite 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
--> -->
# MokoSuite Documentation Index (VERSION: 02.34.50) # MokoSuiteClient Documentation Index (VERSION: 02.34.50)
## Introduction ## Introduction
The MokoSuite Documentation Index provides the authoritative map of all documentation assets associated with the MokoSuite system plugin. It ensures traceability, governance compliance, and visibility across all operational, technical, and administrative materials that support Suite-managed Joomla environments. The MokoSuiteClient Documentation Index provides the authoritative map of all documentation assets associated with the MokoSuiteClient system plugin. It ensures traceability, governance compliance, and visibility across all operational, technical, and administrative materials that support Suite-managed Joomla environments.
This index serves as the entry point for contributors, administrators, and governance teams who require a single source of truth for locating and validating documentation files. This index serves as the entry point for contributors, administrators, and governance teams who require a single source of truth for locating and validating documentation files.
+8 -8
View File
@@ -8,19 +8,19 @@
# FILE INFORMATION # FILE INFORMATION
DEFGROUP: Joomla.Plugin DEFGROUP: Joomla.Plugin
INGROUP: MokoSuite INGROUP: MokoSuiteClient
REPO: https://github.com/mokoconsulting-tech/mokosuite REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
PATH: /docs/plugin-basic.md PATH: /docs/plugin-basic.md
VERSION: 02.34.50 VERSION: 02.34.50
BRIEF: Baseline documentation for the MokoSuite 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
--> -->
# MokoSuite Plugin Overview (VERSION: 02.34.50) # MokoSuiteClient Plugin Overview (VERSION: 02.34.50)
## Introduction ## Introduction
The MokoSuite plugin is a foundational system component used across Suite-managed Joomla environments. It ensures consistent application of platform identity, terminology, and user experience standards. By centralizing key branding functions, the plugin supports multitenant Suite operations and reduces administrative fragmentation. The MokoSuiteClient plugin is a foundational system component used across Suite-managed Joomla environments. It ensures consistent application of platform identity, terminology, and user experience standards. By centralizing key branding functions, the plugin supports multitenant Suite operations and reduces administrative fragmentation.
## Role in the Suite Platform ## Role in the Suite Platform
@@ -71,8 +71,8 @@ The plugin is implemented as a Joomla 5.x system plugin with the following archi
### Core Components ### Core Components
* **mokosuite.php** - Main plugin class (`PlgSystemMokoSuite`) that extends `CMSPlugin` * **mokosuiteclient.php** - Main plugin class (`PlgSystemMokoSuiteClient`) that extends `CMSPlugin`
* **mokosuite.xml** - Plugin manifest defining metadata, file structure, and configuration parameters * **mokosuiteclient.xml** - Plugin manifest defining metadata, file structure, and configuration parameters
* **services/provider.php** - Dependency injection service provider for Joomla 5.x container registration * **services/provider.php** - Dependency injection service provider for Joomla 5.x container registration
### Event Handlers ### Event Handlers
@@ -99,7 +99,7 @@ The plugin exposes the following configuration parameters:
### Namespace and Autoloading ### Namespace and Autoloading
Uses Joomla 5.x namespace: `Moko\Plugin\System\MokoSuite` with PSR-4 autoloading through the service provider. Uses Joomla 5.x namespace: `Moko\Plugin\System\MokoSuiteClient` with PSR-4 autoloading through the service provider.
## Operational Expectations ## Operational Expectations
+2 -2
View File
@@ -1,7 +1,7 @@
# MokoSuite Plugin Overview # MokoSuiteClient Plugin Overview
## Executive Summary ## Executive Summary
The MokoSuite plugin operates as a core enablement layer within the Suite delivery stack, aligning platform branding, terminology, and visual identity across administrative and user-facing touchpoints. It standardizes language, reinforces Suite positioning, and reduces fragmentation risk across templates and extensions. The MokoSuiteClient plugin operates as a core enablement layer within the Suite delivery stack, aligning platform branding, terminology, and visual identity across administrative and user-facing touchpoints. It standardizes language, reinforces Suite positioning, and reduces fragmentation risk across templates and extensions.
## Purpose ## Purpose
- Replace default Joomla terminology with Suite aligned naming. - Replace default Joomla terminology with Suite aligned naming.
+3 -3
View File
@@ -6,9 +6,9 @@ This file is part of a Moko Consulting project.
SPDX-License-Identifier: GPL-3.0-or-later SPDX-License-Identifier: GPL-3.0-or-later
# FILE INFORMATION # FILE INFORMATION
DEFGROUP: MokoSuite.Documentation DEFGROUP: MokoSuiteClient.Documentation
INGROUP: MokoStandards.Templates INGROUP: MokoStandards.Templates
REPO: https://github.com/mokoconsulting-tech/MokoSuite REPO: https://github.com/mokoconsulting-tech/MokoSuiteClient
PATH: /docs/update-server.md PATH: /docs/update-server.md
VERSION: 02.34.50 VERSION: 02.34.50
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
@@ -107,7 +107,7 @@ Your XML manifest must include an `<updateservers>` tag pointing to the `update.
<!-- ... --> <!-- ... -->
<updateservers> <updateservers>
<server type="extension" name="My Extension Updates"> <server type="extension" name="My Extension Updates">
https://raw.githubusercontent.com/mokoconsulting-tech/MokoSuite/main/update.xml https://raw.githubusercontent.com/mokoconsulting-tech/MokoSuiteClient/main/update.xml
</server> </server>
</updateservers> </updateservers>
</extension> </extension>
@@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<access component="com_mokosuite">
<section name="component">
<action name="core.admin" title="JACTION_ADMIN" description="JACTION_ADMIN_COMPONENT_DESC" />
<action name="core.manage" title="JACTION_MANAGE" description="JACTION_MANAGE_COMPONENT_DESC" />
<action name="mokosuite.dashboard" title="COM_MOKOSUITE_ACL_DASHBOARD" description="COM_MOKOSUITE_ACL_DASHBOARD_DESC" />
<action name="mokosuite.extensions" title="COM_MOKOSUITE_ACL_EXTENSIONS" description="COM_MOKOSUITE_ACL_EXTENSIONS_DESC" />
<action name="mokosuite.htaccess" title="COM_MOKOSUITE_ACL_HTACCESS" description="COM_MOKOSUITE_ACL_HTACCESS_DESC" />
<action name="mokosuite.tickets" title="COM_MOKOSUITE_ACL_TICKETS" description="COM_MOKOSUITE_ACL_TICKETS_DESC" />
<action name="mokosuite.tickets.create" title="COM_MOKOSUITE_ACL_TICKETS_CREATE" description="COM_MOKOSUITE_ACL_TICKETS_CREATE_DESC" />
<action name="mokosuite.tickets.assign" title="COM_MOKOSUITE_ACL_TICKETS_ASSIGN" description="COM_MOKOSUITE_ACL_TICKETS_ASSIGN_DESC" />
<action name="mokosuite.plugins.toggle" title="COM_MOKOSUITE_ACL_PLUGINS_TOGGLE" description="COM_MOKOSUITE_ACL_PLUGINS_TOGGLE_DESC" />
<action name="mokosuite.cache" title="COM_MOKOSUITE_ACL_CACHE" description="COM_MOKOSUITE_ACL_CACHE_DESC" />
</section>
</access>
@@ -1,41 +0,0 @@
; MokoSuite Admin Dashboard - Language Strings
; Copyright (C) 2026 Moko Consulting. All rights reserved.
; License: GPL-3.0-or-later
COM_MOKOSUITE_DASHBOARD_TITLE="MokoSuite Control Panel"
COM_MOKOSUITE_SITE="Site"
COM_MOKOSUITE_DATABASE="Database"
COM_MOKOSUITE_DEBUG_ON="Debug ON"
COM_MOKOSUITE_OFFLINE="Offline"
COM_MOKOSUITE_CLEAR_CACHE="Clear Cache"
COM_MOKOSUITE_CHECK_UPDATES="Check Updates"
COM_MOKOSUITE_ENABLED="Enabled"
COM_MOKOSUITE_DISABLED="Disabled"
COM_MOKOSUITE_PROTECTED="Protected"
COM_MOKOSUITE_CONFIGURE="Configure"
COM_MOKOSUITE_TOGGLE_SUCCESS="Plugin state updated."
COM_MOKOSUITE_TOGGLE_FAIL="Failed to update plugin state."
COM_MOKOSUITE_CACHE_CLEARED="Cache cleared successfully."
COM_MOKOSUITE_EXTENSIONS_TITLE="Moko Extensions"
COM_MOKOSUITE_EXTENSIONS_INFO="Install Moko Consulting Joomla packages from the official release server. Updates are handled through Joomla's native System > Update mechanism — each package registers its own update server."
COM_MOKOSUITE_EXTENSIONS_LINK="Moko Extensions"
COM_MOKOSUITE_HTACCESS_TITLE=".htaccess Maker"
COM_MOKOSUITE_TICKETS_TITLE="Helpdesk"
; ACL
COM_MOKOSUITE_ACL_DASHBOARD="View Dashboard"
COM_MOKOSUITE_ACL_DASHBOARD_DESC="Allow viewing the MokoSuite control panel dashboard."
COM_MOKOSUITE_ACL_EXTENSIONS="Manage Extensions"
COM_MOKOSUITE_ACL_EXTENSIONS_DESC="Allow installing and uninstalling Moko extensions."
COM_MOKOSUITE_ACL_HTACCESS="Manage .htaccess"
COM_MOKOSUITE_ACL_HTACCESS_DESC="Allow editing and saving the .htaccess configuration."
COM_MOKOSUITE_ACL_TICKETS="View Tickets"
COM_MOKOSUITE_ACL_TICKETS_DESC="Allow viewing helpdesk tickets."
COM_MOKOSUITE_ACL_TICKETS_CREATE="Create Tickets"
COM_MOKOSUITE_ACL_TICKETS_CREATE_DESC="Allow creating new helpdesk tickets."
COM_MOKOSUITE_ACL_TICKETS_ASSIGN="Assign Tickets"
COM_MOKOSUITE_ACL_TICKETS_ASSIGN_DESC="Allow assigning tickets to other users."
COM_MOKOSUITE_ACL_PLUGINS_TOGGLE="Toggle Plugins"
COM_MOKOSUITE_ACL_PLUGINS_TOGGLE_DESC="Allow enabling and disabling MokoSuite feature plugins."
COM_MOKOSUITE_ACL_CACHE="Clear Cache"
COM_MOKOSUITE_ACL_CACHE_DESC="Allow clearing the Joomla cache from the dashboard."
@@ -1,19 +0,0 @@
; MokoSuite Admin Dashboard - System Language Strings
; Copyright (C) 2026 Moko Consulting. All rights reserved.
; License: GPL-3.0-or-later
COM_MOKOSUITE="MokoSuite"
COM_MOKOSUITE_DESCRIPTION="MokoSuite admin dashboard and REST API. Control panel for managing site features, health monitoring, and remote management."
COM_MOKOSUITE_DASHBOARD_TITLE="MokoSuite Control Panel"
COM_MOKOSUITE_MENU_DASHBOARD="Dashboard"
COM_MOKOSUITE_MENU_EXTENSIONS="Moko Extensions"
COM_MOKOSUITE_MENU_PLUGINS="Feature Plugins"
COM_MOKOSUITE_MENU_UPDATES="Joomla Updates"
COM_MOKOSUITE_MENU_CHECKIN="Global Check-in"
COM_MOKOSUITE_MENU_TICKETS="Helpdesk"
COM_MOKOSUITE_MENU_HTACCESS=".htaccess Maker"
COM_MOKOSUITE_MENU_PRIVACY="Privacy Guard"
COM_MOKOSUITE_MENU_WAFLOG="WAF Log"
COM_MOKOSUITE_MENU_DATABASE="Database Tools"
COM_MOKOSUITE_MENU_CLEANUP="Cache Cleanup"
COM_MOKOSUITE_MENU_CACHE="Cache Management"
@@ -1,13 +0,0 @@
--
-- MokoSuite component uninstall — drop all tables
--
DROP TABLE IF EXISTS `#__mokosuite_download_keys`;
DROP TABLE IF EXISTS `#__mokosuite_retention_policies`;
DROP TABLE IF EXISTS `#__mokosuite_data_requests`;
DROP TABLE IF EXISTS `#__mokosuite_consent_log`;
DROP TABLE IF EXISTS `#__mokosuite_waf_log`;
DROP TABLE IF EXISTS `#__mokosuite_ticket_automation`;
DROP TABLE IF EXISTS `#__mokosuite_ticket_canned`;
DROP TABLE IF EXISTS `#__mokosuite_ticket_replies`;
DROP TABLE IF EXISTS `#__mokosuite_tickets`;
DROP TABLE IF EXISTS `#__mokosuite_ticket_categories`;
@@ -1,92 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
SPDX-LICENSE-IDENTIFIER: GPL-3.0-or-later
FILE INFORMATION
DEFGROUP: Joomla.Component
INGROUP: MokoSuite
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoSuite
VERSION: 02.34.16
PATH: /mokosuite.xml
BRIEF: Component manifest for MokoSuite admin dashboard and REST API
-->
<extension type="component" method="upgrade">
<name>MokoSuite</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.50-dev</version>
<description>MokoSuite admin dashboard and REST API. Provides a control panel for managing MokoSuite feature plugins, site health monitoring, and remote management endpoints.</description>
<namespace path="src">Moko\Component\MokoSuite</namespace>
<install>
<sql><file driver="mysql" charset="utf8">sql/install.mysql.sql</file></sql>
</install>
<uninstall>
<sql><file driver="mysql" charset="utf8">sql/uninstall.mysql.sql</file></sql>
</uninstall>
<update>
<schemas>
<schemapath type="mysql">sql/updates/mysql</schemapath>
</schemas>
</update>
<administration>
<menu img="class:cogs">MokoSuite</menu>
<submenu>
<menu link="option=com_mokosuite" img="class:cogs">COM_MOKOSUITE_MENU_DASHBOARD</menu>
<menu link="option=com_mokosuite&amp;view=extensions" img="class:puzzle-piece">COM_MOKOSUITE_MENU_EXTENSIONS</menu>
<menu link="option=com_mokosuite&amp;view=tickets" img="class:headphones">COM_MOKOSUITE_MENU_TICKETS</menu>
<menu link="option=com_mokosuite&amp;view=htaccess" img="class:file-code">COM_MOKOSUITE_MENU_HTACCESS</menu>
<menu link="option=com_mokosuite&amp;view=privacy" img="class:lock">COM_MOKOSUITE_MENU_PRIVACY</menu>
<menu link="option=com_mokosuite&amp;view=waflog" img="class:shield-alt">COM_MOKOSUITE_MENU_WAFLOG</menu>
<menu link="option=com_mokosuite&amp;view=database" img="class:database">COM_MOKOSUITE_MENU_DATABASE</menu>
<menu link="option=com_mokosuite&amp;view=cleanup" img="class:trash">COM_MOKOSUITE_MENU_CLEANUP</menu>
<menu link="option=com_plugins&amp;filter[folder]=system&amp;filter[search]=mokosuite" img="class:power-off">COM_MOKOSUITE_MENU_PLUGINS</menu>
<menu link="option=com_installer&amp;view=update" img="class:refresh">COM_MOKOSUITE_MENU_UPDATES</menu>
<menu link="option=com_checkin" img="class:check-square">COM_MOKOSUITE_MENU_CHECKIN</menu>
<menu link="option=com_cache" img="class:bolt">COM_MOKOSUITE_MENU_CACHE</menu>
</submenu>
<files folder="admin">
<filename>access.xml</filename>
<filename>catalog.xml</filename>
<filename>config.xml</filename>
<folder>language</folder>
<folder>services</folder>
<folder>sql</folder>
<folder>src</folder>
<folder>tmpl</folder>
</files>
<languages folder="admin/language">
<language tag="en-GB">en-GB/com_mokosuite.sys.ini</language>
</languages>
</administration>
<files folder="site">
<folder>language</folder>
<folder>services</folder>
<folder>src</folder>
<folder>tmpl</folder>
</files>
<install>
<sql><file driver="mysql" charset="utf8">admin/sql/install.mysql.sql</file></sql>
</install>
<api>
<files folder="api">
<folder>src</folder>
</files>
</api>
<media destination="com_mokosuite" folder="media">
<folder>css</folder>
<folder>js</folder>
</media>
</extension>
@@ -1,11 +0,0 @@
; MokoSuite Customer Portal - Language Strings
; Copyright (C) 2026 Moko Consulting. All rights reserved.
; License: GPL-3.0-or-later
COM_MOKOSUITE_PORTAL_TITLE="Support Portal"
COM_MOKOSUITE_PORTAL_MY_TICKETS="My Support Tickets"
COM_MOKOSUITE_PORTAL_NEW_TICKET="New Ticket"
COM_MOKOSUITE_PORTAL_SUBMIT="Submit Ticket"
COM_MOKOSUITE_PORTAL_REPLY="Send Reply"
COM_MOKOSUITE_PORTAL_NO_TICKETS="You haven't submitted any support tickets yet."
COM_MOKOSUITE_PORTAL_LOGIN_REQUIRED="Please log in to access the support portal."
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<access component="com_mokosuiteclient">
<section name="component">
<action name="core.admin" title="JACTION_ADMIN" description="JACTION_ADMIN_COMPONENT_DESC" />
<action name="core.manage" title="JACTION_MANAGE" description="JACTION_MANAGE_COMPONENT_DESC" />
<action name="mokosuiteclient.dashboard" title="COM_MOKOSUITECLIENT_ACL_DASHBOARD" description="COM_MOKOSUITECLIENT_ACL_DASHBOARD_DESC" />
<action name="mokosuiteclient.extensions" title="COM_MOKOSUITECLIENT_ACL_EXTENSIONS" description="COM_MOKOSUITECLIENT_ACL_EXTENSIONS_DESC" />
<action name="mokosuiteclient.htaccess" title="COM_MOKOSUITECLIENT_ACL_HTACCESS" description="COM_MOKOSUITECLIENT_ACL_HTACCESS_DESC" />
<action name="mokosuiteclient.tickets" title="COM_MOKOSUITECLIENT_ACL_TICKETS" description="COM_MOKOSUITECLIENT_ACL_TICKETS_DESC" />
<action name="mokosuiteclient.tickets.create" title="COM_MOKOSUITECLIENT_ACL_TICKETS_CREATE" description="COM_MOKOSUITECLIENT_ACL_TICKETS_CREATE_DESC" />
<action name="mokosuiteclient.tickets.assign" title="COM_MOKOSUITECLIENT_ACL_TICKETS_ASSIGN" description="COM_MOKOSUITECLIENT_ACL_TICKETS_ASSIGN_DESC" />
<action name="mokosuiteclient.plugins.toggle" title="COM_MOKOSUITECLIENT_ACL_PLUGINS_TOGGLE" description="COM_MOKOSUITECLIENT_ACL_PLUGINS_TOGGLE_DESC" />
<action name="mokosuiteclient.cache" title="COM_MOKOSUITECLIENT_ACL_CACHE" description="COM_MOKOSUITECLIENT_ACL_CACHE_DESC" />
</section>
</access>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- <!--
Extension catalog for MokoSuite Extension Manager. Extension catalog for MokoSuiteClient Extension Manager.
Each entry points to the extension's own updates.xml. The installer Each entry points to the extension's own updates.xml. The installer
resolves the latest version and download URL at runtime, respecting resolves the latest version and download URL at runtime, respecting
the site's configured update channel (dev/stable). the site's configured update channel (dev/stable).
@@ -9,31 +9,31 @@
--> -->
<catalog> <catalog>
<extension> <extension>
<name>MokoSuite</name> <name>MokoSuiteClient</name>
<element>pkg_mokosuite</element> <element>pkg_mokosuiteclient</element>
<type>package</type> <type>package</type>
<description>Admin dashboard, security firewall, tenant restrictions, health monitoring, and REST API.</description> <description>Admin dashboard, security firewall, tenant restrictions, health monitoring, and REST API.</description>
<icon>icon-shield-alt</icon> <icon>icon-shield-alt</icon>
<category>Platform</category> <category>Platform</category>
<article>https://mokoconsulting.tech/support/products/mokosuite-platform</article> <article>https://mokoconsulting.tech/support/products/mokosuiteclient-platform</article>
<protected>true</protected> <protected>true</protected>
<updateserver>https://git.mokoconsulting.tech/MokoConsulting/MokoSuite/raw/branch/dev/updates.xml</updateserver> <updateserver>https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient/raw/branch/dev/updates.xml</updateserver>
</extension> </extension>
<extension> <extension>
<name>MokoSuiteHQ</name> <name>MokoSuiteClientHQ</name>
<element>pkg_mokosuitehq</element> <element>pkg_mokosuiteclienthq</element>
<type>package</type> <type>package</type>
<description>Centralized control panel for managing all MokoSuite client installations.</description> <description>Centralized control panel for managing all MokoSuiteClient client installations.</description>
<icon>icon-tachometer-alt</icon> <icon>icon-tachometer-alt</icon>
<category>Platform</category> <category>Platform</category>
<article>https://mokoconsulting.tech/support/products/mokosuite-base</article> <article>https://mokoconsulting.tech/support/products/mokosuiteclient-base</article>
<updateserver>https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteHQ/raw/branch/dev/updates.xml</updateserver> <updateserver>https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClientHQ/raw/branch/dev/updates.xml</updateserver>
</extension> </extension>
<extension> <extension>
<name>MokoOnyx</name> <name>MokoOnyx</name>
<element>mokoonyx</element> <element>mokoonyx</element>
<type>template</type> <type>template</type>
<description>Modern Joomla site template with dark mode, custom layouts, and MokoSuite integration.</description> <description>Modern Joomla site template with dark mode, custom layouts, and MokoSuiteClient integration.</description>
<icon>icon-paint-brush</icon> <icon>icon-paint-brush</icon>
<category>Templates</category> <category>Templates</category>
<article>https://mokoconsulting.tech/support/products/mokoonyx-template</article> <article>https://mokoconsulting.tech/support/products/mokoonyx-template</article>
@@ -22,7 +22,7 @@
<field name="default_category" type="sql" default="" <field name="default_category" type="sql" default=""
label="Default Ticket Category" label="Default Ticket Category"
description="Category assigned to tickets without a selection." description="Category assigned to tickets without a selection."
query="SELECT id AS value, title AS text FROM #__mokosuite_ticket_categories WHERE published = 1 ORDER BY ordering" /> query="SELECT id AS value, title AS text FROM #__mokosuiteclient_ticket_categories WHERE published = 1 ORDER BY ordering" />
<field name="autoclose_days" type="number" default="7" <field name="autoclose_days" type="number" default="7"
label="Auto-Close After (days)" label="Auto-Close After (days)"
description="Resolved tickets are auto-closed after this many days. 0 = disabled." /> description="Resolved tickets are auto-closed after this many days. 0 = disabled." />
@@ -35,13 +35,13 @@
</field> </field>
</fieldset> </fieldset>
<fieldset name="permissions" label="COM_MOKOSUITE_ACL_TITLE" <fieldset name="permissions" label="COM_MOKOSUITECLIENT_ACL_TITLE"
description="COM_MOKOSUITE_ACL_DESC"> description="COM_MOKOSUITECLIENT_ACL_DESC">
<field name="rules" type="rules" <field name="rules" type="rules"
label="COM_MOKOSUITE_ACL_TITLE" label="COM_MOKOSUITECLIENT_ACL_TITLE"
validate="rules" validate="rules"
filter="rules" filter="rules"
component="com_mokosuite" component="com_mokosuiteclient"
section="component" /> section="component" />
</fieldset> </fieldset>
</config> </config>
@@ -0,0 +1,41 @@
; MokoSuiteClient Admin Dashboard - Language Strings
; Copyright (C) 2026 Moko Consulting. All rights reserved.
; License: GPL-3.0-or-later
COM_MOKOSUITECLIENT_DASHBOARD_TITLE="MokoSuiteClient Control Panel"
COM_MOKOSUITECLIENT_SITE="Site"
COM_MOKOSUITECLIENT_DATABASE="Database"
COM_MOKOSUITECLIENT_DEBUG_ON="Debug ON"
COM_MOKOSUITECLIENT_OFFLINE="Offline"
COM_MOKOSUITECLIENT_CLEAR_CACHE="Clear Cache"
COM_MOKOSUITECLIENT_CHECK_UPDATES="Check Updates"
COM_MOKOSUITECLIENT_ENABLED="Enabled"
COM_MOKOSUITECLIENT_DISABLED="Disabled"
COM_MOKOSUITECLIENT_PROTECTED="Protected"
COM_MOKOSUITECLIENT_CONFIGURE="Configure"
COM_MOKOSUITECLIENT_TOGGLE_SUCCESS="Plugin state updated."
COM_MOKOSUITECLIENT_TOGGLE_FAIL="Failed to update plugin state."
COM_MOKOSUITECLIENT_CACHE_CLEARED="Cache cleared successfully."
COM_MOKOSUITECLIENT_EXTENSIONS_TITLE="Moko Extensions"
COM_MOKOSUITECLIENT_EXTENSIONS_INFO="Install Moko Consulting Joomla packages from the official release server. Updates are handled through Joomla's native System > Update mechanism — each package registers its own update server."
COM_MOKOSUITECLIENT_EXTENSIONS_LINK="Moko Extensions"
COM_MOKOSUITECLIENT_HTACCESS_TITLE=".htaccess Maker"
COM_MOKOSUITECLIENT_TICKETS_TITLE="Helpdesk"
; ACL
COM_MOKOSUITECLIENT_ACL_DASHBOARD="View Dashboard"
COM_MOKOSUITECLIENT_ACL_DASHBOARD_DESC="Allow viewing the MokoSuiteClient control panel dashboard."
COM_MOKOSUITECLIENT_ACL_EXTENSIONS="Manage Extensions"
COM_MOKOSUITECLIENT_ACL_EXTENSIONS_DESC="Allow installing and uninstalling Moko extensions."
COM_MOKOSUITECLIENT_ACL_HTACCESS="Manage .htaccess"
COM_MOKOSUITECLIENT_ACL_HTACCESS_DESC="Allow editing and saving the .htaccess configuration."
COM_MOKOSUITECLIENT_ACL_TICKETS="View Tickets"
COM_MOKOSUITECLIENT_ACL_TICKETS_DESC="Allow viewing helpdesk tickets."
COM_MOKOSUITECLIENT_ACL_TICKETS_CREATE="Create Tickets"
COM_MOKOSUITECLIENT_ACL_TICKETS_CREATE_DESC="Allow creating new helpdesk tickets."
COM_MOKOSUITECLIENT_ACL_TICKETS_ASSIGN="Assign Tickets"
COM_MOKOSUITECLIENT_ACL_TICKETS_ASSIGN_DESC="Allow assigning tickets to other users."
COM_MOKOSUITECLIENT_ACL_PLUGINS_TOGGLE="Toggle Plugins"
COM_MOKOSUITECLIENT_ACL_PLUGINS_TOGGLE_DESC="Allow enabling and disabling MokoSuiteClient feature plugins."
COM_MOKOSUITECLIENT_ACL_CACHE="Clear Cache"
COM_MOKOSUITECLIENT_ACL_CACHE_DESC="Allow clearing the Joomla cache from the dashboard."
@@ -0,0 +1,19 @@
; MokoSuiteClient Admin Dashboard - System Language Strings
; Copyright (C) 2026 Moko Consulting. All rights reserved.
; License: GPL-3.0-or-later
COM_MOKOSUITECLIENT="MokoSuiteClient"
COM_MOKOSUITECLIENT_DESCRIPTION="MokoSuiteClient admin dashboard and REST API. Control panel for managing site features, health monitoring, and remote management."
COM_MOKOSUITECLIENT_DASHBOARD_TITLE="MokoSuiteClient Control Panel"
COM_MOKOSUITECLIENT_MENU_DASHBOARD="Dashboard"
COM_MOKOSUITECLIENT_MENU_EXTENSIONS="Moko Extensions"
COM_MOKOSUITECLIENT_MENU_PLUGINS="Feature Plugins"
COM_MOKOSUITECLIENT_MENU_UPDATES="Joomla Updates"
COM_MOKOSUITECLIENT_MENU_CHECKIN="Global Check-in"
COM_MOKOSUITECLIENT_MENU_TICKETS="Helpdesk"
COM_MOKOSUITECLIENT_MENU_HTACCESS=".htaccess Maker"
COM_MOKOSUITECLIENT_MENU_PRIVACY="Privacy Guard"
COM_MOKOSUITECLIENT_MENU_WAFLOG="WAF Log"
COM_MOKOSUITECLIENT_MENU_DATABASE="Database Tools"
COM_MOKOSUITECLIENT_MENU_CLEANUP="Cache Cleanup"
COM_MOKOSUITECLIENT_MENU_CACHE="Cache Management"
@@ -1,7 +1,7 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite.site * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
@@ -20,8 +20,8 @@ return new class implements ServiceProviderInterface
{ {
public function register(Container $container): void public function register(Container $container): void
{ {
$container->registerServiceProvider(new MVCFactory('\\Moko\\Component\\MokoSuite')); $container->registerServiceProvider(new MVCFactory('\\Moko\\Component\\MokoSuiteClient'));
$container->registerServiceProvider(new ComponentDispatcherFactory('\\Moko\\Component\\MokoSuite')); $container->registerServiceProvider(new ComponentDispatcherFactory('\\Moko\\Component\\MokoSuiteClient'));
$container->set( $container->set(
ComponentInterface::class, ComponentInterface::class,
@@ -1,8 +1,8 @@
-- --
-- MokoSuite Helpdesk Tables -- MokoSuiteClient Helpdesk Tables
-- --
CREATE TABLE IF NOT EXISTS `#__mokosuite_ticket_categories` ( CREATE TABLE IF NOT EXISTS `#__mokosuiteclient_ticket_categories` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255) NOT NULL, `title` VARCHAR(255) NOT NULL,
`alias` VARCHAR(255) NOT NULL DEFAULT '', `alias` VARCHAR(255) NOT NULL DEFAULT '',
@@ -16,7 +16,7 @@ CREATE TABLE IF NOT EXISTS `#__mokosuite_ticket_categories` (
KEY `idx_alias` (`alias`) KEY `idx_alias` (`alias`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `#__mokosuite_ticket_statuses` ( CREATE TABLE IF NOT EXISTS `#__mokosuiteclient_ticket_statuses` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`title` VARCHAR(100) NOT NULL, `title` VARCHAR(100) NOT NULL,
`alias` VARCHAR(100) NOT NULL, `alias` VARCHAR(100) NOT NULL,
@@ -28,14 +28,14 @@ CREATE TABLE IF NOT EXISTS `#__mokosuite_ticket_statuses` (
UNIQUE KEY `idx_alias` (`alias`) UNIQUE KEY `idx_alias` (`alias`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT IGNORE INTO `#__mokosuite_ticket_statuses` (`id`, `title`, `alias`, `color`, `is_default`, `is_closed`, `ordering`) VALUES INSERT IGNORE INTO `#__mokosuiteclient_ticket_statuses` (`id`, `title`, `alias`, `color`, `is_default`, `is_closed`, `ordering`) VALUES
(1, 'Open', 'open', 'bg-primary', 1, 0, 1), (1, 'Open', 'open', 'bg-primary', 1, 0, 1),
(2, 'In Progress', 'in_progress', 'bg-info', 0, 0, 2), (2, 'In Progress', 'in_progress', 'bg-info', 0, 0, 2),
(3, 'Waiting', 'waiting', 'bg-warning text-dark', 0, 0, 3), (3, 'Waiting', 'waiting', 'bg-warning text-dark', 0, 0, 3),
(4, 'Resolved', 'resolved', 'bg-success', 0, 0, 4), (4, 'Resolved', 'resolved', 'bg-success', 0, 0, 4),
(5, 'Closed', 'closed', 'bg-secondary', 0, 1, 5); (5, 'Closed', 'closed', 'bg-secondary', 0, 1, 5);
CREATE TABLE IF NOT EXISTS `#__mokosuite_ticket_priorities` ( CREATE TABLE IF NOT EXISTS `#__mokosuiteclient_ticket_priorities` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`title` VARCHAR(100) NOT NULL, `title` VARCHAR(100) NOT NULL,
`alias` VARCHAR(100) NOT NULL, `alias` VARCHAR(100) NOT NULL,
@@ -47,13 +47,13 @@ CREATE TABLE IF NOT EXISTS `#__mokosuite_ticket_priorities` (
UNIQUE KEY `idx_alias` (`alias`) UNIQUE KEY `idx_alias` (`alias`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT IGNORE INTO `#__mokosuite_ticket_priorities` (`id`, `title`, `alias`, `color`, `is_default`, `weight`, `ordering`) VALUES INSERT IGNORE INTO `#__mokosuiteclient_ticket_priorities` (`id`, `title`, `alias`, `color`, `is_default`, `weight`, `ordering`) VALUES
(1, 'Low', 'low', 'bg-secondary', 0, 10, 1), (1, 'Low', 'low', 'bg-secondary', 0, 10, 1),
(2, 'Normal', 'normal', 'bg-primary', 1, 20, 2), (2, 'Normal', 'normal', 'bg-primary', 1, 20, 2),
(3, 'High', 'high', 'bg-warning text-dark', 0, 30, 3), (3, 'High', 'high', 'bg-warning text-dark', 0, 30, 3),
(4, 'Urgent', 'urgent', 'bg-danger', 0, 40, 4); (4, 'Urgent', 'urgent', 'bg-danger', 0, 40, 4);
CREATE TABLE IF NOT EXISTS `#__mokosuite_tickets` ( CREATE TABLE IF NOT EXISTS `#__mokosuiteclient_tickets` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`subject` VARCHAR(512) NOT NULL, `subject` VARCHAR(512) NOT NULL,
`body` TEXT NOT NULL, `body` TEXT NOT NULL,
@@ -83,14 +83,14 @@ CREATE TABLE IF NOT EXISTS `#__mokosuite_tickets` (
KEY `idx_created` (`created`) KEY `idx_created` (`created`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `#__mokosuite_ticket_category_field_groups` ( CREATE TABLE IF NOT EXISTS `#__mokosuiteclient_ticket_category_field_groups` (
`category_id` INT UNSIGNED NOT NULL, `category_id` INT UNSIGNED NOT NULL,
`field_group_id` INT NOT NULL, `field_group_id` INT NOT NULL,
PRIMARY KEY (`category_id`, `field_group_id`), PRIMARY KEY (`category_id`, `field_group_id`),
KEY `idx_field_group` (`field_group_id`) KEY `idx_field_group` (`field_group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `#__mokosuite_ticket_replies` ( CREATE TABLE IF NOT EXISTS `#__mokosuiteclient_ticket_replies` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`ticket_id` INT UNSIGNED NOT NULL, `ticket_id` INT UNSIGNED NOT NULL,
`user_id` INT NOT NULL DEFAULT 0, `user_id` INT NOT NULL DEFAULT 0,
@@ -102,7 +102,7 @@ CREATE TABLE IF NOT EXISTS `#__mokosuite_ticket_replies` (
KEY `idx_created` (`created`) KEY `idx_created` (`created`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `#__mokosuite_ticket_canned` ( CREATE TABLE IF NOT EXISTS `#__mokosuiteclient_ticket_canned` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255) NOT NULL, `title` VARCHAR(255) NOT NULL,
`body` TEXT NOT NULL, `body` TEXT NOT NULL,
@@ -111,7 +111,7 @@ CREATE TABLE IF NOT EXISTS `#__mokosuite_ticket_canned` (
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `#__mokosuite_ticket_automation` ( CREATE TABLE IF NOT EXISTS `#__mokosuiteclient_ticket_automation` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255) NOT NULL, `title` VARCHAR(255) NOT NULL,
`trigger_event` VARCHAR(50) NOT NULL DEFAULT 'ticket_created', `trigger_event` VARCHAR(50) NOT NULL DEFAULT 'ticket_created',
@@ -122,7 +122,7 @@ CREATE TABLE IF NOT EXISTS `#__mokosuite_ticket_automation` (
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `#__mokosuite_ticket_assignees` ( CREATE TABLE IF NOT EXISTS `#__mokosuiteclient_ticket_assignees` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`ticket_id` INT UNSIGNED NOT NULL, `ticket_id` INT UNSIGNED NOT NULL,
`assignee_type` ENUM('user','group') NOT NULL DEFAULT 'user', `assignee_type` ENUM('user','group') NOT NULL DEFAULT 'user',
@@ -134,13 +134,13 @@ CREATE TABLE IF NOT EXISTS `#__mokosuite_ticket_assignees` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Default automation rules -- Default automation rules
INSERT IGNORE INTO `#__mokosuite_ticket_automation` (`id`, `title`, `trigger_event`, `conditions`, `actions`, `enabled`, `ordering`) VALUES INSERT IGNORE INTO `#__mokosuiteclient_ticket_automation` (`id`, `title`, `trigger_event`, `conditions`, `actions`, `enabled`, `ordering`) VALUES
(1, 'Auto-close resolved tickets after 7 days', 'scheduled', '[{"field":"status","op":"eq","value":"resolved"},{"field":"age_hours","op":"gt","value":"168"}]', '[{"type":"set_status","value":"closed"},{"type":"add_note","value":"Auto-closed after 7 days with no response."}]', 1, 1), (1, 'Auto-close resolved tickets after 7 days', 'scheduled', '[{"field":"status","op":"eq","value":"resolved"},{"field":"age_hours","op":"gt","value":"168"}]', '[{"type":"set_status","value":"closed"},{"type":"add_note","value":"Auto-closed after 7 days with no response."}]', 1, 1),
(2, 'Escalate urgent tickets with no response in 1 hour', 'scheduled', '[{"field":"priority","op":"eq","value":"urgent"},{"field":"sla_responded","op":"eq","value":"0"},{"field":"age_hours","op":"gt","value":"1"}]', '[{"type":"add_note","value":"SLA BREACH: Urgent ticket has no staff response after 1 hour."}]', 1, 2), (2, 'Escalate urgent tickets with no response in 1 hour', 'scheduled', '[{"field":"priority","op":"eq","value":"urgent"},{"field":"sla_responded","op":"eq","value":"0"},{"field":"age_hours","op":"gt","value":"1"}]', '[{"type":"add_note","value":"SLA BREACH: Urgent ticket has no staff response after 1 hour."}]', 1, 2),
(3, 'Notify on high priority ticket creation', 'ticket_created', '[{"field":"priority","op":"in","value":"high,urgent"}]', '[{"type":"add_note","value":"High/urgent ticket created — requires immediate attention."}]', 1, 3); (3, 'Notify on high priority ticket creation', 'ticket_created', '[{"field":"priority","op":"in","value":"high,urgent"}]', '[{"type":"add_note","value":"High/urgent ticket created — requires immediate attention."}]', 1, 3);
-- Default categories -- Default categories
INSERT IGNORE INTO `#__mokosuite_ticket_categories` (`id`, `title`, `alias`, `description`, `sla_response_minutes`, `sla_resolution_minutes`, `ordering`) VALUES INSERT IGNORE INTO `#__mokosuiteclient_ticket_categories` (`id`, `title`, `alias`, `description`, `sla_response_minutes`, `sla_resolution_minutes`, `ordering`) VALUES
(1, 'General Support', 'general-support', 'General questions and assistance', 480, 2880, 1), (1, 'General Support', 'general-support', 'General questions and assistance', 480, 2880, 1),
(2, 'Bug Report', 'bug-report', 'Report a software bug or issue', 240, 1440, 2), (2, 'Bug Report', 'bug-report', 'Report a software bug or issue', 240, 1440, 2),
(3, 'Feature Request', 'feature-request', 'Request a new feature or enhancement', 1440, 10080, 3), (3, 'Feature Request', 'feature-request', 'Request a new feature or enhancement', 1440, 10080, 3),
@@ -151,7 +151,7 @@ INSERT IGNORE INTO `#__mokosuite_ticket_categories` (`id`, `title`, `alias`, `de
-- Privacy Guard Tables -- Privacy Guard Tables
-- --
CREATE TABLE IF NOT EXISTS `#__mokosuite_consent_log` ( CREATE TABLE IF NOT EXISTS `#__mokosuiteclient_consent_log` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` INT NOT NULL, `user_id` INT NOT NULL,
`category` VARCHAR(50) NOT NULL, `category` VARCHAR(50) NOT NULL,
@@ -163,7 +163,7 @@ CREATE TABLE IF NOT EXISTS `#__mokosuite_consent_log` (
KEY `idx_category` (`category`) KEY `idx_category` (`category`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `#__mokosuite_data_requests` ( CREATE TABLE IF NOT EXISTS `#__mokosuiteclient_data_requests` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` INT NOT NULL, `user_id` INT NOT NULL,
`type` ENUM('export','delete','anonymize') NOT NULL, `type` ENUM('export','delete','anonymize') NOT NULL,
@@ -177,7 +177,7 @@ CREATE TABLE IF NOT EXISTS `#__mokosuite_data_requests` (
KEY `idx_status` (`status`) KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `#__mokosuite_retention_policies` ( CREATE TABLE IF NOT EXISTS `#__mokosuiteclient_retention_policies` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`content_type` VARCHAR(100) NOT NULL, `content_type` VARCHAR(100) NOT NULL,
`retention_days` INT UNSIGNED NOT NULL DEFAULT 365, `retention_days` INT UNSIGNED NOT NULL DEFAULT 365,
@@ -188,7 +188,7 @@ CREATE TABLE IF NOT EXISTS `#__mokosuite_retention_policies` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Default retention policies -- Default retention policies
INSERT IGNORE INTO `#__mokosuite_retention_policies` (`id`, `content_type`, `retention_days`, `action`, `enabled`, `description`) VALUES INSERT IGNORE INTO `#__mokosuiteclient_retention_policies` (`id`, `content_type`, `retention_days`, `action`, `enabled`, `description`) VALUES
(1, 'action_logs', 90, 'delete', 1, 'Delete action log entries older than 90 days'), (1, 'action_logs', 90, 'delete', 1, 'Delete action log entries older than 90 days'),
(2, 'waf_logs', 30, 'delete', 1, 'Delete WAF block logs older than 30 days'), (2, 'waf_logs', 30, 'delete', 1, 'Delete WAF block logs older than 30 days'),
(3, 'sessions', 7, 'delete', 1, 'Purge expired sessions older than 7 days'), (3, 'sessions', 7, 'delete', 1, 'Purge expired sessions older than 7 days'),
@@ -0,0 +1,13 @@
--
-- MokoSuiteClient component uninstall — drop all tables
--
DROP TABLE IF EXISTS `#__mokosuiteclient_download_keys`;
DROP TABLE IF EXISTS `#__mokosuiteclient_retention_policies`;
DROP TABLE IF EXISTS `#__mokosuiteclient_data_requests`;
DROP TABLE IF EXISTS `#__mokosuiteclient_consent_log`;
DROP TABLE IF EXISTS `#__mokosuiteclient_waf_log`;
DROP TABLE IF EXISTS `#__mokosuiteclient_ticket_automation`;
DROP TABLE IF EXISTS `#__mokosuiteclient_ticket_canned`;
DROP TABLE IF EXISTS `#__mokosuiteclient_ticket_replies`;
DROP TABLE IF EXISTS `#__mokosuiteclient_tickets`;
DROP TABLE IF EXISTS `#__mokosuiteclient_ticket_categories`;
@@ -1,2 +1,2 @@
-- Remove download_keys table (feature reverted — preflight handles key preservation) -- Remove download_keys table (feature reverted — preflight handles key preservation)
DROP TABLE IF EXISTS `#__mokosuite_download_keys`; DROP TABLE IF EXISTS `#__mokosuiteclient_download_keys`;
@@ -1,2 +1,2 @@
-- RSA signing replaces key ring — drop table if it was created -- RSA signing replaces key ring — drop table if it was created
DROP TABLE IF EXISTS `#__mokosuite_api_keys`; DROP TABLE IF EXISTS `#__mokosuiteclient_api_keys`;
@@ -1,10 +1,10 @@
-- Add contact link to tickets (optional FK to #__contact_details) -- Add contact link to tickets (optional FK to #__contact_details)
ALTER TABLE `#__mokosuite_tickets` ALTER TABLE `#__mokosuiteclient_tickets`
ADD COLUMN `contact_id` INT UNSIGNED DEFAULT NULL AFTER `category_id`, ADD COLUMN `contact_id` INT UNSIGNED DEFAULT NULL AFTER `category_id`,
ADD KEY `idx_contact` (`contact_id`); ADD KEY `idx_contact` (`contact_id`);
-- Multi-assignee junction table (replaces single assigned_to column) -- Multi-assignee junction table (replaces single assigned_to column)
CREATE TABLE IF NOT EXISTS `#__mokosuite_ticket_assignees` ( CREATE TABLE IF NOT EXISTS `#__mokosuiteclient_ticket_assignees` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`ticket_id` INT UNSIGNED NOT NULL, `ticket_id` INT UNSIGNED NOT NULL,
`assignee_type` ENUM('user','group') NOT NULL DEFAULT 'user', `assignee_type` ENUM('user','group') NOT NULL DEFAULT 'user',
@@ -16,11 +16,11 @@ CREATE TABLE IF NOT EXISTS `#__mokosuite_ticket_assignees` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Migrate existing single-assignee data to junction table -- Migrate existing single-assignee data to junction table
INSERT IGNORE INTO `#__mokosuite_ticket_assignees` (`ticket_id`, `assignee_type`, `assignee_id`) INSERT IGNORE INTO `#__mokosuiteclient_ticket_assignees` (`ticket_id`, `assignee_type`, `assignee_id`)
SELECT `id`, 'user', `assigned_to` FROM `#__mokosuite_tickets` WHERE `assigned_to` IS NOT NULL AND `assigned_to` > 0; SELECT `id`, 'user', `assigned_to` FROM `#__mokosuiteclient_tickets` WHERE `assigned_to` IS NOT NULL AND `assigned_to` > 0;
-- Customizable ticket statuses (replaces ENUM) -- Customizable ticket statuses (replaces ENUM)
CREATE TABLE IF NOT EXISTS `#__mokosuite_ticket_statuses` ( CREATE TABLE IF NOT EXISTS `#__mokosuiteclient_ticket_statuses` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`title` VARCHAR(100) NOT NULL, `title` VARCHAR(100) NOT NULL,
`alias` VARCHAR(100) NOT NULL, `alias` VARCHAR(100) NOT NULL,
@@ -32,7 +32,7 @@ CREATE TABLE IF NOT EXISTS `#__mokosuite_ticket_statuses` (
UNIQUE KEY `idx_alias` (`alias`) UNIQUE KEY `idx_alias` (`alias`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT IGNORE INTO `#__mokosuite_ticket_statuses` (`id`, `title`, `alias`, `color`, `is_default`, `is_closed`, `ordering`) VALUES INSERT IGNORE INTO `#__mokosuiteclient_ticket_statuses` (`id`, `title`, `alias`, `color`, `is_default`, `is_closed`, `ordering`) VALUES
(1, 'Open', 'open', 'bg-primary', 1, 0, 1), (1, 'Open', 'open', 'bg-primary', 1, 0, 1),
(2, 'In Progress', 'in_progress', 'bg-info', 0, 0, 2), (2, 'In Progress', 'in_progress', 'bg-info', 0, 0, 2),
(3, 'Waiting', 'waiting', 'bg-warning text-dark', 0, 0, 3), (3, 'Waiting', 'waiting', 'bg-warning text-dark', 0, 0, 3),
@@ -40,7 +40,7 @@ INSERT IGNORE INTO `#__mokosuite_ticket_statuses` (`id`, `title`, `alias`, `colo
(5, 'Closed', 'closed', 'bg-secondary', 0, 1, 5); (5, 'Closed', 'closed', 'bg-secondary', 0, 1, 5);
-- Customizable ticket priorities (replaces ENUM) -- Customizable ticket priorities (replaces ENUM)
CREATE TABLE IF NOT EXISTS `#__mokosuite_ticket_priorities` ( CREATE TABLE IF NOT EXISTS `#__mokosuiteclient_ticket_priorities` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`title` VARCHAR(100) NOT NULL, `title` VARCHAR(100) NOT NULL,
`alias` VARCHAR(100) NOT NULL, `alias` VARCHAR(100) NOT NULL,
@@ -52,32 +52,32 @@ CREATE TABLE IF NOT EXISTS `#__mokosuite_ticket_priorities` (
UNIQUE KEY `idx_alias` (`alias`) UNIQUE KEY `idx_alias` (`alias`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT IGNORE INTO `#__mokosuite_ticket_priorities` (`id`, `title`, `alias`, `color`, `is_default`, `weight`, `ordering`) VALUES INSERT IGNORE INTO `#__mokosuiteclient_ticket_priorities` (`id`, `title`, `alias`, `color`, `is_default`, `weight`, `ordering`) VALUES
(1, 'Low', 'low', 'bg-secondary', 0, 10, 1), (1, 'Low', 'low', 'bg-secondary', 0, 10, 1),
(2, 'Normal', 'normal', 'bg-primary', 1, 20, 2), (2, 'Normal', 'normal', 'bg-primary', 1, 20, 2),
(3, 'High', 'high', 'bg-warning text-dark', 0, 30, 3), (3, 'High', 'high', 'bg-warning text-dark', 0, 30, 3),
(4, 'Urgent', 'urgent', 'bg-danger', 0, 40, 4); (4, 'Urgent', 'urgent', 'bg-danger', 0, 40, 4);
-- Add INT FK columns for status/priority (coexist with ENUM during migration) -- Add INT FK columns for status/priority (coexist with ENUM during migration)
ALTER TABLE `#__mokosuite_tickets` ALTER TABLE `#__mokosuiteclient_tickets`
ADD COLUMN `status_id` INT UNSIGNED DEFAULT NULL AFTER `status`, ADD COLUMN `status_id` INT UNSIGNED DEFAULT NULL AFTER `status`,
ADD COLUMN `priority_id` INT UNSIGNED DEFAULT NULL AFTER `priority`, ADD COLUMN `priority_id` INT UNSIGNED DEFAULT NULL AFTER `priority`,
ADD KEY `idx_status_id` (`status_id`), ADD KEY `idx_status_id` (`status_id`),
ADD KEY `idx_priority_id` (`priority_id`); ADD KEY `idx_priority_id` (`priority_id`);
-- Populate new columns from existing ENUM values -- Populate new columns from existing ENUM values
UPDATE `#__mokosuite_tickets` t UPDATE `#__mokosuiteclient_tickets` t
JOIN `#__mokosuite_ticket_statuses` s ON s.alias = t.status JOIN `#__mokosuiteclient_ticket_statuses` s ON s.alias = t.status
SET t.status_id = s.id SET t.status_id = s.id
WHERE t.status_id IS NULL; WHERE t.status_id IS NULL;
UPDATE `#__mokosuite_tickets` t UPDATE `#__mokosuiteclient_tickets` t
JOIN `#__mokosuite_ticket_priorities` p ON p.alias = t.priority JOIN `#__mokosuiteclient_ticket_priorities` p ON p.alias = t.priority
SET t.priority_id = p.id SET t.priority_id = p.id
WHERE t.priority_id IS NULL; WHERE t.priority_id IS NULL;
-- Junction: which Joomla field groups apply to which ticket categories -- Junction: which Joomla field groups apply to which ticket categories
CREATE TABLE IF NOT EXISTS `#__mokosuite_ticket_category_field_groups` ( CREATE TABLE IF NOT EXISTS `#__mokosuiteclient_ticket_category_field_groups` (
`category_id` INT UNSIGNED NOT NULL, `category_id` INT UNSIGNED NOT NULL,
`field_group_id` INT NOT NULL, `field_group_id` INT NOT NULL,
PRIMARY KEY (`category_id`, `field_group_id`), PRIMARY KEY (`category_id`, `field_group_id`),
@@ -1,12 +1,12 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
namespace Moko\Component\MokoSuite\Administrator\Controller; namespace Moko\Component\MokoSuiteClient\Administrator\Controller;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -24,18 +24,18 @@ class DisplayController extends BaseController
* ACL map: view name => required permission. * ACL map: view name => required permission.
*/ */
private const VIEW_ACL = [ private const VIEW_ACL = [
'dashboard' => 'mokosuite.dashboard', 'dashboard' => 'mokosuiteclient.dashboard',
'extensions' => 'mokosuite.extensions', 'extensions' => 'mokosuiteclient.extensions',
'htaccess' => 'mokosuite.htaccess', 'htaccess' => 'mokosuiteclient.htaccess',
'tickets' => 'mokosuite.tickets', 'tickets' => 'mokosuiteclient.tickets',
'ticket' => 'mokosuite.tickets', 'ticket' => 'mokosuiteclient.tickets',
'privacy' => 'core.admin', 'privacy' => 'core.admin',
'waflog' => 'core.admin', 'waflog' => 'core.admin',
'categories' => 'mokosuite.tickets', 'categories' => 'mokosuiteclient.tickets',
'canned' => 'mokosuite.tickets', 'canned' => 'mokosuiteclient.tickets',
'automation' => 'core.admin', 'automation' => 'core.admin',
'database' => 'core.admin', 'database' => 'core.admin',
'cleanup' => 'mokosuite.cache', 'cleanup' => 'mokosuiteclient.cache',
]; ];
public function display($cachable = false, $urlparams = []) public function display($cachable = false, $urlparams = [])
@@ -62,7 +62,7 @@ class DisplayController extends BaseController
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuite.plugins.toggle')) if (!$this->checkAcl('mokosuiteclient.plugins.toggle'))
{ {
$this->jsonForbidden(); $this->jsonForbidden();
return; return;
@@ -89,7 +89,7 @@ class DisplayController extends BaseController
try try
{ {
$monitorPlugin = \Joomla\CMS\Plugin\PluginHelper::getPlugin('system', 'mokosuite_monitor'); $monitorPlugin = \Joomla\CMS\Plugin\PluginHelper::getPlugin('system', 'mokosuiteclient_monitor');
if (!$monitorPlugin) if (!$monitorPlugin)
{ {
@@ -104,7 +104,7 @@ class DisplayController extends BaseController
// Fall back to manifest XML default if not yet saved in params // Fall back to manifest XML default if not yet saved in params
if (empty($baseUrl)) if (empty($baseUrl))
{ {
$manifestFile = JPATH_PLUGINS . '/system/mokosuite_monitor/mokosuite_monitor.xml'; $manifestFile = JPATH_PLUGINS . '/system/mokosuiteclient_monitor/mokosuiteclient_monitor.xml';
if (is_file($manifestFile)) if (is_file($manifestFile))
{ {
@@ -123,12 +123,12 @@ class DisplayController extends BaseController
if (empty($baseUrl)) if (empty($baseUrl))
{ {
$this->jsonResponse(['success' => false, 'message' => 'MokoSuiteHQ URL not configured in monitor plugin.']); $this->jsonResponse(['success' => false, 'message' => 'MokoSuiteClientHQ URL not configured in monitor plugin.']);
return; return;
} }
$corePlugin = \Joomla\CMS\Plugin\PluginHelper::getPlugin('system', 'mokosuite'); $corePlugin = \Joomla\CMS\Plugin\PluginHelper::getPlugin('system', 'mokosuiteclient');
$coreParams = new \Joomla\Registry\Registry($corePlugin ? $corePlugin->params : '{}'); $coreParams = new \Joomla\Registry\Registry($corePlugin ? $corePlugin->params : '{}');
$healthToken = $coreParams->get('health_api_token', ''); $healthToken = $coreParams->get('health_api_token', '');
@@ -160,7 +160,7 @@ class DisplayController extends BaseController
// Fall back to manifest XML default if not yet saved in params // Fall back to manifest XML default if not yet saved in params
if (empty($signingKeyB64)) if (empty($signingKeyB64))
{ {
$manifestFile = JPATH_PLUGINS . '/system/mokosuite_monitor/mokosuite_monitor.xml'; $manifestFile = JPATH_PLUGINS . '/system/mokosuiteclient_monitor/mokosuiteclient_monitor.xml';
if (is_file($manifestFile)) if (is_file($manifestFile))
{ {
@@ -189,13 +189,13 @@ class DisplayController extends BaseController
if (openssl_sign($message, $signature, $privateKey, OPENSSL_ALGO_SHA256)) if (openssl_sign($message, $signature, $privateKey, OPENSSL_ALGO_SHA256))
{ {
$headers[] = 'X-MokoSuite-Signature: ' . base64_encode($signature); $headers[] = 'X-MokoSuiteClient-Signature: ' . base64_encode($signature);
$headers[] = 'X-MokoSuite-Timestamp: ' . $timestamp; $headers[] = 'X-MokoSuiteClient-Timestamp: ' . $timestamp;
} }
} }
} }
$endpoint = $baseUrl . '/api/index.php/v1/mokosuitehq/heartbeat'; $endpoint = $baseUrl . '/api/index.php/v1/mokosuiteclienthq/heartbeat';
$ch = curl_init($endpoint); $ch = curl_init($endpoint);
curl_setopt_array($ch, [ curl_setopt_array($ch, [
@@ -241,7 +241,7 @@ class DisplayController extends BaseController
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuite.cache')) if (!$this->checkAcl('mokosuiteclient.cache'))
{ {
$this->jsonForbidden(); $this->jsonForbidden();
return; return;
@@ -254,7 +254,7 @@ class DisplayController extends BaseController
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuite.cache')) if (!$this->checkAcl('mokosuiteclient.cache'))
{ {
$this->jsonForbidden(); $this->jsonForbidden();
return; return;
@@ -271,7 +271,7 @@ class DisplayController extends BaseController
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuite.extensions')) if (!$this->checkAcl('mokosuiteclient.extensions'))
{ {
$this->jsonForbidden(); $this->jsonForbidden();
return; return;
@@ -296,7 +296,7 @@ class DisplayController extends BaseController
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuite.htaccess')) if (!$this->checkAcl('mokosuiteclient.htaccess'))
{ {
$this->jsonForbidden(); $this->jsonForbidden();
return; return;
@@ -328,7 +328,7 @@ class DisplayController extends BaseController
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuite.htaccess')) if (!$this->checkAcl('mokosuiteclient.htaccess'))
{ {
$this->jsonForbidden(); $this->jsonForbidden();
return; return;
@@ -356,7 +356,7 @@ class DisplayController extends BaseController
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuite.tickets.create')) if (!$this->checkAcl('mokosuiteclient.tickets.create'))
{ {
$this->jsonForbidden(); $this->jsonForbidden();
return; return;
@@ -376,7 +376,7 @@ class DisplayController extends BaseController
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuite.tickets')) if (!$this->checkAcl('mokosuiteclient.tickets'))
{ {
$this->jsonForbidden(); $this->jsonForbidden();
return; return;
@@ -395,7 +395,7 @@ class DisplayController extends BaseController
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuite.tickets')) if (!$this->checkAcl('mokosuiteclient.tickets'))
{ {
$this->jsonForbidden(); $this->jsonForbidden();
return; return;
@@ -459,7 +459,7 @@ class DisplayController extends BaseController
{ {
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(); return; }
$model = new \Moko\Component\MokoSuite\Administrator\Model\MaintenanceModel(); $model = new \Moko\Component\MokoSuiteClient\Administrator\Model\MaintenanceModel();
$this->jsonResponse($model->optimizeTables()); $this->jsonResponse($model->optimizeTables());
} }
@@ -467,7 +467,7 @@ class DisplayController extends BaseController
{ {
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(); return; }
$model = new \Moko\Component\MokoSuite\Administrator\Model\MaintenanceModel(); $model = new \Moko\Component\MokoSuiteClient\Administrator\Model\MaintenanceModel();
$this->jsonResponse($model->repairTables()); $this->jsonResponse($model->repairTables());
} }
@@ -475,16 +475,16 @@ class DisplayController extends BaseController
{ {
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(); return; }
$model = new \Moko\Component\MokoSuite\Administrator\Model\MaintenanceModel(); $model = new \Moko\Component\MokoSuiteClient\Administrator\Model\MaintenanceModel();
$this->jsonResponse($model->purgeSessions()); $this->jsonResponse($model->purgeSessions());
} }
public function cleanDirectory() public function cleanDirectory()
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuite.cache')) { $this->jsonForbidden(); return; } if (!$this->checkAcl('mokosuiteclient.cache')) { $this->jsonForbidden(); return; }
$dirKey = Factory::getApplication()->getInput()->getString('dir_key', ''); $dirKey = Factory::getApplication()->getInput()->getString('dir_key', '');
$model = new \Moko\Component\MokoSuite\Administrator\Model\MaintenanceModel(); $model = new \Moko\Component\MokoSuiteClient\Administrator\Model\MaintenanceModel();
$this->jsonResponse($model->cleanDirectory($dirKey)); $this->jsonResponse($model->cleanDirectory($dirKey));
} }
@@ -495,7 +495,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('mokosuite.tickets')) { $this->jsonForbidden(); } 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);
@@ -509,10 +509,10 @@ class DisplayController extends BaseController
]; ];
if ($id) { if ($id) {
$data->id = $id; $data->id = $id;
$db->updateObject('#__mokosuite_ticket_categories', $data, 'id'); $db->updateObject('#__mokosuiteclient_ticket_categories', $data, 'id');
} else { } else {
$data->ordering = 0; $data->ordering = 0;
$db->insertObject('#__mokosuite_ticket_categories', $data, 'id'); $db->insertObject('#__mokosuiteclient_ticket_categories', $data, 'id');
} }
$this->jsonResponse(['success' => true, 'message' => 'Category saved.', 'id' => (int) $data->id]); $this->jsonResponse(['success' => true, 'message' => 'Category saved.', 'id' => (int) $data->id]);
} }
@@ -520,16 +520,16 @@ 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('mokosuite.tickets')) { $this->jsonForbidden(); } if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); }
$db = Factory::getDbo(); $db = Factory::getDbo();
$db->setQuery($db->getQuery(true)->delete('#__mokosuite_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.']);
} }
public function saveCanned() public function saveCanned()
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuite.tickets')) { $this->jsonForbidden(); } 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) [
@@ -539,17 +539,17 @@ class DisplayController extends BaseController
'ordering' => 0, 'ordering' => 0,
]; ];
$id = $input->getInt('id', 0); $id = $input->getInt('id', 0);
if ($id) { $data->id = $id; $db->updateObject('#__mokosuite_ticket_canned', $data, 'id'); } if ($id) { $data->id = $id; $db->updateObject('#__mokosuiteclient_ticket_canned', $data, 'id'); }
else { $db->insertObject('#__mokosuite_ticket_canned', $data, 'id'); } else { $db->insertObject('#__mokosuiteclient_ticket_canned', $data, 'id'); }
$this->jsonResponse(['success' => true, 'message' => 'Canned response saved.', 'id' => (int) $data->id]); $this->jsonResponse(['success' => true, 'message' => 'Canned response saved.', 'id' => (int) $data->id]);
} }
public function deleteCanned() public function deleteCanned()
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuite.tickets')) { $this->jsonForbidden(); } if (!$this->checkAcl('mokosuiteclient.tickets')) { $this->jsonForbidden(); }
$db = Factory::getDbo(); $db = Factory::getDbo();
$db->setQuery($db->getQuery(true)->delete('#__mokosuite_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.']);
} }
@@ -568,8 +568,8 @@ class DisplayController extends BaseController
'ordering' => 0, 'ordering' => 0,
]; ];
$id = $input->getInt('id', 0); $id = $input->getInt('id', 0);
if ($id) { $data->id = $id; $db->updateObject('#__mokosuite_ticket_automation', $data, 'id'); } if ($id) { $data->id = $id; $db->updateObject('#__mokosuiteclient_ticket_automation', $data, 'id'); }
else { $db->insertObject('#__mokosuite_ticket_automation', $data, 'id'); } else { $db->insertObject('#__mokosuiteclient_ticket_automation', $data, 'id'); }
$this->jsonResponse(['success' => true, 'message' => 'Rule saved.', 'id' => (int) $data->id]); $this->jsonResponse(['success' => true, 'message' => 'Rule saved.', 'id' => (int) $data->id]);
} }
@@ -578,7 +578,7 @@ class DisplayController extends BaseController
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('core.admin')) { $this->jsonForbidden(); } if (!$this->checkAcl('core.admin')) { $this->jsonForbidden(); }
$db = Factory::getDbo(); $db = Factory::getDbo();
$db->setQuery($db->getQuery(true)->delete('#__mokosuite_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.']);
} }
@@ -588,7 +588,7 @@ class DisplayController extends BaseController
if (!$this->checkAcl('core.admin')) { $this->jsonForbidden(); } 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('#__mokosuite_ticket_automation') $db->setQuery($db->getQuery(true)->update('#__mokosuiteclient_ticket_automation')
->set('enabled = ' . $input->getInt('enabled', 0)) ->set('enabled = ' . $input->getInt('enabled', 0))
->where('id = ' . $input->getInt('id', 0)))->execute(); ->where('id = ' . $input->getInt('id', 0)))->execute();
$this->jsonResponse(['success' => true, 'message' => 'Rule updated.']); $this->jsonResponse(['success' => true, 'message' => 'Rule updated.']);
@@ -611,8 +611,8 @@ class DisplayController extends BaseController
$db = Factory::getDbo(); $db = Factory::getDbo();
$settings = []; $settings = [];
// Export all MokoSuite plugin params // Export all MokoSuiteClient plugin params
$plugins = ['mokosuite', 'mokosuite_firewall', 'mokosuite_tenant', 'mokosuite_devtools', 'mokosuite_offline']; $plugins = ['mokosuiteclient', 'mokosuiteclient_firewall', 'mokosuiteclient_tenant', 'mokosuiteclient_devtools', 'mokosuiteclient_offline'];
foreach ($plugins as $element) foreach ($plugins as $element)
{ {
@@ -632,7 +632,7 @@ class DisplayController extends BaseController
$db->getQuery(true) $db->getQuery(true)
->select($db->quoteName('params')) ->select($db->quoteName('params'))
->from($db->quoteName('#__extensions')) ->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuite')) ->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuiteclient'))
->where($db->quoteName('type') . ' = ' . $db->quote('component')) ->where($db->quoteName('type') . ' = ' . $db->quote('component'))
); );
$settings['component'] = json_decode($db->loadResult() ?? '{}', true); $settings['component'] = json_decode($db->loadResult() ?? '{}', true);
@@ -688,7 +688,7 @@ class DisplayController extends BaseController
$db->getQuery(true) $db->getQuery(true)
->update($db->quoteName('#__extensions')) ->update($db->quoteName('#__extensions'))
->set($db->quoteName('params') . ' = ' . $db->quote(json_encode($data['component']))) ->set($db->quoteName('params') . ' = ' . $db->quote(json_encode($data['component'])))
->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuite')) ->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuiteclient'))
->where($db->quoteName('type') . ' = ' . $db->quote('component')) ->where($db->quoteName('type') . ' = ' . $db->quote('component'))
)->execute(); )->execute();
$count++; $count++;
@@ -712,7 +712,7 @@ class DisplayController extends BaseController
} }
$days = Factory::getApplication()->getInput()->getInt('days', 30); $days = Factory::getApplication()->getInput()->getInt('days', 30);
$model = new \Moko\Component\MokoSuite\Administrator\Model\WaflogModel(); $model = new \Moko\Component\MokoSuiteClient\Administrator\Model\WaflogModel();
$this->jsonResponse($model->purgeLogs($days)); $this->jsonResponse($model->purgeLogs($days));
} }
@@ -728,7 +728,7 @@ class DisplayController extends BaseController
} }
$ip = Factory::getApplication()->getInput()->getString('ip', ''); $ip = Factory::getApplication()->getInput()->getString('ip', '');
$model = new \Moko\Component\MokoSuite\Administrator\Model\WaflogModel(); $model = new \Moko\Component\MokoSuiteClient\Administrator\Model\WaflogModel();
$this->jsonResponse($model->banIp($ip)); $this->jsonResponse($model->banIp($ip));
} }
@@ -748,7 +748,7 @@ class DisplayController extends BaseController
} }
$input = Factory::getApplication()->getInput(); $input = Factory::getApplication()->getInput();
$model = new \Moko\Component\MokoSuite\Administrator\Model\PrivacyModel(); $model = new \Moko\Component\MokoSuiteClient\Administrator\Model\PrivacyModel();
$action = $input->getString('action', 'deny'); $action = $input->getString('action', 'deny');
if ($action === 'create') if ($action === 'create')
@@ -794,7 +794,7 @@ class DisplayController extends BaseController
return; return;
} }
$model = new \Moko\Component\MokoSuite\Administrator\Model\PrivacyModel(); $model = new \Moko\Component\MokoSuiteClient\Administrator\Model\PrivacyModel();
$this->jsonResponse($model->exportUserData( $this->jsonResponse($model->exportUserData(
Factory::getApplication()->getInput()->getInt('user_id', 0) Factory::getApplication()->getInput()->getInt('user_id', 0)
@@ -809,7 +809,7 @@ class DisplayController extends BaseController
{ {
Session::checkToken() or die(Text::_('JINVALID_TOKEN')); Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokosuite.tickets')) if (!$this->checkAcl('mokosuiteclient.tickets'))
{ {
$this->jsonForbidden(); $this->jsonForbidden();
return; return;
@@ -836,19 +836,19 @@ class DisplayController extends BaseController
// ================================================================== // ==================================================================
/** /**
* Check a MokoSuite ACL permission for the current user. * Check a MokoSuiteClient ACL permission for the current user.
*/ */
private function checkAcl(string $action): bool private function checkAcl(string $action): bool
{ {
$user = Factory::getApplication()->getIdentity(); $user = Factory::getApplication()->getIdentity();
// Super admins always pass // Super admins always pass
if ($user->authorise('core.admin', 'com_mokosuite')) if ($user->authorise('core.admin', 'com_mokosuiteclient'))
{ {
return true; return true;
} }
return $user->authorise($action, 'com_mokosuite'); return $user->authorise($action, 'com_mokosuiteclient');
} }
/** /**
@@ -1,12 +1,12 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
namespace Moko\Component\MokoSuite\Administrator\Model; namespace Moko\Component\MokoSuiteClient\Administrator\Model;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -21,7 +21,7 @@ class DashboardModel extends BaseDatabaseModel
* Provides icon, category, and description for dashboard display. * Provides icon, category, and description for dashboard display.
*/ */
private const PLUGIN_META = [ private const PLUGIN_META = [
'mokosuite' => [ 'mokosuiteclient' => [
'icon' => 'icon-shield-alt', 'icon' => 'icon-shield-alt',
'category' => 'core', 'category' => 'core',
'label' => 'Core', 'label' => 'Core',
@@ -29,7 +29,7 @@ class DashboardModel extends BaseDatabaseModel
'protected' => true, 'protected' => true,
'configure_only' => false, 'configure_only' => false,
], ],
'mokosuite_firewall' => [ 'mokosuiteclient_firewall' => [
'icon' => 'icon-lock', 'icon' => 'icon-lock',
'category' => 'security', 'category' => 'security',
'label' => 'Firewall', 'label' => 'Firewall',
@@ -37,7 +37,7 @@ class DashboardModel extends BaseDatabaseModel
'protected' => false, 'protected' => false,
'configure_only' => false, 'configure_only' => false,
], ],
'mokosuite_tenant' => [ 'mokosuiteclient_tenant' => [
'icon' => 'icon-users', 'icon' => 'icon-users',
'category' => 'security', 'category' => 'security',
'label' => 'Tenant Restrictions', 'label' => 'Tenant Restrictions',
@@ -45,7 +45,7 @@ class DashboardModel extends BaseDatabaseModel
'protected' => false, 'protected' => false,
'configure_only' => false, 'configure_only' => false,
], ],
'mokosuite_offline' => [ 'mokosuiteclient_offline' => [
'icon' => 'icon-globe', 'icon' => 'icon-globe',
'category' => 'security', 'category' => 'security',
'label' => 'Offline Bypass', 'label' => 'Offline Bypass',
@@ -53,7 +53,7 @@ class DashboardModel extends BaseDatabaseModel
'protected' => false, 'protected' => false,
'configure_only' => true, 'configure_only' => true,
], ],
'mokosuite_devtools' => [ 'mokosuiteclient_devtools' => [
'icon' => 'icon-wrench', 'icon' => 'icon-wrench',
'category' => 'tools', 'category' => 'tools',
'label' => 'Developer Tools', 'label' => 'Developer Tools',
@@ -61,7 +61,7 @@ class DashboardModel extends BaseDatabaseModel
'protected' => false, 'protected' => false,
'configure_only' => true, 'configure_only' => true,
], ],
'mokosuitedemo' => [ 'mokosuiteclientdemo' => [
'icon' => 'icon-undo', 'icon' => 'icon-undo',
'category' => 'content', 'category' => 'content',
'label' => 'Demo Reset Task', 'label' => 'Demo Reset Task',
@@ -69,11 +69,11 @@ class DashboardModel extends BaseDatabaseModel
'protected' => false, 'protected' => false,
'configure_only' => true, 'configure_only' => true,
], ],
'mokosuitesync' => [ 'mokosuiteclientsync' => [
'icon' => 'icon-sync', 'icon' => 'icon-sync',
'category' => 'content', 'category' => 'content',
'label' => 'Content Sync Task', 'label' => 'Content Sync Task',
'description' => 'Scheduled content synchronisation to remote MokoSuite sites.', 'description' => 'Scheduled content synchronisation to remote MokoSuiteClient sites.',
'protected' => false, 'protected' => false,
'configure_only' => true, 'configure_only' => true,
], ],
@@ -92,7 +92,7 @@ class DashboardModel extends BaseDatabaseModel
]; ];
/** /**
* Discover all installed MokoSuite plugins. * Discover all installed MokoSuiteClient plugins.
* *
* @return array Plugin rows enriched with dashboard metadata. * @return array Plugin rows enriched with dashboard metadata.
*/ */
@@ -114,20 +114,20 @@ class DashboardModel extends BaseDatabaseModel
->from($db->quoteName('#__extensions')) ->from($db->quoteName('#__extensions'))
->where([ ->where([
'(' . '(' .
// System plugins: mokosuite, mokosuite_* // System plugins: mokosuiteclient, mokosuiteclient_*
'(' . $db->quoteName('type') . ' = ' . $db->quote('plugin') '(' . $db->quoteName('type') . ' = ' . $db->quote('plugin')
. ' AND ' . $db->quoteName('folder') . ' = ' . $db->quote('system') . ' AND ' . $db->quoteName('folder') . ' = ' . $db->quote('system')
. ' AND (' . $db->quoteName('element') . ' = ' . $db->quote('mokosuite') . ' AND (' . $db->quoteName('element') . ' = ' . $db->quote('mokosuiteclient')
. ' OR ' . $db->quoteName('element') . ' LIKE ' . $db->quote('mokosuite\_%') . ')' . ' OR ' . $db->quoteName('element') . ' LIKE ' . $db->quote('mokosuiteclient\_%') . ')'
. ' AND ' . $db->quoteName('element') . ' != ' . $db->quote('mokosuite_monitor') . ')' . ' AND ' . $db->quoteName('element') . ' != ' . $db->quote('mokosuiteclient_monitor') . ')'
// Webservices plugins // Webservices plugins
. ' OR (' . $db->quoteName('type') . ' = ' . $db->quote('plugin') . ' OR (' . $db->quoteName('type') . ' = ' . $db->quote('plugin')
. ' AND ' . $db->quoteName('folder') . ' = ' . $db->quote('webservices') . ' AND ' . $db->quoteName('folder') . ' = ' . $db->quote('webservices')
. ' AND ' . $db->quoteName('element') . ' = ' . $db->quote('mokosuite') . ')' . ' AND ' . $db->quoteName('element') . ' = ' . $db->quote('mokosuiteclient') . ')'
// Task plugins // Task plugins
. ' OR (' . $db->quoteName('type') . ' = ' . $db->quote('plugin') . ' OR (' . $db->quoteName('type') . ' = ' . $db->quote('plugin')
. ' AND ' . $db->quoteName('folder') . ' = ' . $db->quote('task') . ' AND ' . $db->quoteName('folder') . ' = ' . $db->quote('task')
. ' AND ' . $db->quoteName('element') . ' LIKE ' . $db->quote('mokosuite%') . ')' . ' AND ' . $db->quoteName('element') . ' LIKE ' . $db->quote('mokosuiteclient%') . ')'
. ')', . ')',
]) ])
->order($db->quoteName('folder') . ' ASC, ' . $db->quoteName('element') . ' ASC'); ->order($db->quoteName('folder') . ' ASC, ' . $db->quoteName('element') . ' ASC');
@@ -190,11 +190,11 @@ class DashboardModel extends BaseDatabaseModel
$config = $app->getConfig(); $config = $app->getConfig();
$db = $this->getDatabase(); $db = $this->getDatabase();
// Get MokoSuite package version // Get MokoSuiteClient package version
$query = $db->getQuery(true) $query = $db->getQuery(true)
->select($db->quoteName('manifest_cache')) ->select($db->quoteName('manifest_cache'))
->from($db->quoteName('#__extensions')) ->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' = ' . $db->quote('pkg_mokosuite')) ->where($db->quoteName('element') . ' = ' . $db->quote('pkg_mokosuiteclient'))
->where($db->quoteName('type') . ' = ' . $db->quote('package')); ->where($db->quoteName('type') . ' = ' . $db->quote('package'));
$db->setQuery($query); $db->setQuery($query);
$pkgCache = json_decode($db->loadResult() ?? '{}'); $pkgCache = json_decode($db->loadResult() ?? '{}');
@@ -204,7 +204,7 @@ class DashboardModel extends BaseDatabaseModel
'joomla_version' => (new Version())->getShortVersion(), 'joomla_version' => (new Version())->getShortVersion(),
'php_version' => PHP_VERSION, 'php_version' => PHP_VERSION,
'db_type' => $db->getServerType(), 'db_type' => $db->getServerType(),
'mokosuite_version' => $pkgCache->version ?? '—', 'mokosuiteclient_version' => $pkgCache->version ?? '—',
'debug' => (bool) $config->get('debug'), 'debug' => (bool) $config->get('debug'),
'offline' => (bool) $config->get('offline'), 'offline' => (bool) $config->get('offline'),
'sef' => (bool) $config->get('sef'), 'sef' => (bool) $config->get('sef'),
@@ -213,7 +213,7 @@ class DashboardModel extends BaseDatabaseModel
} }
/** /**
* Get installed MokoSuite component and modules with versions. * Get installed MokoSuiteClient component and modules with versions.
* *
* @return array Array of extension objects with name, element, type, version. * @return array Array of extension objects with name, element, type, version.
*/ */
@@ -232,10 +232,10 @@ class DashboardModel extends BaseDatabaseModel
->where('(' ->where('('
// The component // The component
. '(' . $db->quoteName('type') . ' = ' . $db->quote('component') . '(' . $db->quoteName('type') . ' = ' . $db->quote('component')
. ' AND ' . $db->quoteName('element') . ' = ' . $db->quote('com_mokosuite') . ')' . ' AND ' . $db->quoteName('element') . ' = ' . $db->quote('com_mokosuiteclient') . ')'
// Admin modules // Admin modules
. ' OR (' . $db->quoteName('type') . ' = ' . $db->quote('module') . ' OR (' . $db->quoteName('type') . ' = ' . $db->quote('module')
. ' AND ' . $db->quoteName('element') . ' LIKE ' . $db->quote('mod_mokosuite%') . ')' . ' AND ' . $db->quoteName('element') . ' LIKE ' . $db->quote('mod_mokosuiteclient%') . ')'
. ')') . ')')
->order($db->quoteName('type') . ' ASC, ' . $db->quoteName('element') . ' ASC'); ->order($db->quoteName('type') . ' ASC, ' . $db->quoteName('element') . ' ASC');
@@ -272,7 +272,7 @@ class DashboardModel extends BaseDatabaseModel
{ {
$db = $this->getDatabase(); $db = $this->getDatabase();
// Verify the extension exists and is a MokoSuite plugin // Verify the extension exists and is a MokoSuiteClient plugin
$query = $db->getQuery(true) $query = $db->getQuery(true)
->select([$db->quoteName('element'), $db->quoteName('protected')]) ->select([$db->quoteName('element'), $db->quoteName('protected')])
->from($db->quoteName('#__extensions')) ->from($db->quoteName('#__extensions'))
@@ -287,7 +287,7 @@ class DashboardModel extends BaseDatabaseModel
} }
// Don't allow disabling protected/core plugins // Don't allow disabling protected/core plugins
if (!$enabled && ((int) $ext->protected || $ext->element === 'mokosuite')) if (!$enabled && ((int) $ext->protected || $ext->element === 'mokosuiteclient'))
{ {
return ['success' => false, 'message' => 'This plugin is protected and cannot be disabled.']; return ['success' => false, 'message' => 'This plugin is protected and cannot be disabled.'];
} }
@@ -425,7 +425,7 @@ class DashboardModel extends BaseDatabaseModel
if (str_contains($row->element, 'sync')) if (str_contains($row->element, 'sync'))
{ {
$meta['label'] = 'Content Sync Task'; $meta['label'] = 'Content Sync Task';
$meta['description'] = 'Scheduled content synchronisation to remote MokoSuite sites.'; $meta['description'] = 'Scheduled content synchronisation to remote MokoSuiteClient sites.';
} }
elseif (str_contains($row->element, 'demo')) elseif (str_contains($row->element, 'demo'))
{ {
@@ -544,7 +544,7 @@ class DashboardModel extends BaseDatabaseModel
$db = $this->getDatabase(); $db = $this->getDatabase();
$query = $db->getQuery(true) $query = $db->getQuery(true)
->select('*') ->select('*')
->from($db->quoteName('#__mokosuite_waf_log')) ->from($db->quoteName('#__mokosuiteclient_waf_log'))
->order($db->quoteName('created') . ' DESC') ->order($db->quoteName('created') . ' DESC')
->setLimit($limit); ->setLimit($limit);
$db->setQuery($query); $db->setQuery($query);
@@ -567,7 +567,7 @@ class DashboardModel extends BaseDatabaseModel
$db = $this->getDatabase(); $db = $this->getDatabase();
$db->setQuery( $db->setQuery(
"SELECT DATE(" . $db->quoteName('created') . ") AS day, COUNT(*) AS total" "SELECT DATE(" . $db->quoteName('created') . ") AS day, COUNT(*) AS total"
. " FROM " . $db->quoteName('#__mokosuite_waf_log') . " FROM " . $db->quoteName('#__mokosuiteclient_waf_log')
. " WHERE " . $db->quoteName('created') . " >= DATE_SUB(NOW(), INTERVAL $days DAY)" . " WHERE " . $db->quoteName('created') . " >= DATE_SUB(NOW(), INTERVAL $days DAY)"
. " GROUP BY day ORDER BY day" . " GROUP BY day ORDER BY day"
); );
@@ -1,5 +1,5 @@
<?php <?php
namespace Moko\Component\MokoSuite\Administrator\Model; namespace Moko\Component\MokoSuiteClient\Administrator\Model;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -15,7 +15,7 @@ class ErpReportsModel extends BaseDatabaseModel
$db->setQuery($db->getQuery(true) $db->setQuery($db->getQuery(true)
->select('DATE_FORMAT(inv.created, ' . $db->quote($fmt) . ') AS period') ->select('DATE_FORMAT(inv.created, ' . $db->quote($fmt) . ') AS period')
->select('COUNT(*) AS invoice_count, COALESCE(SUM(inv.total), 0) AS revenue, COALESCE(SUM(inv.amount_paid), 0) AS collected') ->select('COUNT(*) AS invoice_count, COALESCE(SUM(inv.total), 0) AS revenue, COALESCE(SUM(inv.amount_paid), 0) AS collected')
->from($db->quoteName('#__mokosuite_erp_invoices', 'inv')) ->from($db->quoteName('#__mokosuiteclient_erp_invoices', 'inv'))
->where($db->quoteName('inv.created') . ' >= ' . $db->quote($from)) ->where($db->quoteName('inv.created') . ' >= ' . $db->quote($from))
->where($db->quoteName('inv.created') . ' <= ' . $db->quote($to . ' 23:59:59')) ->where($db->quoteName('inv.created') . ' <= ' . $db->quote($to . ' 23:59:59'))
->where($db->quoteName('inv.type') . ' = ' . $db->quote('standard')) ->where($db->quoteName('inv.type') . ' = ' . $db->quote('standard'))
@@ -28,7 +28,7 @@ class ErpReportsModel extends BaseDatabaseModel
$db = $this->getDatabase(); $db = $this->getDatabase();
$db->setQuery($db->getQuery(true) $db->setQuery($db->getQuery(true)
->select('cd.name AS contact_name, COALESCE(SUM(inv.total), 0) AS total_revenue, COUNT(*) AS invoice_count') ->select('cd.name AS contact_name, COALESCE(SUM(inv.total), 0) AS total_revenue, COUNT(*) AS invoice_count')
->from($db->quoteName('#__mokosuite_erp_invoices', 'inv')) ->from($db->quoteName('#__mokosuiteclient_erp_invoices', 'inv'))
->join('LEFT', $db->quoteName('#__contact_details', 'cd') . ' ON cd.id = inv.contact_id') ->join('LEFT', $db->quoteName('#__contact_details', 'cd') . ' ON cd.id = inv.contact_id')
->where($db->quoteName('inv.created') . ' >= ' . $db->quote($from)) ->where($db->quoteName('inv.created') . ' >= ' . $db->quote($from))
->where($db->quoteName('inv.created') . ' <= ' . $db->quote($to . ' 23:59:59')) ->where($db->quoteName('inv.created') . ' <= ' . $db->quote($to . ' 23:59:59'))
@@ -41,9 +41,9 @@ class ErpReportsModel extends BaseDatabaseModel
$db = $this->getDatabase(); $db = $this->getDatabase();
$db->setQuery($db->getQuery(true) $db->setQuery($db->getQuery(true)
->select('p.sku, c.title AS product_name, COALESCE(SUM(ii.quantity), 0) AS qty_sold, COALESCE(SUM(ii.line_total), 0) AS revenue') ->select('p.sku, c.title AS product_name, COALESCE(SUM(ii.quantity), 0) AS qty_sold, COALESCE(SUM(ii.line_total), 0) AS revenue')
->from($db->quoteName('#__mokosuite_erp_invoice_items', 'ii')) ->from($db->quoteName('#__mokosuiteclient_erp_invoice_items', 'ii'))
->join('INNER', $db->quoteName('#__mokosuite_erp_invoices', 'inv') . ' ON inv.id = ii.invoice_id') ->join('INNER', $db->quoteName('#__mokosuiteclient_erp_invoices', 'inv') . ' ON inv.id = ii.invoice_id')
->join('LEFT', $db->quoteName('#__mokosuite_erp_products', 'p') . ' ON p.id = ii.product_id') ->join('LEFT', $db->quoteName('#__mokosuiteclient_erp_products', 'p') . ' ON p.id = ii.product_id')
->join('LEFT', $db->quoteName('#__content', 'c') . ' ON c.id = p.article_id') ->join('LEFT', $db->quoteName('#__content', 'c') . ' ON c.id = p.article_id')
->where($db->quoteName('inv.created') . ' >= ' . $db->quote($from)) ->where($db->quoteName('inv.created') . ' >= ' . $db->quote($from))
->where($db->quoteName('inv.created') . ' <= ' . $db->quote($to . ' 23:59:59')) ->where($db->quoteName('inv.created') . ' <= ' . $db->quote($to . ' 23:59:59'))
@@ -57,7 +57,7 @@ class ErpReportsModel extends BaseDatabaseModel
$db = $this->getDatabase(); $db = $this->getDatabase();
$db->setQuery($db->getQuery(true) $db->setQuery($db->getQuery(true)
->select('status, COUNT(*) AS cnt, COALESCE(SUM(value), 0) AS total_value') ->select('status, COUNT(*) AS cnt, COALESCE(SUM(value), 0) AS total_value')
->from($db->quoteName('#__mokosuite_erp_deals')) ->from($db->quoteName('#__mokosuiteclient_erp_deals'))
->where($db->quoteName('created') . ' >= ' . $db->quote($from)) ->where($db->quoteName('created') . ' >= ' . $db->quote($from))
->where($db->quoteName('created') . ' <= ' . $db->quote($to . ' 23:59:59')) ->where($db->quoteName('created') . ' <= ' . $db->quote($to . ' 23:59:59'))
->group('status')); ->group('status'));
@@ -70,7 +70,7 @@ class ErpReportsModel extends BaseDatabaseModel
$db->setQuery($db->getQuery(true) $db->setQuery($db->getQuery(true)
->select('inv.id, inv.ref, inv.total, inv.amount_paid, inv.due_date, (inv.total - inv.amount_paid) AS balance, DATEDIFF(CURDATE(), inv.due_date) AS days_overdue') ->select('inv.id, inv.ref, inv.total, inv.amount_paid, inv.due_date, (inv.total - inv.amount_paid) AS balance, DATEDIFF(CURDATE(), inv.due_date) AS days_overdue')
->select('cd.name AS contact_name') ->select('cd.name AS contact_name')
->from($db->quoteName('#__mokosuite_erp_invoices', 'inv')) ->from($db->quoteName('#__mokosuiteclient_erp_invoices', 'inv'))
->join('LEFT', $db->quoteName('#__contact_details', 'cd') . ' ON cd.id = inv.contact_id') ->join('LEFT', $db->quoteName('#__contact_details', 'cd') . ' ON cd.id = inv.contact_id')
->where($db->quoteName('inv.status') . ' IN (' . $db->quote('sent') . ',' . $db->quote('partial') . ',' . $db->quote('overdue') . ')') ->where($db->quoteName('inv.status') . ' IN (' . $db->quote('sent') . ',' . $db->quote('partial') . ',' . $db->quote('overdue') . ')')
->where('(inv.total - inv.amount_paid) > 0') ->where('(inv.total - inv.amount_paid) > 0')
@@ -1,12 +1,12 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
namespace Moko\Component\MokoSuite\Administrator\Model; namespace Moko\Component\MokoSuiteClient\Administrator\Model;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -104,7 +104,7 @@ class ExtensionsModel extends BaseDatabaseModel
public function installFromUrl(string $url): array public function installFromUrl(string $url): array
{ {
$tmpPath = Factory::getConfig()->get('tmp_path', JPATH_ROOT . '/tmp'); $tmpPath = Factory::getConfig()->get('tmp_path', JPATH_ROOT . '/tmp');
$tmpFile = $tmpPath . '/mokosuite_install_' . md5($url) . '.zip'; $tmpFile = $tmpPath . '/mokosuiteclient_install_' . md5($url) . '.zip';
try try
{ {
@@ -160,7 +160,7 @@ class ExtensionsModel extends BaseDatabaseModel
return $this->catalogCache; return $this->catalogCache;
} }
$catalogFile = JPATH_ADMINISTRATOR . '/components/com_mokosuite/catalog.xml'; $catalogFile = JPATH_ADMINISTRATOR . '/components/com_mokosuiteclient/catalog.xml';
if (!file_exists($catalogFile)) if (!file_exists($catalogFile))
{ {
@@ -1,12 +1,12 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
namespace Moko\Component\MokoSuite\Administrator\Model; namespace Moko\Component\MokoSuiteClient\Administrator\Model;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -63,7 +63,7 @@ class HtaccessModel extends BaseDatabaseModel
$query = $db->getQuery(true) $query = $db->getQuery(true)
->select($db->quoteName('params')) ->select($db->quoteName('params'))
->from($db->quoteName('#__extensions')) ->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuite')) ->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuiteclient'))
->where($db->quoteName('type') . ' = ' . $db->quote('component')); ->where($db->quoteName('type') . ' = ' . $db->quote('component'));
$db->setQuery($query); $db->setQuery($query);
$params = new Registry($db->loadResult() ?? '{}'); $params = new Registry($db->loadResult() ?? '{}');
@@ -89,7 +89,7 @@ class HtaccessModel extends BaseDatabaseModel
$query = $db->getQuery(true) $query = $db->getQuery(true)
->select($db->quoteName('params')) ->select($db->quoteName('params'))
->from($db->quoteName('#__extensions')) ->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuite')) ->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuiteclient'))
->where($db->quoteName('type') . ' = ' . $db->quote('component')); ->where($db->quoteName('type') . ' = ' . $db->quote('component'));
$db->setQuery($query); $db->setQuery($query);
$params = new Registry($db->loadResult() ?? '{}'); $params = new Registry($db->loadResult() ?? '{}');
@@ -107,7 +107,7 @@ class HtaccessModel extends BaseDatabaseModel
$db->getQuery(true) $db->getQuery(true)
->update($db->quoteName('#__extensions')) ->update($db->quoteName('#__extensions'))
->set($db->quoteName('params') . ' = ' . $db->quote($params->toString())) ->set($db->quoteName('params') . ' = ' . $db->quote($params->toString()))
->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuite')) ->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuiteclient'))
->where($db->quoteName('type') . ' = ' . $db->quote('component')) ->where($db->quoteName('type') . ' = ' . $db->quote('component'))
)->execute(); )->execute();
@@ -135,7 +135,7 @@ class HtaccessModel extends BaseDatabaseModel
public function saveHtaccess(string $content): array public function saveHtaccess(string $content): array
{ {
$path = JPATH_ROOT . '/.htaccess'; $path = JPATH_ROOT . '/.htaccess';
$backup = JPATH_ROOT . '/.htaccess.mokosuite.bak'; $backup = JPATH_ROOT . '/.htaccess.mokosuiteclient.bak';
try try
{ {
@@ -158,7 +158,7 @@ class HtaccessModel extends BaseDatabaseModel
return ['success' => false, 'message' => '.htaccess is not writable.']; return ['success' => false, 'message' => '.htaccess is not writable.'];
} }
return ['success' => true, 'message' => '.htaccess saved. Backup at .htaccess.mokosuite.bak']; return ['success' => true, 'message' => '.htaccess saved. Backup at .htaccess.mokosuiteclient.bak'];
} }
catch (\Throwable $e) catch (\Throwable $e)
{ {
@@ -178,9 +178,9 @@ class HtaccessModel extends BaseDatabaseModel
{ {
$lines = []; $lines = [];
$lines[] = '##'; $lines[] = '##';
$lines[] = '## MokoSuite Generated .htaccess'; $lines[] = '## MokoSuiteClient Generated .htaccess';
$lines[] = '## Generated: ' . gmdate('Y-m-d H:i:s') . ' UTC'; $lines[] = '## Generated: ' . gmdate('Y-m-d H:i:s') . ' UTC';
$lines[] = '## DO NOT EDIT — regenerate from MokoSuite > .htaccess Maker'; $lines[] = '## DO NOT EDIT — regenerate from MokoSuiteClient > .htaccess Maker';
$lines[] = '##'; $lines[] = '##';
$lines[] = ''; $lines[] = '';
@@ -412,7 +412,7 @@ class HtaccessModel extends BaseDatabaseModel
public function generateNginx(array $opts): string public function generateNginx(array $opts): string
{ {
$lines = []; $lines = [];
$lines[] = '## MokoSuite Generated NginX Configuration'; $lines[] = '## MokoSuiteClient Generated NginX Configuration';
$lines[] = '## Add these directives inside your server { } block'; $lines[] = '## Add these directives inside your server { } block';
$lines[] = ''; $lines[] = '';
@@ -1,12 +1,12 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
namespace Moko\Component\MokoSuite\Administrator\Model; namespace Moko\Component\MokoSuiteClient\Administrator\Model;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -16,10 +16,10 @@ use Joomla\CMS\MVC\Model\BaseDatabaseModel;
use Joomla\Registry\Registry; use Joomla\Registry\Registry;
/** /**
* Importer for migrating from Akeeba Admin Tools to MokoSuite. * Importer for migrating from Akeeba Admin Tools to MokoSuiteClient.
* *
* Reads Admin Tools WAF config, htaccess settings, IP blocklists, * Reads Admin Tools WAF config, htaccess settings, IP blocklists,
* and security headers maps them to MokoSuite firewall plugin params * and security headers maps them to MokoSuiteClient firewall plugin params
* and htaccess maker options. * and htaccess maker options.
* *
* @since 02.32.00 * @since 02.32.00
@@ -94,7 +94,7 @@ class ImportModel extends BaseDatabaseModel
} }
/** /**
* Import Admin Tools settings into MokoSuite. * Import Admin Tools settings into MokoSuiteClient.
*/ */
public function importAdminTools(): array public function importAdminTools(): array
{ {
@@ -111,7 +111,7 @@ class ImportModel extends BaseDatabaseModel
if (!empty($firewallParams)) if (!empty($firewallParams))
{ {
$this->mergePluginParams('mokosuite_firewall', 'system', $firewallParams); $this->mergePluginParams('mokosuiteclient_firewall', 'system', $firewallParams);
$results['firewall'] = \count($firewallParams); $results['firewall'] = \count($firewallParams);
} }
@@ -260,7 +260,7 @@ class ImportModel extends BaseDatabaseModel
} }
/** /**
* Map Admin Tools WAF config to MokoSuite firewall plugin params. * Map Admin Tools WAF config to MokoSuiteClient firewall plugin params.
*/ */
private function mapWafToFirewall(array $waf): array private function mapWafToFirewall(array $waf): array
{ {
@@ -332,7 +332,7 @@ class ImportModel extends BaseDatabaseModel
} }
/** /**
* Map Admin Tools config to MokoSuite htaccess maker options. * Map Admin Tools config to MokoSuiteClient htaccess maker options.
*/ */
private function mapToHtaccess(array $storage, array $waf): array private function mapToHtaccess(array $storage, array $waf): array
{ {
@@ -448,7 +448,7 @@ class ImportModel extends BaseDatabaseModel
$query = $db->getQuery(true) $query = $db->getQuery(true)
->select($db->quoteName('params')) ->select($db->quoteName('params'))
->from($db->quoteName('#__extensions')) ->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuite')) ->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuiteclient'))
->where($db->quoteName('type') . ' = ' . $db->quote('component')); ->where($db->quoteName('type') . ' = ' . $db->quote('component'));
$db->setQuery($query); $db->setQuery($query);
$params = new Registry($db->loadResult() ?? '{}'); $params = new Registry($db->loadResult() ?? '{}');
@@ -466,7 +466,7 @@ class ImportModel extends BaseDatabaseModel
$db->getQuery(true) $db->getQuery(true)
->update($db->quoteName('#__extensions')) ->update($db->quoteName('#__extensions'))
->set($db->quoteName('params') . ' = ' . $db->quote($params->toString())) ->set($db->quoteName('params') . ' = ' . $db->quote($params->toString()))
->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuite')) ->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuiteclient'))
->where($db->quoteName('type') . ' = ' . $db->quote('component')) ->where($db->quoteName('type') . ' = ' . $db->quote('component'))
)->execute(); )->execute();
} }
@@ -481,7 +481,7 @@ class ImportModel extends BaseDatabaseModel
$query = $db->getQuery(true) $query = $db->getQuery(true)
->select($db->quoteName('params')) ->select($db->quoteName('params'))
->from($db->quoteName('#__extensions')) ->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' = ' . $db->quote('mokosuite_firewall')) ->where($db->quoteName('element') . ' = ' . $db->quote('mokosuiteclient_firewall'))
->where($db->quoteName('type') . ' = ' . $db->quote('plugin')) ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' . $db->quote('system')); ->where($db->quoteName('folder') . ' = ' . $db->quote('system'));
$db->setQuery($query); $db->setQuery($query);
@@ -513,7 +513,7 @@ class ImportModel extends BaseDatabaseModel
$db->getQuery(true) $db->getQuery(true)
->update($db->quoteName('#__extensions')) ->update($db->quoteName('#__extensions'))
->set($db->quoteName('params') . ' = ' . $db->quote($params->toString())) ->set($db->quoteName('params') . ' = ' . $db->quote($params->toString()))
->where($db->quoteName('element') . ' = ' . $db->quote('mokosuite_firewall')) ->where($db->quoteName('element') . ' = ' . $db->quote('mokosuiteclient_firewall'))
->where($db->quoteName('type') . ' = ' . $db->quote('plugin')) ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' . $db->quote('system')) ->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
)->execute(); )->execute();
@@ -541,7 +541,7 @@ class ImportModel extends BaseDatabaseModel
->where($db->quoteName('type') . ' = ' . $db->quote('plugin')) ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
)->execute(); )->execute();
Log::add('Admin Tools component and plugins disabled after MokoSuite import', Log::INFO, 'mokosuite'); Log::add('Admin Tools component and plugins disabled after MokoSuiteClient import', Log::INFO, 'mokosuiteclient');
} }
// ================================================================== // ==================================================================
@@ -619,7 +619,7 @@ class ImportModel extends BaseDatabaseModel
)->execute(); )->execute();
$result['message'] .= ' Akeeba Ticket System has been disabled.'; $result['message'] .= ' Akeeba Ticket System has been disabled.';
Log::add('Akeeba Ticket System disabled after MokoSuite import', Log::INFO, 'mokosuite'); Log::add('Akeeba Ticket System disabled after MokoSuiteClient import', Log::INFO, 'mokosuiteclient');
} }
catch (\Throwable $e) catch (\Throwable $e)
{ {
@@ -644,7 +644,7 @@ class ImportModel extends BaseDatabaseModel
$db->getQuery(true) $db->getQuery(true)
->select($db->quoteName('params')) ->select($db->quoteName('params'))
->from($db->quoteName('#__extensions')) ->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuite')) ->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuiteclient'))
->where($db->quoteName('type') . ' = ' . $db->quote('component')) ->where($db->quoteName('type') . ' = ' . $db->quote('component'))
); );
$params = new Registry($db->loadResult() ?? '{}'); $params = new Registry($db->loadResult() ?? '{}');
@@ -666,7 +666,7 @@ class ImportModel extends BaseDatabaseModel
$db->getQuery(true) $db->getQuery(true)
->select($db->quoteName('params')) ->select($db->quoteName('params'))
->from($db->quoteName('#__extensions')) ->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuite')) ->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuiteclient'))
->where($db->quoteName('type') . ' = ' . $db->quote('component')) ->where($db->quoteName('type') . ' = ' . $db->quote('component'))
); );
$params = new Registry($db->loadResult() ?? '{}'); $params = new Registry($db->loadResult() ?? '{}');
@@ -676,13 +676,13 @@ class ImportModel extends BaseDatabaseModel
$db->getQuery(true) $db->getQuery(true)
->update($db->quoteName('#__extensions')) ->update($db->quoteName('#__extensions'))
->set($db->quoteName('params') . ' = ' . $db->quote($params->toString())) ->set($db->quoteName('params') . ' = ' . $db->quote($params->toString()))
->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuite')) ->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuiteclient'))
->where($db->quoteName('type') . ' = ' . $db->quote('component')) ->where($db->quoteName('type') . ' = ' . $db->quote('component'))
)->execute(); )->execute();
} }
catch (\Throwable $e) catch (\Throwable $e)
{ {
Log::add('Import marker error: ' . $e->getMessage(), Log::WARNING, 'mokosuite'); Log::add('Import marker error: ' . $e->getMessage(), Log::WARNING, 'mokosuiteclient');
} }
} }
} }
@@ -1,5 +1,5 @@
<?php <?php
namespace Moko\Component\MokoSuite\Administrator\Model; namespace Moko\Component\MokoSuiteClient\Administrator\Model;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -36,7 +36,7 @@ class MaintenanceModel extends BaseDatabaseModel
'engine' => $t->Engine, 'engine' => $t->Engine,
'size_mb' => $sizeMb, 'size_mb' => $sizeMb,
'overhead_kb' => $overheadKb, 'overhead_kb' => $overheadKb,
'is_moko' => str_contains($t->Name, 'mokosuite'), 'is_moko' => str_contains($t->Name, 'mokosuiteclient'),
]; ];
} }
@@ -1,12 +1,12 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
namespace Moko\Component\MokoSuite\Administrator\Model; namespace Moko\Component\MokoSuiteClient\Administrator\Model;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -30,7 +30,7 @@ class PrivacyModel extends BaseDatabaseModel
$db->quoteName('u.username'), $db->quoteName('u.username'),
$db->quoteName('p.name', 'processed_by_name'), $db->quoteName('p.name', 'processed_by_name'),
]) ])
->from($db->quoteName('#__mokosuite_data_requests', 'r')) ->from($db->quoteName('#__mokosuiteclient_data_requests', 'r'))
->leftJoin($db->quoteName('#__users', 'u') . ' ON u.id = r.user_id') ->leftJoin($db->quoteName('#__users', 'u') . ' ON u.id = r.user_id')
->leftJoin($db->quoteName('#__users', 'p') . ' ON p.id = r.processed_by'); ->leftJoin($db->quoteName('#__users', 'p') . ' ON p.id = r.processed_by');
@@ -68,7 +68,7 @@ class PrivacyModel extends BaseDatabaseModel
'created' => Factory::getDate()->toSql(), 'created' => Factory::getDate()->toSql(),
]; ];
$db->insertObject('#__mokosuite_data_requests', $row, 'id'); $db->insertObject('#__mokosuiteclient_data_requests', $row, 'id');
return ['success' => true, 'message' => ucfirst($type) . ' request #' . $row->id . ' created.', 'id' => (int) $row->id]; return ['success' => true, 'message' => ucfirst($type) . ' request #' . $row->id . ' created.', 'id' => (int) $row->id];
} }
@@ -90,7 +90,7 @@ class PrivacyModel extends BaseDatabaseModel
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->select('*') ->select('*')
->from($db->quoteName('#__mokosuite_data_requests')) ->from($db->quoteName('#__mokosuiteclient_data_requests'))
->where($db->quoteName('id') . ' = ' . $requestId) ->where($db->quoteName('id') . ' = ' . $requestId)
); );
$request = $db->loadObject(); $request = $db->loadObject();
@@ -104,7 +104,7 @@ class PrivacyModel extends BaseDatabaseModel
{ {
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->update($db->quoteName('#__mokosuite_data_requests')) ->update($db->quoteName('#__mokosuiteclient_data_requests'))
->set($db->quoteName('status') . ' = ' . $db->quote('denied')) ->set($db->quoteName('status') . ' = ' . $db->quote('denied'))
->set($db->quoteName('processed_by') . ' = ' . (int) Factory::getApplication()->getIdentity()->id) ->set($db->quoteName('processed_by') . ' = ' . (int) Factory::getApplication()->getIdentity()->id)
->set($db->quoteName('processed') . ' = ' . $db->quote(Factory::getDate()->toSql())) ->set($db->quoteName('processed') . ' = ' . $db->quote(Factory::getDate()->toSql()))
@@ -117,7 +117,7 @@ class PrivacyModel extends BaseDatabaseModel
// Mark as processing // Mark as processing
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->update($db->quoteName('#__mokosuite_data_requests')) ->update($db->quoteName('#__mokosuiteclient_data_requests'))
->set($db->quoteName('status') . ' = ' . $db->quote('processing')) ->set($db->quoteName('status') . ' = ' . $db->quote('processing'))
->where($db->quoteName('id') . ' = ' . $requestId) ->where($db->quoteName('id') . ' = ' . $requestId)
)->execute(); )->execute();
@@ -143,7 +143,7 @@ class PrivacyModel extends BaseDatabaseModel
// Mark completed // Mark completed
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->update($db->quoteName('#__mokosuite_data_requests')) ->update($db->quoteName('#__mokosuiteclient_data_requests'))
->set($db->quoteName('status') . ' = ' . $db->quote('completed')) ->set($db->quoteName('status') . ' = ' . $db->quote('completed'))
->set($db->quoteName('processed_by') . ' = ' . (int) Factory::getApplication()->getIdentity()->id) ->set($db->quoteName('processed_by') . ' = ' . (int) Factory::getApplication()->getIdentity()->id)
->set($db->quoteName('processed') . ' = ' . $db->quote(Factory::getDate()->toSql())) ->set($db->quoteName('processed') . ' = ' . $db->quote(Factory::getDate()->toSql()))
@@ -201,7 +201,7 @@ class PrivacyModel extends BaseDatabaseModel
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->select(['id', 'subject', 'body', 'status', 'priority', 'created']) ->select(['id', 'subject', 'body', 'status', 'priority', 'created'])
->from($db->quoteName('#__mokosuite_tickets')) ->from($db->quoteName('#__mokosuiteclient_tickets'))
->where($db->quoteName('created_by') . ' = ' . $userId) ->where($db->quoteName('created_by') . ' = ' . $userId)
); );
$data['tickets'] = $db->loadObjectList() ?: []; $data['tickets'] = $db->loadObjectList() ?: [];
@@ -210,7 +210,7 @@ class PrivacyModel extends BaseDatabaseModel
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->select(['r.id', 'r.ticket_id', 'r.body', 'r.created']) ->select(['r.id', 'r.ticket_id', 'r.body', 'r.created'])
->from($db->quoteName('#__mokosuite_ticket_replies', 'r')) ->from($db->quoteName('#__mokosuiteclient_ticket_replies', 'r'))
->where($db->quoteName('r.user_id') . ' = ' . $userId) ->where($db->quoteName('r.user_id') . ' = ' . $userId)
); );
$data['ticket_replies'] = $db->loadObjectList() ?: []; $data['ticket_replies'] = $db->loadObjectList() ?: [];
@@ -219,7 +219,7 @@ class PrivacyModel extends BaseDatabaseModel
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->select('*') ->select('*')
->from($db->quoteName('#__mokosuite_consent_log')) ->from($db->quoteName('#__mokosuiteclient_consent_log'))
->where($db->quoteName('user_id') . ' = ' . $userId) ->where($db->quoteName('user_id') . ' = ' . $userId)
->order('created ASC') ->order('created ASC')
); );
@@ -295,7 +295,7 @@ class PrivacyModel extends BaseDatabaseModel
// Anonymize ticket replies // Anonymize ticket replies
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->update($db->quoteName('#__mokosuite_ticket_replies')) ->update($db->quoteName('#__mokosuiteclient_ticket_replies'))
->set($db->quoteName('body') . ' = ' . $db->quote('[Content removed per data request]')) ->set($db->quoteName('body') . ' = ' . $db->quote('[Content removed per data request]'))
->where($db->quoteName('user_id') . ' = ' . $userId) ->where($db->quoteName('user_id') . ' = ' . $userId)
)->execute(); )->execute();
@@ -374,7 +374,7 @@ class PrivacyModel extends BaseDatabaseModel
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->select($db->quoteName('id')) ->select($db->quoteName('id'))
->from($db->quoteName('#__mokosuite_tickets')) ->from($db->quoteName('#__mokosuiteclient_tickets'))
->where($db->quoteName('created_by') . ' = ' . $userId) ->where($db->quoteName('created_by') . ' = ' . $userId)
); );
$ticketIds = $db->loadColumn() ?: []; $ticketIds = $db->loadColumn() ?: [];
@@ -383,13 +383,13 @@ class PrivacyModel extends BaseDatabaseModel
{ {
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->delete($db->quoteName('#__mokosuite_ticket_replies')) ->delete($db->quoteName('#__mokosuiteclient_ticket_replies'))
->where($db->quoteName('ticket_id') . ' IN (' . implode(',', $ticketIds) . ')') ->where($db->quoteName('ticket_id') . ' IN (' . implode(',', $ticketIds) . ')')
)->execute(); )->execute();
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->delete($db->quoteName('#__mokosuite_tickets')) ->delete($db->quoteName('#__mokosuiteclient_tickets'))
->where($db->quoteName('created_by') . ' = ' . $userId) ->where($db->quoteName('created_by') . ' = ' . $userId)
)->execute(); )->execute();
} }
@@ -397,7 +397,7 @@ class PrivacyModel extends BaseDatabaseModel
// Delete consent log // Delete consent log
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->delete($db->quoteName('#__mokosuite_consent_log')) ->delete($db->quoteName('#__mokosuiteclient_consent_log'))
->where($db->quoteName('user_id') . ' = ' . $userId) ->where($db->quoteName('user_id') . ' = ' . $userId)
)->execute(); )->execute();
@@ -429,7 +429,7 @@ class PrivacyModel extends BaseDatabaseModel
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->select('*') ->select('*')
->from($db->quoteName('#__mokosuite_consent_log')) ->from($db->quoteName('#__mokosuiteclient_consent_log'))
->where($db->quoteName('user_id') . ' = ' . $userId) ->where($db->quoteName('user_id') . ' = ' . $userId)
->order($db->quoteName('created') . ' DESC') ->order($db->quoteName('created') . ' DESC')
); );
@@ -450,7 +450,7 @@ class PrivacyModel extends BaseDatabaseModel
'ip_address' => $_SERVER['REMOTE_ADDR'] ?? '', 'ip_address' => $_SERVER['REMOTE_ADDR'] ?? '',
'created' => Factory::getDate()->toSql(), 'created' => Factory::getDate()->toSql(),
]; ];
$db->insertObject('#__mokosuite_consent_log', $row, 'id'); $db->insertObject('#__mokosuiteclient_consent_log', $row, 'id');
} }
// ================================================================== // ==================================================================
@@ -466,7 +466,7 @@ class PrivacyModel extends BaseDatabaseModel
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->select('*') ->select('*')
->from($db->quoteName('#__mokosuite_retention_policies')) ->from($db->quoteName('#__mokosuiteclient_retention_policies'))
->order($db->quoteName('id') . ' ASC') ->order($db->quoteName('id') . ' ASC')
); );
@@ -508,7 +508,7 @@ class PrivacyModel extends BaseDatabaseModel
case 'waf_logs': case 'waf_logs':
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->delete($db->quoteName('#__mokosuite_waf_log')) ->delete($db->quoteName('#__mokosuiteclient_waf_log'))
->where($db->quoteName('created') . ' < ' . $db->quote($cutoff)) ->where($db->quoteName('created') . ' < ' . $db->quote($cutoff))
)->execute(); )->execute();
$count = $db->getAffectedRows(); $count = $db->getAffectedRows();
@@ -528,7 +528,7 @@ class PrivacyModel extends BaseDatabaseModel
{ {
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->update($db->quoteName('#__mokosuite_tickets')) ->update($db->quoteName('#__mokosuiteclient_tickets'))
->set($db->quoteName('body') . ' = ' . $db->quote('[Removed per retention policy]')) ->set($db->quoteName('body') . ' = ' . $db->quote('[Removed per retention policy]'))
->where($db->quoteName('status') . ' = ' . $db->quote('closed')) ->where($db->quoteName('status') . ' = ' . $db->quote('closed'))
->where($db->quoteName('closed') . ' < ' . $db->quote($cutoff)) ->where($db->quoteName('closed') . ' < ' . $db->quote($cutoff))
@@ -565,12 +565,12 @@ class PrivacyModel extends BaseDatabaseModel
{ {
$results['policies_run']++; $results['policies_run']++;
$results['items_affected'] += $count; $results['items_affected'] += $count;
Log::add(\sprintf('Retention: %s — %d items affected', $policy->content_type, $count), Log::INFO, 'mokosuite'); Log::add(\sprintf('Retention: %s — %d items affected', $policy->content_type, $count), Log::INFO, 'mokosuiteclient');
} }
} }
catch (\Throwable $e) catch (\Throwable $e)
{ {
Log::add('Retention policy error (' . $policy->content_type . '): ' . $e->getMessage(), Log::WARNING, 'mokosuite'); Log::add('Retention policy error (' . $policy->content_type . '): ' . $e->getMessage(), Log::WARNING, 'mokosuiteclient');
} }
} }
@@ -593,16 +593,16 @@ class PrivacyModel extends BaseDatabaseModel
try try
{ {
$db->setQuery('SELECT COUNT(*) FROM #__mokosuite_data_requests WHERE status = ' . $db->quote('pending')); $db->setQuery('SELECT COUNT(*) FROM #__mokosuiteclient_data_requests WHERE status = ' . $db->quote('pending'));
$summary->pending_requests = (int) $db->loadResult(); $summary->pending_requests = (int) $db->loadResult();
$db->setQuery('SELECT COUNT(*) FROM #__mokosuite_data_requests'); $db->setQuery('SELECT COUNT(*) FROM #__mokosuiteclient_data_requests');
$summary->total_requests = (int) $db->loadResult(); $summary->total_requests = (int) $db->loadResult();
$db->setQuery('SELECT COUNT(*) FROM #__mokosuite_consent_log'); $db->setQuery('SELECT COUNT(*) FROM #__mokosuiteclient_consent_log');
$summary->consent_entries = (int) $db->loadResult(); $summary->consent_entries = (int) $db->loadResult();
$db->setQuery('SELECT COUNT(*) FROM #__mokosuite_retention_policies WHERE enabled = 1'); $db->setQuery('SELECT COUNT(*) FROM #__mokosuiteclient_retention_policies WHERE enabled = 1');
$summary->policies_active = (int) $db->loadResult(); $summary->policies_active = (int) $db->loadResult();
} }
catch (\Throwable $e) {} catch (\Throwable $e) {}
@@ -1,18 +1,18 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
namespace Moko\Component\MokoSuite\Administrator\Model; namespace Moko\Component\MokoSuiteClient\Administrator\Model;
defined('_JEXEC') or die; defined('_JEXEC') or die;
use Joomla\CMS\Factory; use Joomla\CMS\Factory;
use Joomla\CMS\MVC\Model\BaseDatabaseModel; use Joomla\CMS\MVC\Model\BaseDatabaseModel;
use Moko\Component\MokoSuite\Administrator\Service\NotificationService; use Moko\Component\MokoSuiteClient\Administrator\Service\NotificationService;
class TicketsModel extends BaseDatabaseModel class TicketsModel extends BaseDatabaseModel
{ {
@@ -45,12 +45,12 @@ class TicketsModel extends BaseDatabaseModel
$db->quoteName('pr.color', 'priority_color'), $db->quoteName('pr.color', 'priority_color'),
$db->quoteName('st.is_closed', 'status_is_closed'), $db->quoteName('st.is_closed', 'status_is_closed'),
]) ])
->from($db->quoteName('#__mokosuite_tickets', 't')) ->from($db->quoteName('#__mokosuiteclient_tickets', 't'))
->leftJoin($db->quoteName('#__mokosuite_ticket_categories', 'c') . ' ON c.id = t.category_id') ->leftJoin($db->quoteName('#__mokosuiteclient_ticket_categories', 'c') . ' ON c.id = t.category_id')
->leftJoin($db->quoteName('#__users', 'u') . ' ON u.id = t.created_by') ->leftJoin($db->quoteName('#__users', 'u') . ' ON u.id = t.created_by')
->leftJoin($db->quoteName('#__contact_details', 'ct') . ' ON ct.id = t.contact_id') ->leftJoin($db->quoteName('#__contact_details', 'ct') . ' ON ct.id = t.contact_id')
->leftJoin($db->quoteName('#__mokosuite_ticket_statuses', 'st') . ' ON st.id = t.status_id') ->leftJoin($db->quoteName('#__mokosuiteclient_ticket_statuses', 'st') . ' ON st.id = t.status_id')
->leftJoin($db->quoteName('#__mokosuite_ticket_priorities', 'pr') . ' ON pr.id = t.priority_id'); ->leftJoin($db->quoteName('#__mokosuiteclient_ticket_priorities', 'pr') . ' ON pr.id = t.priority_id');
if (!empty($filters['status_id'])) if (!empty($filters['status_id']))
{ {
@@ -115,12 +115,12 @@ class TicketsModel extends BaseDatabaseModel
$db->quoteName('pr.alias', 'priority_alias'), $db->quoteName('pr.alias', 'priority_alias'),
$db->quoteName('pr.color', 'priority_color'), $db->quoteName('pr.color', 'priority_color'),
]) ])
->from($db->quoteName('#__mokosuite_tickets', 't')) ->from($db->quoteName('#__mokosuiteclient_tickets', 't'))
->leftJoin($db->quoteName('#__mokosuite_ticket_categories', 'c') . ' ON c.id = t.category_id') ->leftJoin($db->quoteName('#__mokosuiteclient_ticket_categories', 'c') . ' ON c.id = t.category_id')
->leftJoin($db->quoteName('#__users', 'u') . ' ON u.id = t.created_by') ->leftJoin($db->quoteName('#__users', 'u') . ' ON u.id = t.created_by')
->leftJoin($db->quoteName('#__contact_details', 'ct') . ' ON ct.id = t.contact_id') ->leftJoin($db->quoteName('#__contact_details', 'ct') . ' ON ct.id = t.contact_id')
->leftJoin($db->quoteName('#__mokosuite_ticket_statuses', 'st') . ' ON st.id = t.status_id') ->leftJoin($db->quoteName('#__mokosuiteclient_ticket_statuses', 'st') . ' ON st.id = t.status_id')
->leftJoin($db->quoteName('#__mokosuite_ticket_priorities', 'pr') . ' ON pr.id = t.priority_id') ->leftJoin($db->quoteName('#__mokosuiteclient_ticket_priorities', 'pr') . ' ON pr.id = t.priority_id')
->where($db->quoteName('t.id') . ' = ' . $id); ->where($db->quoteName('t.id') . ' = ' . $id);
$db->setQuery($query); $db->setQuery($query);
$ticket = $db->loadObject(); $ticket = $db->loadObject();
@@ -136,7 +136,7 @@ class TicketsModel extends BaseDatabaseModel
$db->quoteName('r') . '.*', $db->quoteName('r') . '.*',
$db->quoteName('u.name', 'user_name'), $db->quoteName('u.name', 'user_name'),
]) ])
->from($db->quoteName('#__mokosuite_ticket_replies', 'r')) ->from($db->quoteName('#__mokosuiteclient_ticket_replies', 'r'))
->leftJoin($db->quoteName('#__users', 'u') . ' ON u.id = r.user_id') ->leftJoin($db->quoteName('#__users', 'u') . ' ON u.id = r.user_id')
->where($db->quoteName('r.ticket_id') . ' = ' . $id) ->where($db->quoteName('r.ticket_id') . ' = ' . $id)
->order($db->quoteName('r.created') . ' ASC'); ->order($db->quoteName('r.created') . ' ASC');
@@ -187,7 +187,7 @@ class TicketsModel extends BaseDatabaseModel
{ {
$query = $db->getQuery(true) $query = $db->getQuery(true)
->select($db->quoteName('auto_assign_user')) ->select($db->quoteName('auto_assign_user'))
->from($db->quoteName('#__mokosuite_ticket_categories')) ->from($db->quoteName('#__mokosuiteclient_ticket_categories'))
->where($db->quoteName('id') . ' = ' . (int) $ticket->category_id); ->where($db->quoteName('id') . ' = ' . (int) $ticket->category_id);
$db->setQuery($query); $db->setQuery($query);
$autoAssign = (int) $db->loadResult(); $autoAssign = (int) $db->loadResult();
@@ -203,7 +203,7 @@ class TicketsModel extends BaseDatabaseModel
{ {
$query = $db->getQuery(true) $query = $db->getQuery(true)
->select([$db->quoteName('sla_response_minutes'), $db->quoteName('sla_resolution_minutes')]) ->select([$db->quoteName('sla_response_minutes'), $db->quoteName('sla_resolution_minutes')])
->from($db->quoteName('#__mokosuite_ticket_categories')) ->from($db->quoteName('#__mokosuiteclient_ticket_categories'))
->where($db->quoteName('id') . ' = ' . (int) $ticket->category_id); ->where($db->quoteName('id') . ' = ' . (int) $ticket->category_id);
$db->setQuery($query); $db->setQuery($query);
$sla = $db->loadObject(); $sla = $db->loadObject();
@@ -215,7 +215,7 @@ class TicketsModel extends BaseDatabaseModel
} }
} }
$db->insertObject('#__mokosuite_tickets', $ticket, 'id'); $db->insertObject('#__mokosuiteclient_tickets', $ticket, 'id');
// Handle multi-assignee (users and groups) // Handle multi-assignee (users and groups)
$assignUsers = array_filter(array_map('intval', (array) ($data['assign_users'] ?? []))); $assignUsers = array_filter(array_map('intval', (array) ($data['assign_users'] ?? [])));
@@ -271,14 +271,14 @@ class TicketsModel extends BaseDatabaseModel
'created' => $now, 'created' => $now,
]; ];
$db->insertObject('#__mokosuite_ticket_replies', $reply, 'id'); $db->insertObject('#__mokosuiteclient_ticket_replies', $reply, 'id');
// Mark SLA as responded only for staff replies (not customer self-replies) // Mark SLA as responded only for staff replies (not customer self-replies)
$ticket = $this->getTicket($ticketId); $ticket = $this->getTicket($ticketId);
$isStaffReply = $ticket && (int) $user->id !== (int) $ticket->created_by; $isStaffReply = $ticket && (int) $user->id !== (int) $ticket->created_by;
$updateQuery = $db->getQuery(true) $updateQuery = $db->getQuery(true)
->update($db->quoteName('#__mokosuite_tickets')) ->update($db->quoteName('#__mokosuiteclient_tickets'))
->set($db->quoteName('modified') . ' = ' . $db->quote($now)) ->set($db->quoteName('modified') . ' = ' . $db->quote($now))
->where($db->quoteName('id') . ' = ' . $ticketId); ->where($db->quoteName('id') . ' = ' . $ticketId);
@@ -323,7 +323,7 @@ class TicketsModel extends BaseDatabaseModel
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->select('*') ->select('*')
->from($db->quoteName('#__mokosuite_ticket_statuses')) ->from($db->quoteName('#__mokosuiteclient_ticket_statuses'))
->where($db->quoteName('id') . ' = ' . $statusId) ->where($db->quoteName('id') . ' = ' . $statusId)
); );
$status = $db->loadObject(); $status = $db->loadObject();
@@ -337,7 +337,7 @@ class TicketsModel extends BaseDatabaseModel
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->select($db->quoteName('status_id')) ->select($db->quoteName('status_id'))
->from($db->quoteName('#__mokosuite_tickets')) ->from($db->quoteName('#__mokosuiteclient_tickets'))
->where($db->quoteName('id') . ' = ' . $ticketId) ->where($db->quoteName('id') . ' = ' . $ticketId)
); );
$oldStatusId = (int) $db->loadResult(); $oldStatusId = (int) $db->loadResult();
@@ -361,7 +361,7 @@ class TicketsModel extends BaseDatabaseModel
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->update($db->quoteName('#__mokosuite_tickets')) ->update($db->quoteName('#__mokosuiteclient_tickets'))
->set($sets) ->set($sets)
->where($db->quoteName('id') . ' = ' . $ticketId) ->where($db->quoteName('id') . ' = ' . $ticketId)
)->execute(); )->execute();
@@ -394,7 +394,7 @@ class TicketsModel extends BaseDatabaseModel
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->select('*') ->select('*')
->from($db->quoteName('#__mokosuite_ticket_categories')) ->from($db->quoteName('#__mokosuiteclient_ticket_categories'))
->where($db->quoteName('published') . ' = 1') ->where($db->quoteName('published') . ' = 1')
->order($db->quoteName('ordering') . ' ASC') ->order($db->quoteName('ordering') . ' ASC')
); );
@@ -411,7 +411,7 @@ class TicketsModel extends BaseDatabaseModel
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->select('*') ->select('*')
->from($db->quoteName('#__mokosuite_ticket_assignees')) ->from($db->quoteName('#__mokosuiteclient_ticket_assignees'))
->where($db->quoteName('ticket_id') . ' = ' . $ticketId) ->where($db->quoteName('ticket_id') . ' = ' . $ticketId)
); );
$rows = $db->loadObjectList() ?: []; $rows = $db->loadObjectList() ?: [];
@@ -457,7 +457,7 @@ class TicketsModel extends BaseDatabaseModel
// Clear existing // Clear existing
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->delete($db->quoteName('#__mokosuite_ticket_assignees')) ->delete($db->quoteName('#__mokosuiteclient_ticket_assignees'))
->where($db->quoteName('ticket_id') . ' = ' . $ticketId) ->where($db->quoteName('ticket_id') . ' = ' . $ticketId)
)->execute(); )->execute();
@@ -468,7 +468,7 @@ class TicketsModel extends BaseDatabaseModel
if ($uid > 0) if ($uid > 0)
{ {
$db->insertObject('#__mokosuite_ticket_assignees', (object) [ $db->insertObject('#__mokosuiteclient_ticket_assignees', (object) [
'ticket_id' => $ticketId, 'ticket_id' => $ticketId,
'assignee_type' => 'user', 'assignee_type' => 'user',
'assignee_id' => $uid, 'assignee_id' => $uid,
@@ -483,7 +483,7 @@ class TicketsModel extends BaseDatabaseModel
if ($gid > 0) if ($gid > 0)
{ {
$db->insertObject('#__mokosuite_ticket_assignees', (object) [ $db->insertObject('#__mokosuiteclient_ticket_assignees', (object) [
'ticket_id' => $ticketId, 'ticket_id' => $ticketId,
'assignee_type' => 'group', 'assignee_type' => 'group',
'assignee_id' => $gid, 'assignee_id' => $gid,
@@ -518,7 +518,7 @@ class TicketsModel extends BaseDatabaseModel
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->select('*') ->select('*')
->from($db->quoteName('#__mokosuite_ticket_statuses')) ->from($db->quoteName('#__mokosuiteclient_ticket_statuses'))
->where($db->quoteName('is_default') . ' = 1') ->where($db->quoteName('is_default') . ' = 1')
->setLimit(1) ->setLimit(1)
); );
@@ -535,7 +535,7 @@ class TicketsModel extends BaseDatabaseModel
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->select('*') ->select('*')
->from($db->quoteName('#__mokosuite_ticket_priorities')) ->from($db->quoteName('#__mokosuiteclient_ticket_priorities'))
->where($db->quoteName('is_default') . ' = 1') ->where($db->quoteName('is_default') . ' = 1')
->setLimit(1) ->setLimit(1)
); );
@@ -552,7 +552,7 @@ class TicketsModel extends BaseDatabaseModel
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->select('*') ->select('*')
->from($db->quoteName('#__mokosuite_ticket_statuses')) ->from($db->quoteName('#__mokosuiteclient_ticket_statuses'))
->order($db->quoteName('ordering') . ' ASC') ->order($db->quoteName('ordering') . ' ASC')
); );
@@ -568,7 +568,7 @@ class TicketsModel extends BaseDatabaseModel
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->select('*') ->select('*')
->from($db->quoteName('#__mokosuite_ticket_priorities')) ->from($db->quoteName('#__mokosuiteclient_ticket_priorities'))
->order($db->quoteName('ordering') . ' ASC') ->order($db->quoteName('ordering') . ' ASC')
); );
@@ -584,7 +584,7 @@ class TicketsModel extends BaseDatabaseModel
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->select([$db->quoteName('fg.id'), $db->quoteName('fg.title')]) ->select([$db->quoteName('fg.id'), $db->quoteName('fg.title')])
->from($db->quoteName('#__mokosuite_ticket_category_field_groups', 'cfg')) ->from($db->quoteName('#__mokosuiteclient_ticket_category_field_groups', 'cfg'))
->innerJoin($db->quoteName('#__fields_groups', 'fg') . ' ON fg.id = cfg.field_group_id') ->innerJoin($db->quoteName('#__fields_groups', 'fg') . ' ON fg.id = cfg.field_group_id')
->where($db->quoteName('cfg.category_id') . ' = ' . $categoryId) ->where($db->quoteName('cfg.category_id') . ' = ' . $categoryId)
->where($db->quoteName('fg.state') . ' = 1') ->where($db->quoteName('fg.state') . ' = 1')
@@ -595,7 +595,7 @@ class TicketsModel extends BaseDatabaseModel
} }
/** /**
* Get Joomla custom fields for given field group IDs (context: com_mokosuite.ticket). * Get Joomla custom fields for given field group IDs (context: com_mokosuiteclient.ticket).
*/ */
public function getFieldsForGroups(array $groupIds): array public function getFieldsForGroups(array $groupIds): array
{ {
@@ -610,7 +610,7 @@ class TicketsModel extends BaseDatabaseModel
$db->getQuery(true) $db->getQuery(true)
->select('*') ->select('*')
->from($db->quoteName('#__fields')) ->from($db->quoteName('#__fields'))
->where($db->quoteName('context') . ' = ' . $db->quote('com_mokosuite.ticket')) ->where($db->quoteName('context') . ' = ' . $db->quote('com_mokosuiteclient.ticket'))
->where($db->quoteName('group_id') . ' IN (' . $ids . ')') ->where($db->quoteName('group_id') . ' IN (' . $ids . ')')
->where($db->quoteName('state') . ' = 1') ->where($db->quoteName('state') . ' = 1')
->order($db->quoteName('ordering') . ' ASC') ->order($db->quoteName('ordering') . ' ASC')
@@ -682,7 +682,7 @@ class TicketsModel extends BaseDatabaseModel
$db = $this->getDatabase(); $db = $this->getDatabase();
$query = $db->getQuery(true) $query = $db->getQuery(true)
->select('*') ->select('*')
->from($db->quoteName('#__mokosuite_ticket_canned')) ->from($db->quoteName('#__mokosuiteclient_ticket_canned'))
->order($db->quoteName('ordering') . ' ASC'); ->order($db->quoteName('ordering') . ' ASC');
if ($categoryId) if ($categoryId)
@@ -712,8 +712,8 @@ class TicketsModel extends BaseDatabaseModel
$db->quoteName('s.is_closed'), $db->quoteName('s.is_closed'),
'COUNT(' . $db->quoteName('t.id') . ') AS ' . $db->quoteName('cnt'), 'COUNT(' . $db->quoteName('t.id') . ') AS ' . $db->quoteName('cnt'),
]) ])
->from($db->quoteName('#__mokosuite_ticket_statuses', 's')) ->from($db->quoteName('#__mokosuiteclient_ticket_statuses', 's'))
->leftJoin($db->quoteName('#__mokosuite_tickets', 't') . ' ON t.status_id = s.id') ->leftJoin($db->quoteName('#__mokosuiteclient_tickets', 't') . ' ON t.status_id = s.id')
->group($db->quoteName('s.id')) ->group($db->quoteName('s.id'))
->order($db->quoteName('s.ordering') . ' ASC') ->order($db->quoteName('s.ordering') . ' ASC')
); );
@@ -732,8 +732,8 @@ class TicketsModel extends BaseDatabaseModel
$query = $db->getQuery(true) $query = $db->getQuery(true)
->select(['t.' . $db->quoteName('id'), $db->quoteName('t.subject'), $db->quoteName('t.priority'), ->select(['t.' . $db->quoteName('id'), $db->quoteName('t.subject'), $db->quoteName('t.priority'),
$db->quoteName('t.sla_response_due'), $db->quoteName('t.sla_resolution_due'), $db->quoteName('t.sla_responded')]) $db->quoteName('t.sla_response_due'), $db->quoteName('t.sla_resolution_due'), $db->quoteName('t.sla_responded')])
->from($db->quoteName('#__mokosuite_tickets', 't')) ->from($db->quoteName('#__mokosuiteclient_tickets', 't'))
->leftJoin($db->quoteName('#__mokosuite_ticket_statuses', 's') . ' ON s.id = t.status_id') ->leftJoin($db->quoteName('#__mokosuiteclient_ticket_statuses', 's') . ' ON s.id = t.status_id')
->where('(' . $db->quoteName('s.is_closed') . ' = 0 OR ' . $db->quoteName('s.is_closed') . ' IS NULL)') ->where('(' . $db->quoteName('s.is_closed') . ' = 0 OR ' . $db->quoteName('s.is_closed') . ' IS NULL)')
->where('((' . $db->quoteName('sla_response_due') . ' < ' . $db->quote($now) . ' AND ' . $db->quoteName('sla_responded') . ' = 0)' ->where('((' . $db->quoteName('sla_response_due') . ' < ' . $db->quote($now) . ' AND ' . $db->quoteName('sla_responded') . ' = 0)'
. ' OR ' . $db->quoteName('sla_resolution_due') . ' < ' . $db->quote($now) . ')') . ' OR ' . $db->quoteName('sla_resolution_due') . ' < ' . $db->quote($now) . ')')
@@ -762,7 +762,7 @@ class TicketsModel extends BaseDatabaseModel
// Load enabled rules for this event // Load enabled rules for this event
$query = $db->getQuery(true) $query = $db->getQuery(true)
->select('*') ->select('*')
->from($db->quoteName('#__mokosuite_ticket_automation')) ->from($db->quoteName('#__mokosuiteclient_ticket_automation'))
->where($db->quoteName('trigger_event') . ' = ' . $db->quote($event)) ->where($db->quoteName('trigger_event') . ' = ' . $db->quote($event))
->where($db->quoteName('enabled') . ' = 1') ->where($db->quoteName('enabled') . ' = 1')
->order($db->quoteName('ordering') . ' ASC'); ->order($db->quoteName('ordering') . ' ASC');
@@ -798,7 +798,7 @@ class TicketsModel extends BaseDatabaseModel
} }
catch (\Throwable $e) catch (\Throwable $e)
{ {
\Joomla\CMS\Log\Log::add('Automation error: ' . $e->getMessage(), \Joomla\CMS\Log\Log::WARNING, 'mokosuite'); \Joomla\CMS\Log\Log::add('Automation error: ' . $e->getMessage(), \Joomla\CMS\Log\Log::WARNING, 'mokosuiteclient');
} }
} }
@@ -813,7 +813,7 @@ class TicketsModel extends BaseDatabaseModel
// Load scheduled rules // Load scheduled rules
$query = $db->getQuery(true) $query = $db->getQuery(true)
->select('*') ->select('*')
->from($db->quoteName('#__mokosuite_ticket_automation')) ->from($db->quoteName('#__mokosuiteclient_ticket_automation'))
->where($db->quoteName('trigger_event') . ' = ' . $db->quote('scheduled')) ->where($db->quoteName('trigger_event') . ' = ' . $db->quote('scheduled'))
->where($db->quoteName('enabled') . ' = 1') ->where($db->quoteName('enabled') . ' = 1')
->order($db->quoteName('ordering') . ' ASC'); ->order($db->quoteName('ordering') . ' ASC');
@@ -828,7 +828,7 @@ class TicketsModel extends BaseDatabaseModel
// Load all non-closed tickets // Load all non-closed tickets
$query = $db->getQuery(true) $query = $db->getQuery(true)
->select('*') ->select('*')
->from($db->quoteName('#__mokosuite_tickets')) ->from($db->quoteName('#__mokosuiteclient_tickets'))
->where($db->quoteName('status') . ' != ' . $db->quote('closed')); ->where($db->quoteName('status') . ' != ' . $db->quote('closed'));
$db->setQuery($query); $db->setQuery($query);
$tickets = $db->loadObjectList() ?: []; $tickets = $db->loadObjectList() ?: [];
@@ -925,7 +925,7 @@ class TicketsModel extends BaseDatabaseModel
case 'set_priority': case 'set_priority':
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->update($db->quoteName('#__mokosuite_tickets')) ->update($db->quoteName('#__mokosuiteclient_tickets'))
->set($db->quoteName('priority') . ' = ' . $db->quote($value)) ->set($db->quoteName('priority') . ' = ' . $db->quote($value))
->set($db->quoteName('modified') . ' = ' . $db->quote($now)) ->set($db->quoteName('modified') . ' = ' . $db->quote($now))
->where($db->quoteName('id') . ' = ' . $ticketId) ->where($db->quoteName('id') . ' = ' . $ticketId)
@@ -935,7 +935,7 @@ class TicketsModel extends BaseDatabaseModel
case 'assign': case 'assign':
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->update($db->quoteName('#__mokosuite_tickets')) ->update($db->quoteName('#__mokosuiteclient_tickets'))
->set($db->quoteName('assigned_to') . ' = ' . (int) $value) ->set($db->quoteName('assigned_to') . ' = ' . (int) $value)
->set($db->quoteName('modified') . ' = ' . $db->quote($now)) ->set($db->quoteName('modified') . ' = ' . $db->quote($now))
->where($db->quoteName('id') . ' = ' . $ticketId) ->where($db->quoteName('id') . ' = ' . $ticketId)
@@ -950,7 +950,7 @@ class TicketsModel extends BaseDatabaseModel
'is_internal' => 1, 'is_internal' => 1,
'created' => $now, 'created' => $now,
]; ];
$db->insertObject('#__mokosuite_ticket_replies', $reply, 'id'); $db->insertObject('#__mokosuiteclient_ticket_replies', $reply, 'id');
break; break;
case 'send_email': case 'send_email':
@@ -970,7 +970,7 @@ class TicketsModel extends BaseDatabaseModel
} }
catch (\Throwable $e) catch (\Throwable $e)
{ {
\Joomla\CMS\Log\Log::add('Automation email failed: ' . $e->getMessage(), \Joomla\CMS\Log\Log::WARNING, 'mokosuite'); \Joomla\CMS\Log\Log::add('Automation email failed: ' . $e->getMessage(), \Joomla\CMS\Log\Log::WARNING, 'mokosuiteclient');
} }
} }
break; break;
@@ -988,7 +988,7 @@ class TicketsModel extends BaseDatabaseModel
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->select($db->quoteName('id')) ->select($db->quoteName('id'))
->from($db->quoteName('#__mokosuite_tickets')) ->from($db->quoteName('#__mokosuiteclient_tickets'))
->where($db->quoteName('created_by') . ' = ' . $userId) ->where($db->quoteName('created_by') . ' = ' . $userId)
->where($db->quoteName('status') . ' NOT IN (' . $db->quote('resolved') . ',' . $db->quote('closed') . ')') ->where($db->quoteName('status') . ' NOT IN (' . $db->quote('resolved') . ',' . $db->quote('closed') . ')')
->where($catId ? $db->quoteName('category_id') . ' = ' . $catId : '1=1') ->where($catId ? $db->quoteName('category_id') . ' = ' . $catId : '1=1')
@@ -1008,7 +1008,7 @@ class TicketsModel extends BaseDatabaseModel
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->select('COUNT(*)') ->select('COUNT(*)')
->from($db->quoteName('#__mokosuite_tickets')) ->from($db->quoteName('#__mokosuiteclient_tickets'))
->where($db->quoteName('created_by') . ' = ' . $userId) ->where($db->quoteName('created_by') . ' = ' . $userId)
->where($db->quoteName('status') . ' NOT IN (' . $db->quote('resolved') . ',' . $db->quote('closed') . ')') ->where($db->quoteName('status') . ' NOT IN (' . $db->quote('resolved') . ',' . $db->quote('closed') . ')')
); );
@@ -1043,7 +1043,7 @@ class TicketsModel extends BaseDatabaseModel
$query = $db->getQuery(true) $query = $db->getQuery(true)
->select('*') ->select('*')
->from($db->quoteName('#__mokosuite_ticket_automation')) ->from($db->quoteName('#__mokosuiteclient_ticket_automation'))
->where($db->quoteName('trigger_event') . ' = ' . $db->quote($event)) ->where($db->quoteName('trigger_event') . ' = ' . $db->quote($event))
->where($db->quoteName('enabled') . ' = 1') ->where($db->quoteName('enabled') . ' = 1')
->order($db->quoteName('ordering') . ' ASC'); ->order($db->quoteName('ordering') . ' ASC');
@@ -1080,7 +1080,7 @@ class TicketsModel extends BaseDatabaseModel
} }
catch (\Throwable $e) catch (\Throwable $e)
{ {
\Joomla\CMS\Log\Log::add('System event automation error: ' . $e->getMessage(), \Joomla\CMS\Log\Log::WARNING, 'mokosuite'); \Joomla\CMS\Log\Log::add('System event automation error: ' . $e->getMessage(), \Joomla\CMS\Log\Log::WARNING, 'mokosuiteclient');
} }
} }
@@ -1093,7 +1093,7 @@ class TicketsModel extends BaseDatabaseModel
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->select('*') ->select('*')
->from($db->quoteName('#__mokosuite_ticket_automation')) ->from($db->quoteName('#__mokosuiteclient_ticket_automation'))
->order($db->quoteName('ordering') . ' ASC') ->order($db->quoteName('ordering') . ' ASC')
); );
@@ -1140,7 +1140,7 @@ class TicketsModel extends BaseDatabaseModel
try try
{ {
// Status mapping: ATS → MokoSuite // Status mapping: ATS → MokoSuiteClient
$statusMap = [ $statusMap = [
'O' => 'open', // Open 'O' => 'open', // Open
'P' => 'in_progress', // Pending (staff action needed) 'P' => 'in_progress', // Pending (staff action needed)
@@ -1174,7 +1174,7 @@ class TicketsModel extends BaseDatabaseModel
$exists = $db->setQuery( $exists = $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->select('COUNT(*)') ->select('COUNT(*)')
->from('#__mokosuite_ticket_canned') ->from('#__mokosuiteclient_ticket_canned')
->where($db->quoteName('title') . ' = ' . $db->quote($c->title)) ->where($db->quoteName('title') . ' = ' . $db->quote($c->title))
)->loadResult(); )->loadResult();
@@ -1189,7 +1189,7 @@ class TicketsModel extends BaseDatabaseModel
'category_id' => null, 'category_id' => null,
'ordering' => (int) ($c->ordering ?? 0), 'ordering' => (int) ($c->ordering ?? 0),
]; ];
$db->insertObject('#__mokosuite_ticket_canned', $row, 'id'); $db->insertObject('#__mokosuiteclient_ticket_canned', $row, 'id');
$results['canned']++; $results['canned']++;
} }
@@ -1197,7 +1197,7 @@ class TicketsModel extends BaseDatabaseModel
$db->setQuery('SELECT * FROM #__ats_tickets ORDER BY id'); $db->setQuery('SELECT * FROM #__ats_tickets ORDER BY id');
$atsTickets = $db->loadObjectList() ?: []; $atsTickets = $db->loadObjectList() ?: [];
$ticketIdMap = []; // ATS id → MokoSuite id $ticketIdMap = []; // ATS id → MokoSuiteClient id
foreach ($atsTickets as $t) foreach ($atsTickets as $t)
{ {
@@ -1205,7 +1205,7 @@ class TicketsModel extends BaseDatabaseModel
$exists = $db->setQuery( $exists = $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->select('COUNT(*)') ->select('COUNT(*)')
->from('#__mokosuite_tickets') ->from('#__mokosuiteclient_tickets')
->where($db->quoteName('subject') . ' = ' . $db->quote($t->title)) ->where($db->quoteName('subject') . ' = ' . $db->quote($t->title))
->where($db->quoteName('created_by') . ' = ' . (int) $t->created_by) ->where($db->quoteName('created_by') . ' = ' . (int) $t->created_by)
)->loadResult(); )->loadResult();
@@ -1233,7 +1233,7 @@ class TicketsModel extends BaseDatabaseModel
'sla_responded' => 1, 'sla_responded' => 1,
]; ];
$db->insertObject('#__mokosuite_tickets', $row, 'id'); $db->insertObject('#__mokosuiteclient_tickets', $row, 'id');
$ticketIdMap[(int) $t->id] = (int) $row->id; $ticketIdMap[(int) $t->id] = (int) $row->id;
$results['tickets']++; $results['tickets']++;
} }
@@ -1258,7 +1258,7 @@ class TicketsModel extends BaseDatabaseModel
$body = strip_tags($p->content_html ?? ''); $body = strip_tags($p->content_html ?? '');
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->update('#__mokosuite_tickets') ->update('#__mokosuiteclient_tickets')
->set($db->quoteName('body') . ' = ' . $db->quote($body)) ->set($db->quoteName('body') . ' = ' . $db->quote($body))
->where($db->quoteName('id') . ' = ' . $newTicketId) ->where($db->quoteName('id') . ' = ' . $newTicketId)
)->execute(); )->execute();
@@ -1274,7 +1274,7 @@ class TicketsModel extends BaseDatabaseModel
'created' => $p->created ?: Factory::getDate()->toSql(), 'created' => $p->created ?: Factory::getDate()->toSql(),
]; ];
$db->insertObject('#__mokosuite_ticket_replies', $row, 'id'); $db->insertObject('#__mokosuiteclient_ticket_replies', $row, 'id');
$results['replies']++; $results['replies']++;
} }
@@ -1,12 +1,12 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
namespace Moko\Component\MokoSuite\Administrator\Model; namespace Moko\Component\MokoSuiteClient\Administrator\Model;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -23,7 +23,7 @@ class WaflogModel extends BaseDatabaseModel
$db = $this->getDatabase(); $db = $this->getDatabase();
$query = $db->getQuery(true) $query = $db->getQuery(true)
->select('*') ->select('*')
->from($db->quoteName('#__mokosuite_waf_log')); ->from($db->quoteName('#__mokosuiteclient_waf_log'));
if (!empty($filters['rule'])) if (!empty($filters['rule']))
{ {
@@ -69,7 +69,7 @@ class WaflogModel extends BaseDatabaseModel
$db = $this->getDatabase(); $db = $this->getDatabase();
$query = $db->getQuery(true) $query = $db->getQuery(true)
->select('COUNT(*)') ->select('COUNT(*)')
->from($db->quoteName('#__mokosuite_waf_log')); ->from($db->quoteName('#__mokosuiteclient_waf_log'));
if (!empty($filters['rule'])) if (!empty($filters['rule']))
{ {
@@ -95,7 +95,7 @@ class WaflogModel extends BaseDatabaseModel
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->select([$db->quoteName('rule'), 'COUNT(*) AS ' . $db->quoteName('cnt')]) ->select([$db->quoteName('rule'), 'COUNT(*) AS ' . $db->quoteName('cnt')])
->from($db->quoteName('#__mokosuite_waf_log')) ->from($db->quoteName('#__mokosuiteclient_waf_log'))
->group($db->quoteName('rule')) ->group($db->quoteName('rule'))
->order($db->quoteName('cnt') . ' DESC') ->order($db->quoteName('cnt') . ' DESC')
); );
@@ -113,7 +113,7 @@ class WaflogModel extends BaseDatabaseModel
$db->getQuery(true) $db->getQuery(true)
->select([$db->quoteName('ip'), 'COUNT(*) AS ' . $db->quoteName('cnt'), ->select([$db->quoteName('ip'), 'COUNT(*) AS ' . $db->quoteName('cnt'),
'MAX(' . $db->quoteName('created') . ') AS ' . $db->quoteName('last_seen')]) 'MAX(' . $db->quoteName('created') . ') AS ' . $db->quoteName('last_seen')])
->from($db->quoteName('#__mokosuite_waf_log')) ->from($db->quoteName('#__mokosuiteclient_waf_log'))
->group($db->quoteName('ip')) ->group($db->quoteName('ip'))
->order($db->quoteName('cnt') . ' DESC') ->order($db->quoteName('cnt') . ' DESC')
->setLimit($limit) ->setLimit($limit)
@@ -131,7 +131,7 @@ class WaflogModel extends BaseDatabaseModel
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->select('DISTINCT ' . $db->quoteName('rule')) ->select('DISTINCT ' . $db->quoteName('rule'))
->from($db->quoteName('#__mokosuite_waf_log')) ->from($db->quoteName('#__mokosuiteclient_waf_log'))
->order($db->quoteName('rule') . ' ASC') ->order($db->quoteName('rule') . ' ASC')
); );
@@ -150,7 +150,7 @@ class WaflogModel extends BaseDatabaseModel
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->delete($db->quoteName('#__mokosuite_waf_log')) ->delete($db->quoteName('#__mokosuiteclient_waf_log'))
->where($db->quoteName('created') . ' < ' . $db->quote($cutoff)) ->where($db->quoteName('created') . ' < ' . $db->quote($cutoff))
)->execute(); )->execute();
@@ -176,7 +176,7 @@ class WaflogModel extends BaseDatabaseModel
$query = $db->getQuery(true) $query = $db->getQuery(true)
->select($db->quoteName('params')) ->select($db->quoteName('params'))
->from($db->quoteName('#__extensions')) ->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' = ' . $db->quote('mokosuite_firewall')) ->where($db->quoteName('element') . ' = ' . $db->quote('mokosuiteclient_firewall'))
->where($db->quoteName('type') . ' = ' . $db->quote('plugin')) ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' . $db->quote('system')); ->where($db->quoteName('folder') . ' = ' . $db->quote('system'));
$db->setQuery($query); $db->setQuery($query);
@@ -200,7 +200,7 @@ class WaflogModel extends BaseDatabaseModel
$db->getQuery(true) $db->getQuery(true)
->update($db->quoteName('#__extensions')) ->update($db->quoteName('#__extensions'))
->set($db->quoteName('params') . ' = ' . $db->quote($params->toString())) ->set($db->quoteName('params') . ' = ' . $db->quote($params->toString()))
->where($db->quoteName('element') . ' = ' . $db->quote('mokosuite_firewall')) ->where($db->quoteName('element') . ' = ' . $db->quote('mokosuiteclient_firewall'))
->where($db->quoteName('type') . ' = ' . $db->quote('plugin')) ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' . $db->quote('system')) ->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
)->execute(); )->execute();
@@ -1,12 +1,12 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
namespace Moko\Component\MokoSuite\Administrator\Service; namespace Moko\Component\MokoSuiteClient\Administrator\Service;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -67,13 +67,13 @@ class NotificationService
} }
catch (\Throwable $e) catch (\Throwable $e)
{ {
Log::add('Notification send failed to ' . $email . ': ' . $e->getMessage(), Log::WARNING, 'mokosuite'); Log::add('Notification send failed to ' . $email . ': ' . $e->getMessage(), Log::WARNING, 'mokosuiteclient');
} }
} }
} }
catch (\Throwable $e) catch (\Throwable $e)
{ {
Log::add('Notification error: ' . $e->getMessage(), Log::WARNING, 'mokosuite'); Log::add('Notification error: ' . $e->getMessage(), Log::WARNING, 'mokosuiteclient');
} }
} }
@@ -207,7 +207,7 @@ class NotificationService
{ {
$siteName = Factory::getConfig()->get('sitename', 'Support'); $siteName = Factory::getConfig()->get('sitename', 'Support');
$siteUrl = rtrim(Uri::root(), '/'); $siteUrl = rtrim(Uri::root(), '/');
$ticketUrl = $siteUrl . '/index.php?option=com_mokosuite&view=ticket&id=' . $ticket->id; $ticketUrl = $siteUrl . '/index.php?option=com_mokosuiteclient&view=ticket&id=' . $ticket->id;
$lines = []; $lines = [];
$lines[] = $siteName . ' Support'; $lines[] = $siteName . ' Support';
@@ -273,7 +273,7 @@ class NotificationService
$lines[] = 'View ticket: ' . $ticketUrl; $lines[] = 'View ticket: ' . $ticketUrl;
$lines[] = ''; $lines[] = '';
$lines[] = '-- '; $lines[] = '-- ';
$lines[] = $siteName . ' | Powered by MokoSuite'; $lines[] = $siteName . ' | Powered by MokoSuiteClient';
return implode("\n", $lines); return implode("\n", $lines);
} }
@@ -318,7 +318,7 @@ class NotificationService
$db->getQuery(true) $db->getQuery(true)
->select($db->quoteName('params')) ->select($db->quoteName('params'))
->from($db->quoteName('#__extensions')) ->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuite')) ->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuiteclient'))
->where($db->quoteName('type') . ' = ' . $db->quote('component')) ->where($db->quoteName('type') . ' = ' . $db->quote('component'))
); );
@@ -386,7 +386,7 @@ class NotificationService
$body, $body,
'', '',
'-- ', '-- ',
$siteName . ' | MokoSuite Security', $siteName . ' | MokoSuiteClient Security',
]; ];
$mailer = Factory::getMailer(); $mailer = Factory::getMailer();
@@ -404,13 +404,13 @@ class NotificationService
} }
catch (\Throwable $e) catch (\Throwable $e)
{ {
Log::add('Security alert send failed: ' . $e->getMessage(), Log::WARNING, 'mokosuite'); Log::add('Security alert send failed: ' . $e->getMessage(), Log::WARNING, 'mokosuiteclient');
} }
} }
} }
catch (\Throwable $e) catch (\Throwable $e)
{ {
Log::add('Security alert error: ' . $e->getMessage(), Log::WARNING, 'mokosuite'); Log::add('Security alert error: ' . $e->getMessage(), Log::WARNING, 'mokosuiteclient');
} }
} }
} }
@@ -1,5 +1,5 @@
<?php <?php
namespace Moko\Component\MokoSuite\Administrator\View\Automation; namespace Moko\Component\MokoSuiteClient\Administrator\View\Automation;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -13,14 +13,14 @@ class HtmlView extends BaseHtmlView
public function display($tpl = null) public function display($tpl = null)
{ {
$model = new \Moko\Component\MokoSuite\Administrator\Model\TicketsModel(); $model = new \Moko\Component\MokoSuiteClient\Administrator\Model\TicketsModel();
$this->rules = $model->getAutomationRules(); $this->rules = $model->getAutomationRules();
ToolbarHelper::title('Automation Rules', 'cogs'); ToolbarHelper::title('Automation Rules', 'cogs');
ToolbarHelper::back('JTOOLBAR_BACK', 'index.php?option=com_mokosuite&view=tickets'); ToolbarHelper::back('JTOOLBAR_BACK', 'index.php?option=com_mokosuiteclient&view=tickets');
$wa = Factory::getApplication()->getDocument()->getWebAssetManager(); $wa = Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->registerAndUseStyle('com_mokosuite.dashboard', 'com_mokosuite/dashboard.css'); $wa->registerAndUseStyle('com_mokosuiteclient.dashboard', 'com_mokosuiteclient/dashboard.css');
parent::display($tpl); parent::display($tpl);
} }
@@ -1,5 +1,5 @@
<?php <?php
namespace Moko\Component\MokoSuite\Administrator\View\Canned; namespace Moko\Component\MokoSuiteClient\Administrator\View\Canned;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -16,17 +16,17 @@ class HtmlView extends BaseHtmlView
{ {
$db = Factory::getContainer()->get('Joomla\Database\DatabaseInterface'); $db = Factory::getContainer()->get('Joomla\Database\DatabaseInterface');
$db->setQuery('SELECT * FROM #__mokosuite_ticket_canned ORDER BY ordering ASC'); $db->setQuery('SELECT * FROM #__mokosuiteclient_ticket_canned ORDER BY ordering ASC');
$this->responses = $db->loadObjectList() ?: []; $this->responses = $db->loadObjectList() ?: [];
$db->setQuery('SELECT id, title FROM #__mokosuite_ticket_categories WHERE published = 1 ORDER BY ordering'); $db->setQuery('SELECT id, title FROM #__mokosuiteclient_ticket_categories WHERE published = 1 ORDER BY ordering');
$this->categories = $db->loadObjectList() ?: []; $this->categories = $db->loadObjectList() ?: [];
ToolbarHelper::title('Canned Responses', 'comment'); ToolbarHelper::title('Canned Responses', 'comment');
ToolbarHelper::back('JTOOLBAR_BACK', 'index.php?option=com_mokosuite&view=tickets'); ToolbarHelper::back('JTOOLBAR_BACK', 'index.php?option=com_mokosuiteclient&view=tickets');
$wa = Factory::getApplication()->getDocument()->getWebAssetManager(); $wa = Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->registerAndUseStyle('com_mokosuite.dashboard', 'com_mokosuite/dashboard.css'); $wa->registerAndUseStyle('com_mokosuiteclient.dashboard', 'com_mokosuiteclient/dashboard.css');
parent::display($tpl); parent::display($tpl);
} }
@@ -1,5 +1,5 @@
<?php <?php
namespace Moko\Component\MokoSuite\Administrator\View\Categories; namespace Moko\Component\MokoSuiteClient\Administrator\View\Categories;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -16,7 +16,7 @@ class HtmlView extends BaseHtmlView
{ {
$db = Factory::getContainer()->get('Joomla\Database\DatabaseInterface'); $db = Factory::getContainer()->get('Joomla\Database\DatabaseInterface');
$db->setQuery('SELECT * FROM #__mokosuite_ticket_categories ORDER BY ordering ASC'); $db->setQuery('SELECT * FROM #__mokosuiteclient_ticket_categories ORDER BY ordering ASC');
$this->categories = $db->loadObjectList() ?: []; $this->categories = $db->loadObjectList() ?: [];
// Get admin users for auto-assign dropdown // Get admin users for auto-assign dropdown
@@ -31,10 +31,10 @@ class HtmlView extends BaseHtmlView
$this->users = $db->loadObjectList() ?: []; $this->users = $db->loadObjectList() ?: [];
ToolbarHelper::title('Ticket Categories', 'folder'); ToolbarHelper::title('Ticket Categories', 'folder');
ToolbarHelper::back('JTOOLBAR_BACK', 'index.php?option=com_mokosuite&view=tickets'); ToolbarHelper::back('JTOOLBAR_BACK', 'index.php?option=com_mokosuiteclient&view=tickets');
$wa = Factory::getApplication()->getDocument()->getWebAssetManager(); $wa = Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->registerAndUseStyle('com_mokosuite.dashboard', 'com_mokosuite/dashboard.css'); $wa->registerAndUseStyle('com_mokosuiteclient.dashboard', 'com_mokosuiteclient/dashboard.css');
parent::display($tpl); parent::display($tpl);
} }
@@ -1,5 +1,5 @@
<?php <?php
namespace Moko\Component\MokoSuite\Administrator\View\Cleanup; namespace Moko\Component\MokoSuiteClient\Administrator\View\Cleanup;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -13,14 +13,14 @@ class HtmlView extends BaseHtmlView
public function display($tpl = null) public function display($tpl = null)
{ {
$model = new \Moko\Component\MokoSuite\Administrator\Model\MaintenanceModel(); $model = new \Moko\Component\MokoSuiteClient\Administrator\Model\MaintenanceModel();
$this->dirs = $model->getCleanupInfo(); $this->dirs = $model->getCleanupInfo();
ToolbarHelper::title('Cache &amp; Temp Cleanup', 'trash'); ToolbarHelper::title('Cache &amp; Temp Cleanup', 'trash');
ToolbarHelper::back('JTOOLBAR_BACK', 'index.php?option=com_mokosuite'); ToolbarHelper::back('JTOOLBAR_BACK', 'index.php?option=com_mokosuiteclient');
$wa = Factory::getApplication()->getDocument()->getWebAssetManager(); $wa = Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->registerAndUseStyle('com_mokosuite.dashboard', 'com_mokosuite/dashboard.css'); $wa->registerAndUseStyle('com_mokosuiteclient.dashboard', 'com_mokosuiteclient/dashboard.css');
parent::display($tpl); parent::display($tpl);
} }
@@ -1,12 +1,12 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
namespace Moko\Component\MokoSuite\Administrator\View\Dashboard; namespace Moko\Component\MokoSuiteClient\Administrator\View\Dashboard;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -44,7 +44,7 @@ class HtmlView extends BaseHtmlView
// Check for importable Akeeba data // Check for importable Akeeba data
try try
{ {
$importModel = new \Moko\Component\MokoSuite\Administrator\Model\ImportModel(); $importModel = new \Moko\Component\MokoSuiteClient\Administrator\Model\ImportModel();
$this->adminToolsAvailable = $importModel->checkAdminToolsAvailable(); $this->adminToolsAvailable = $importModel->checkAdminToolsAvailable();
$this->atsAvailable = $importModel->checkAtsAvailable(); $this->atsAvailable = $importModel->checkAtsAvailable();
} }
@@ -57,21 +57,21 @@ class HtmlView extends BaseHtmlView
$this->addToolbar(); $this->addToolbar();
$wa = Factory::getApplication()->getDocument()->getWebAssetManager(); $wa = Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->registerAndUseStyle('com_mokosuite.dashboard', 'com_mokosuite/dashboard.css'); $wa->registerAndUseStyle('com_mokosuiteclient.dashboard', 'com_mokosuiteclient/dashboard.css');
$wa->registerAndUseScript('com_mokosuite.dashboard', 'com_mokosuite/dashboard.js', [], ['defer' => true]); $wa->registerAndUseScript('com_mokosuiteclient.dashboard', 'com_mokosuiteclient/dashboard.js', [], ['defer' => true]);
parent::display($tpl); parent::display($tpl);
} }
protected function addToolbar(): void protected function addToolbar(): void
{ {
ToolbarHelper::title(Text::_('COM_MOKOSUITE_DASHBOARD_TITLE'), 'cogs'); ToolbarHelper::title(Text::_('COM_MOKOSUITECLIENT_DASHBOARD_TITLE'), 'cogs');
$user = Factory::getApplication()->getIdentity(); $user = Factory::getApplication()->getIdentity();
if ($user->authorise('core.admin', 'com_mokosuite')) if ($user->authorise('core.admin', 'com_mokosuiteclient'))
{ {
ToolbarHelper::preferences('com_mokosuite'); ToolbarHelper::preferences('com_mokosuiteclient');
} }
} }
} }
@@ -1,5 +1,5 @@
<?php <?php
namespace Moko\Component\MokoSuite\Administrator\View\Database; namespace Moko\Component\MokoSuiteClient\Administrator\View\Database;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -13,14 +13,14 @@ class HtmlView extends BaseHtmlView
public function display($tpl = null) public function display($tpl = null)
{ {
$model = new \Moko\Component\MokoSuite\Administrator\Model\MaintenanceModel(); $model = new \Moko\Component\MokoSuiteClient\Administrator\Model\MaintenanceModel();
$this->tableData = $model->getTableStatus(); $this->tableData = $model->getTableStatus();
ToolbarHelper::title('Database Tools', 'database'); ToolbarHelper::title('Database Tools', 'database');
ToolbarHelper::back('JTOOLBAR_BACK', 'index.php?option=com_mokosuite'); ToolbarHelper::back('JTOOLBAR_BACK', 'index.php?option=com_mokosuiteclient');
$wa = Factory::getApplication()->getDocument()->getWebAssetManager(); $wa = Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->registerAndUseStyle('com_mokosuite.dashboard', 'com_mokosuite/dashboard.css'); $wa->registerAndUseStyle('com_mokosuiteclient.dashboard', 'com_mokosuiteclient/dashboard.css');
parent::display($tpl); parent::display($tpl);
} }
@@ -1,5 +1,5 @@
<?php <?php
namespace Moko\Component\MokoSuite\Administrator\View\ErpReports; namespace Moko\Component\MokoSuiteClient\Administrator\View\ErpReports;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -32,9 +32,9 @@ class HtmlView extends BaseHtmlView
$this->agingData = $model->getAgingReceivables(); $this->agingData = $model->getAgingReceivables();
ToolbarHelper::title('ERP Reports', 'icon-chart-bar'); ToolbarHelper::title('ERP Reports', 'icon-chart-bar');
$wa = Factory::getApplication()->getDocument()->getWebAssetManager(); $wa = Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->registerAndUseStyle('com_mokosuite.erp', 'com_mokosuite/erp.css'); $wa->registerAndUseStyle('com_mokosuiteclient.erp', 'com_mokosuiteclient/erp.css');
$wa->registerAndUseScript('com_mokosuite.erp-dashboard', 'com_mokosuite/erp-dashboard.js', [], ['defer' => true]); $wa->registerAndUseScript('com_mokosuiteclient.erp-dashboard', 'com_mokosuiteclient/erp-dashboard.js', [], ['defer' => true]);
Factory::getApplication()->getDocument()->addScriptOptions('mokosuite.erp', ['revenueChart' => $this->salesData]); Factory::getApplication()->getDocument()->addScriptOptions('mokosuiteclient.erp', ['revenueChart' => $this->salesData]);
parent::display($tpl); parent::display($tpl);
} }
} }
@@ -1,12 +1,12 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
namespace Moko\Component\MokoSuite\Administrator\View\Extensions; namespace Moko\Component\MokoSuiteClient\Administrator\View\Extensions;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -28,14 +28,14 @@ class HtmlView extends BaseHtmlView
$this->addToolbar(); $this->addToolbar();
$wa = Factory::getApplication()->getDocument()->getWebAssetManager(); $wa = Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->registerAndUseStyle('com_mokosuite.dashboard', 'com_mokosuite/dashboard.css'); $wa->registerAndUseStyle('com_mokosuiteclient.dashboard', 'com_mokosuiteclient/dashboard.css');
parent::display($tpl); parent::display($tpl);
} }
protected function addToolbar(): void protected function addToolbar(): void
{ {
ToolbarHelper::title(Text::_('COM_MOKOSUITE_EXTENSIONS_TITLE'), 'puzzle-piece'); ToolbarHelper::title(Text::_('COM_MOKOSUITECLIENT_EXTENSIONS_TITLE'), 'puzzle-piece');
ToolbarHelper::back('JTOOLBAR_BACK', 'index.php?option=com_mokosuite'); ToolbarHelper::back('JTOOLBAR_BACK', 'index.php?option=com_mokosuiteclient');
} }
} }
@@ -1,12 +1,12 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
namespace Moko\Component\MokoSuite\Administrator\View\Htaccess; namespace Moko\Component\MokoSuiteClient\Administrator\View\Htaccess;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -34,14 +34,14 @@ class HtmlView extends BaseHtmlView
$this->addToolbar(); $this->addToolbar();
$wa = Factory::getApplication()->getDocument()->getWebAssetManager(); $wa = Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->registerAndUseStyle('com_mokosuite.dashboard', 'com_mokosuite/dashboard.css'); $wa->registerAndUseStyle('com_mokosuiteclient.dashboard', 'com_mokosuiteclient/dashboard.css');
parent::display($tpl); parent::display($tpl);
} }
protected function addToolbar(): void protected function addToolbar(): void
{ {
ToolbarHelper::title(Text::_('COM_MOKOSUITE_HTACCESS_TITLE'), 'file-code'); ToolbarHelper::title(Text::_('COM_MOKOSUITECLIENT_HTACCESS_TITLE'), 'file-code');
ToolbarHelper::back('JTOOLBAR_BACK', 'index.php?option=com_mokosuite'); ToolbarHelper::back('JTOOLBAR_BACK', 'index.php?option=com_mokosuiteclient');
} }
} }
@@ -1,5 +1,5 @@
<?php <?php
namespace Moko\Component\MokoSuite\Administrator\View\Privacy; namespace Moko\Component\MokoSuiteClient\Administrator\View\Privacy;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -16,7 +16,7 @@ class HtmlView extends BaseHtmlView
public function display($tpl = null) public function display($tpl = null)
{ {
$model = new \Moko\Component\MokoSuite\Administrator\Model\PrivacyModel(); $model = new \Moko\Component\MokoSuiteClient\Administrator\Model\PrivacyModel();
$filterStatus = Factory::getApplication()->getInput()->getString('filter_status', ''); $filterStatus = Factory::getApplication()->getInput()->getString('filter_status', '');
$this->requests = $model->getDataRequests($filterStatus); $this->requests = $model->getDataRequests($filterStatus);
@@ -26,7 +26,7 @@ class HtmlView extends BaseHtmlView
$this->addToolbar(); $this->addToolbar();
$wa = Factory::getApplication()->getDocument()->getWebAssetManager(); $wa = Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->registerAndUseStyle('com_mokosuite.dashboard', 'com_mokosuite/dashboard.css'); $wa->registerAndUseStyle('com_mokosuiteclient.dashboard', 'com_mokosuiteclient/dashboard.css');
parent::display($tpl); parent::display($tpl);
} }
@@ -34,6 +34,6 @@ class HtmlView extends BaseHtmlView
protected function addToolbar(): void protected function addToolbar(): void
{ {
ToolbarHelper::title('Privacy Guard', 'lock'); ToolbarHelper::title('Privacy Guard', 'lock');
ToolbarHelper::back('JTOOLBAR_BACK', 'index.php?option=com_mokosuite'); ToolbarHelper::back('JTOOLBAR_BACK', 'index.php?option=com_mokosuiteclient');
} }
} }
@@ -1,12 +1,12 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
namespace Moko\Component\MokoSuite\Administrator\View\Ticket; namespace Moko\Component\MokoSuiteClient\Administrator\View\Ticket;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -46,7 +46,7 @@ class HtmlView extends BaseHtmlView
if (!$this->ticket) if (!$this->ticket)
{ {
Factory::getApplication()->enqueueMessage('Ticket not found.', 'error'); Factory::getApplication()->enqueueMessage('Ticket not found.', 'error');
Factory::getApplication()->redirect('index.php?option=com_mokosuite&view=tickets'); Factory::getApplication()->redirect('index.php?option=com_mokosuiteclient&view=tickets');
return; return;
} }
@@ -54,7 +54,7 @@ class HtmlView extends BaseHtmlView
$this->addToolbar(); $this->addToolbar();
$wa = Factory::getApplication()->getDocument()->getWebAssetManager(); $wa = Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->registerAndUseStyle('com_mokosuite.dashboard', 'com_mokosuite/dashboard.css'); $wa->registerAndUseStyle('com_mokosuiteclient.dashboard', 'com_mokosuiteclient/dashboard.css');
parent::display($tpl); parent::display($tpl);
} }
@@ -63,6 +63,6 @@ class HtmlView extends BaseHtmlView
{ {
$title = $this->ticket ? 'Ticket #' . $this->ticket->id . ' — ' . $this->ticket->subject : 'Ticket'; $title = $this->ticket ? 'Ticket #' . $this->ticket->id . ' — ' . $this->ticket->subject : 'Ticket';
ToolbarHelper::title($title, 'headphones'); ToolbarHelper::title($title, 'headphones');
ToolbarHelper::back('JTOOLBAR_BACK', 'index.php?option=com_mokosuite&view=tickets'); ToolbarHelper::back('JTOOLBAR_BACK', 'index.php?option=com_mokosuiteclient&view=tickets');
} }
} }
@@ -1,12 +1,12 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
namespace Moko\Component\MokoSuite\Administrator\View\Tickets; namespace Moko\Component\MokoSuiteClient\Administrator\View\Tickets;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -50,14 +50,14 @@ class HtmlView extends BaseHtmlView
$this->addToolbar(); $this->addToolbar();
$wa = Factory::getApplication()->getDocument()->getWebAssetManager(); $wa = Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->registerAndUseStyle('com_mokosuite.dashboard', 'com_mokosuite/dashboard.css'); $wa->registerAndUseStyle('com_mokosuiteclient.dashboard', 'com_mokosuiteclient/dashboard.css');
parent::display($tpl); parent::display($tpl);
} }
protected function addToolbar(): void protected function addToolbar(): void
{ {
ToolbarHelper::title(Text::_('COM_MOKOSUITE_TICKETS_TITLE'), 'headphones'); ToolbarHelper::title(Text::_('COM_MOKOSUITECLIENT_TICKETS_TITLE'), 'headphones');
ToolbarHelper::back('JTOOLBAR_BACK', 'index.php?option=com_mokosuite'); ToolbarHelper::back('JTOOLBAR_BACK', 'index.php?option=com_mokosuiteclient');
} }
} }
@@ -1,5 +1,5 @@
<?php <?php
namespace Moko\Component\MokoSuite\Administrator\View\Waflog; namespace Moko\Component\MokoSuiteClient\Administrator\View\Waflog;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -18,7 +18,7 @@ class HtmlView extends BaseHtmlView
public function display($tpl = null) public function display($tpl = null)
{ {
$model = new \Moko\Component\MokoSuite\Administrator\Model\WaflogModel(); $model = new \Moko\Component\MokoSuiteClient\Administrator\Model\WaflogModel();
$input = Factory::getApplication()->getInput(); $input = Factory::getApplication()->getInput();
$this->filters = [ $this->filters = [
@@ -42,7 +42,7 @@ class HtmlView extends BaseHtmlView
$this->addToolbar(); $this->addToolbar();
$wa = Factory::getApplication()->getDocument()->getWebAssetManager(); $wa = Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->registerAndUseStyle('com_mokosuite.dashboard', 'com_mokosuite/dashboard.css'); $wa->registerAndUseStyle('com_mokosuiteclient.dashboard', 'com_mokosuiteclient/dashboard.css');
parent::display($tpl); parent::display($tpl);
} }
@@ -50,6 +50,6 @@ class HtmlView extends BaseHtmlView
protected function addToolbar(): void protected function addToolbar(): void
{ {
ToolbarHelper::title('WAF Log Viewer', 'shield-alt'); ToolbarHelper::title('WAF Log Viewer', 'shield-alt');
ToolbarHelper::back('JTOOLBAR_BACK', 'index.php?option=com_mokosuite'); ToolbarHelper::back('JTOOLBAR_BACK', 'index.php?option=com_mokosuiteclient');
} }
} }
@@ -6,14 +6,14 @@ use Joomla\CMS\Session\Session;
$rules = $this->rules; $rules = $this->rules;
$token = Session::getFormToken(); $token = Session::getFormToken();
$saveUrl = Route::_('index.php?option=com_mokosuite&task=display.saveAutomation&format=json'); $saveUrl = Route::_('index.php?option=com_mokosuiteclient&task=display.saveAutomation&format=json');
$deleteUrl = Route::_('index.php?option=com_mokosuite&task=display.deleteAutomation&format=json'); $deleteUrl = Route::_('index.php?option=com_mokosuiteclient&task=display.deleteAutomation&format=json');
$toggleUrl = Route::_('index.php?option=com_mokosuite&task=display.toggleAutomation&format=json'); $toggleUrl = Route::_('index.php?option=com_mokosuiteclient&task=display.toggleAutomation&format=json');
$triggerLabels = ['ticket_created' => 'On Ticket Created', 'ticket_replied' => 'On Reply', 'status_changed' => 'On Status Change', 'scheduled' => 'Scheduled (Cron)']; $triggerLabels = ['ticket_created' => 'On Ticket Created', 'ticket_replied' => 'On Reply', 'status_changed' => 'On Status Change', 'scheduled' => 'Scheduled (Cron)'];
?> ?>
<div id="mokosuite-automation"> <div id="mokosuiteclient-automation">
<div class="d-flex justify-content-between align-items-center mb-3"> <div class="d-flex justify-content-between align-items-center mb-3">
<h4><?php echo count($rules); ?> Automation Rules</h4> <h4><?php echo count($rules); ?> Automation Rules</h4>
<button type="button" class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#newRuleModal"> <button type="button" class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#newRuleModal">
@@ -7,11 +7,11 @@ use Joomla\CMS\Session\Session;
$responses = $this->responses; $responses = $this->responses;
$categories = $this->categories; $categories = $this->categories;
$token = Session::getFormToken(); $token = Session::getFormToken();
$saveUrl = Route::_('index.php?option=com_mokosuite&task=display.saveCanned&format=json'); $saveUrl = Route::_('index.php?option=com_mokosuiteclient&task=display.saveCanned&format=json');
$deleteUrl = Route::_('index.php?option=com_mokosuite&task=display.deleteCanned&format=json'); $deleteUrl = Route::_('index.php?option=com_mokosuiteclient&task=display.deleteCanned&format=json');
?> ?>
<div id="mokosuite-canned"> <div id="mokosuiteclient-canned">
<div class="d-flex justify-content-between align-items-center mb-3"> <div class="d-flex justify-content-between align-items-center mb-3">
<h4><?php echo count($responses); ?> Canned Responses</h4> <h4><?php echo count($responses); ?> Canned Responses</h4>
<button type="button" class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#newCannedModal"> <button type="button" class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#newCannedModal">
@@ -7,11 +7,11 @@ use Joomla\CMS\Session\Session;
$categories = $this->categories; $categories = $this->categories;
$users = $this->users; $users = $this->users;
$token = Session::getFormToken(); $token = Session::getFormToken();
$saveUrl = Route::_('index.php?option=com_mokosuite&task=display.saveCategory&format=json'); $saveUrl = Route::_('index.php?option=com_mokosuiteclient&task=display.saveCategory&format=json');
$deleteUrl = Route::_('index.php?option=com_mokosuite&task=display.deleteCategory&format=json'); $deleteUrl = Route::_('index.php?option=com_mokosuiteclient&task=display.deleteCategory&format=json');
?> ?>
<div id="mokosuite-categories"> <div id="mokosuiteclient-categories">
<div class="d-flex justify-content-between align-items-center mb-3"> <div class="d-flex justify-content-between align-items-center mb-3">
<h4><?php echo count($categories); ?> Categories</h4> <h4><?php echo count($categories); ?> Categories</h4>
<button type="button" class="btn btn-primary btn-sm" id="btn-add-cat"> <button type="button" class="btn btn-primary btn-sm" id="btn-add-cat">
@@ -6,7 +6,7 @@ use Joomla\CMS\Session\Session;
$dirs = $this->dirs; $dirs = $this->dirs;
$token = Session::getFormToken(); $token = Session::getFormToken();
$cleanUrl = Route::_('index.php?option=com_mokosuite&task=display.cleanDirectory&format=json'); $cleanUrl = Route::_('index.php?option=com_mokosuiteclient&task=display.cleanDirectory&format=json');
$dirKeys = ['site_cache', 'admin_cache', 'tmp', 'logs']; $dirKeys = ['site_cache', 'admin_cache', 'tmp', 'logs'];
$totalMb = 0; $totalMb = 0;
@@ -14,7 +14,7 @@ $totalFiles = 0;
foreach ($dirs as $d) { $totalMb += $d->size_mb; $totalFiles += $d->files; } foreach ($dirs as $d) { $totalMb += $d->size_mb; $totalFiles += $d->files; }
?> ?>
<div id="mokosuite-cleanup"> <div id="mokosuiteclient-cleanup">
<div class="row g-3 mb-4"> <div class="row g-3 mb-4">
<div class="col-6 col-md-3"><div class="card text-center p-3"><span class="fw-bold fs-3"><?php echo number_format($totalMb, 1); ?> MB</span><small class="text-muted">Total Size</small></div></div> <div class="col-6 col-md-3"><div class="card text-center p-3"><span class="fw-bold fs-3"><?php echo number_format($totalMb, 1); ?> MB</span><small class="text-muted">Total Size</small></div></div>
<div class="col-6 col-md-3"><div class="card text-center p-3"><span class="fw-bold fs-3"><?php echo number_format($totalFiles); ?></span><small class="text-muted">Total Files</small></div></div> <div class="col-6 col-md-3"><div class="card text-center p-3"><span class="fw-bold fs-3"><?php echo number_format($totalFiles); ?></span><small class="text-muted">Total Files</small></div></div>
@@ -1,7 +1,7 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
@@ -13,7 +13,7 @@ use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route; use Joomla\CMS\Router\Route;
use Joomla\CMS\Session\Session; use Joomla\CMS\Session\Session;
/** @var \Moko\Component\MokoSuite\Administrator\View\Dashboard\HtmlView $this */ /** @var \Moko\Component\MokoSuiteClient\Administrator\View\Dashboard\HtmlView $this */
$siteInfo = $this->siteInfo; $siteInfo = $this->siteInfo;
$plugins = $this->plugins; $plugins = $this->plugins;
@@ -36,37 +36,37 @@ foreach ($plugins as $plugin)
$categoryOrder = ['core', 'security', 'monitoring', 'content', 'tools', 'api']; $categoryOrder = ['core', 'security', 'monitoring', 'content', 'tools', 'api'];
?> ?>
<div id="mokosuite-dashboard"> <div id="mokosuiteclient-dashboard">
<!-- Site Info Bar --> <!-- Site Info Bar -->
<div class="mokosuite-info-bar card mb-4"> <div class="mokosuiteclient-info-bar card mb-4">
<div class="card-body d-flex flex-wrap align-items-center gap-4"> <div class="card-body d-flex flex-wrap align-items-center gap-4">
<div class="mokosuite-info-item"> <div class="mokosuiteclient-info-item">
<span class="mokosuite-info-label"><?php echo Text::_('COM_MOKOSUITE_SITE'); ?></span> <span class="mokosuiteclient-info-label"><?php echo Text::_('COM_MOKOSUITECLIENT_SITE'); ?></span>
<span class="mokosuite-info-value fw-bold"><?php echo $this->escape($siteInfo->sitename); ?></span> <span class="mokosuiteclient-info-value fw-bold"><?php echo $this->escape($siteInfo->sitename); ?></span>
</div> </div>
<div class="mokosuite-info-item"> <div class="mokosuiteclient-info-item">
<span class="mokosuite-info-label">MokoSuite</span> <span class="mokosuiteclient-info-label">MokoSuiteClient</span>
<span class="mokosuite-info-value"><span class="badge bg-primary"><?php echo $this->escape($siteInfo->mokosuite_version); ?></span></span> <span class="mokosuiteclient-info-value"><span class="badge bg-primary"><?php echo $this->escape($siteInfo->mokosuiteclient_version); ?></span></span>
</div> </div>
<div class="mokosuite-info-item"> <div class="mokosuiteclient-info-item">
<span class="mokosuite-info-label">Joomla</span> <span class="mokosuiteclient-info-label">Joomla</span>
<span class="mokosuite-info-value"><span class="badge bg-secondary"><?php echo $this->escape($siteInfo->joomla_version); ?></span></span> <span class="mokosuiteclient-info-value"><span class="badge bg-secondary"><?php echo $this->escape($siteInfo->joomla_version); ?></span></span>
</div> </div>
<div class="mokosuite-info-item"> <div class="mokosuiteclient-info-item">
<span class="mokosuite-info-label">PHP</span> <span class="mokosuiteclient-info-label">PHP</span>
<span class="mokosuite-info-value"><span class="badge bg-secondary"><?php echo $this->escape($siteInfo->php_version); ?></span></span> <span class="mokosuiteclient-info-value"><span class="badge bg-secondary"><?php echo $this->escape($siteInfo->php_version); ?></span></span>
</div> </div>
<div class="mokosuite-info-item"> <div class="mokosuiteclient-info-item">
<span class="mokosuite-info-label"><?php echo Text::_('COM_MOKOSUITE_DATABASE'); ?></span> <span class="mokosuiteclient-info-label"><?php echo Text::_('COM_MOKOSUITECLIENT_DATABASE'); ?></span>
<span class="mokosuite-info-value"><span class="badge bg-secondary"><?php echo $this->escape($siteInfo->db_type); ?></span></span> <span class="mokosuiteclient-info-value"><span class="badge bg-secondary"><?php echo $this->escape($siteInfo->db_type); ?></span></span>
</div> </div>
<?php if ($siteInfo->debug): ?> <?php if ($siteInfo->debug): ?>
<span class="badge bg-warning text-dark"><?php echo Text::_('COM_MOKOSUITE_DEBUG_ON'); ?></span> <span class="badge bg-warning text-dark"><?php echo Text::_('COM_MOKOSUITECLIENT_DEBUG_ON'); ?></span>
<?php endif; ?> <?php endif; ?>
<?php if ($siteInfo->offline): ?> <?php if ($siteInfo->offline): ?>
<span class="badge bg-danger"><?php echo Text::_('COM_MOKOSUITE_OFFLINE'); ?></span> <span class="badge bg-danger"><?php echo Text::_('COM_MOKOSUITECLIENT_OFFLINE'); ?></span>
<?php endif; ?> <?php endif; ?>
<div class="mokosuite-info-item ms-auto"> <div class="mokosuiteclient-info-item ms-auto">
<span class="icon-globe" aria-hidden="true"></span> <span class="icon-globe" aria-hidden="true"></span>
<code><?php echo $this->escape($_SERVER['REMOTE_ADDR'] ?? ''); ?></code> <code><?php echo $this->escape($_SERVER['REMOTE_ADDR'] ?? ''); ?></code>
</div> </div>
@@ -78,15 +78,15 @@ $categoryOrder = ['core', 'security', 'monitoring', 'content', 'tools', 'api'];
<div class="d-flex flex-wrap gap-2 mb-4"> <div class="d-flex flex-wrap gap-2 mb-4">
<?php <?php
$extIcons = [ $extIcons = [
'com_mokosuite' => 'icon-cogs', 'com_mokosuiteclient' => 'icon-cogs',
'mod_mokosuite_cpanel' => 'icon-tachometer-alt', 'mod_mokosuiteclient_cpanel' => 'icon-tachometer-alt',
'mod_mokosuite_menu' => 'icon-bars', 'mod_mokosuiteclient_menu' => 'icon-bars',
'mod_mokosuite_cache' => 'icon-bolt', 'mod_mokosuiteclient_cache' => 'icon-bolt',
'mod_mokosuite_categories' => 'icon-folder', 'mod_mokosuiteclient_categories' => 'icon-folder',
]; ];
foreach ($mokoExts as $ext): foreach ($mokoExts as $ext):
$icon = $extIcons[$ext->element] ?? 'icon-puzzle-piece'; $icon = $extIcons[$ext->element] ?? 'icon-puzzle-piece';
$label = str_replace(['mod_mokosuite_', 'com_mokosuite'], ['', 'Component'], $ext->element); $label = str_replace(['mod_mokosuiteclient_', 'com_mokosuiteclient'], ['', 'Component'], $ext->element);
$label = ucfirst($label ?: 'Component'); $label = ucfirst($label ?: 'Component');
?> ?>
<div class="d-flex align-items-center gap-2 px-3 py-2 rounded border bg-white" style="font-size:0.85rem;"> <div class="d-flex align-items-center gap-2 px-3 py-2 rounded border bg-white" style="font-size:0.85rem;">
@@ -102,17 +102,17 @@ $categoryOrder = ['core', 'security', 'monitoring', 'content', 'tools', 'api'];
<!-- Akeeba Import Banner --> <!-- Akeeba Import Banner -->
<div class="alert alert-info d-flex flex-wrap align-items-center gap-3 mb-4"> <div class="alert alert-info d-flex flex-wrap align-items-center gap-3 mb-4">
<span class="icon-info-circle" style="font-size:1.25rem"></span> <span class="icon-info-circle" style="font-size:1.25rem"></span>
<strong>Akeeba data detected import into MokoSuite:</strong> <strong>Akeeba data detected import into MokoSuiteClient:</strong>
<?php if ($adminToolsAvail): ?> <?php if ($adminToolsAvail): ?>
<button type="button" class="btn btn-sm btn-info" id="btn-import-admintools" <button type="button" class="btn btn-sm btn-info" id="btn-import-admintools"
data-url="<?php echo Route::_('index.php?option=com_mokosuite&task=display.importAdminTools&format=json'); ?>" data-url="<?php echo Route::_('index.php?option=com_mokosuiteclient&task=display.importAdminTools&format=json'); ?>"
data-token="<?php echo $token; ?>"> data-token="<?php echo $token; ?>">
<span class="icon-shield-alt"></span> Import Admin Tools Settings <span class="icon-shield-alt"></span> Import Admin Tools Settings
</button> </button>
<?php endif; ?> <?php endif; ?>
<?php if ($atsAvail): ?> <?php if ($atsAvail): ?>
<button type="button" class="btn btn-sm btn-info" id="btn-import-ats-dash" <button type="button" class="btn btn-sm btn-info" id="btn-import-ats-dash"
data-url="<?php echo Route::_('index.php?option=com_mokosuite&task=display.importAts&format=json'); ?>" data-url="<?php echo Route::_('index.php?option=com_mokosuiteclient&task=display.importAts&format=json'); ?>"
data-token="<?php echo $token; ?>"> data-token="<?php echo $token; ?>">
<span class="icon-headphones"></span> Import Tickets (<?php echo $atsAvail->tickets; ?> tickets) <span class="icon-headphones"></span> Import Tickets (<?php echo $atsAvail->tickets; ?> tickets)
</button> </button>
@@ -123,8 +123,8 @@ $categoryOrder = ['core', 'security', 'monitoring', 'content', 'tools', 'api'];
<!-- Quick Actions (large buttons) --> <!-- Quick Actions (large buttons) -->
<div class="row g-3 mb-4"> <div class="row g-3 mb-4">
<div class="col-6 col-md-4 col-xl-3"> <div class="col-6 col-md-4 col-xl-3">
<button type="button" class="btn btn-outline-primary w-100 py-3" id="mokosuite-btn-cache" <button type="button" class="btn btn-outline-primary w-100 py-3" id="mokosuiteclient-btn-cache"
data-url="<?php echo Route::_('index.php?option=com_mokosuite&task=display.clearCache&format=json'); ?>" data-url="<?php echo Route::_('index.php?option=com_mokosuiteclient&task=display.clearCache&format=json'); ?>"
data-token="<?php echo $token; ?>"> data-token="<?php echo $token; ?>">
<span class="icon-bolt d-block mb-1" aria-hidden="true" style="font-size:1.5rem"></span> <span class="icon-bolt d-block mb-1" aria-hidden="true" style="font-size:1.5rem"></span>
Clear Cache Clear Cache
@@ -137,7 +137,7 @@ $categoryOrder = ['core', 'security', 'monitoring', 'content', 'tools', 'api'];
</a> </a>
</div> </div>
<div class="col-6 col-md-4 col-xl-3"> <div class="col-6 col-md-4 col-xl-3">
<a href="<?php echo Route::_('index.php?option=com_mokosuite&view=extensions'); ?>" class="btn btn-outline-primary w-100 py-3"> <a href="<?php echo Route::_('index.php?option=com_mokosuiteclient&view=extensions'); ?>" class="btn btn-outline-primary w-100 py-3">
<span class="icon-puzzle-piece d-block mb-1" aria-hidden="true" style="font-size:1.5rem"></span> <span class="icon-puzzle-piece d-block mb-1" aria-hidden="true" style="font-size:1.5rem"></span>
Moko Extensions Moko Extensions
</a> </a>
@@ -192,18 +192,18 @@ $categoryOrder = ['core', 'security', 'monitoring', 'content', 'tools', 'api'];
$catPlugins = $grouped[$catKey]; $catPlugins = $grouped[$catKey];
$first = $catPlugins[0]; $first = $catPlugins[0];
?> ?>
<h3 class="mokosuite-category-heading mb-3"> <h3 class="mokosuiteclient-category-heading mb-3">
<span class="badge <?php echo $this->escape($first->categoryBadge); ?>"><?php echo $this->escape($first->categoryLabel); ?></span> <span class="badge <?php echo $this->escape($first->categoryBadge); ?>"><?php echo $this->escape($first->categoryLabel); ?></span>
</h3> </h3>
<div class="mokosuite-plugin-grid row g-3 mb-4"> <div class="mokosuiteclient-plugin-grid row g-3 mb-4">
<?php foreach ($catPlugins as $plugin): ?> <?php foreach ($catPlugins as $plugin): ?>
<div class="col-12 col-md-6"> <div class="col-12 col-md-6">
<div class="card mokosuite-plugin-card h-100 <?php echo $plugin->enabled ? '' : 'mokosuite-plugin-disabled'; ?>" <div class="card mokosuiteclient-plugin-card h-100 <?php echo $plugin->enabled ? '' : 'mokosuiteclient-plugin-disabled'; ?>"
data-extension-id="<?php echo $plugin->extension_id; ?>"> data-extension-id="<?php echo $plugin->extension_id; ?>">
<div class="card-body d-flex flex-column"> <div class="card-body d-flex flex-column">
<div class="d-flex align-items-start justify-content-between mb-2"> <div class="d-flex align-items-start justify-content-between mb-2">
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
<span class="<?php echo $this->escape($plugin->icon); ?> mokosuite-plugin-icon" aria-hidden="true"></span> <span class="<?php echo $this->escape($plugin->icon); ?> mokosuiteclient-plugin-icon" aria-hidden="true"></span>
<h5 class="card-title mb-0"><?php echo $this->escape($plugin->name); ?></h5> <h5 class="card-title mb-0"><?php echo $this->escape($plugin->name); ?></h5>
</div> </div>
<?php if ($plugin->version): ?> <?php if ($plugin->version): ?>
@@ -213,27 +213,27 @@ $categoryOrder = ['core', 'security', 'monitoring', 'content', 'tools', 'api'];
<p class="card-text text-muted text-muted flex-grow-1"><?php echo $this->escape($plugin->description); ?></p> <p class="card-text text-muted text-muted flex-grow-1"><?php echo $this->escape($plugin->description); ?></p>
<div class="d-flex align-items-center justify-content-between mt-auto pt-2 border-top"> <div class="d-flex align-items-center justify-content-between mt-auto pt-2 border-top">
<?php if ($plugin->protected): ?> <?php if ($plugin->protected): ?>
<span class="badge bg-dark"><?php echo Text::_('COM_MOKOSUITE_PROTECTED'); ?></span> <span class="badge bg-dark"><?php echo Text::_('COM_MOKOSUITECLIENT_PROTECTED'); ?></span>
<?php elseif ($plugin->configure_only): ?> <?php elseif ($plugin->configure_only): ?>
<span class="badge bg-<?php echo $plugin->enabled ? 'success' : 'secondary'; ?>"> <span class="badge bg-<?php echo $plugin->enabled ? 'success' : 'secondary'; ?>">
<?php echo $plugin->enabled ? Text::_('COM_MOKOSUITE_ENABLED') : Text::_('COM_MOKOSUITE_DISABLED'); ?> <?php echo $plugin->enabled ? Text::_('COM_MOKOSUITECLIENT_ENABLED') : Text::_('COM_MOKOSUITECLIENT_DISABLED'); ?>
</span> </span>
<?php else: ?> <?php else: ?>
<div class="form-check form-switch"> <div class="form-check form-switch">
<input type="checkbox" class="form-check-input mokosuite-toggle" role="switch" <input type="checkbox" class="form-check-input mokosuiteclient-toggle" role="switch"
id="toggle-<?php echo $plugin->extension_id; ?>" id="toggle-<?php echo $plugin->extension_id; ?>"
data-extension-id="<?php echo $plugin->extension_id; ?>" data-extension-id="<?php echo $plugin->extension_id; ?>"
data-url="<?php echo Route::_('index.php?option=com_mokosuite&task=display.togglePlugin&format=json'); ?>" data-url="<?php echo Route::_('index.php?option=com_mokosuiteclient&task=display.togglePlugin&format=json'); ?>"
data-token="<?php echo $token; ?>" data-token="<?php echo $token; ?>"
<?php echo $plugin->enabled ? 'checked' : ''; ?>> <?php echo $plugin->enabled ? 'checked' : ''; ?>>
<label class="form-check-label" for="toggle-<?php echo $plugin->extension_id; ?>"> <label class="form-check-label" for="toggle-<?php echo $plugin->extension_id; ?>">
<?php echo $plugin->enabled ? Text::_('COM_MOKOSUITE_ENABLED') : Text::_('COM_MOKOSUITE_DISABLED'); ?> <?php echo $plugin->enabled ? Text::_('COM_MOKOSUITECLIENT_ENABLED') : Text::_('COM_MOKOSUITECLIENT_DISABLED'); ?>
</label> </label>
</div> </div>
<?php endif; ?> <?php endif; ?>
<?php if ($plugin->type === 'plugin'): ?> <?php if ($plugin->type === 'plugin'): ?>
<a href="<?php echo Route::_('index.php?option=com_plugins&task=plugin.edit&extension_id=' . $plugin->extension_id); ?>" class="btn btn-sm btn-outline-secondary"> <a href="<?php echo Route::_('index.php?option=com_plugins&task=plugin.edit&extension_id=' . $plugin->extension_id); ?>" class="btn btn-sm btn-outline-secondary">
<span class="icon-cog" aria-hidden="true"></span> <?php echo Text::_('COM_MOKOSUITE_CONFIGURE'); ?> <span class="icon-cog" aria-hidden="true"></span> <?php echo Text::_('COM_MOKOSUITECLIENT_CONFIGURE'); ?>
</a> </a>
<?php endif; ?> <?php endif; ?>
</div> </div>
@@ -254,7 +254,7 @@ $categoryOrder = ['core', 'security', 'monitoring', 'content', 'tools', 'api'];
<strong><span class="icon-shield-alt" aria-hidden="true"></span> WAF Activity (14 days)</strong> <strong><span class="icon-shield-alt" aria-hidden="true"></span> WAF Activity (14 days)</strong>
</div> </div>
<div class="card-body py-2"> <div class="card-body py-2">
<canvas id="mokosuite-chart-waf" height="140"></canvas> <canvas id="mokosuiteclient-chart-waf" height="140"></canvas>
</div> </div>
</div> </div>
@@ -264,7 +264,7 @@ $categoryOrder = ['core', 'security', 'monitoring', 'content', 'tools', 'api'];
<strong><span class="icon-user" aria-hidden="true"></span> Login Activity (14 days)</strong> <strong><span class="icon-user" aria-hidden="true"></span> Login Activity (14 days)</strong>
</div> </div>
<div class="card-body py-2"> <div class="card-body py-2">
<canvas id="mokosuite-chart-logins" height="140"></canvas> <canvas id="mokosuiteclient-chart-logins" height="140"></canvas>
</div> </div>
</div> </div>
@@ -409,7 +409,7 @@ document.addEventListener('DOMContentLoaded', function() {
}; };
// WAF chart // WAF chart
var wafCtx = document.getElementById('mokosuite-chart-waf'); var wafCtx = document.getElementById('mokosuiteclient-chart-waf');
if (wafCtx) { if (wafCtx) {
new Chart(wafCtx, { new Chart(wafCtx, {
type: 'bar', type: 'bar',
@@ -428,7 +428,7 @@ document.addEventListener('DOMContentLoaded', function() {
} }
// Login chart // Login chart
var loginCtx = document.getElementById('mokosuite-chart-logins'); var loginCtx = document.getElementById('mokosuiteclient-chart-logins');
if (loginCtx) { if (loginCtx) {
new Chart(loginCtx, { new Chart(loginCtx, {
type: 'line', type: 'line',
@@ -7,12 +7,12 @@ use Joomla\CMS\Session\Session;
$data = $this->tableData; $data = $this->tableData;
$tables = $data['tables'] ?? []; $tables = $data['tables'] ?? [];
$token = Session::getFormToken(); $token = Session::getFormToken();
$optimizeUrl = Route::_('index.php?option=com_mokosuite&task=display.optimizeDb&format=json'); $optimizeUrl = Route::_('index.php?option=com_mokosuiteclient&task=display.optimizeDb&format=json');
$repairUrl = Route::_('index.php?option=com_mokosuite&task=display.repairDb&format=json'); $repairUrl = Route::_('index.php?option=com_mokosuiteclient&task=display.repairDb&format=json');
$purgeUrl = Route::_('index.php?option=com_mokosuite&task=display.purgeSessions&format=json'); $purgeUrl = Route::_('index.php?option=com_mokosuiteclient&task=display.purgeSessions&format=json');
?> ?>
<div id="mokosuite-database"> <div id="mokosuiteclient-database">
<div class="row g-3 mb-4"> <div class="row g-3 mb-4">
<div class="col-6 col-md-3"><div class="card text-center p-3"><span class="fw-bold fs-3"><?php echo $data['count']; ?></span><small class="text-muted">Tables</small></div></div> <div class="col-6 col-md-3"><div class="card text-center p-3"><span class="fw-bold fs-3"><?php echo $data['count']; ?></span><small class="text-muted">Tables</small></div></div>
<div class="col-6 col-md-3"><div class="card text-center p-3"><span class="fw-bold fs-3"><?php echo $data['total_size_mb']; ?> MB</span><small class="text-muted">Total Size</small></div></div> <div class="col-6 col-md-3"><div class="card text-center p-3"><span class="fw-bold fs-3"><?php echo $data['total_size_mb']; ?> MB</span><small class="text-muted">Total Size</small></div></div>
@@ -11,19 +11,19 @@ $openDeals = $this->pipelineData['open'] ?? (object) ['cnt' => 0, 'total_value'
$closedTotal = ((int) ($wonDeals->cnt ?? 0)) + ((int) ($lostDeals->cnt ?? 0)); $closedTotal = ((int) ($wonDeals->cnt ?? 0)) + ((int) ($lostDeals->cnt ?? 0));
$winRate = $closedTotal > 0 ? round((int) ($wonDeals->cnt ?? 0) / $closedTotal * 100, 1) : 0; $winRate = $closedTotal > 0 ? round((int) ($wonDeals->cnt ?? 0) / $closedTotal * 100, 1) : 0;
?> ?>
<div class="mokosuite-erp-reports"> <div class="mokosuiteclient-erp-reports">
<div class="card shadow-sm mb-3"><div class="card-body py-2"> <div class="card shadow-sm mb-3"><div class="card-body py-2">
<form method="get" class="d-flex gap-2 align-items-center flex-wrap"> <form method="get" class="d-flex gap-2 align-items-center flex-wrap">
<input type="hidden" name="option" value="com_mokosuite"><input type="hidden" name="view" value="erpreports"><input type="hidden" name="tab" value="<?php echo $this->escape($tab); ?>"> <input type="hidden" name="option" value="com_mokosuiteclient"><input type="hidden" name="view" value="erpreports"><input type="hidden" name="tab" value="<?php echo $this->escape($tab); ?>">
<label class="form-label mb-0 small">From:</label><input type="date" name="from" class="form-control form-control-sm" style="max-width:160px" value="<?php echo $this->escape($this->dateFrom); ?>"> <label class="form-label mb-0 small">From:</label><input type="date" name="from" class="form-control form-control-sm" style="max-width:160px" value="<?php echo $this->escape($this->dateFrom); ?>">
<label class="form-label mb-0 small">To:</label><input type="date" name="to" class="form-control form-control-sm" style="max-width:160px" value="<?php echo $this->escape($this->dateTo); ?>"> <label class="form-label mb-0 small">To:</label><input type="date" name="to" class="form-control form-control-sm" style="max-width:160px" value="<?php echo $this->escape($this->dateTo); ?>">
<button type="submit" class="btn btn-sm btn-primary">Apply</button> <button type="submit" class="btn btn-sm btn-primary">Apply</button>
</form> </form>
</div></div> </div></div>
<ul class="nav nav-tabs mb-3"> <ul class="nav nav-tabs mb-3">
<li class="nav-item"><a class="nav-link <?php echo $tab === 'sales' ? 'active' : ''; ?>" href="<?php echo Route::_('index.php?option=com_mokosuite&view=erpreports&tab=sales&from=' . $this->dateFrom . '&to=' . $this->dateTo); ?>">Sales</a></li> <li class="nav-item"><a class="nav-link <?php echo $tab === 'sales' ? 'active' : ''; ?>" href="<?php echo Route::_('index.php?option=com_mokosuiteclient&view=erpreports&tab=sales&from=' . $this->dateFrom . '&to=' . $this->dateTo); ?>">Sales</a></li>
<li class="nav-item"><a class="nav-link <?php echo $tab === 'pipeline' ? 'active' : ''; ?>" href="<?php echo Route::_('index.php?option=com_mokosuite&view=erpreports&tab=pipeline&from=' . $this->dateFrom . '&to=' . $this->dateTo); ?>">Pipeline</a></li> <li class="nav-item"><a class="nav-link <?php echo $tab === 'pipeline' ? 'active' : ''; ?>" href="<?php echo Route::_('index.php?option=com_mokosuiteclient&view=erpreports&tab=pipeline&from=' . $this->dateFrom . '&to=' . $this->dateTo); ?>">Pipeline</a></li>
<li class="nav-item"><a class="nav-link <?php echo $tab === 'aging' ? 'active' : ''; ?>" href="<?php echo Route::_('index.php?option=com_mokosuite&view=erpreports&tab=aging'); ?>">Aging Receivables</a></li> <li class="nav-item"><a class="nav-link <?php echo $tab === 'aging' ? 'active' : ''; ?>" href="<?php echo Route::_('index.php?option=com_mokosuiteclient&view=erpreports&tab=aging'); ?>">Aging Receivables</a></li>
</ul> </ul>
<?php if ($tab === 'sales') : ?> <?php if ($tab === 'sales') : ?>
<div class="row g-3 mb-3"> <div class="row g-3 mb-3">
@@ -61,7 +61,7 @@ $winRate = $closedTotal > 0 ? round((int) ($wonDeals->cnt ?? 0) / $closedTotal *
<div class="card shadow-sm"><div class="card-body p-0"><table class="table table-sm table-hover mb-0"><thead class="table-light"><tr><th>Ref</th><th>Contact</th><th class="text-end">Total</th><th class="text-end">Paid</th><th class="text-end">Balance</th><th>Due</th><th>Days</th></tr></thead><tbody> <div class="card shadow-sm"><div class="card-body p-0"><table class="table table-sm table-hover mb-0"><thead class="table-light"><tr><th>Ref</th><th>Contact</th><th class="text-end">Total</th><th class="text-end">Paid</th><th class="text-end">Balance</th><th>Due</th><th>Days</th></tr></thead><tbody>
<?php foreach ($this->agingData as $r) : ?> <?php foreach ($this->agingData as $r) : ?>
<tr class="<?php echo (int) $r->days_overdue > 60 ? 'table-danger' : ((int) $r->days_overdue > 30 ? 'table-warning' : ''); ?>"> <tr class="<?php echo (int) $r->days_overdue > 60 ? 'table-danger' : ((int) $r->days_overdue > 30 ? 'table-warning' : ''); ?>">
<td><a href="<?php echo Route::_('index.php?option=com_mokosuite&view=erpinvoice&id=' . (int) $r->id); ?>" class="font-monospace"><?php echo $this->escape($r->ref); ?></a></td> <td><a href="<?php echo Route::_('index.php?option=com_mokosuiteclient&view=erpinvoice&id=' . (int) $r->id); ?>" class="font-monospace"><?php echo $this->escape($r->ref); ?></a></td>
<td><?php echo $this->escape($r->contact_name ?? '—'); ?></td><td class="text-end">$<?php echo number_format((float) $r->total, 2); ?></td> <td><?php echo $this->escape($r->contact_name ?? '—'); ?></td><td class="text-end">$<?php echo number_format((float) $r->total, 2); ?></td>
<td class="text-end">$<?php echo number_format((float) $r->amount_paid, 2); ?></td><td class="text-end fw-bold text-danger">$<?php echo number_format((float) $r->balance, 2); ?></td> <td class="text-end">$<?php echo number_format((float) $r->amount_paid, 2); ?></td><td class="text-end fw-bold text-danger">$<?php echo number_format((float) $r->balance, 2); ?></td>
<td class="small"><?php echo $this->escape($r->due_date); ?></td><td class="fw-bold"><?php echo (int) $r->days_overdue; ?></td> <td class="small"><?php echo $this->escape($r->due_date); ?></td><td class="fw-bold"><?php echo (int) $r->days_overdue; ?></td>
@@ -1,7 +1,7 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
@@ -12,7 +12,7 @@ use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route; use Joomla\CMS\Router\Route;
use Joomla\CMS\Session\Session; use Joomla\CMS\Session\Session;
/** @var \Moko\Component\MokoSuite\Administrator\View\Extensions\HtmlView $this */ /** @var \Moko\Component\MokoSuiteClient\Administrator\View\Extensions\HtmlView $this */
$packages = $this->packages; $packages = $this->packages;
$token = Session::getFormToken(); $token = Session::getFormToken();
@@ -31,10 +31,10 @@ $statusBadge = [
]; ];
?> ?>
<div id="mokosuite-extensions"> <div id="mokosuiteclient-extensions">
<div class="alert alert-info"> <div class="alert alert-info">
<span class="icon-info-circle" aria-hidden="true"></span> <span class="icon-info-circle" aria-hidden="true"></span>
<?php echo Text::_('COM_MOKOSUITE_EXTENSIONS_INFO'); ?> <?php echo Text::_('COM_MOKOSUITECLIENT_EXTENSIONS_INFO'); ?>
</div> </div>
<?php foreach ($grouped as $category => $pkgs): ?> <?php foreach ($grouped as $category => $pkgs): ?>
@@ -86,8 +86,8 @@ $statusBadge = [
</a> </a>
<?php endif; ?> <?php endif; ?>
<?php if ($pkg->download_url && $pkg->status === 'update_available'): ?> <?php if ($pkg->download_url && $pkg->status === 'update_available'): ?>
<button type="button" class="btn btn-sm btn-warning mokosuite-install-btn" <button type="button" class="btn btn-sm btn-warning mokosuiteclient-install-btn"
data-url="<?php echo Route::_('index.php?option=com_mokosuite&task=display.installExtension&format=json'); ?>" data-url="<?php echo Route::_('index.php?option=com_mokosuiteclient&task=display.installExtension&format=json'); ?>"
data-download="<?php echo htmlspecialchars($pkg->download_url); ?>" data-download="<?php echo htmlspecialchars($pkg->download_url); ?>"
data-token="<?php echo $token; ?>" data-token="<?php echo $token; ?>"
data-label="<?php echo htmlspecialchars($pkg->label); ?>" data-label="<?php echo htmlspecialchars($pkg->label); ?>"
@@ -97,8 +97,8 @@ $statusBadge = [
Update to <?php echo htmlspecialchars($pkg->remote_version); ?> Update to <?php echo htmlspecialchars($pkg->remote_version); ?>
</button> </button>
<?php elseif ($pkg->download_url && $pkg->status === 'not_installed'): ?> <?php elseif ($pkg->download_url && $pkg->status === 'not_installed'): ?>
<button type="button" class="btn btn-sm btn-primary mokosuite-install-btn" <button type="button" class="btn btn-sm btn-primary mokosuiteclient-install-btn"
data-url="<?php echo Route::_('index.php?option=com_mokosuite&task=display.installExtension&format=json'); ?>" data-url="<?php echo Route::_('index.php?option=com_mokosuiteclient&task=display.installExtension&format=json'); ?>"
data-download="<?php echo htmlspecialchars($pkg->download_url); ?>" data-download="<?php echo htmlspecialchars($pkg->download_url); ?>"
data-token="<?php echo $token; ?>" data-token="<?php echo $token; ?>"
data-label="<?php echo htmlspecialchars($pkg->label); ?>" data-label="<?php echo htmlspecialchars($pkg->label); ?>"
@@ -154,7 +154,7 @@ $statusBadge = [
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.mokosuite-install-btn').forEach(function(btn) { document.querySelectorAll('.mokosuiteclient-install-btn').forEach(function(btn) {
btn.addEventListener('click', function() { btn.addEventListener('click', function() {
var el = this; var el = this;
var url = el.dataset.url; var url = el.dataset.url;
@@ -1,7 +1,7 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
@@ -17,8 +17,8 @@ $preview = $this->preview;
$nginx = $this->nginxPreview; $nginx = $this->nginxPreview;
$current = $this->currentHtaccess; $current = $this->currentHtaccess;
$token = Session::getFormToken(); $token = Session::getFormToken();
$saveUrl = Route::_('index.php?option=com_mokosuite&task=display.saveHtaccess&format=json'); $saveUrl = Route::_('index.php?option=com_mokosuiteclient&task=display.saveHtaccess&format=json');
$genUrl = Route::_('index.php?option=com_mokosuite&task=display.generateHtaccess&format=json'); $genUrl = Route::_('index.php?option=com_mokosuiteclient&task=display.generateHtaccess&format=json');
// Helper for toggle switch // Helper for toggle switch
$sw = function($name, $label, $desc = '') use ($opts) { $sw = function($name, $label, $desc = '') use ($opts) {
@@ -33,7 +33,7 @@ $sw = function($name, $label, $desc = '') use ($opts) {
}; };
?> ?>
<div id="mokosuite-htaccess"> <div id="mokosuiteclient-htaccess">
<ul class="nav nav-tabs mb-3" role="tablist"> <ul class="nav nav-tabs mb-3" role="tablist">
<li class="nav-item"><a class="nav-link active" data-bs-toggle="tab" href="#tab-htaccess" role="tab">.htaccess</a></li> <li class="nav-item"><a class="nav-link active" data-bs-toggle="tab" href="#tab-htaccess" role="tab">.htaccess</a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#tab-nginx" role="tab">NginX</a></li> <li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#tab-nginx" role="tab">NginX</a></li>
@@ -263,7 +263,7 @@ document.addEventListener('DOMContentLoaded', function() {
// Save to disk // Save to disk
saveBtn.addEventListener('click', function() { saveBtn.addEventListener('click', function() {
if (!confirm('This will overwrite your current .htaccess file. A backup will be created at .htaccess.mokosuite.bak. Continue?')) return; if (!confirm('This will overwrite your current .htaccess file. A backup will be created at .htaccess.mokosuiteclient.bak. Continue?')) return;
var btn = this; var btn = this;
btn.disabled = true; btn.disabled = true;
@@ -300,7 +300,7 @@ document.addEventListener('DOMContentLoaded', function() {
downloadText(preview.value, '.htaccess'); downloadText(preview.value, '.htaccess');
}); });
document.getElementById('nginx-download').addEventListener('click', function() { document.getElementById('nginx-download').addEventListener('click', function() {
downloadText(document.getElementById('nginx-preview').value, 'mokosuite-nginx.conf'); downloadText(document.getElementById('nginx-preview').value, 'mokosuiteclient-nginx.conf');
}); });
}); });
</script> </script>
@@ -24,7 +24,7 @@ $typeBadge = [
]; ];
?> ?>
<div id="mokosuite-privacy"> <div id="mokosuiteclient-privacy">
<!-- Summary cards --> <!-- Summary cards -->
<div class="row g-3 mb-4"> <div class="row g-3 mb-4">
<div class="col-6 col-md-3"> <div class="col-6 col-md-3">
@@ -100,7 +100,7 @@ $typeBadge = [
</div> </div>
<div class="col-12 col-md-2 d-flex align-items-end"> <div class="col-12 col-md-2 d-flex align-items-end">
<button type="submit" class="btn btn-primary w-100" id="btnCreateRequest" <button type="submit" class="btn btn-primary w-100" id="btnCreateRequest"
data-url="<?php echo Route::_('index.php?option=com_mokosuite&task=display.processDataRequest&format=json'); ?>" data-url="<?php echo Route::_('index.php?option=com_mokosuiteclient&task=display.processDataRequest&format=json'); ?>"
data-token="<?php echo $token; ?>"> data-token="<?php echo $token; ?>">
<span class="icon-check"></span> Submit <span class="icon-check"></span> Submit
</button> </button>
@@ -117,7 +117,7 @@ $typeBadge = [
<div class="card-header d-flex justify-content-between align-items-center"> <div class="card-header d-flex justify-content-between align-items-center">
<strong><span class="icon-user-shield"></span> Data Subject Requests</strong> <strong><span class="icon-user-shield"></span> Data Subject Requests</strong>
<form method="get" class="d-inline"> <form method="get" class="d-inline">
<input type="hidden" name="option" value="com_mokosuite"> <input type="hidden" name="option" value="com_mokosuiteclient">
<input type="hidden" name="view" value="privacy"> <input type="hidden" name="view" value="privacy">
<select name="filter_status" class="form-select form-select-sm" style="width:auto" onchange="this.form.submit()"> <select name="filter_status" class="form-select form-select-sm" style="width:auto" onchange="this.form.submit()">
<option value="">All</option> <option value="">All</option>
@@ -146,15 +146,15 @@ $typeBadge = [
<?php if ($r->status === 'pending'): ?> <?php if ($r->status === 'pending'): ?>
<div class="btn-group btn-group-sm"> <div class="btn-group btn-group-sm">
<button type="button" class="btn btn-success btn-privacy-action" data-id="<?php echo $r->id; ?>" data-action="approve" <button type="button" class="btn btn-success btn-privacy-action" data-id="<?php echo $r->id; ?>" data-action="approve"
data-url="<?php echo Route::_('index.php?option=com_mokosuite&task=display.processDataRequest&format=json'); ?>" data-url="<?php echo Route::_('index.php?option=com_mokosuiteclient&task=display.processDataRequest&format=json'); ?>"
data-token="<?php echo $token; ?>">Approve</button> data-token="<?php echo $token; ?>">Approve</button>
<button type="button" class="btn btn-outline-danger btn-privacy-action" data-id="<?php echo $r->id; ?>" data-action="deny" <button type="button" class="btn btn-outline-danger btn-privacy-action" data-id="<?php echo $r->id; ?>" data-action="deny"
data-url="<?php echo Route::_('index.php?option=com_mokosuite&task=display.processDataRequest&format=json'); ?>" data-url="<?php echo Route::_('index.php?option=com_mokosuiteclient&task=display.processDataRequest&format=json'); ?>"
data-token="<?php echo $token; ?>">Deny</button> data-token="<?php echo $token; ?>">Deny</button>
</div> </div>
<?php elseif ($r->status === 'completed' && $r->type === 'export'): ?> <?php elseif ($r->status === 'completed' && $r->type === 'export'): ?>
<button type="button" class="btn btn-sm btn-outline-primary btn-export-download" data-user="<?php echo $r->user_id; ?>" <button type="button" class="btn btn-sm btn-outline-primary btn-export-download" data-user="<?php echo $r->user_id; ?>"
data-url="<?php echo Route::_('index.php?option=com_mokosuite&task=display.exportUserData&format=json'); ?>" data-url="<?php echo Route::_('index.php?option=com_mokosuiteclient&task=display.exportUserData&format=json'); ?>"
data-token="<?php echo $token; ?>"> data-token="<?php echo $token; ?>">
<span class="icon-download"></span> Download <span class="icon-download"></span> Download
</button> </button>
@@ -13,7 +13,7 @@ $statuses = $this->statuses ?? [];
$priorities = $this->priorities ?? []; $priorities = $this->priorities ?? [];
?> ?>
<div id="mokosuite-ticket" class="row"> <div id="mokosuiteclient-ticket" class="row">
<!-- Left: conversation thread --> <!-- Left: conversation thread -->
<div class="col-12 col-xl-8"> <div class="col-12 col-xl-8">
<!-- Original ticket --> <!-- Original ticket -->
@@ -61,12 +61,12 @@ $priorities = $this->priorities ?? [];
<textarea id="reply-body" class="form-control mb-2" rows="5" placeholder="Type your reply..."></textarea> <textarea id="reply-body" class="form-control mb-2" rows="5" placeholder="Type your reply..."></textarea>
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<button type="button" class="btn btn-primary" id="btn-reply" <button type="button" class="btn btn-primary" id="btn-reply"
data-url="<?php echo Route::_('index.php?option=com_mokosuite&task=display.addTicketReply&format=json'); ?>" data-url="<?php echo Route::_('index.php?option=com_mokosuiteclient&task=display.addTicketReply&format=json'); ?>"
data-ticket="<?php echo $t->id; ?>" data-token="<?php echo $token; ?>"> data-ticket="<?php echo $t->id; ?>" data-token="<?php echo $token; ?>">
<span class="icon-reply"></span> Send Reply <span class="icon-reply"></span> Send Reply
</button> </button>
<button type="button" class="btn btn-outline-warning" id="btn-internal" <button type="button" class="btn btn-outline-warning" id="btn-internal"
data-url="<?php echo Route::_('index.php?option=com_mokosuite&task=display.addTicketReply&format=json'); ?>" data-url="<?php echo Route::_('index.php?option=com_mokosuiteclient&task=display.addTicketReply&format=json'); ?>"
data-ticket="<?php echo $t->id; ?>" data-token="<?php echo $token; ?>" data-internal="1"> data-ticket="<?php echo $t->id; ?>" data-token="<?php echo $token; ?>" data-internal="1">
<span class="icon-eye-slash"></span> Internal Note <span class="icon-eye-slash"></span> Internal Note
</button> </button>
@@ -152,7 +152,7 @@ $priorities = $this->priorities ?? [];
<?php foreach ($statuses as $s): ?> <?php foreach ($statuses as $s): ?>
<?php if ((int) $s->id !== (int) $t->status_id): ?> <?php if ((int) $s->id !== (int) $t->status_id): ?>
<button type="button" class="btn btn-sm btn-outline-<?php echo $s->is_closed ? 'danger' : 'secondary'; ?> btn-status" <button type="button" class="btn btn-sm btn-outline-<?php echo $s->is_closed ? 'danger' : 'secondary'; ?> btn-status"
data-url="<?php echo Route::_('index.php?option=com_mokosuite&task=display.updateTicketStatus&format=json'); ?>" data-url="<?php echo Route::_('index.php?option=com_mokosuiteclient&task=display.updateTicketStatus&format=json'); ?>"
data-ticket="<?php echo $t->id; ?>" data-status="<?php echo $s->id; ?>" data-token="<?php echo $token; ?>"> data-ticket="<?php echo $t->id; ?>" data-status="<?php echo $s->id; ?>" data-token="<?php echo $token; ?>">
<?php echo $this->escape($s->title); ?> <?php echo $this->escape($s->title); ?>
</button> </button>
@@ -17,7 +17,7 @@ $atsAvailable = $this->atsAvailable;
$token = Session::getFormToken(); $token = Session::getFormToken();
?> ?>
<div id="mokosuite-tickets"> <div id="mokosuiteclient-tickets">
<!-- Status summary cards --> <!-- Status summary cards -->
<div class="row g-3 mb-4"> <div class="row g-3 mb-4">
<?php foreach ($counts as $sc): ?> <?php foreach ($counts as $sc): ?>
@@ -36,7 +36,7 @@ $token = Session::getFormToken();
</button> </button>
<?php if ($atsAvailable): ?> <?php if ($atsAvailable): ?>
<button type="button" class="btn btn-outline-info" id="btn-import-ats" <button type="button" class="btn btn-outline-info" id="btn-import-ats"
data-url="<?php echo Route::_('index.php?option=com_mokosuite&task=display.importAts&format=json'); ?>" data-url="<?php echo Route::_('index.php?option=com_mokosuiteclient&task=display.importAts&format=json'); ?>"
data-token="<?php echo $token; ?>" data-token="<?php echo $token; ?>"
data-tickets="<?php echo $atsAvailable->tickets; ?>" data-tickets="<?php echo $atsAvailable->tickets; ?>"
data-posts="<?php echo $atsAvailable->posts; ?>"> data-posts="<?php echo $atsAvailable->posts; ?>">
@@ -45,7 +45,7 @@ $token = Session::getFormToken();
<?php endif; ?> <?php endif; ?>
</div> </div>
<form method="get" class="d-flex gap-2"> <form method="get" class="d-flex gap-2">
<input type="hidden" name="option" value="com_mokosuite"> <input type="hidden" name="option" value="com_mokosuiteclient">
<input type="hidden" name="view" value="tickets"> <input type="hidden" name="view" value="tickets">
<select name="filter_status" class="form-select form-select-sm" style="width:auto" onchange="this.form.submit()"> <select name="filter_status" class="form-select form-select-sm" style="width:auto" onchange="this.form.submit()">
<option value="">All Statuses</option> <option value="">All Statuses</option>
@@ -93,8 +93,8 @@ $token = Session::getFormToken();
elseif ($t->sla_response_due && !$t->sla_responded && strtotime($t->sla_response_due) < $now + 3600) $slaClass = 'table-warning'; elseif ($t->sla_response_due && !$t->sla_responded && strtotime($t->sla_response_due) < $now + 3600) $slaClass = 'table-warning';
?> ?>
<tr class="<?php echo $slaClass; ?>"> <tr class="<?php echo $slaClass; ?>">
<td><a href="<?php echo Route::_('index.php?option=com_mokosuite&view=ticket&id=' . $t->id); ?>"><?php echo $t->id; ?></a></td> <td><a href="<?php echo Route::_('index.php?option=com_mokosuiteclient&view=ticket&id=' . $t->id); ?>"><?php echo $t->id; ?></a></td>
<td><a href="<?php echo Route::_('index.php?option=com_mokosuite&view=ticket&id=' . $t->id); ?>"><?php echo $this->escape(mb_substr($t->subject, 0, 60)); ?></a></td> <td><a href="<?php echo Route::_('index.php?option=com_mokosuiteclient&view=ticket&id=' . $t->id); ?>"><?php echo $this->escape(mb_substr($t->subject, 0, 60)); ?></a></td>
<td><span class="badge <?php echo $this->escape($t->status_color ?? 'bg-secondary'); ?>"><?php echo $this->escape($t->status_title ?? $t->status); ?></span></td> <td><span class="badge <?php echo $this->escape($t->status_color ?? 'bg-secondary'); ?>"><?php echo $this->escape($t->status_title ?? $t->status); ?></span></td>
<td><span class="badge <?php echo $this->escape($t->priority_color ?? 'bg-secondary'); ?>"><?php echo $this->escape($t->priority_title ?? $t->priority); ?></span></td> <td><span class="badge <?php echo $this->escape($t->priority_color ?? 'bg-secondary'); ?>"><?php echo $this->escape($t->priority_title ?? $t->priority); ?></span></td>
<td><?php echo $this->escape($t->category_title ?? '—'); ?></td> <td><?php echo $this->escape($t->category_title ?? '—'); ?></td>
@@ -149,7 +149,7 @@ $token = Session::getFormToken();
</div> </div>
<!-- Ticket form step (hidden initially) --> <!-- Ticket form step (hidden initially) -->
<form id="modal-ticket-form" class="d-none" method="post" action="<?php echo Route::_('index.php?option=com_mokosuite&task=display.createTicket&format=json'); ?>"> <form id="modal-ticket-form" class="d-none" method="post" action="<?php echo Route::_('index.php?option=com_mokosuiteclient&task=display.createTicket&format=json'); ?>">
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Subject</label> <label class="form-label">Subject</label>
<input type="text" name="subject" id="modal-subject" class="form-control" required> <input type="text" name="subject" id="modal-subject" class="form-control" required>
@@ -210,7 +210,7 @@ var modalSubject = document.getElementById('modal-subject');
function modalDoSearch() { function modalDoSearch() {
var q = modalSearch.value.trim(); var q = modalSearch.value.trim();
if (q.length < 3) return; if (q.length < 3) return;
fetch('<?php echo Route::_('index.php?option=com_mokosuite&task=display.searchKb&format=json'); ?>&q=' + encodeURIComponent(q), { fetch('<?php echo Route::_('index.php?option=com_mokosuiteclient&task=display.searchKb&format=json'); ?>&q=' + encodeURIComponent(q), {
headers: {'X-Requested-With': 'XMLHttpRequest'} headers: {'X-Requested-With': 'XMLHttpRequest'}
}).then(function(r){return r.json()}).then(function(d) { }).then(function(r){return r.json()}).then(function(d) {
modalResults.textContent = ''; modalResults.textContent = '';
@@ -260,7 +260,7 @@ if (modalForm) {
fetch(form.action, {method:'POST', body:fd, headers:{'X-Requested-With':'XMLHttpRequest'}}) fetch(form.action, {method:'POST', body:fd, headers:{'X-Requested-With':'XMLHttpRequest'}})
.then(function(r){return r.json()}) .then(function(r){return r.json()})
.then(function(d){ .then(function(d){
if (d.success) { location.href = 'index.php?option=com_mokosuite&view=ticket&id=' + d.id; } if (d.success) { location.href = 'index.php?option=com_mokosuiteclient&view=ticket&id=' + d.id; }
else { Joomla.renderMessages({error:[d.message]}); } else { Joomla.renderMessages({error:[d.message]}); }
}); });
}); });
@@ -25,7 +25,7 @@ $ruleBadge = [
]; ];
?> ?>
<div id="mokosuite-waflog"> <div id="mokosuiteclient-waflog">
<!-- Rule distribution cards --> <!-- Rule distribution cards -->
<div class="d-flex flex-wrap gap-2 mb-4"> <div class="d-flex flex-wrap gap-2 mb-4">
<?php foreach ($ruleCounts as $rc): ?> <?php foreach ($ruleCounts as $rc): ?>
@@ -46,7 +46,7 @@ $ruleBadge = [
<!-- Filters --> <!-- Filters -->
<form method="get" class="card mb-3"> <form method="get" class="card mb-3">
<div class="card-body"> <div class="card-body">
<input type="hidden" name="option" value="com_mokosuite"> <input type="hidden" name="option" value="com_mokosuiteclient">
<input type="hidden" name="view" value="waflog"> <input type="hidden" name="view" value="waflog">
<div class="row g-2"> <div class="row g-2">
<div class="col-md-2"> <div class="col-md-2">
@@ -71,7 +71,7 @@ $ruleBadge = [
</div> </div>
<div class="col-md-2 d-flex gap-1"> <div class="col-md-2 d-flex gap-1">
<button type="submit" class="btn btn-sm btn-primary"><span class="icon-search"></span> Filter</button> <button type="submit" class="btn btn-sm btn-primary"><span class="icon-search"></span> Filter</button>
<a href="<?php echo Route::_('index.php?option=com_mokosuite&view=waflog'); ?>" class="btn btn-sm btn-outline-secondary">Reset</a> <a href="<?php echo Route::_('index.php?option=com_mokosuiteclient&view=waflog'); ?>" class="btn btn-sm btn-outline-secondary">Reset</a>
</div> </div>
</div> </div>
</div> </div>
@@ -82,7 +82,7 @@ $ruleBadge = [
<div class="card-header d-flex justify-content-between align-items-center"> <div class="card-header d-flex justify-content-between align-items-center">
<strong><?php echo number_format($total); ?> blocked requests</strong> <strong><?php echo number_format($total); ?> blocked requests</strong>
<button type="button" class="btn btn-sm btn-outline-danger" id="btn-purge" <button type="button" class="btn btn-sm btn-outline-danger" id="btn-purge"
data-url="<?php echo Route::_('index.php?option=com_mokosuite&task=display.purgeWafLog&format=json'); ?>" data-url="<?php echo Route::_('index.php?option=com_mokosuiteclient&task=display.purgeWafLog&format=json'); ?>"
data-token="<?php echo $token; ?>"> data-token="<?php echo $token; ?>">
<span class="icon-trash"></span> Purge Old Logs <span class="icon-trash"></span> Purge Old Logs
</button> </button>
@@ -106,7 +106,7 @@ $ruleBadge = [
<td class="small" style="max-width:200px;overflow:hidden;text-overflow:ellipsis"><?php echo htmlspecialchars(mb_substr($log->user_agent, 0, 40)); ?></td> <td class="small" style="max-width:200px;overflow:hidden;text-overflow:ellipsis"><?php echo htmlspecialchars(mb_substr($log->user_agent, 0, 40)); ?></td>
<td> <td>
<button type="button" class="btn btn-sm btn-outline-danger btn-ban-ip" data-ip="<?php echo htmlspecialchars($log->ip); ?>" <button type="button" class="btn btn-sm btn-outline-danger btn-ban-ip" data-ip="<?php echo htmlspecialchars($log->ip); ?>"
data-url="<?php echo Route::_('index.php?option=com_mokosuite&task=display.banIpFromLog&format=json'); ?>" data-url="<?php echo Route::_('index.php?option=com_mokosuiteclient&task=display.banIpFromLog&format=json'); ?>"
data-token="<?php echo $token; ?>" title="Ban this IP"> data-token="<?php echo $token; ?>" title="Ban this IP">
<span class="icon-ban"></span> <span class="icon-ban"></span>
</button> </button>
@@ -124,10 +124,10 @@ $ruleBadge = [
<nav> <nav>
<ul class="pagination pagination-sm mb-0"> <ul class="pagination pagination-sm mb-0">
<?php if ($page > 1): ?> <?php if ($page > 1): ?>
<li class="page-item"><a class="page-link" href="<?php echo Route::_('index.php?option=com_mokosuite&view=waflog&page=' . ($page - 1)); ?>">Prev</a></li> <li class="page-item"><a class="page-link" href="<?php echo Route::_('index.php?option=com_mokosuiteclient&view=waflog&page=' . ($page - 1)); ?>">Prev</a></li>
<?php endif; ?> <?php endif; ?>
<?php if ($page < $totalPages): ?> <?php if ($page < $totalPages): ?>
<li class="page-item"><a class="page-link" href="<?php echo Route::_('index.php?option=com_mokosuite&view=waflog&page=' . ($page + 1)); ?>">Next</a></li> <li class="page-item"><a class="page-link" href="<?php echo Route::_('index.php?option=com_mokosuiteclient&view=waflog&page=' . ($page + 1)); ?>">Next</a></li>
<?php endif; ?> <?php endif; ?>
</ul> </ul>
</nav> </nav>
@@ -151,7 +151,7 @@ $ruleBadge = [
<td class="small text-nowrap"><?php echo HTMLHelper::_('date', $tip->last_seen, 'M d'); ?></td> <td class="small text-nowrap"><?php echo HTMLHelper::_('date', $tip->last_seen, 'M d'); ?></td>
<td> <td>
<button type="button" class="btn btn-sm btn-outline-danger btn-ban-ip" data-ip="<?php echo htmlspecialchars($tip->ip); ?>" <button type="button" class="btn btn-sm btn-outline-danger btn-ban-ip" data-ip="<?php echo htmlspecialchars($tip->ip); ?>"
data-url="<?php echo Route::_('index.php?option=com_mokosuite&task=display.banIpFromLog&format=json'); ?>" data-url="<?php echo Route::_('index.php?option=com_mokosuiteclient&task=display.banIpFromLog&format=json'); ?>"
data-token="<?php echo $token; ?>" title="Ban"> data-token="<?php echo $token; ?>" title="Ban">
<span class="icon-ban"></span> <span class="icon-ban"></span>
</button> </button>
@@ -1,12 +1,12 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
namespace Moko\Component\MokoSuite\Api\Controller; namespace Moko\Component\MokoSuiteClient\Api\Controller;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -16,7 +16,7 @@ use Joomla\CMS\MVC\Controller\BaseController;
/** /**
* Cache management API controller. * Cache management API controller.
* *
* POST /api/index.php/v1/mokosuite/cache * POST /api/index.php/v1/mokosuiteclient/cache
* *
* @since 1.0.0 * @since 1.0.0
*/ */
@@ -1,12 +1,12 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
namespace Moko\Component\MokoSuite\Api\Controller; namespace Moko\Component\MokoSuiteClient\Api\Controller;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -20,7 +20,7 @@ use Joomla\Registry\Registry;
/** /**
* Dashboard summary API controller. * Dashboard summary API controller.
* *
* GET /api/index.php/v1/mokosuite/dashboard * GET /api/index.php/v1/mokosuiteclient/dashboard
* *
* Returns a combined payload of site info and feature plugin states, * Returns a combined payload of site info and feature plugin states,
* suitable for remote dashboards and monitoring. * suitable for remote dashboards and monitoring.
@@ -53,7 +53,7 @@ class DashboardController extends BaseController
$query = $db->getQuery(true) $query = $db->getQuery(true)
->select($db->quoteName('manifest_cache')) ->select($db->quoteName('manifest_cache'))
->from($db->quoteName('#__extensions')) ->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' = ' . $db->quote('pkg_mokosuite')) ->where($db->quoteName('element') . ' = ' . $db->quote('pkg_mokosuiteclient'))
->where($db->quoteName('type') . ' = ' . $db->quote('package')); ->where($db->quoteName('type') . ' = ' . $db->quote('package'));
$db->setQuery($query); $db->setQuery($query);
$pkgCache = json_decode($db->loadResult() ?? '{}'); $pkgCache = json_decode($db->loadResult() ?? '{}');
@@ -71,8 +71,8 @@ class DashboardController extends BaseController
->from($db->quoteName('#__extensions')) ->from($db->quoteName('#__extensions'))
->where($db->quoteName('type') . ' = ' . $db->quote('plugin')) ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' . $db->quote('system')) ->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
->where('(' . $db->quoteName('element') . ' = ' . $db->quote('mokosuite') ->where('(' . $db->quoteName('element') . ' = ' . $db->quote('mokosuiteclient')
. ' OR ' . $db->quoteName('element') . ' LIKE ' . $db->quote('mokosuite\\_%') . ')') . ' OR ' . $db->quoteName('element') . ' LIKE ' . $db->quote('mokosuiteclient\\_%') . ')')
->order($db->quoteName('element') . ' ASC'); ->order($db->quoteName('element') . ' ASC');
$db->setQuery($query); $db->setQuery($query);
$pluginRows = $db->loadObjectList() ?: []; $pluginRows = $db->loadObjectList() ?: [];
@@ -118,7 +118,7 @@ class DashboardController extends BaseController
'site' => [ 'site' => [
'name' => $config->get('sitename', ''), 'name' => $config->get('sitename', ''),
'url' => rtrim(Uri::root(), '/'), 'url' => rtrim(Uri::root(), '/'),
'mokosuite_version' => $pkgCache->version ?? '', 'mokosuiteclient_version' => $pkgCache->version ?? '',
'joomla_version' => (new Version())->getShortVersion(), 'joomla_version' => (new Version())->getShortVersion(),
'php_version' => PHP_VERSION, 'php_version' => PHP_VERSION,
'db_type' => $db->getServerType(), 'db_type' => $db->getServerType(),
@@ -1,12 +1,12 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
namespace Moko\Component\MokoSuite\Api\Controller; namespace Moko\Component\MokoSuiteClient\Api\Controller;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -16,7 +16,7 @@ use Joomla\CMS\MVC\Controller\BaseController;
/** /**
* Extensions list API controller. * Extensions list API controller.
* *
* GET /api/index.php/v1/mokosuite/extensions * GET /api/index.php/v1/mokosuiteclient/extensions
* *
* Returns all installed extensions with type, element, folder, version, * Returns all installed extensions with type, element, folder, version,
* enabled/protected/locked status, and update server info. * enabled/protected/locked status, and update server info.
@@ -1,12 +1,12 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
namespace Moko\Component\MokoSuite\Api\Controller; namespace Moko\Component\MokoSuiteClient\Api\Controller;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -19,9 +19,9 @@ use Joomla\Registry\Registry;
/** /**
* Health check API controller. * Health check API controller.
* *
* GET /api/index.php/v1/mokosuite/health * GET /api/index.php/v1/mokosuiteclient/health
* *
* Returns full health diagnostics from the MokoSuite system plugin. * Returns full health diagnostics from the MokoSuiteClient system plugin.
* Requires a Joomla API token with core.manage permissions. * Requires a Joomla API token with core.manage permissions.
* *
* @since 1.0.0 * @since 1.0.0
@@ -46,11 +46,11 @@ class HealthController extends BaseController
return; return;
} }
$plugin = PluginHelper::getPlugin('system', 'mokosuite'); $plugin = PluginHelper::getPlugin('system', 'mokosuiteclient');
if (!$plugin) if (!$plugin)
{ {
$this->sendJson(503, ['error' => 'MokoSuite system plugin not enabled']); $this->sendJson(503, ['error' => 'MokoSuiteClient system plugin not enabled']);
return; return;
} }
@@ -74,7 +74,7 @@ class HealthController extends BaseController
'caching' => (bool) $config->get('caching', 0), 'caching' => (bool) $config->get('caching', 0),
], ],
'plugin' => [ 'plugin' => [
'brand' => $params->get('brand_name', 'MokoSuite'), 'brand' => $params->get('brand_name', 'MokoSuiteClient'),
'company' => $params->get('company_name', 'Moko Consulting'), 'company' => $params->get('company_name', 'Moko Consulting'),
], ],
]; ];
@@ -1,12 +1,12 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
namespace Moko\Component\MokoSuite\Api\Controller; namespace Moko\Component\MokoSuiteClient\Api\Controller;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -17,7 +17,7 @@ use Joomla\CMS\MVC\Controller\BaseController;
/** /**
* Extension install-from-URL API controller. * Extension install-from-URL API controller.
* *
* POST /api/index.php/v1/mokosuite/install * POST /api/index.php/v1/mokosuiteclient/install
* Body: {"url": "https://example.com/path/to/extension.zip"} * Body: {"url": "https://example.com/path/to/extension.zip"}
* *
* Downloads a ZIP from the given URL and installs it via Joomla's Installer. * Downloads a ZIP from the given URL and installs it via Joomla's Installer.
@@ -115,7 +115,7 @@ class InstallController extends BaseController
{ {
$config = Factory::getConfig(); $config = Factory::getConfig();
$tmpPath = $config->get('tmp_path', JPATH_ROOT . '/tmp'); $tmpPath = $config->get('tmp_path', JPATH_ROOT . '/tmp');
$zipFile = $tmpPath . '/mokosuite_install_' . bin2hex(random_bytes(8)) . '.zip'; $zipFile = $tmpPath . '/mokosuiteclient_install_' . bin2hex(random_bytes(8)) . '.zip';
// Download // Download
$this->downloadFile($url, $zipFile); $this->downloadFile($url, $zipFile);
@@ -123,7 +123,7 @@ class InstallController extends BaseController
try try
{ {
// Extract // Extract
$extractDir = $tmpPath . '/mokosuite_extract_' . bin2hex(random_bytes(8)); $extractDir = $tmpPath . '/mokosuiteclient_extract_' . bin2hex(random_bytes(8));
if (!mkdir($extractDir, 0755, true)) if (!mkdir($extractDir, 0755, true))
{ {
@@ -207,7 +207,7 @@ class InstallController extends BaseController
CURLOPT_TIMEOUT => 120, CURLOPT_TIMEOUT => 120,
CURLOPT_CONNECTTIMEOUT => 15, CURLOPT_CONNECTTIMEOUT => 15,
CURLOPT_FAILONERROR => true, CURLOPT_FAILONERROR => true,
CURLOPT_USERAGENT => 'MokoSuite-Installer/1.0', CURLOPT_USERAGENT => 'MokoSuiteClient-Installer/1.0',
]); ]);
$success = curl_exec($ch); $success = curl_exec($ch);
@@ -1,12 +1,12 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
namespace Moko\Component\MokoSuite\Api\Controller; namespace Moko\Component\MokoSuiteClient\Api\Controller;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -16,15 +16,15 @@ use Joomla\CMS\MVC\Controller\BaseController;
/** /**
* Feature plugins API controller. * Feature plugins API controller.
* *
* GET /api/index.php/v1/mokosuite/plugins list MokoSuite plugins + status * GET /api/index.php/v1/mokosuiteclient/plugins list MokoSuiteClient plugins + status
* POST /api/index.php/v1/mokosuite/plugins/toggle enable/disable a feature plugin * POST /api/index.php/v1/mokosuiteclient/plugins/toggle enable/disable a feature plugin
* *
* @since 02.32.00 * @since 02.32.00
*/ */
class PluginsController extends BaseController class PluginsController extends BaseController
{ {
/** /**
* List all MokoSuite feature plugins with their enabled state. * List all MokoSuiteClient feature plugins with their enabled state.
* *
* @return void * @return void
*/ */
@@ -57,14 +57,14 @@ class PluginsController extends BaseController
'(' . '(' .
'(' . $db->quoteName('type') . ' = ' . $db->quote('plugin') '(' . $db->quoteName('type') . ' = ' . $db->quote('plugin')
. ' AND ' . $db->quoteName('folder') . ' = ' . $db->quote('system') . ' AND ' . $db->quoteName('folder') . ' = ' . $db->quote('system')
. ' AND (' . $db->quoteName('element') . ' = ' . $db->quote('mokosuite') . ' AND (' . $db->quoteName('element') . ' = ' . $db->quote('mokosuiteclient')
. ' OR ' . $db->quoteName('element') . ' LIKE ' . $db->quote('mokosuite\\_%') . '))' . ' OR ' . $db->quoteName('element') . ' LIKE ' . $db->quote('mokosuiteclient\\_%') . '))'
. ' OR (' . $db->quoteName('type') . ' = ' . $db->quote('plugin') . ' OR (' . $db->quoteName('type') . ' = ' . $db->quote('plugin')
. ' AND ' . $db->quoteName('folder') . ' = ' . $db->quote('webservices') . ' AND ' . $db->quoteName('folder') . ' = ' . $db->quote('webservices')
. ' AND ' . $db->quoteName('element') . ' = ' . $db->quote('mokosuite') . ')' . ' AND ' . $db->quoteName('element') . ' = ' . $db->quote('mokosuiteclient') . ')'
. ' OR (' . $db->quoteName('type') . ' = ' . $db->quote('plugin') . ' OR (' . $db->quoteName('type') . ' = ' . $db->quote('plugin')
. ' AND ' . $db->quoteName('folder') . ' = ' . $db->quote('task') . ' AND ' . $db->quoteName('folder') . ' = ' . $db->quote('task')
. ' AND ' . $db->quoteName('element') . ' LIKE ' . $db->quote('mokosuite%') . ')' . ' AND ' . $db->quoteName('element') . ' LIKE ' . $db->quote('mokosuiteclient%') . ')'
. ')', . ')',
]) ])
->order($db->quoteName('folder') . ' ASC, ' . $db->quoteName('element') . ' ASC'); ->order($db->quoteName('folder') . ' ASC, ' . $db->quoteName('element') . ' ASC');
@@ -98,7 +98,7 @@ class PluginsController extends BaseController
} }
/** /**
* Toggle a MokoSuite feature plugin on or off. * Toggle a MokoSuiteClient feature plugin on or off.
* *
* Expects JSON body: {"extension_id": 123, "enabled": true} * Expects JSON body: {"extension_id": 123, "enabled": true}
* *
@@ -130,7 +130,7 @@ class PluginsController extends BaseController
$db = Factory::getDbo(); $db = Factory::getDbo();
// Verify the extension exists and is a MokoSuite plugin // Verify the extension exists and is a MokoSuiteClient plugin
$query = $db->getQuery(true) $query = $db->getQuery(true)
->select([$db->quoteName('element'), $db->quoteName('protected')]) ->select([$db->quoteName('element'), $db->quoteName('protected')])
->from($db->quoteName('#__extensions')) ->from($db->quoteName('#__extensions'))
@@ -147,7 +147,7 @@ class PluginsController extends BaseController
} }
// Don't allow disabling protected/core plugins // Don't allow disabling protected/core plugins
if (!$enabled && ((int) $ext->protected || $ext->element === 'mokosuite')) if (!$enabled && ((int) $ext->protected || $ext->element === 'mokosuiteclient'))
{ {
$this->sendJson(409, ['error' => 'This plugin is protected and cannot be disabled']); $this->sendJson(409, ['error' => 'This plugin is protected and cannot be disabled']);
@@ -1,12 +1,12 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
namespace Moko\Component\MokoSuite\Api\Controller; namespace Moko\Component\MokoSuiteClient\Api\Controller;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -16,7 +16,7 @@ use Joomla\CMS\MVC\Controller\BaseController;
/** /**
* Provision reset API controller. * Provision reset API controller.
* *
* POST /api/index.php/v1/mokosuite/provision-reset * POST /api/index.php/v1/mokosuiteclient/provision-reset
* *
* Resets a site for new client provisioning: clears hits, versions, * Resets a site for new client provisioning: clears hits, versions,
* download keys, and flags the site for fresh client info collection. * download keys, and flags the site for fresh client info collection.
@@ -36,7 +36,7 @@ class ProvisionController extends BaseController
$app = Factory::getApplication(); $app = Factory::getApplication();
$user = $app->getIdentity(); $user = $app->getIdentity();
if (!$user->authorise('core.manage', 'com_mokosuite')) if (!$user->authorise('core.manage', 'com_mokosuiteclient'))
{ {
$this->sendJson(403, ['error' => 'Not authorized']); $this->sendJson(403, ['error' => 'Not authorized']);
@@ -91,7 +91,7 @@ class ProvisionController extends BaseController
{ {
$newToken = bin2hex(random_bytes(32)); $newToken = bin2hex(random_bytes(32));
$plugin = \Joomla\CMS\Plugin\PluginHelper::getPlugin('system', 'mokosuite'); $plugin = \Joomla\CMS\Plugin\PluginHelper::getPlugin('system', 'mokosuiteclient');
if ($plugin) if ($plugin)
{ {
@@ -102,7 +102,7 @@ class ProvisionController extends BaseController
$db->getQuery(true) $db->getQuery(true)
->update($db->quoteName('#__extensions')) ->update($db->quoteName('#__extensions'))
->set($db->quoteName('params') . ' = ' . $db->quote($pluginParams->toString())) ->set($db->quoteName('params') . ' = ' . $db->quote($pluginParams->toString()))
->where($db->quoteName('element') . ' = ' . $db->quote('mokosuite')) ->where($db->quoteName('element') . ' = ' . $db->quote('mokosuiteclient'))
->where($db->quoteName('type') . ' = ' . $db->quote('plugin')) ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' . $db->quote('system')) ->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
)->execute(); )->execute();
@@ -156,7 +156,7 @@ class ProvisionController extends BaseController
try try
{ {
// Write a flag file that the core plugin checks on next admin load // Write a flag file that the core plugin checks on next admin load
$flagFile = JPATH_ADMINISTRATOR . '/cache/mokosuite_setup_required.flag'; $flagFile = JPATH_ADMINISTRATOR . '/cache/mokosuiteclient_setup_required.flag';
file_put_contents($flagFile, json_encode([ file_put_contents($flagFile, json_encode([
'created' => gmdate('Y-m-d\TH:i:s\Z'), 'created' => gmdate('Y-m-d\TH:i:s\Z'),
'reason' => 'provision-reset', 'reason' => 'provision-reset',
@@ -1,12 +1,12 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
namespace Moko\Component\MokoSuite\Api\Controller; namespace Moko\Component\MokoSuiteClient\Api\Controller;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -19,8 +19,8 @@ use Joomla\Registry\Registry;
/** /**
* Remote login API controller. * Remote login API controller.
* *
* POST /api/index.php/v1/mokosuite/remote-login * POST /api/index.php/v1/mokosuiteclient/remote-login
* Body: {"token": "health_api_token", "user": "requesting_username", "origin": "MokoSuiteHQ"} * Body: {"token": "health_api_token", "user": "requesting_username", "origin": "MokoSuiteClientHQ"}
* *
* Validates the health API token, generates a one-time login token * Validates the health API token, generates a one-time login token
* for the master user, and returns a URL that auto-authenticates. * for the master user, and returns a URL that auto-authenticates.
@@ -55,11 +55,11 @@ class RemoteLoginController extends BaseController
} }
// Validate against the core plugin's health_api_token // Validate against the core plugin's health_api_token
$plugin = PluginHelper::getPlugin('system', 'mokosuite'); $plugin = PluginHelper::getPlugin('system', 'mokosuiteclient');
if (!$plugin) if (!$plugin)
{ {
$this->sendJson(503, ['error' => 'MokoSuite core plugin not found']); $this->sendJson(503, ['error' => 'MokoSuiteClient core plugin not found']);
return; return;
} }
@@ -110,7 +110,7 @@ class RemoteLoginController extends BaseController
$expires = time() + self::OTL_TTL; $expires = time() + self::OTL_TTL;
// Store in a temp file (avoids DB schema changes) // Store in a temp file (avoids DB schema changes)
$otlFile = JPATH_ADMINISTRATOR . '/cache/mokosuite_otl_' . md5($otlToken) . '.json'; $otlFile = JPATH_ADMINISTRATOR . '/cache/mokosuiteclient_otl_' . md5($otlToken) . '.json';
file_put_contents($otlFile, json_encode([ file_put_contents($otlFile, json_encode([
'token' => $otlToken, 'token' => $otlToken,
'user_id' => (int) $user->id, 'user_id' => (int) $user->id,
@@ -120,7 +120,7 @@ class RemoteLoginController extends BaseController
])); ]));
// Build login URL // Build login URL
$loginUrl = rtrim(Uri::root(), '/') . '/administrator/index.php?mokosuite_otl=' . $otlToken; $loginUrl = rtrim(Uri::root(), '/') . '/administrator/index.php?mokosuiteclient_otl=' . $otlToken;
$this->sendJson(200, [ $this->sendJson(200, [
'status' => 'ok', 'status' => 'ok',
@@ -139,16 +139,16 @@ class RemoteLoginController extends BaseController
*/ */
private function getMasterUsernames(Registry $params): array private function getMasterUsernames(Registry $params): array
{ {
// Use MokoSuiteHelper if available // Use MokoSuiteClientHelper if available
$helperFile = JPATH_PLUGINS . '/system/mokosuite/Helper/MokoSuiteHelper.php'; $helperFile = JPATH_PLUGINS . '/system/mokosuiteclient/Helper/MokoSuiteClientHelper.php';
if (file_exists($helperFile)) if (file_exists($helperFile))
{ {
require_once $helperFile; require_once $helperFile;
if (method_exists(\Moko\Plugin\System\MokoSuite\Helper\MokoSuiteHelper::class, 'getMasterUsernames')) if (method_exists(\Moko\Plugin\System\MokoSuiteClient\Helper\MokoSuiteClientHelper::class, 'getMasterUsernames'))
{ {
return \Moko\Plugin\System\MokoSuite\Helper\MokoSuiteHelper::getMasterUsernames(); return \Moko\Plugin\System\MokoSuiteClient\Helper\MokoSuiteClientHelper::getMasterUsernames();
} }
} }
@@ -1,12 +1,12 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
namespace Moko\Component\MokoSuite\Api\Controller; namespace Moko\Component\MokoSuiteClient\Api\Controller;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -18,7 +18,7 @@ use Joomla\Registry\Registry;
/** /**
* Demo site reset API controller. * Demo site reset API controller.
* *
* POST /api/index.php/v1/mokosuite/reset * POST /api/index.php/v1/mokosuiteclient/reset
* Body: {"baseline": "default"} * Body: {"baseline": "default"}
* *
* Restores the site to a named baseline snapshot. * Restores the site to a named baseline snapshot.
@@ -53,11 +53,11 @@ class ResetController extends BaseController
return; return;
} }
$plugin = PluginHelper::getPlugin('system', 'mokosuite'); $plugin = PluginHelper::getPlugin('system', 'mokosuiteclient');
if (!$plugin) if (!$plugin)
{ {
$this->sendJson(503, ['error' => 'MokoSuite system plugin not enabled']); $this->sendJson(503, ['error' => 'MokoSuiteClient system plugin not enabled']);
return; return;
} }
@@ -84,13 +84,13 @@ class ResetController extends BaseController
* *
* @param Registry $params Plugin parameters * @param Registry $params Plugin parameters
* *
* @return \Moko\Plugin\System\MokoSuite\Service\DemoResetService * @return \Moko\Plugin\System\MokoSuiteClient\Service\DemoResetService
* *
* @since 02.21.00 * @since 02.21.00
*/ */
private function createService(Registry $params) private function createService(Registry $params)
{ {
$serviceFile = JPATH_PLUGINS . '/task/mokosuitedemo/src/Service/DemoResetService.php'; $serviceFile = JPATH_PLUGINS . '/task/mokosuiteclientdemo/src/Service/DemoResetService.php';
if (!file_exists($serviceFile)) if (!file_exists($serviceFile))
{ {
@@ -101,7 +101,7 @@ class ResetController extends BaseController
$media = (bool) $params->get('demo_snapshot_include_media', 1); $media = (bool) $params->get('demo_snapshot_include_media', 1);
return new \Moko\Plugin\Task\MokoSuiteDemo\Service\DemoResetService($media); return new \Moko\Plugin\Task\MokoSuiteClientDemo\Service\DemoResetService($media);
} }
/** /**
@@ -1,12 +1,12 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
namespace Moko\Component\MokoSuite\Api\Controller; namespace Moko\Component\MokoSuiteClient\Api\Controller;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -18,8 +18,8 @@ use Joomla\Registry\Registry;
/** /**
* Snapshot management API controller. * Snapshot management API controller.
* *
* GET /api/index.php/v1/mokosuite/snapshot list snapshots * GET /api/index.php/v1/mokosuiteclient/snapshot list snapshots
* POST /api/index.php/v1/mokosuite/snapshot create snapshot * POST /api/index.php/v1/mokosuiteclient/snapshot create snapshot
* *
* @since 02.21.00 * @since 02.21.00
*/ */
@@ -88,7 +88,7 @@ class SnapshotController extends BaseController
try try
{ {
$plugin = PluginHelper::getPlugin('system', 'mokosuite'); $plugin = PluginHelper::getPlugin('system', 'mokosuiteclient');
$params = $plugin ? new Registry($plugin->params) : new Registry; $params = $plugin ? new Registry($plugin->params) : new Registry;
$body = json_decode($app->input->json->getRaw(), true); $body = json_decode($app->input->json->getRaw(), true);
@@ -112,13 +112,13 @@ class SnapshotController extends BaseController
/** /**
* Create DemoResetService from plugin params. * Create DemoResetService from plugin params.
* *
* @return \Moko\Plugin\System\MokoSuite\Service\DemoResetService * @return \Moko\Plugin\System\MokoSuiteClient\Service\DemoResetService
* *
* @since 02.21.00 * @since 02.21.00
*/ */
private function createService() private function createService()
{ {
$serviceFile = JPATH_PLUGINS . '/task/mokosuitedemo/src/Service/DemoResetService.php'; $serviceFile = JPATH_PLUGINS . '/task/mokosuiteclientdemo/src/Service/DemoResetService.php';
if (!file_exists($serviceFile)) if (!file_exists($serviceFile))
{ {
@@ -127,12 +127,12 @@ class SnapshotController extends BaseController
require_once $serviceFile; require_once $serviceFile;
$plugin = PluginHelper::getPlugin('system', 'mokosuite'); $plugin = PluginHelper::getPlugin('system', 'mokosuiteclient');
$params = $plugin ? new Registry($plugin->params) : new Registry; $params = $plugin ? new Registry($plugin->params) : new Registry;
$media = (bool) $params->get('demo_snapshot_include_media', 1); $media = (bool) $params->get('demo_snapshot_include_media', 1);
return new \Moko\Plugin\Task\MokoSuiteDemo\Service\DemoResetService($media); return new \Moko\Plugin\Task\MokoSuiteClientDemo\Service\DemoResetService($media);
} }
/** /**
@@ -1,12 +1,12 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
namespace Moko\Component\MokoSuite\Api\Controller; namespace Moko\Component\MokoSuiteClient\Api\Controller;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -18,7 +18,7 @@ use Joomla\Registry\Registry;
/** /**
* Content sync trigger API controller (sender side). * Content sync trigger API controller (sender side).
* *
* POST /api/index.php/v1/mokosuite/sync * POST /api/index.php/v1/mokosuiteclient/sync
* *
* Pushes content to all configured sync targets. * Pushes content to all configured sync targets.
* *
@@ -44,11 +44,11 @@ class SyncController extends BaseController
return; return;
} }
$plugin = PluginHelper::getPlugin('system', 'mokosuite'); $plugin = PluginHelper::getPlugin('system', 'mokosuiteclient');
if (!$plugin) if (!$plugin)
{ {
$this->sendJson(503, ['error' => 'MokoSuite system plugin not enabled']); $this->sendJson(503, ['error' => 'MokoSuiteClient system plugin not enabled']);
return; return;
} }
@@ -57,10 +57,10 @@ class SyncController extends BaseController
$params = new Registry($plugin->params); $params = new Registry($plugin->params);
$targets = json_decode($params->get('sync_targets', '[]'), true) ?: []; $targets = json_decode($params->get('sync_targets', '[]'), true) ?: [];
$serviceFile = JPATH_PLUGINS . '/task/mokosuitesync/src/Service/ContentSyncService.php'; $serviceFile = JPATH_PLUGINS . '/task/mokosuiteclientsync/src/Service/ContentSyncService.php';
require_once $serviceFile; require_once $serviceFile;
$service = new \Moko\Plugin\Task\MokoSuiteSync\Service\ContentSyncService(); $service = new \Moko\Plugin\Task\MokoSuiteClientSync\Service\ContentSyncService();
$result = $service->syncAllTargets($targets); $result = $service->syncAllTargets($targets);
$this->sendJson(200, $result); $this->sendJson(200, $result);
@@ -1,12 +1,12 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
namespace Moko\Component\MokoSuite\Api\Controller; namespace Moko\Component\MokoSuiteClient\Api\Controller;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -16,7 +16,7 @@ use Joomla\CMS\MVC\Controller\BaseController;
/** /**
* Content sync receiver API controller (target side). * Content sync receiver API controller (target side).
* *
* POST /api/index.php/v1/mokosuite/sync-receive * POST /api/index.php/v1/mokosuiteclient/sync-receive
* *
* Accepts a JSON payload from a source site and applies the content locally. * Accepts a JSON payload from a source site and applies the content locally.
* *
@@ -46,16 +46,16 @@ class SyncReceiveController extends BaseController
{ {
$payload = json_decode($app->input->json->getRaw(), true); $payload = json_decode($app->input->json->getRaw(), true);
if (empty($payload['mokosuite_sync'])) if (empty($payload['mokosuiteclient_sync']))
{ {
$this->sendJson(400, ['error' => 'Invalid payload — missing mokosuite_sync version']); $this->sendJson(400, ['error' => 'Invalid payload — missing mokosuiteclient_sync version']);
return; return;
} }
$serviceFile = JPATH_PLUGINS . '/task/mokosuitesync/src/Service/ContentSyncReceiver.php'; $serviceFile = JPATH_PLUGINS . '/task/mokosuiteclientsync/src/Service/ContentSyncReceiver.php';
require_once $serviceFile; require_once $serviceFile;
$receiver = new \Moko\Plugin\Task\MokoSuiteSync\Service\ContentSyncReceiver(); $receiver = new \Moko\Plugin\Task\MokoSuiteClientSync\Service\ContentSyncReceiver();
$result = $receiver->receive($payload); $result = $receiver->receive($payload);
$this->sendJson(200, $result); $this->sendJson(200, $result);
@@ -1,12 +1,12 @@
<?php <?php
/** /**
* @package MokoSuite * @package MokoSuiteClient
* @subpackage com_mokosuite * @subpackage com_mokosuiteclient
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
*/ */
namespace Moko\Component\MokoSuite\Api\Controller; namespace Moko\Component\MokoSuiteClient\Api\Controller;
defined('_JEXEC') or die; defined('_JEXEC') or die;
@@ -16,7 +16,7 @@ use Joomla\CMS\MVC\Controller\BaseController;
/** /**
* Update check API controller. * Update check API controller.
* *
* POST /api/index.php/v1/mokosuite/update * POST /api/index.php/v1/mokosuiteclient/update
* *
* @since 1.0.0 * @since 1.0.0
*/ */

Some files were not shown because too many files have changed in this diff Show More