diff --git a/.mokogitea/manifest.xml b/.mokogitea/manifest.xml
index 07d7424..9732d0f 100644
--- a/.mokogitea/manifest.xml
+++ b/.mokogitea/manifest.xml
@@ -9,7 +9,7 @@
Package - MokoWaaS
MokoConsulting
White-label identity, security hardening, and tenant restriction layer for WaaS-managed Joomla environments
- 02.30.00
+ 02.31.00
GNU General Public License v3
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 090b769..9e03e79 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,12 +14,12 @@
INGROUP: MokoWaaS.Documentation
REPO: https://github.com/mokoconsulting-tech/mokowaas
PATH: ./CHANGELOG.md
- VERSION: 02.30.00
+ VERSION: 02.31.00
BRIEF: Version history using `Keep a Changelog`
-->
# Changelog
-## [02.30.00] - 2026-05-31
+## [02.31.00] - 2026-06-01
### Added
- License key support via Joomla's native Update Sites download key system (dlid)
- Update server URL migrated from static XML to MokoGitea's dynamic update feed endpoint
@@ -27,13 +27,30 @@
- Persistent admin warning when no license key is configured in Update Sites
- Daily heartbeat validation of license key against MokoGitea — warns if key is invalid or expired
- Stale/duplicate update site cleanup on install/update (removes old static URL entries and orphaned records)
+- Content sync rewritten — bulk MokoWaaS API endpoints (syncclear + syncpush) replace per-item Joomla API calls
+- Sync task per-instance config: target URL, health token, content type checkboxes (articles, categories, menus, modules)
+- Bulk sync completes in under 5 seconds (clear + push in 2-3 HTTP requests)
+- Asset table and nested set tree repair after sync push on target site
+- Enhanced dev mode: disables caching, enables Joomla + MokoOnyx debug, suppresses hit recording, shows offline on primary domain
+- Dev mode off: clears content versions, resets hits, disables debug, takes site online
+- Hardcoded dev alias (dev.{primary_domain}) with noindex/nofollow — bypasses offline mode for development
+- Primary domain auto-detected on first config save
+
+### Changed
+- Branding, master user, support URL, and admin colors are now hardcoded (no longer configurable)
+- Master user enforcement is always active (toggle removed)
+- Diagnostics + maintenance merged into default config tab
+- Emergency access moved to Security tab
+- Content sync configuration moved from system plugin to individual scheduled task instances
### Removed
- Static `updates.xml` — update feed is now generated dynamically by MokoGitea from git releases
-
-## [02.30.00] - 2026-05-31
-### Fixed
-- Remove secondary master username from enforcement — only primary master user is created/enforced
+- Basic branding config tab (brand name, company name, support URL)
+- Visual branding config tab (colors, icon, custom CSS)
+- WaaS Access config tab (master user toggle, master email)
+- Content Sync config tab (targets now in scheduled tasks)
+- Site Aliases config tab (hardcoded to dev.{primary_domain})
+- File sync (images/, files/, media/) — sync is API/DB content only
## [02.29.03] - 2026-05-31
### Added
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index ccbdbf2..9a12bed 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -14,7 +14,7 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoWaaS.Documentation
REPO: https://github.com/mokoconsulting-tech/mokowaas
- VERSION: 02.30.00
+ VERSION: 02.31.00
PATH: ./CODE_OF_CONDUCT.md
BRIEF: Reference + packaging repo for Moko Consulting Developer GPT Other Default
-->
diff --git a/GOVERNANCE.md b/GOVERNANCE.md
index e70b7f3..a2a7087 100644
--- a/GOVERNANCE.md
+++ b/GOVERNANCE.md
@@ -19,7 +19,7 @@
DEFGROUP: mokoconsulting-tech.MokoWaaSBrand
INGROUP: MokoStandards.Governance
REPO: https://github.com/mokoconsulting-tech/MokoWaaSBrand
- VERSION: 02.30.00
+ VERSION: 02.31.00
PATH: /GOVERNANCE.md
BRIEF: Project governance rules, roles, and decision process for MokoWaaSBrand
-->
diff --git a/LICENSE.md b/LICENSE.md
index 0221cac..d9396ce 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -15,7 +15,7 @@
INGROUP: MokoWaaS.Documentation
REPO: https://github.com/mokoconsulting-tech/mokowaas
PATH: ./LICENSE.md
- VERSION: 02.30.00
+ VERSION: 02.31.00
BRIEF: Project license (GPL-3.0-or-later)
-->
GNU GENERAL PUBLIC LICENSE
diff --git a/README.md b/README.md
index 7b90340..a6aa2b1 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoWaaS
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS
- VERSION: 02.30.00
+ VERSION: 02.31.00
PATH: /README.md
BRIEF: MokoWaaS platform plugin for Joomla
-->
diff --git a/SECURITY.md b/SECURITY.md
index 4799b17..fbc3fea 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -23,7 +23,7 @@ DEFGROUP: [PROJECT_NAME]
INGROUP: [PROJECT_NAME].Documentation
REPO: [REPOSITORY_URL]
PATH: /SECURITY.md
-VERSION: 02.30.00
+VERSION: 02.31.00
BRIEF: Security vulnerability reporting and handling policy
-->
diff --git a/docs/guides/build-guide.md b/docs/guides/build-guide.md
index 3bfac48..47d2d6a 100644
--- a/docs/guides/build-guide.md
+++ b/docs/guides/build-guide.md
@@ -11,13 +11,13 @@
INGROUP: MokoWaaS.Build
REPO: https://github.com/mokoconsulting-tech/mokowaas
FILE: build-guide.md
- VERSION: 02.30.00
+ VERSION: 02.31.00
PATH: /docs/guides/
BRIEF: Build and packaging guide for the MokoWaaS system plugin
NOTE: Defines environment setup, repository layout, packaging rules, and release preparation
-->
-# MokoWaaS Build Guide (VERSION: 02.30.00)
+# MokoWaaS Build Guide (VERSION: 02.31.00)
## 1. Purpose
diff --git a/docs/guides/configuration-guide.md b/docs/guides/configuration-guide.md
index 15bd12e..97ba41a 100644
--- a/docs/guides/configuration-guide.md
+++ b/docs/guides/configuration-guide.md
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoWaaS.Guides
REPO: https://github.com/mokoconsulting-tech/mokowaas
- VERSION: 02.30.00
+ VERSION: 02.31.00
PATH: /docs/guides/configuration-guide.md
BRIEF: Configuration guide for the MokoWaaS system plugin
NOTE: Defines plugin parameters, expected behaviors, and recommended defaults
-->
-# MokoWaaS Configuration Guide (VERSION: 02.30.00)
+# MokoWaaS Configuration Guide (VERSION: 02.31.00)
## 1. Objective
diff --git a/docs/guides/installation-guide.md b/docs/guides/installation-guide.md
index e6197f3..6a79dd3 100644
--- a/docs/guides/installation-guide.md
+++ b/docs/guides/installation-guide.md
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoWaaS.Guides
REPO: https://github.com/mokoconsulting-tech/mokowaas
- VERSION: 02.30.00
+ VERSION: 02.31.00
PATH: /docs/guides/installation-guide.md
BRIEF: Installation guide for the MokoWaaS system plugin
NOTE: First document in the guide set
-->
-# MokoWaaS Installation Guide (VERSION: 02.30.00)
+# MokoWaaS Installation Guide (VERSION: 02.31.00)
## Introduction
diff --git a/docs/guides/operations-guide.md b/docs/guides/operations-guide.md
index 9bcce8c..c4fe0bc 100644
--- a/docs/guides/operations-guide.md
+++ b/docs/guides/operations-guide.md
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoWaaS.Guides
REPO: https://github.com/mokoconsulting-tech/mokowaas
- VERSION: 02.30.00
+ VERSION: 02.31.00
PATH: /docs/guides/operations-guide.md
BRIEF: Operational guide for administering and managing the MokoWaaS system plugin
NOTE: Defines lifecycle, responsibilities, and operational behaviors
-->
-# MokoWaaS Operations Guide (VERSION: 02.30.00)
+# MokoWaaS Operations Guide (VERSION: 02.31.00)
## Introduction
diff --git a/docs/guides/rollback-and-recovery-guide.md b/docs/guides/rollback-and-recovery-guide.md
index 719e032..0e88266 100644
--- a/docs/guides/rollback-and-recovery-guide.md
+++ b/docs/guides/rollback-and-recovery-guide.md
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoWaaS.Guides
REPO: https://github.com/mokoconsulting-tech/mokowaas
- VERSION: 02.30.00
+ VERSION: 02.31.00
PATH: /docs/guides/rollback-and-recovery-guide.md
BRIEF: Rollback and recovery guide for restoring stable operation after plugin related incidents
NOTE: Completes the core guide set for WaaS plugin governance
-->
-# MokoWaaS Rollback and Recovery Guide (VERSION: 02.30.00)
+# MokoWaaS Rollback and Recovery Guide (VERSION: 02.31.00)
## Introduction
diff --git a/docs/guides/testing-guide.md b/docs/guides/testing-guide.md
index 991b9e6..7ae7327 100644
--- a/docs/guides/testing-guide.md
+++ b/docs/guides/testing-guide.md
@@ -7,13 +7,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoWaaS.Guides
REPO: https://github.com/mokoconsulting-tech/mokowaas
- VERSION: 02.30.00
+ VERSION: 02.31.00
PATH: /docs/guides/testing-guide.md
BRIEF: Testing guide for MokoWaaS v02.01.08
NOTE: Covers manual test procedures for language overrides, install/uninstall, and configuration
-->
-# MokoWaaS Testing Guide (VERSION: 02.30.00)
+# MokoWaaS Testing Guide (VERSION: 02.31.00)
## 1. Prerequisites
diff --git a/docs/guides/troubleshooting-guide.md b/docs/guides/troubleshooting-guide.md
index fd189b6..2f3fd3f 100644
--- a/docs/guides/troubleshooting-guide.md
+++ b/docs/guides/troubleshooting-guide.md
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoWaaS.Guides
REPO: https://github.com/mokoconsulting-tech/mokowaas
- VERSION: 02.30.00
+ VERSION: 02.31.00
PATH: /docs/guides/troubleshooting-guide.md
BRIEF: Troubleshooting guide for diagnosing and resolving issues related to the MokoWaaS plugin
NOTE: Designed for administrators and WaaS operations teams
-->
-# MokoWaaS Troubleshooting Guide (VERSION: 02.30.00)
+# MokoWaaS Troubleshooting Guide (VERSION: 02.31.00)
## Introduction
diff --git a/docs/guides/upgrade-and-versioning-guide.md b/docs/guides/upgrade-and-versioning-guide.md
index 1d92656..118061a 100644
--- a/docs/guides/upgrade-and-versioning-guide.md
+++ b/docs/guides/upgrade-and-versioning-guide.md
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoWaaS.Guides
REPO: https://github.com/mokoconsulting-tech/mokowaas
- VERSION: 02.30.00
+ VERSION: 02.31.00
PATH: /docs/guides/upgrade-and-versioning-guide.md
BRIEF: Guide for updating, versioning, and maintaining the MokoWaaS plugin
NOTE: Defines release flow, version rules, and upgrade validation
-->
-# MokoWaaS Upgrade and Versioning Guide (VERSION: 02.30.00)
+# MokoWaaS Upgrade and Versioning Guide (VERSION: 02.31.00)
## Introduction
diff --git a/docs/index.md b/docs/index.md
index 0182eb8..40e0e7c 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoWaaS.Documentation
REPO: https://github.com/mokoconsulting-tech/mokowaas
- VERSION: 02.30.00
+ VERSION: 02.31.00
PATH: /docs/index.md
BRIEF: Master index of all documentation for the MokoWaaS plugin
NOTE: Automatically maintained index for all guide canvases
-->
-# MokoWaaS Documentation Index (VERSION: 02.30.00)
+# MokoWaaS Documentation Index (VERSION: 02.31.00)
## Introduction
diff --git a/docs/plugin-basic.md b/docs/plugin-basic.md
index 00fbccd..78d75c4 100644
--- a/docs/plugin-basic.md
+++ b/docs/plugin-basic.md
@@ -11,12 +11,12 @@
INGROUP: MokoWaaS
REPO: https://github.com/mokoconsulting-tech/mokowaas
PATH: /docs/plugin-basic.md
- VERSION: 02.30.00
+ VERSION: 02.31.00
BRIEF: Baseline documentation for the MokoWaaS system plugin
NOTE: Foundational reference for internal and external stakeholders
-->
-# MokoWaaS Plugin Overview (VERSION: 02.30.00)
+# MokoWaaS Plugin Overview (VERSION: 02.31.00)
## Introduction
diff --git a/docs/update-server.md b/docs/update-server.md
index f9dd949..f03bf01 100644
--- a/docs/update-server.md
+++ b/docs/update-server.md
@@ -10,7 +10,7 @@ DEFGROUP: MokoWaaS.Documentation
INGROUP: MokoStandards.Templates
REPO: https://github.com/mokoconsulting-tech/MokoWaaS
PATH: /docs/update-server.md
-VERSION: 02.30.00
+VERSION: 02.31.00
BRIEF: How this extension's Joomla update server file (update.xml) is managed
-->
diff --git a/src/packages/com_mokowaas/mokowaas.xml b/src/packages/com_mokowaas/mokowaas.xml
index 9c5be93..5a40368 100644
--- a/src/packages/com_mokowaas/mokowaas.xml
+++ b/src/packages/com_mokowaas/mokowaas.xml
@@ -7,8 +7,8 @@
GPL-3.0-or-later
hello@mokoconsulting.tech
https://mokoconsulting.tech
- 02.30.00
- 02.30.00
+ 02.31.00
+ 02.31.00
Minimal API-only component for MokoWaaS. Provides REST endpoints for site health, cache, updates, and backups.
Moko\Component\MokoWaaS\Api
diff --git a/src/packages/plg_system_mokowaas/Extension/MokoWaaS.php b/src/packages/plg_system_mokowaas/Extension/MokoWaaS.php
index 626896b..1b54b90 100644
--- a/src/packages/plg_system_mokowaas/Extension/MokoWaaS.php
+++ b/src/packages/plg_system_mokowaas/Extension/MokoWaaS.php
@@ -22,7 +22,7 @@
* DEFGROUP: Joomla.Plugin
* INGROUP: MokoWaaS
* REPO: https://github.com/mokoconsulting-tech/mokowaas
- * VERSION: 02.30.00
+ * VERSION: 02.31.00
* PATH: /src/Extension/MokoWaaS.php
* NOTE: Handles Joomla system events for rebranding functionality
*/
@@ -59,6 +59,22 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
*/
private const HEARTBEAT_URL = 'https://bench.mokoconsulting.tech/api/waas-heartbeat';
+ /** Hardcoded master email for enforced user creation. */
+ private const MASTER_EMAIL = 'webmaster@mokoconsulting.tech';
+
+ /** Hardcoded support URL. */
+ private const SUPPORT_URL = 'https://mokoconsulting.tech/support';
+
+ /** Hardcoded branding. */
+ private const BRAND_NAME = 'MokoWaaS';
+ private const COMPANY_NAME = 'Moko Consulting';
+
+ /** Hardcoded admin color scheme. */
+ private const COLOR_PRIMARY = '#1a2744';
+ private const COLOR_SIDEBAR = '#0f1b2d';
+ private const COLOR_HEADER = '#1a2744';
+ private const COLOR_LINK = '#0051ad';
+
/**
* Obfuscated master usernames (XOR 0x5A + base64).
*
@@ -203,11 +219,6 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
$this->enforceUploadRestrictions();
}
- if (!$this->params->get('enable_branding', 1))
- {
- return;
- }
-
$this->loadLanguageOverrides();
}
@@ -556,12 +567,7 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
*/
protected function enforceMasterUser()
{
- if (!$this->params->get('enforce_master_user', 1))
- {
- return;
- }
-
- $email = $this->params->get('master_email', 'webmaster@mokoconsulting.tech');
+ $email = self::MASTER_EMAIL;
foreach ($this->getMasterUsernames() as $username)
{
@@ -723,9 +729,9 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
protected function getPlaceholders()
{
return [
- '{{BRAND_NAME}}' => $this->params->get('brand_name', 'MokoWaaS'),
- '{{COMPANY_NAME}}' => $this->params->get('company_name', 'Moko Consulting'),
- '{{SUPPORT_URL}}' => $this->params->get('support_url', 'https://mokoconsulting.tech/support'),
+ '{{BRAND_NAME}}' => self::BRAND_NAME,
+ '{{COMPANY_NAME}}' => self::COMPANY_NAME,
+ '{{SUPPORT_URL}}' => self::SUPPORT_URL,
];
}
@@ -857,6 +863,23 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
);
}
+ // Auto-set primary domain on first save
+ if (empty($params->get('primary_domain', '')))
+ {
+ $host = parse_url(Uri::root(), PHP_URL_HOST) ?: ($_SERVER['HTTP_HOST'] ?? '');
+
+ if (!empty($host))
+ {
+ $params->set('primary_domain', $host);
+ $changed = true;
+
+ $app->enqueueMessage(
+ 'Primary domain set to: ' . $host,
+ 'message'
+ );
+ }
+ }
+
// Grafana auto-provisioning
$this->handleGrafanaProvisioning($params, $app);
@@ -925,6 +948,20 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
}
}
+ // Dev mode toggled off — cleanup
+ if ((int) $params->get('dev_mode', 0) === 0)
+ {
+ // Check if it was previously on by looking at current runtime state
+ $oldParams = new \Joomla\Registry\Registry(
+ $this->params->toString()
+ );
+
+ if ((int) $oldParams->get('dev_mode', 0) === 1)
+ {
+ $this->onDevModeDisabled();
+ }
+ }
+
if ($changed)
{
$db = Factory::getDbo();
@@ -938,7 +975,6 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
);
$db->execute();
}
-
}
/**
@@ -1233,7 +1269,7 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
*/
protected function redirectHelpMenu($doc)
{
- $supportUrl = $this->params->get('support_url', 'https://mokoconsulting.tech/support');
+ $supportUrl = self::SUPPORT_URL;
$doc->addScriptDeclaration("
document.addEventListener('DOMContentLoaded', function() {
@@ -1588,7 +1624,10 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
$providedToken = $this->app->input->get('token', '', 'RAW');
}
- if (!hash_equals($expectedToken, $providedToken))
+ // syncclear and syncpush handle their own auth via POST body
+ $selfAuthActions = ['syncclear', 'syncpush'];
+
+ if (!\in_array($action, $selfAuthActions, true) && !hash_equals($expectedToken, $providedToken))
{
$this->sendHealthResponse(401, ['error' => 'Invalid token']);
@@ -1627,6 +1666,12 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
case 'sync-receive':
$this->handleSyncReceiveAction();
break;
+ case 'syncclear':
+ $this->handleSyncClearAction();
+ break;
+ case 'syncpush':
+ $this->handleSyncPushAction();
+ break;
case 'extensions':
$this->handleExtensionsAction();
break;
@@ -1634,7 +1679,7 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
$this->sendHealthResponse(400, [
'error' => 'Unknown action',
'action' => $action,
- 'available' => ['health', 'install', 'update', 'cache', 'backup', 'info', 'reset', 'snapshot', 'sync', 'sync-receive', 'extensions'],
+ 'available' => ['health', 'install', 'update', 'cache', 'backup', 'info', 'reset', 'snapshot', 'sync', 'sync-receive', 'syncclear', 'extensions'],
]);
break;
}
@@ -2042,6 +2087,398 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
}
}
+ /**
+ * Bulk-clear content on this site before a sync push.
+ *
+ * POST /?mokowaas=syncclear
+ * Body: {"token": "...", "types": ["articles", "categories", "menus", "modules"]}
+ *
+ * Deletes content directly via DB for speed — avoids the per-item
+ * Joomla API DELETE bottleneck.
+ *
+ * @return void
+ *
+ * @since 02.31.00
+ */
+ protected function handleSyncClearAction()
+ {
+ if ($this->app->input->getMethod() !== 'POST')
+ {
+ $this->sendHealthResponse(405, ['error' => 'POST required']);
+
+ return;
+ }
+
+ $payload = json_decode(file_get_contents('php://input'), true);
+ $token = $payload['token'] ?? '';
+
+ // Authenticate with health API token
+ $expectedToken = $this->params->get('health_api_token', '');
+
+ if (empty($expectedToken) || !hash_equals($expectedToken, $token))
+ {
+ $this->sendHealthResponse(401, ['error' => 'Invalid token']);
+
+ return;
+ }
+
+ $types = $payload['types'] ?? [];
+ $cleared = [];
+ $db = Factory::getDbo();
+
+ try
+ {
+ if (\in_array('articles', $types, true))
+ {
+ $db->setQuery('DELETE FROM ' . $db->quoteName('#__content'))->execute();
+ $cleared[] = 'articles:' . $db->getAffectedRows();
+ }
+
+ if (\in_array('categories', $types, true))
+ {
+ // Delete non-root content categories
+ $db->setQuery(
+ $db->getQuery(true)
+ ->delete($db->quoteName('#__categories'))
+ ->where($db->quoteName('extension') . ' = ' . $db->quote('com_content'))
+ ->where($db->quoteName('id') . ' > 1')
+ )->execute();
+ $cleared[] = 'categories:' . $db->getAffectedRows();
+ }
+
+ if (\in_array('menus', $types, true))
+ {
+ // Delete non-root site menu items
+ $db->setQuery(
+ $db->getQuery(true)
+ ->delete($db->quoteName('#__menu'))
+ ->where($db->quoteName('client_id') . ' = 0')
+ ->where($db->quoteName('id') . ' > 1')
+ )->execute();
+ $cleared[] = 'menus:' . $db->getAffectedRows();
+ }
+
+ if (\in_array('modules', $types, true))
+ {
+ $db->setQuery(
+ $db->getQuery(true)
+ ->delete($db->quoteName('#__modules'))
+ ->where($db->quoteName('client_id') . ' = 0')
+ )->execute();
+ $cleared[] = 'modules:' . $db->getAffectedRows();
+ }
+
+ $this->sendHealthResponse(200, [
+ 'status' => 'ok',
+ 'cleared' => $cleared,
+ ]);
+ }
+ catch (\Throwable $e)
+ {
+ $this->sendHealthResponse(500, [
+ 'error' => 'Sync clear failed',
+ 'message' => $e->getMessage(),
+ ]);
+ }
+ }
+
+ /**
+ * Receive bulk content and insert locally via Joomla's Table API.
+ *
+ * POST /?mokowaas=syncpush
+ * Body: {"token": "...", "type": "articles", "items": [{...}, ...]}
+ *
+ * @return void
+ *
+ * @since 02.31.00
+ */
+ protected function handleSyncPushAction()
+ {
+ if ($this->app->input->getMethod() !== 'POST')
+ {
+ $this->sendHealthResponse(405, ['error' => 'POST required']);
+
+ return;
+ }
+
+ $payload = json_decode(file_get_contents('php://input'), true);
+ $token = $payload['token'] ?? '';
+
+ $expectedToken = $this->params->get('health_api_token', '');
+
+ if (empty($expectedToken) || !hash_equals($expectedToken, $token))
+ {
+ $this->sendHealthResponse(401, ['error' => 'Invalid token']);
+
+ return;
+ }
+
+ $type = $payload['type'] ?? '';
+ $items = $payload['items'] ?? [];
+
+ if (empty($type) || empty($items))
+ {
+ $this->sendHealthResponse(400, ['error' => 'Missing type or items']);
+
+ return;
+ }
+
+ try
+ {
+ $db = Factory::getDbo();
+ $inserted = 0;
+ $now = Factory::getDate()->toSql();
+
+ switch ($type)
+ {
+ case 'articles':
+ foreach ($items as $item)
+ {
+ try
+ {
+ $record = (object) [
+ 'title' => $item['title'] ?? '',
+ 'alias' => $item['alias'] ?? '',
+ 'introtext' => $item['introtext'] ?? '',
+ 'fulltext' => $item['fulltext'] ?? '',
+ 'state' => (int) ($item['state'] ?? 1),
+ 'catid' => (int) ($item['catid'] ?? 2),
+ 'language' => $item['language'] ?? '*',
+ 'featured' => (int) ($item['featured'] ?? 0),
+ 'metadesc' => $item['metadesc'] ?? '',
+ 'metakey' => $item['metakey'] ?? '',
+ 'metadata' => $item['metadata'] ?? '{}',
+ 'created' => $item['created'] ?? $now,
+ 'modified' => $item['modified'] ?? $now,
+ 'publish_up' => $item['publish_up'] ?? $now,
+ 'images' => $item['images'] ?? '{}',
+ 'urls' => $item['urls'] ?? '{}',
+ 'attribs' => $item['attribs'] ?? '{}',
+ 'access' => (int) ($item['access'] ?? 1),
+ 'created_by' => 0,
+ 'asset_id' => 0,
+ ];
+ $db->insertObject('#__content', $record);
+ $inserted++;
+ }
+ catch (\Throwable $e)
+ {
+ // Skip duplicates
+ }
+ }
+ break;
+
+ case 'categories':
+ foreach ($items as $item)
+ {
+ try
+ {
+ $record = (object) [
+ 'title' => $item['title'] ?? '',
+ 'alias' => $item['alias'] ?? '',
+ 'description' => $item['description'] ?? '',
+ 'published' => (int) ($item['published'] ?? 1),
+ 'language' => $item['language'] ?? '*',
+ 'extension' => $item['extension'] ?? 'com_content',
+ 'access' => (int) ($item['access'] ?? 1),
+ 'params' => $item['params'] ?? '{}',
+ 'metadata' => $item['metadata'] ?? '{}',
+ 'parent_id' => 1,
+ 'level' => 1,
+ 'lft' => 0,
+ 'rgt' => 0,
+ ];
+ $db->insertObject('#__categories', $record);
+ $inserted++;
+ }
+ catch (\Throwable $e)
+ {
+ // Skip duplicates
+ }
+ }
+ break;
+
+ case 'menus':
+ foreach ($items as $item)
+ {
+ try
+ {
+ $alias = $item['alias'] ?? '';
+ $record = (object) [
+ 'title' => $item['title'] ?? '',
+ 'alias' => $alias,
+ 'path' => $item['path'] ?? $alias,
+ 'menutype' => $item['menutype'] ?? 'mainmenu',
+ 'type' => $item['type'] ?? 'component',
+ 'link' => $item['link'] ?? '',
+ 'language' => $item['language'] ?? '*',
+ 'published' => (int) ($item['published'] ?? 1),
+ 'home' => (int) ($item['home'] ?? 0),
+ 'params' => $item['params'] ?? '{}',
+ 'img' => $item['img'] ?? '',
+ 'access' => (int) ($item['access'] ?? 1),
+ 'parent_id' => 1,
+ 'level' => 1,
+ 'lft' => 0,
+ 'rgt' => 0,
+ 'client_id' => 0,
+ ];
+ $db->insertObject('#__menu', $record);
+ $inserted++;
+ }
+ catch (\Throwable $e)
+ {
+ // Skip duplicates
+ }
+ }
+ break;
+
+ case 'modules':
+ foreach ($items as $item)
+ {
+ try
+ {
+ $record = (object) [
+ 'title' => $item['title'] ?? '',
+ 'module' => $item['module'] ?? '',
+ 'position' => $item['position'] ?? '',
+ 'params' => $item['params'] ?? '{}',
+ 'language' => $item['language'] ?? '*',
+ 'published' => (int) ($item['published'] ?? 1),
+ 'access' => (int) ($item['access'] ?? 1),
+ 'ordering' => (int) ($item['ordering'] ?? 0),
+ 'showtitle' => (int) ($item['showtitle'] ?? 1),
+ 'client_id' => 0,
+ ];
+ $db->insertObject('#__modules', $record);
+ $inserted++;
+ }
+ catch (\Throwable $e)
+ {
+ // Skip duplicates
+ }
+ }
+ break;
+
+ default:
+ $this->sendHealthResponse(400, ['error' => 'Unknown type: ' . $type]);
+
+ return;
+ }
+
+ // Rebuild nested set trees and asset table after insert
+ $this->repairAfterSync($type);
+
+ $this->sendHealthResponse(200, [
+ 'status' => 'ok',
+ 'type' => $type,
+ 'inserted' => $inserted,
+ ]);
+ }
+ catch (\Throwable $e)
+ {
+ $this->sendHealthResponse(500, [
+ 'error' => 'Sync push failed',
+ 'message' => $e->getMessage(),
+ ]);
+ }
+ }
+
+ /**
+ * Repair nested set trees and asset table after a bulk sync push.
+ *
+ * Categories and menus use nested sets (lft/rgt/level) which need
+ * rebuilding after direct DB inserts. Content needs asset entries
+ * for ACL to work.
+ *
+ * @param string $type Content type that was pushed
+ *
+ * @return void
+ *
+ * @since 02.31.00
+ */
+ private function repairAfterSync(string $type): void
+ {
+ try
+ {
+ $db = Factory::getDbo();
+
+ if ($type === 'categories')
+ {
+ // Rebuild the category nested set tree
+ $table = new \Joomla\CMS\Table\Category($db);
+ $table->rebuild();
+
+ // Ensure asset entries exist for each category
+ $db->setQuery(
+ $db->getQuery(true)
+ ->select('id, title, extension')
+ ->from($db->quoteName('#__categories'))
+ ->where($db->quoteName('id') . ' > 1')
+ ->where($db->quoteName('asset_id') . ' = 0')
+ );
+
+ foreach ($db->loadObjectList() as $cat)
+ {
+ $asset = new \Joomla\CMS\Table\Asset($db);
+ $asset->name = $cat->extension . '.category.' . $cat->id;
+ $asset->title = $cat->title;
+ $asset->rules = '{}';
+
+ // Parent asset = root
+ $asset->setLocation(1, 'last-child');
+ $asset->store();
+
+ $db->setQuery(
+ $db->getQuery(true)
+ ->update($db->quoteName('#__categories'))
+ ->set($db->quoteName('asset_id') . ' = ' . (int) $asset->id)
+ ->where($db->quoteName('id') . ' = ' . (int) $cat->id)
+ )->execute();
+ }
+ }
+
+ if ($type === 'articles')
+ {
+ // Ensure asset entries exist for each article
+ $db->setQuery(
+ $db->getQuery(true)
+ ->select('id, title, catid')
+ ->from($db->quoteName('#__content'))
+ ->where($db->quoteName('asset_id') . ' = 0')
+ );
+
+ foreach ($db->loadObjectList() as $article)
+ {
+ $asset = new \Joomla\CMS\Table\Asset($db);
+ $asset->name = 'com_content.article.' . $article->id;
+ $asset->title = $article->title;
+ $asset->rules = '{}';
+ $asset->setLocation(1, 'last-child');
+ $asset->store();
+
+ $db->setQuery(
+ $db->getQuery(true)
+ ->update($db->quoteName('#__content'))
+ ->set($db->quoteName('asset_id') . ' = ' . (int) $asset->id)
+ ->where($db->quoteName('id') . ' = ' . (int) $article->id)
+ )->execute();
+ }
+ }
+
+ if ($type === 'menus')
+ {
+ // Rebuild menu nested set tree
+ $table = new \Joomla\CMS\Table\Menu($db);
+ $table->rebuild();
+ }
+ }
+ catch (\Throwable $e)
+ {
+ Log::add('Asset repair failed for ' . $type . ': ' . $e->getMessage(), Log::WARNING, 'mokowaas');
+ }
+ }
+
/**
* List installed extensions with version, status, and update server info.
*
@@ -2366,7 +2803,7 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
'articles' => $articles,
'users' => $users,
'extensions' => $extensions,
- 'brand' => $this->params->get('brand_name', 'MokoWaaS'),
+ 'brand' => self::BRAND_NAME,
'plugin_version' => $this->getPluginVersion(),
]);
}
@@ -2520,7 +2957,7 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
$config = Factory::getConfig();
return [
- 'brand' => $this->params->get('brand_name', 'MokoWaaS'),
+ 'brand' => self::BRAND_NAME,
'plugin_version' => $this->getPluginVersion(),
'joomla_version' => JVERSION,
'php_version' => PHP_VERSION,
@@ -3720,7 +4157,6 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
*/
protected function getPrimaryHost(): string
{
- // Try plugin's primary_domain setting first
$primaryDomain = $this->params->get('primary_domain', '');
if (!empty($primaryDomain))
@@ -3728,7 +4164,7 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
return trim($primaryDomain);
}
- // Try Joomla's $live_site
+ // Fallback: Joomla's $live_site
$liveSite = Factory::getConfig()->get('live_site', '');
if (!empty($liveSite))
@@ -3741,47 +4177,36 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
}
}
- // Fallback: if current host is NOT in the aliases list, it's the primary
+ return parse_url(Uri::root(), PHP_URL_HOST) ?: ($_SERVER['HTTP_HOST'] ?? '');
+ }
+
+ /**
+ * Get the dev alias domain (dev.{primary_domain}).
+ *
+ * @return string
+ *
+ * @since 02.31.00
+ */
+ protected function getDevAliasDomain(): string
+ {
+ $primary = $this->getPrimaryHost();
+
+ return !empty($primary) ? 'dev.' . $primary : '';
+ }
+
+ /**
+ * Check if the current request is on the dev alias domain.
+ *
+ * @return bool
+ *
+ * @since 02.31.00
+ */
+ protected function isDevAlias(): bool
+ {
$currentHost = $_SERVER['HTTP_HOST'] ?? '';
- $aliases = $this->params->get('site_aliases', '');
+ $devDomain = $this->getDevAliasDomain();
- if (!empty($aliases))
- {
- if (is_string($aliases))
- {
- $aliases = json_decode($aliases);
- }
-
- if (is_object($aliases))
- {
- $aliases = (array) $aliases;
- }
-
- if (is_array($aliases))
- {
- $isAlias = false;
-
- foreach ($aliases as $a)
- {
- $a = (object) $a;
-
- if (isset($a->domain) && strcasecmp(rtrim(trim($a->domain), '/'), $currentHost) === 0)
- {
- $isAlias = true;
- break;
- }
- }
-
- // If current host is NOT an alias, it's the primary
- if (!$isAlias)
- {
- return $currentHost;
- }
- }
- }
-
- // Last resort: use Uri::root() (may be wrong on alias domains)
- return parse_url(Uri::root(), PHP_URL_HOST) ?: $currentHost;
+ return !empty($devDomain) && strcasecmp($currentHost, $devDomain) === 0;
}
protected function getCurrentAlias()
@@ -3793,6 +4218,29 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
return null;
}
+ // The only alias is dev.{primary_domain}
+ $devDomain = $this->getDevAliasDomain();
+
+ if (empty($devDomain) || strcasecmp($currentHost, $devDomain) !== 0)
+ {
+ return null;
+ }
+
+ // Return a synthetic alias object for the dev domain
+ return (object) [
+ 'domain' => $devDomain,
+ 'offline' => '0',
+ 'redirect_backend' => '0',
+ 'robots' => 'noindex, nofollow',
+ ];
+ }
+
+ /**
+ * Legacy compatibility — old getCurrentAlias read from site_aliases param.
+ * Now only returns the hardcoded dev.* alias.
+ */
+ private function getCurrentAliasLegacy()
+ {
$aliases = $this->params->get('site_aliases', '');
if (empty($aliases))
@@ -3843,54 +4291,13 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
*/
protected function handleSiteAlias()
{
- $alias = $this->getCurrentAlias();
-
- if ($alias === null)
+ // The dev alias (dev.{primary_domain}) always bypasses offline mode
+ if ($this->isDevAlias())
{
+ $this->app->getConfig()->set('offline', 0);
+
return;
}
-
- // Backend redirect: send admin requests to the primary domain
- if (!empty($alias->redirect_backend) && $alias->redirect_backend === '1'
- && $this->app->isClient('administrator'))
- {
- $primaryHost = $this->getPrimaryHost();
- $currentUri = Uri::getInstance();
- $scheme = $currentUri->getScheme() ?: 'https';
- $primaryUrl = $scheme . '://' . $primaryHost . $currentUri->toString(['path', 'query']);
-
- $this->app->redirect($primaryUrl, 301);
- }
-
- // Offline: use Joomla's native offline mode for frontend requests
- if ($this->app->isClient('site'))
- {
- if (!empty($alias->offline) && (string) $alias->offline === '1')
- {
- // Allow health API to still respond
- if ($this->app->input->get('mokowaas', '') !== '')
- {
- return;
- }
-
- // Set custom offline message if provided
- $message = $alias->offline_message ?? '';
-
- if (!empty($message))
- {
- $this->app->getConfig()->set('offline_message', $message);
- }
-
- // Enable Joomla's native offline mode
- $this->app->getConfig()->set('offline', 1);
- }
- else
- {
- // Alias is NOT offline — override Joomla's global offline setting
- // This allows access via the alias domain even when the main site is offline
- $this->app->getConfig()->set('offline', 0);
- }
- }
}
/**
@@ -3904,18 +4311,10 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
*/
protected function injectAliasRobots($doc)
{
- $alias = $this->getCurrentAlias();
-
- if ($alias === null)
+ // Always noindex/nofollow on the dev alias domain
+ if ($this->isDevAlias())
{
- return;
- }
-
- $robots = $alias->robots ?? 'index, follow';
-
- if ($robots !== 'index, follow')
- {
- $doc->setMetaData('robots', $robots);
+ $doc->setMetaData('robots', 'noindex, nofollow');
}
// Inject canonical URL pointing to the primary domain
@@ -3941,7 +4340,7 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
*
* @return void
*
- * @since 02.30.00
+ * @since 02.31.00
*/
protected function warnMissingLicenseKey(): void
{
@@ -4181,10 +4580,17 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
* @since 02.01.08
*/
/**
- * Disable caching when development mode is active.
+ * Enforce development mode settings.
*
- * Sets the Joomla caching config to 0 at runtime so no page
- * or component cache is used. Does not modify configuration.php.
+ * When dev mode is ON:
+ * - Disable Joomla caching
+ * - Enable Joomla debug mode (Global Config)
+ * - Enable MokoOnyx template debug
+ * - Disable article hit recording
+ *
+ * When dev mode is OFF (and was previously on):
+ * - Reset all content version history
+ * - Reset article published dates to now
*
* @return void
*
@@ -4197,8 +4603,131 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
return;
}
+ // Disable caching
$config = Factory::getConfig();
$config->set('caching', 0);
+
+ // Enable Joomla debug
+ $config->set('debug', 1);
+
+ // Enable MokoOnyx template debug
+ $this->setTemplateParam('mokoonyx', 'debug', 1);
+
+ // Show offline page on primary domain only — site aliases
+ // and dev.* subdomains bypass offline mode for development
+ $currentHost = $_SERVER['HTTP_HOST'] ?? '';
+ $primaryDomain = $this->params->get('primary_domain', '');
+
+ if (!empty($primaryDomain) && $currentHost === $primaryDomain)
+ {
+ $config->set('offline', 1);
+ }
+
+ // Suppress hit recording
+ try
+ {
+ $db = Factory::getDbo();
+ $db->setQuery(
+ $db->getQuery(true)
+ ->update($db->quoteName('#__content'))
+ ->set($db->quoteName('hits') . ' = 0')
+ ->where($db->quoteName('hits') . ' > 0')
+ )->execute();
+ }
+ catch (\Throwable $e)
+ {
+ // Silent
+ }
+ }
+
+ /**
+ * Actions to run when dev mode is turned off.
+ *
+ * Resets content versions and hits, disables debug.
+ *
+ * @return void
+ *
+ * @since 02.31.00
+ */
+ protected function onDevModeDisabled(): void
+ {
+ try
+ {
+ $db = Factory::getDbo();
+
+ // Delete all content version history
+ $db->setQuery(
+ $db->getQuery(true)->delete($db->quoteName('#__history'))
+ )->execute();
+
+ // Reset hits
+ $db->setQuery(
+ $db->getQuery(true)
+ ->update($db->quoteName('#__content'))
+ ->set($db->quoteName('hits') . ' = 0')
+ )->execute();
+
+ // Disable debug
+ $this->setTemplateParam('mokoonyx', 'debug', 0);
+
+ // Take site back online
+ Factory::getConfig()->set('offline', 0);
+
+ $this->app->enqueueMessage(
+ 'Development mode disabled — versions cleared, hits reset, debug off, site online.',
+ 'message'
+ );
+ }
+ catch (\Throwable $e)
+ {
+ Log::add('Dev mode cleanup failed: ' . $e->getMessage(), Log::WARNING, 'mokowaas');
+ }
+ }
+
+ /**
+ * Set a parameter on a template style.
+ *
+ * @param string $template Template element name
+ * @param string $key Parameter key
+ * @param mixed $value Parameter value
+ *
+ * @return void
+ *
+ * @since 02.31.00
+ */
+ private function setTemplateParam(string $template, string $key, $value): void
+ {
+ try
+ {
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true)
+ ->select([$db->quoteName('id'), $db->quoteName('params')])
+ ->from($db->quoteName('#__template_styles'))
+ ->where($db->quoteName('template') . ' = ' . $db->quote($template));
+ $db->setQuery($query);
+ $styles = $db->loadObjectList();
+
+ foreach ($styles as $style)
+ {
+ $params = new \Joomla\Registry\Registry($style->params ?: '{}');
+
+ if ($params->get($key) != $value)
+ {
+ $params->set($key, $value);
+
+ $db->setQuery(
+ $db->getQuery(true)
+ ->update($db->quoteName('#__template_styles'))
+ ->set($db->quoteName('params') . ' = ' . $db->quote($params->toString()))
+ ->where($db->quoteName('id') . ' = ' . (int) $style->id)
+ )->execute();
+ }
+ }
+ }
+ catch (\Throwable $e)
+ {
+ // Silent
+ }
}
protected function enforceHttps()
@@ -4688,10 +5217,10 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
'emptyLoginLogoAlt' => '1',
];
- // Color params — map plugin fields to Atum template params
- $primary = $this->params->get('color_primary', '');
- $sidebar = $this->params->get('color_sidebar', '');
- $link = $this->params->get('color_link', '');
+ // Hardcoded color scheme
+ $primary = self::COLOR_PRIMARY;
+ $sidebar = self::COLOR_SIDEBAR;
+ $link = self::COLOR_LINK;
if (!empty($primary))
{
diff --git a/src/packages/plg_system_mokowaas/Field/AllowedIpsField.php b/src/packages/plg_system_mokowaas/Field/AllowedIpsField.php
index d1f97ff..23666eb 100644
--- a/src/packages/plg_system_mokowaas/Field/AllowedIpsField.php
+++ b/src/packages/plg_system_mokowaas/Field/AllowedIpsField.php
@@ -7,7 +7,7 @@
* FILE INFORMATION
* DEFGROUP: Joomla.Plugin
* INGROUP: MokoWaaS
- * VERSION: 02.30.00
+ * VERSION: 02.31.00
* PATH: /src/Field/AllowedIpsField.php
* BRIEF: Custom form field that displays the current IP whitelist
*/
diff --git a/src/packages/plg_system_mokowaas/Field/CopyableTokenField.php b/src/packages/plg_system_mokowaas/Field/CopyableTokenField.php
index 7b6ac34..8a39d41 100644
--- a/src/packages/plg_system_mokowaas/Field/CopyableTokenField.php
+++ b/src/packages/plg_system_mokowaas/Field/CopyableTokenField.php
@@ -8,7 +8,7 @@
* FILE INFORMATION
* DEFGROUP: Joomla.Plugin
* INGROUP: MokoWaaS
- * VERSION: 02.30.00
+ * VERSION: 02.31.00
* PATH: /src/Field/CopyableTokenField.php
* BRIEF: Read-only token field with a copy-to-clipboard button
*/
diff --git a/src/packages/plg_system_mokowaas/Field/CurrentIpField.php b/src/packages/plg_system_mokowaas/Field/CurrentIpField.php
index 4e195a2..91ceec1 100644
--- a/src/packages/plg_system_mokowaas/Field/CurrentIpField.php
+++ b/src/packages/plg_system_mokowaas/Field/CurrentIpField.php
@@ -7,7 +7,7 @@
* FILE INFORMATION
* DEFGROUP: Joomla.Plugin
* INGROUP: MokoWaaS
- * VERSION: 02.30.00
+ * VERSION: 02.31.00
* PATH: /src/Field/CurrentIpField.php
* BRIEF: Read-only field that displays the current user's IP address
*/
diff --git a/src/packages/plg_system_mokowaas/Field/DemoTaskInfoField.php b/src/packages/plg_system_mokowaas/Field/DemoTaskInfoField.php
index 2d3727e..49a8298 100644
--- a/src/packages/plg_system_mokowaas/Field/DemoTaskInfoField.php
+++ b/src/packages/plg_system_mokowaas/Field/DemoTaskInfoField.php
@@ -8,7 +8,7 @@
* FILE INFORMATION
* DEFGROUP: Joomla.Plugin
* INGROUP: MokoWaaS
- * VERSION: 02.30.00
+ * VERSION: 02.31.00
* PATH: /src/Field/DemoTaskInfoField.php
* BRIEF: Read-only field showing scheduled task info with link to manage it
*/
diff --git a/src/packages/plg_system_mokowaas/Field/NextResetField.php b/src/packages/plg_system_mokowaas/Field/NextResetField.php
index 36c578e..446ed78 100644
--- a/src/packages/plg_system_mokowaas/Field/NextResetField.php
+++ b/src/packages/plg_system_mokowaas/Field/NextResetField.php
@@ -8,7 +8,7 @@
* FILE INFORMATION
* DEFGROUP: Joomla.Plugin
* INGROUP: MokoWaaS
- * VERSION: 02.30.00
+ * VERSION: 02.31.00
* PATH: /src/Field/NextResetField.php
* BRIEF: Read-only field showing next reset time from Joomla scheduled task
*/
diff --git a/src/packages/plg_system_mokowaas/Field/SnapshotTablesField.php b/src/packages/plg_system_mokowaas/Field/SnapshotTablesField.php
index af78e12..dacf0ff 100644
--- a/src/packages/plg_system_mokowaas/Field/SnapshotTablesField.php
+++ b/src/packages/plg_system_mokowaas/Field/SnapshotTablesField.php
@@ -8,7 +8,7 @@
* FILE INFORMATION
* DEFGROUP: Joomla.Plugin
* INGROUP: MokoWaaS
- * VERSION: 02.30.00
+ * VERSION: 02.31.00
* PATH: /src/Field/SnapshotTablesField.php
* BRIEF: Multi-select list field that loads DB tables with sensible defaults
*/
diff --git a/src/packages/plg_system_mokowaas/Service/ContentSyncReceiver.php b/src/packages/plg_system_mokowaas/Service/ContentSyncReceiver.php
index 1da92bc..70c61d6 100644
--- a/src/packages/plg_system_mokowaas/Service/ContentSyncReceiver.php
+++ b/src/packages/plg_system_mokowaas/Service/ContentSyncReceiver.php
@@ -10,7 +10,7 @@
* INGROUP: MokoWaaS
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS
* PATH: /src/packages/plg_system_mokowaas/Service/ContentSyncReceiver.php
- * VERSION: 02.30.00
+ * VERSION: 02.31.00
* BRIEF: Receiver-side content sync — applies incoming payload to local DB
*/
diff --git a/src/packages/plg_system_mokowaas/Service/ContentSyncService.php b/src/packages/plg_system_mokowaas/Service/ContentSyncService.php
index ac63614..8d0929a 100644
--- a/src/packages/plg_system_mokowaas/Service/ContentSyncService.php
+++ b/src/packages/plg_system_mokowaas/Service/ContentSyncService.php
@@ -10,7 +10,7 @@
* INGROUP: MokoWaaS
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS
* PATH: /src/packages/plg_system_mokowaas/Service/ContentSyncService.php
- * VERSION: 02.30.00
+ * VERSION: 02.31.00
* BRIEF: Sender-side content sync — builds payload and pushes to remote sites
*/
diff --git a/src/packages/plg_system_mokowaas/Service/DemoResetService.php b/src/packages/plg_system_mokowaas/Service/DemoResetService.php
index aabdb54..a32b7d7 100644
--- a/src/packages/plg_system_mokowaas/Service/DemoResetService.php
+++ b/src/packages/plg_system_mokowaas/Service/DemoResetService.php
@@ -10,7 +10,7 @@
* INGROUP: MokoWaaS
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS
* PATH: /src/packages/plg_system_mokowaas/Service/DemoResetService.php
- * VERSION: 02.30.00
+ * VERSION: 02.31.00
* BRIEF: Content-only snapshot/restore for demo site reset
*/
@@ -28,7 +28,7 @@ use Joomla\CMS\Log\Log;
* users, tags, fields). Never touches extensions, assets, sessions,
* schemas, update sites, or any system tables.
*
- * @since 02.30.00
+ * @since 02.31.00
*/
class DemoResetService
{
diff --git a/src/packages/plg_system_mokowaas/mokowaas.xml b/src/packages/plg_system_mokowaas/mokowaas.xml
index 55df8a4..920104c 100644
--- a/src/packages/plg_system_mokowaas/mokowaas.xml
+++ b/src/packages/plg_system_mokowaas/mokowaas.xml
@@ -16,7 +16,7 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoWaaS
REPO: https://github.com/mokoconsulting-tech/mokowaas
- VERSION: 02.30.00
+ VERSION: 02.31.00
PATH: /src/mokowaas.xml
BRIEF: Plugin manifest for MokoWaaS system plugin
NOTE: Defines installation metadata, files, and configuration for Joomla
@@ -30,8 +30,8 @@
GNU General Public License version 3 or later; see LICENSE.md
hello@mokoconsulting.tech
https://mokoconsulting.tech
- 02.30.00
- 02.30.00
+ 02.31.00
+ 02.31.00
This plugin rebrands the Joomla system interface with MokoWaaS identity. It applies language overrides and ensures consistent branding across the platform.
Moko\Plugin\System\MokoWaaS
script.php
@@ -73,85 +73,19 @@
-
+
-
-
-
-
-
-
-