chore: 02.31.00 release #109
@@ -9,7 +9,7 @@
|
||||
<display-name>Package - MokoWaaS</display-name>
|
||||
<org>MokoConsulting</org>
|
||||
<description>White-label identity, security hardening, and tenant restriction layer for WaaS-managed Joomla environments</description>
|
||||
<version>02.30.00</version>
|
||||
<version>02.31.00</version>
|
||||
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
|
||||
</identity>
|
||||
<governance>
|
||||
|
||||
+23
-6
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
-->
|
||||
|
||||
+1
-1
@@ -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
|
||||
-->
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -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
|
||||
-->
|
||||
|
||||
+1
-1
@@ -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
|
||||
-->
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
-->
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.30.00</version>
|
||||
<version>02.30.00</version>
|
||||
<version>02.31.00</version>
|
||||
<version>02.31.00</version>
|
||||
<description>Minimal API-only component for MokoWaaS. Provides REST endpoints for site health, cache, updates, and backups.</description>
|
||||
<namespace path="api/src">Moko\Component\MokoWaaS\Api</namespace>
|
||||
<administration>
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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 @@
|
||||
<license>GNU General Public License version 3 or later; see LICENSE.md</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.30.00</version>
|
||||
<version>02.30.00</version>
|
||||
<version>02.31.00</version>
|
||||
<version>02.31.00</version>
|
||||
<description>This plugin rebrands the Joomla system interface with MokoWaaS identity. It applies language overrides and ensures consistent branding across the platform.</description>
|
||||
<namespace path=".">Moko\Plugin\System\MokoWaaS</namespace>
|
||||
<scriptfile>script.php</scriptfile>
|
||||
@@ -73,85 +73,19 @@
|
||||
</administration>
|
||||
|
||||
<config>
|
||||
<fields name="params">
|
||||
<fields name="params"
|
||||
addfieldprefix="Moko\Plugin\System\MokoWaaS\Field"
|
||||
>
|
||||
<fieldset name="basic">
|
||||
<field
|
||||
name="enable_branding"
|
||||
type="radio"
|
||||
label="PLG_SYSTEM_MOKOWAAS_ENABLE_BRANDING_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_ENABLE_BRANDING_DESC"
|
||||
default="1"
|
||||
class="btn-group btn-group-yesno"
|
||||
>
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
<field
|
||||
name="brand_name"
|
||||
type="text"
|
||||
label="PLG_SYSTEM_MOKOWAAS_BRAND_NAME_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_BRAND_NAME_DESC"
|
||||
default="MokoWaaS"
|
||||
name="health_api_token"
|
||||
type="CopyableToken"
|
||||
label="PLG_SYSTEM_MOKOWAAS_HEALTH_TOKEN_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_HEALTH_TOKEN_DESC"
|
||||
default=""
|
||||
filter="raw"
|
||||
readonly="true"
|
||||
/>
|
||||
<field
|
||||
name="company_name"
|
||||
type="text"
|
||||
label="PLG_SYSTEM_MOKOWAAS_COMPANY_NAME_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_COMPANY_NAME_DESC"
|
||||
default="Moko Consulting"
|
||||
/>
|
||||
<field
|
||||
name="support_url"
|
||||
type="url"
|
||||
label="PLG_SYSTEM_MOKOWAAS_SUPPORT_URL_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_SUPPORT_URL_DESC"
|
||||
default="https://mokoconsulting.tech/support"
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset name="waas_access"
|
||||
label="PLG_SYSTEM_MOKOWAAS_FIELDSET_WAAS_ACCESS_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_FIELDSET_WAAS_ACCESS_DESC"
|
||||
addfieldprefix="Moko\Plugin\System\MokoWaaS\Field"
|
||||
>
|
||||
<field
|
||||
name="enforce_master_user"
|
||||
type="radio"
|
||||
label="PLG_SYSTEM_MOKOWAAS_ENFORCE_MASTER_USER_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_ENFORCE_MASTER_USER_DESC"
|
||||
default="1"
|
||||
class="btn-group btn-group-yesno"
|
||||
>
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
<field
|
||||
name="master_email"
|
||||
type="email"
|
||||
label="PLG_SYSTEM_MOKOWAAS_MASTER_EMAIL_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_MASTER_EMAIL_DESC"
|
||||
default="webmaster@mokoconsulting.tech"
|
||||
/>
|
||||
<field
|
||||
name="emergency_access"
|
||||
type="radio"
|
||||
label="PLG_SYSTEM_MOKOWAAS_EMERGENCY_ACCESS_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_EMERGENCY_ACCESS_DESC"
|
||||
default="1"
|
||||
class="btn-group btn-group-yesno"
|
||||
>
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
<field
|
||||
name="allowed_ips_display"
|
||||
type="AllowedIps"
|
||||
label=""
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset name="maintenance"
|
||||
label="PLG_SYSTEM_MOKOWAAS_FIELDSET_MAINTENANCE_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_FIELDSET_MAINTENANCE_DESC"
|
||||
>
|
||||
<field name="dev_mode" type="radio" default="0"
|
||||
label="PLG_SYSTEM_MOKOWAAS_DEV_MODE_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_DEV_MODE_DESC"
|
||||
@@ -182,39 +116,6 @@
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
</fieldset>
|
||||
<fieldset name="visual_branding"
|
||||
label="PLG_SYSTEM_MOKOWAAS_FIELDSET_VISUAL_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_FIELDSET_VISUAL_DESC"
|
||||
>
|
||||
<field name="branding_note" type="note"
|
||||
label="PLG_SYSTEM_MOKOWAAS_BRANDING_NOTE_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_BRANDING_NOTE_DESC"
|
||||
class="alert alert-info" />
|
||||
<field name="color_primary" type="color"
|
||||
label="PLG_SYSTEM_MOKOWAAS_COLOR_PRIMARY_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_COLOR_PRIMARY_DESC"
|
||||
default="#1a2744" />
|
||||
<field name="color_sidebar" type="color"
|
||||
label="PLG_SYSTEM_MOKOWAAS_COLOR_SIDEBAR_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_COLOR_SIDEBAR_DESC"
|
||||
default="#0f1b2d" />
|
||||
<field name="color_header" type="color"
|
||||
label="PLG_SYSTEM_MOKOWAAS_COLOR_HEADER_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_COLOR_HEADER_DESC"
|
||||
default="#1a2744" />
|
||||
<field name="color_link" type="color"
|
||||
label="PLG_SYSTEM_MOKOWAAS_COLOR_LINK_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_COLOR_LINK_DESC"
|
||||
default="#0051ad" />
|
||||
<field name="brand_icon" type="text"
|
||||
label="PLG_SYSTEM_MOKOWAAS_BRAND_ICON_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_BRAND_ICON_DESC"
|
||||
default="" hint="e.g. f6d5 (FontAwesome unicode)" />
|
||||
<field name="custom_css" type="textarea"
|
||||
label="PLG_SYSTEM_MOKOWAAS_CUSTOM_CSS_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_CUSTOM_CSS_DESC"
|
||||
rows="10" filter="raw" />
|
||||
</fieldset>
|
||||
<fieldset name="tenant_restrictions"
|
||||
label="PLG_SYSTEM_MOKOWAAS_FIELDSET_TENANT_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_FIELDSET_TENANT_DESC"
|
||||
@@ -276,73 +177,27 @@
|
||||
label="PLG_SYSTEM_MOKOWAAS_DEMO_TASK_INFO_LABEL"
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset name="site_aliases"
|
||||
label="PLG_SYSTEM_MOKOWAAS_FIELDSET_ALIASES_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_FIELDSET_ALIASES_DESC"
|
||||
>
|
||||
<field
|
||||
name="primary_domain"
|
||||
type="text"
|
||||
label="PLG_SYSTEM_MOKOWAAS_PRIMARY_DOMAIN_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_PRIMARY_DOMAIN_DESC"
|
||||
default=""
|
||||
hint="e.g. waas.dev.mokoconsulting.tech"
|
||||
/>
|
||||
<field
|
||||
name="site_aliases"
|
||||
type="subform"
|
||||
label="PLG_SYSTEM_MOKOWAAS_SITE_ALIASES_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_SITE_ALIASES_DESC"
|
||||
formsource="plugins/system/mokowaas/forms/alias_entry.xml"
|
||||
multiple="true"
|
||||
layout="joomla.form.field.subform.repeatable-table"
|
||||
groupByFieldset="false"
|
||||
buttons="add,remove,move"
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset name="content_sync"
|
||||
label="PLG_SYSTEM_MOKOWAAS_FIELDSET_SYNC_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_FIELDSET_SYNC_DESC"
|
||||
>
|
||||
<field
|
||||
name="sync_targets"
|
||||
type="subform"
|
||||
label="PLG_SYSTEM_MOKOWAAS_SYNC_TARGETS_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_SYNC_TARGETS_DESC"
|
||||
formsource="plugins/system/mokowaas/forms/sync_target_entry.xml"
|
||||
multiple="true"
|
||||
layout="joomla.form.field.subform.repeatable-table"
|
||||
groupByFieldset="false"
|
||||
buttons="add,remove,move"
|
||||
/>
|
||||
<field name="sync_push_now" type="radio" default="0"
|
||||
label="PLG_SYSTEM_MOKOWAAS_SYNC_PUSH_NOW_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_SYNC_PUSH_NOW_DESC"
|
||||
class="btn-group btn-group-yesno">
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
</fieldset>
|
||||
<fieldset name="diagnostics"
|
||||
label="PLG_SYSTEM_MOKOWAAS_FIELDSET_DIAGNOSTICS_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_FIELDSET_DIAGNOSTICS_DESC"
|
||||
addfieldprefix="Moko\Plugin\System\MokoWaaS\Field"
|
||||
>
|
||||
<field
|
||||
name="health_api_token"
|
||||
type="CopyableToken"
|
||||
label="PLG_SYSTEM_MOKOWAAS_HEALTH_TOKEN_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_HEALTH_TOKEN_DESC"
|
||||
default=""
|
||||
filter="raw"
|
||||
readonly="true"
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset name="security"
|
||||
<fieldset name="security"
|
||||
label="PLG_SYSTEM_MOKOWAAS_FIELDSET_SECURITY_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_FIELDSET_SECURITY_DESC"
|
||||
addfieldprefix="Moko\Plugin\System\MokoWaaS\Field"
|
||||
>
|
||||
<field
|
||||
name="emergency_access"
|
||||
type="radio"
|
||||
label="PLG_SYSTEM_MOKOWAAS_EMERGENCY_ACCESS_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_EMERGENCY_ACCESS_DESC"
|
||||
default="1"
|
||||
class="btn-group btn-group-yesno"
|
||||
>
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
<field
|
||||
name="allowed_ips_display"
|
||||
type="AllowedIps"
|
||||
label=""
|
||||
/>
|
||||
<field name="force_https" type="radio" default="1"
|
||||
label="PLG_SYSTEM_MOKOWAAS_FORCE_HTTPS_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_FORCE_HTTPS_DESC"
|
||||
|
||||
@@ -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/script.php
|
||||
* BRIEF: Installation script for MokoWaaS plugin
|
||||
* NOTE: Handles installation, update, and uninstallation tasks including language override deployment
|
||||
|
||||
@@ -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/services/provider.php
|
||||
* BRIEF: Service provider for dependency injection in Joomla 5.x
|
||||
* NOTE: Registers the plugin with Joomla's DI container
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
<license>GNU General Public License version 3 or later; see LICENSE</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.30.00</version>
|
||||
<version>02.30.00</version>
|
||||
<version>02.31.00</version>
|
||||
<version>02.31.00</version>
|
||||
<description>PLG_TASK_MOKOWAASDEMO_DESC</description>
|
||||
<namespace path="src">Moko\Plugin\Task\MokoWaaSDemo</namespace>
|
||||
|
||||
|
||||
@@ -1,8 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form>
|
||||
<fieldset name="sync_params" label="Content Sync Settings">
|
||||
<field name="sync_info" type="note"
|
||||
label="Sync Targets"
|
||||
description="Content sync targets are configured in the MokoWaaS system plugin settings (Content Sync tab). This task will push content to all configured targets on each execution." />
|
||||
</fieldset>
|
||||
<fields name="params">
|
||||
<fieldset name="task_params" label="Sync Target">
|
||||
<field name="target_url" type="text"
|
||||
label="Target Site URL"
|
||||
description="Base URL of the remote Joomla site to sync to."
|
||||
hint="https://demo.example.com" />
|
||||
<field name="health_token" type="text"
|
||||
label="Target Health Token"
|
||||
description="MokoWaaS health API token from the target site. Found in the target's MokoWaaS plugin config (Diagnostics tab)."
|
||||
hint="Health API token from target site" />
|
||||
<field name="sync_articles" type="radio" default="1"
|
||||
label="Sync Articles"
|
||||
description="Delete all articles on target, then push copies from this site."
|
||||
class="btn-group btn-group-yesno">
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
<field name="sync_categories" type="radio" default="1"
|
||||
label="Sync Categories"
|
||||
description="Push content categories to the target site."
|
||||
class="btn-group btn-group-yesno">
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
<field name="sync_menus" type="radio" default="1"
|
||||
label="Sync Menus"
|
||||
description="Delete all menu items on target, then push copies from this site."
|
||||
class="btn-group btn-group-yesno">
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
<field name="sync_modules" type="radio" default="0"
|
||||
label="Sync Modules"
|
||||
description="Push site modules to the target."
|
||||
class="btn-group btn-group-yesno">
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
</fieldset>
|
||||
</fields>
|
||||
</form>
|
||||
|
||||
@@ -3,6 +3,35 @@
|
||||
; SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
PLG_TASK_MOKOWAASSYNC="Task - MokoWaaS Content Sync"
|
||||
PLG_TASK_MOKOWAASSYNC_DESC="Scheduled task to push content (articles, categories, menus, modules) to remote MokoWaaS sites."
|
||||
PLG_TASK_MOKOWAASSYNC_DESC="Scheduled task to sync content to a remote MokoWaaS site via the Joomla API. Each task instance syncs to one target."
|
||||
PLG_TASK_MOKOWAASSYNC_SYNC_TITLE="MokoWaaS Content Sync"
|
||||
PLG_TASK_MOKOWAASSYNC_SYNC_DESC="Push site content to all configured sync targets. Targets are configured in the MokoWaaS system plugin settings."
|
||||
PLG_TASK_MOKOWAASSYNC_SYNC_DESC="Sync selected content types to a single remote target site via the Joomla REST API."
|
||||
|
||||
; ===== Target fieldset =====
|
||||
PLG_TASK_MOKOWAASSYNC_FIELDSET_TARGET="Sync Target"
|
||||
PLG_TASK_MOKOWAASSYNC_TARGET_URL_LABEL="Target Site URL"
|
||||
PLG_TASK_MOKOWAASSYNC_TARGET_URL_DESC="Base URL of the remote Joomla site to sync to (e.g. https://demo.example.com)."
|
||||
PLG_TASK_MOKOWAASSYNC_API_TOKEN_LABEL="API Token"
|
||||
PLG_TASK_MOKOWAASSYNC_API_TOKEN_DESC="Joomla API token (Bearer token) for authenticating with the target site's REST API."
|
||||
PLG_TASK_MOKOWAASSYNC_API_USER_LABEL="API User"
|
||||
PLG_TASK_MOKOWAASSYNC_API_USER_DESC="Optional username on the target site. Used for logging purposes only."
|
||||
|
||||
; ===== Content types fieldset =====
|
||||
PLG_TASK_MOKOWAASSYNC_FIELDSET_CONTENT="Content to Sync"
|
||||
PLG_TASK_MOKOWAASSYNC_SYNC_ARTICLES_LABEL="Articles"
|
||||
PLG_TASK_MOKOWAASSYNC_SYNC_ARTICLES_DESC="Sync articles (com_content). Deletes all articles on the target, then pushes exact copies from this site."
|
||||
PLG_TASK_MOKOWAASSYNC_SYNC_CATEGORIES_LABEL="Categories"
|
||||
PLG_TASK_MOKOWAASSYNC_SYNC_CATEGORIES_DESC="Sync content categories. Ensures category structure matches this site."
|
||||
PLG_TASK_MOKOWAASSYNC_SYNC_MENUS_LABEL="Menus"
|
||||
PLG_TASK_MOKOWAASSYNC_SYNC_MENUS_DESC="Sync menu items. Deletes all menu items on the target, then pushes exact copies from this site."
|
||||
PLG_TASK_MOKOWAASSYNC_SYNC_MODULES_LABEL="Modules"
|
||||
PLG_TASK_MOKOWAASSYNC_SYNC_MODULES_DESC="Sync site modules. Pushes module configuration and assignments."
|
||||
|
||||
; ===== Files fieldset =====
|
||||
PLG_TASK_MOKOWAASSYNC_FIELDSET_FILES="Files to Sync"
|
||||
PLG_TASK_MOKOWAASSYNC_SYNC_IMAGES_LABEL="Images (/images/)"
|
||||
PLG_TASK_MOKOWAASSYNC_SYNC_IMAGES_DESC="Sync the /images/ directory to the target site."
|
||||
PLG_TASK_MOKOWAASSYNC_SYNC_FILES_LABEL="Files (/files/)"
|
||||
PLG_TASK_MOKOWAASSYNC_SYNC_FILES_DESC="Sync the /files/ directory to the target site."
|
||||
PLG_TASK_MOKOWAASSYNC_SYNC_MEDIA_LABEL="Media (/media/)"
|
||||
PLG_TASK_MOKOWAASSYNC_SYNC_MEDIA_DESC="Sync the /media/ directory to the target site. Be careful — this includes extension assets."
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<license>GNU General Public License version 3 or later; see LICENSE</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.30.00</version>
|
||||
<version>02.31.00</version>
|
||||
<description>PLG_TASK_MOKOWAASSYNC_DESC</description>
|
||||
<namespace path="src">Moko\Plugin\Task\MokoWaaSSync</namespace>
|
||||
|
||||
|
||||
@@ -20,16 +20,27 @@ use Joomla\Event\SubscriberInterface;
|
||||
/**
|
||||
* MokoWaaS Content Sync — Joomla Scheduled Task Plugin.
|
||||
*
|
||||
* Pushes site content (articles, categories, menus, modules) to
|
||||
* configured remote MokoWaaS sites on a schedule. Sync targets are
|
||||
* read from the system plugin params (content_sync fieldset).
|
||||
* Syncs selected content types to a single remote Joomla site via the
|
||||
* REST API. Each task instance has its own target URL, API token, and
|
||||
* content type toggles — so multiple targets can sync independently
|
||||
* on different schedules.
|
||||
*
|
||||
* @since 02.27.00
|
||||
* Sync strategy: delete-then-push for articles and menus to avoid
|
||||
* duplicates. Categories are upserted. Files are pushed via the
|
||||
* MokoWaaS sync-receive endpoint.
|
||||
*
|
||||
* @since 02.31.00
|
||||
*/
|
||||
final class ContentSync extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
use TaskPluginTrait;
|
||||
|
||||
/** @var string Target URL for the current sync run. */
|
||||
private string $targetUrl = '';
|
||||
|
||||
/** @var string Target health token for the current sync run. */
|
||||
private string $healthToken = '';
|
||||
|
||||
protected const TASKS_MAP = [
|
||||
'mokowaas.content.sync' => [
|
||||
'langConstPrefix' => 'PLG_TASK_MOKOWAASSYNC_SYNC',
|
||||
@@ -48,117 +59,382 @@ final class ContentSync extends CMSPlugin implements SubscriberInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Push content to all configured sync targets.
|
||||
*
|
||||
* Reads sync_targets from the MokoWaaS system plugin params, then
|
||||
* delegates to ContentSyncService. Task-level overrides (if any)
|
||||
* are merged on top.
|
||||
* Sync content to the configured target.
|
||||
*
|
||||
* @param ExecuteTaskEvent $event The task event
|
||||
*
|
||||
* @return int Status::OK or Status::KNOCKOUT
|
||||
*
|
||||
* @since 02.27.00
|
||||
* @since 02.31.00
|
||||
*/
|
||||
private function syncContent(ExecuteTaskEvent $event): int
|
||||
{
|
||||
$serviceFile = JPATH_PLUGINS . '/system/mokowaas/Service/ContentSyncService.php';
|
||||
$params = $event->getArgument('params');
|
||||
|
||||
if (!file_exists($serviceFile))
|
||||
// Debug: log what we received
|
||||
if (is_object($params))
|
||||
{
|
||||
$this->logTask('ContentSyncService.php not found — is plg_system_mokowaas installed?');
|
||||
$this->logTask('Params type: object, keys: ' . implode(', ', array_keys(get_object_vars($params))));
|
||||
}
|
||||
elseif (is_array($params))
|
||||
{
|
||||
$this->logTask('Params type: array, keys: ' . implode(', ', array_keys($params)));
|
||||
$params = (object) $params;
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->logTask('Params type: ' . gettype($params));
|
||||
}
|
||||
|
||||
$targetUrl = rtrim($params->target_url ?? '', '/');
|
||||
|
||||
if (empty($targetUrl))
|
||||
{
|
||||
$this->logTask('Sync target URL not configured');
|
||||
|
||||
return Status::KNOCKOUT;
|
||||
}
|
||||
|
||||
require_once $serviceFile;
|
||||
$healthToken = trim($params->health_token ?? '');
|
||||
|
||||
// Read sync targets from the system plugin params
|
||||
$targets = $this->getSyncTargets();
|
||||
|
||||
if (empty($targets))
|
||||
if (empty($healthToken))
|
||||
{
|
||||
$this->logTask('No sync targets configured in MokoWaaS system plugin');
|
||||
$this->logTask('Target health token not configured — cannot sync');
|
||||
|
||||
return Status::KNOCKOUT;
|
||||
}
|
||||
|
||||
$this->targetUrl = $targetUrl;
|
||||
$this->healthToken = $healthToken;
|
||||
$errors = 0;
|
||||
$synced = [];
|
||||
|
||||
// Bulk-clear selected content types on target before pushing
|
||||
$clearTypes = [];
|
||||
|
||||
if ((int) ($params->sync_articles ?? 0) === 1)
|
||||
{
|
||||
$clearTypes[] = 'articles';
|
||||
}
|
||||
|
||||
if ((int) ($params->sync_categories ?? 0) === 1)
|
||||
{
|
||||
$clearTypes[] = 'categories';
|
||||
}
|
||||
|
||||
if ((int) ($params->sync_menus ?? 0) === 1)
|
||||
{
|
||||
$clearTypes[] = 'menus';
|
||||
}
|
||||
|
||||
if ((int) ($params->sync_modules ?? 0) === 1)
|
||||
{
|
||||
$clearTypes[] = 'modules';
|
||||
}
|
||||
|
||||
if (!empty($clearTypes))
|
||||
{
|
||||
$clearResult = $this->bulkClear($targetUrl, $healthToken, $clearTypes);
|
||||
|
||||
if (!$clearResult)
|
||||
{
|
||||
$this->logTask('Bulk clear failed — aborting sync');
|
||||
|
||||
return Status::KNOCKOUT;
|
||||
}
|
||||
|
||||
$this->logTask('Cleared on target: ' . implode(', ', $clearTypes));
|
||||
}
|
||||
|
||||
// Push content types
|
||||
if ((int) ($params->sync_categories ?? 0) === 1)
|
||||
{
|
||||
$result = $this->syncCategories();
|
||||
$synced[] = 'categories:' . ($result ? 'ok' : 'fail');
|
||||
if (!$result) $errors++;
|
||||
}
|
||||
|
||||
if ((int) ($params->sync_articles ?? 0) === 1)
|
||||
{
|
||||
$result = $this->syncArticles();
|
||||
$synced[] = 'articles:' . ($result ? 'ok' : 'fail');
|
||||
if (!$result) $errors++;
|
||||
}
|
||||
|
||||
if ((int) ($params->sync_menus ?? 0) === 1)
|
||||
{
|
||||
$result = $this->syncMenus();
|
||||
$synced[] = 'menus:' . ($result ? 'ok' : 'fail');
|
||||
if (!$result) $errors++;
|
||||
}
|
||||
|
||||
if ((int) ($params->sync_modules ?? 0) === 1)
|
||||
{
|
||||
$result = $this->syncModules();
|
||||
$synced[] = 'modules:' . ($result ? 'ok' : 'fail');
|
||||
if (!$result) $errors++;
|
||||
}
|
||||
|
||||
|
||||
$summary = implode(', ', $synced);
|
||||
|
||||
if (empty($synced))
|
||||
{
|
||||
$this->logTask('No content types selected for sync');
|
||||
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
$this->logTask("Sync to {$targetUrl}: {$summary}");
|
||||
|
||||
return $errors > 0 && $errors === count($synced) ? Status::KNOCKOUT : Status::OK;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Bulk push via MokoWaaS syncpush endpoint
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Sync articles: read from source DB, bulk-push to target.
|
||||
*
|
||||
* @param string $apiBase Target API base URL (unused — uses MokoWaaS endpoint)
|
||||
* @param string $token API bearer token (unused — uses health token)
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @since 02.31.00
|
||||
*/
|
||||
private function syncArticles(): bool
|
||||
{
|
||||
try
|
||||
{
|
||||
$service = new \Moko\Plugin\System\MokoWaaS\Service\ContentSyncService();
|
||||
$result = $service->syncAllTargets($targets);
|
||||
$db = Factory::getDbo();
|
||||
$query = $db->getQuery(true)
|
||||
->select($db->quoteName(['title', 'alias', 'introtext', 'fulltext', 'state', 'catid', 'language', 'featured', 'metadesc', 'metakey', 'metadata', 'created', 'modified', 'publish_up', 'images', 'urls', 'attribs', 'access']))
|
||||
->from($db->quoteName('#__content'))
|
||||
->where($db->quoteName('state') . ' >= 0');
|
||||
$db->setQuery($query);
|
||||
$articles = $db->loadAssocList();
|
||||
|
||||
$targetResults = $result['targets'] ?? [];
|
||||
$okCount = 0;
|
||||
$errCount = 0;
|
||||
$targetUrl = rtrim($this->targetUrl, '/');
|
||||
$healthToken = $this->healthToken;
|
||||
|
||||
foreach ($targetResults as $tr)
|
||||
{
|
||||
if (($tr['status'] ?? '') === 'ok')
|
||||
{
|
||||
$okCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
$errCount++;
|
||||
$this->logTask('Sync failed for ' . ($tr['target'] ?? 'unknown') . ': ' . ($tr['message'] ?? ''));
|
||||
}
|
||||
}
|
||||
$result = $this->bulkPush($targetUrl, $healthToken, 'articles', $articles);
|
||||
|
||||
$this->logTask(sprintf('Content sync completed — %d ok, %d failed of %d target(s)', $okCount, $errCount, count($targetResults)));
|
||||
$this->logTask(sprintf('Synced %d articles', count($articles)));
|
||||
|
||||
return $errCount > 0 && $okCount === 0 ? Status::KNOCKOUT : Status::OK;
|
||||
return $result;
|
||||
}
|
||||
catch (\Throwable $e)
|
||||
{
|
||||
$this->logTask('Content sync failed: ' . $e->getMessage());
|
||||
$this->logTask('Article sync failed: ' . $e->getMessage());
|
||||
|
||||
return Status::KNOCKOUT;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read sync targets from the MokoWaaS system plugin configuration.
|
||||
* Sync categories: push from source, creating or updating on target.
|
||||
*
|
||||
* @return array Array of ['url' => ..., 'token' => ..., 'label' => ...]
|
||||
* @param string $apiBase Target API base URL
|
||||
* @param string $token API bearer token
|
||||
*
|
||||
* @since 02.27.00
|
||||
* @return bool
|
||||
*
|
||||
* @since 02.31.00
|
||||
*/
|
||||
private function getSyncTargets(): array
|
||||
private function syncCategories(): bool
|
||||
{
|
||||
try
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
$db = Factory::getDbo();
|
||||
$query = $db->getQuery(true)
|
||||
->select($db->quoteName('params'))
|
||||
->from($db->quoteName('#__extensions'))
|
||||
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
|
||||
->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
|
||||
->where($db->quoteName('element') . ' = ' . $db->quote('mokowaas'));
|
||||
|
||||
->select('title, alias, description, published, language, extension, access, params, metadata')
|
||||
->from($db->quoteName('#__categories'))
|
||||
->where($db->quoteName('extension') . ' = ' . $db->quote('com_content'))
|
||||
->where($db->quoteName('published') . ' >= 0')
|
||||
->where($db->quoteName('id') . ' > 1')
|
||||
->order($db->quoteName('lft') . ' ASC');
|
||||
$db->setQuery($query);
|
||||
$raw = $db->loadResult();
|
||||
$categories = $db->loadAssocList();
|
||||
|
||||
if (empty($raw))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
$targetUrl = rtrim($this->targetUrl, '/');
|
||||
$healthToken = $this->healthToken;
|
||||
|
||||
$params = json_decode($raw, true) ?: [];
|
||||
$targets = $params['sync_targets'] ?? [];
|
||||
$result = $this->bulkPush($targetUrl, $healthToken, 'categories', $categories);
|
||||
|
||||
if (is_string($targets))
|
||||
{
|
||||
$targets = json_decode($targets, true) ?: [];
|
||||
}
|
||||
$this->logTask(sprintf('Synced %d categories', count($categories)));
|
||||
|
||||
return is_array($targets) ? $targets : [];
|
||||
return $result;
|
||||
}
|
||||
catch (\Throwable $e)
|
||||
{
|
||||
$this->logTask('Failed to read sync targets: ' . $e->getMessage());
|
||||
$this->logTask('Category sync failed: ' . $e->getMessage());
|
||||
|
||||
return [];
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync menus via bulk push.
|
||||
*/
|
||||
private function syncMenus(): bool
|
||||
{
|
||||
try
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
$query = $db->getQuery(true)
|
||||
->select('title, alias, path, menutype, type, link, language, published, home, params, img, access')
|
||||
->from($db->quoteName('#__menu'))
|
||||
->where($db->quoteName('client_id') . ' = 0')
|
||||
->where($db->quoteName('id') . ' > 1')
|
||||
->where($db->quoteName('published') . ' >= 0')
|
||||
->order($db->quoteName('lft') . ' ASC');
|
||||
$db->setQuery($query);
|
||||
$items = $db->loadAssocList();
|
||||
|
||||
$targetUrl = rtrim($this->targetUrl, '/');
|
||||
$healthToken = $this->healthToken;
|
||||
|
||||
$result = $this->bulkPush($targetUrl, $healthToken, 'menus', $items);
|
||||
|
||||
$this->logTask(sprintf('Synced %d menu items', count($items)));
|
||||
|
||||
return $result;
|
||||
}
|
||||
catch (\Throwable $e)
|
||||
{
|
||||
$this->logTask('Menu sync failed: ' . $e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync modules via bulk push.
|
||||
*/
|
||||
private function syncModules(): bool
|
||||
{
|
||||
try
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
$query = $db->getQuery(true)
|
||||
->select('title, module, position, params, language, published, access, ordering, showtitle')
|
||||
->from($db->quoteName('#__modules'))
|
||||
->where($db->quoteName('client_id') . ' = 0')
|
||||
->where($db->quoteName('published') . ' >= 0');
|
||||
$db->setQuery($query);
|
||||
$modules = $db->loadAssocList();
|
||||
|
||||
$targetUrl = rtrim($this->targetUrl, '/');
|
||||
$healthToken = $this->healthToken;
|
||||
|
||||
$result = $this->bulkPush($targetUrl, $healthToken, 'modules', $modules);
|
||||
|
||||
$this->logTask(sprintf('Synced %d modules', count($modules)));
|
||||
|
||||
return $result;
|
||||
}
|
||||
catch (\Throwable $e)
|
||||
{
|
||||
$this->logTask('Module sync failed: ' . $e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// HTTP helpers
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Bulk-push content to the target via the MokoWaaS syncpush endpoint.
|
||||
*
|
||||
* Sends items in batches of 50 to prevent payload overload.
|
||||
*
|
||||
* @param string $targetUrl Target base URL
|
||||
* @param string $token MokoWaaS health token
|
||||
* @param string $type Content type (articles, categories, menus, modules)
|
||||
* @param array $items Array of items to push
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @since 02.31.00
|
||||
*/
|
||||
private function bulkPush(string $targetUrl, string $token, string $type, array $items): bool
|
||||
{
|
||||
$batches = array_chunk($items, 50);
|
||||
|
||||
foreach ($batches as $i => $batch)
|
||||
{
|
||||
$payload = json_encode([
|
||||
'token' => $token,
|
||||
'type' => $type,
|
||||
'items' => $batch,
|
||||
]);
|
||||
|
||||
$ch = curl_init($targetUrl . '/?mokowaas=syncpush');
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 120);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode < 200 || $httpCode >= 300)
|
||||
{
|
||||
$this->logTask("Bulk push failed for {$type} batch " . ($i + 1) . ": HTTP {$httpCode} — " . substr($response, 0, 200));
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk-clear content on the target via the MokoWaaS syncclear endpoint.
|
||||
*
|
||||
* @param string $targetUrl Target base URL
|
||||
* @param string $token MokoWaaS health token
|
||||
* @param array $types Content types to clear
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @since 02.31.00
|
||||
*/
|
||||
private function bulkClear(string $targetUrl, string $token, array $types): bool
|
||||
{
|
||||
$payload = json_encode([
|
||||
'token' => $token,
|
||||
'types' => $types,
|
||||
]);
|
||||
|
||||
$ch = curl_init($targetUrl . '/?mokowaas=syncclear');
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 60);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode >= 200 && $httpCode < 300)
|
||||
{
|
||||
$result = json_decode($response, true);
|
||||
$this->logTask('Target cleared: ' . json_encode($result['cleared'] ?? []));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->logTask('Bulk clear failed: HTTP ' . $httpCode . ' — ' . substr($response, 0, 200));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.30.00</version>
|
||||
<version>02.30.00</version>
|
||||
<version>02.31.00</version>
|
||||
<version>02.31.00</version>
|
||||
<description>Joomla Web Services API routes for MokoWaaS site management — health checks, cache, updates, backups, and site info.</description>
|
||||
<namespace path="src">Moko\Plugin\WebServices\MokoWaaS</namespace>
|
||||
<files>
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.30.00</version>
|
||||
<version>02.30.00</version>
|
||||
<version>02.31.00</version>
|
||||
<version>02.31.00</version>
|
||||
<description>Joomla Web Services API routes for Perfect Publisher (com_autotweet) — channels, posts, requests, rules, and feeds.</description>
|
||||
<namespace path="src">Moko\Plugin\WebServices\PerfectPublisher</namespace>
|
||||
<files>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* INGROUP: MokoWaaS
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS
|
||||
* PATH: /src/packages/plg_webservices_perfectpublisher/services/provider.php
|
||||
* VERSION: 02.30.00
|
||||
* VERSION: 02.31.00
|
||||
* BRIEF: DI service provider for Perfect Publisher Web Services plugin
|
||||
*/
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* INGROUP: MokoWaaS
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS
|
||||
* PATH: /src/packages/plg_webservices_perfectpublisher/src/Extension/PerfectPublisherApi.php
|
||||
* VERSION: 02.30.00
|
||||
* VERSION: 02.31.00
|
||||
* BRIEF: Web Services API plugin for Perfect Publisher (com_autotweet)
|
||||
*/
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<extension type="package" method="upgrade">
|
||||
<name>Package - MokoWaaS</name>
|
||||
<packagename>mokowaas</packagename>
|
||||
<version>02.30.00</version>
|
||||
<version>02.30.00</version>
|
||||
<version>02.31.00</version>
|
||||
<version>02.31.00</version>
|
||||
<creationDate>2026-05-23</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
|
||||
+1
-1
@@ -230,7 +230,7 @@ class Pkg_MokowaasInstallerScript
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 02.30.00
|
||||
* @since 02.31.00
|
||||
*/
|
||||
private function cleanupStaleUpdateSites(): void
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user