feat: fuzzy detection of all MokoSuite/MokoJoom ecosystem packages
Universal: PR Check / Branch Policy (pull_request) Successful in 3s
Generic: Repo Health / Access control (pull_request) Successful in 3s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 9s
Generic: Project CI / Lint & Validate (pull_request) Failing after 17s
Universal: Auto Version Bump / Version Bump (push) Successful in 22s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 19s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 44s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 47s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 27s
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (push) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been cancelled
Platform: moko-platform CI / CI Summary (push) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Has been cancelled
Platform: moko-platform CI / CI Summary (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled

- DashboardModel.getMokoExtensions() now fuzzy-matches mokosuite*,
  mokosuiteclient*, mokosuitehq*, mokosuitecrm*, mokojoom* across
  packages, components, modules, plugins, and libraries
- Each extension tagged with product family for grouping
- Heartbeat payload includes moko_packages map (element → version)
  so HQ can see all installed MokoSuite products per site
- Menu module already catches com_moko% (no change needed)
This commit is contained in:
Jonathan Miller
2026-06-23 14:26:49 -05:00
parent 32e76ecc75
commit 0a374ac8d5
2 changed files with 63 additions and 15 deletions
@@ -141,6 +141,22 @@ class DisplayController extends BaseController
$domain = parse_url($siteUrl, PHP_URL_HOST) ?: '';
$timestamp = time();
// Discover all MokoSuite ecosystem packages for HQ
$mokoPackages = [];
try {
$pkgDb = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
$pkgQuery = $pkgDb->getQuery(true)
->select([$pkgDb->quoteName('element'), $pkgDb->quoteName('manifest_cache')])
->from($pkgDb->quoteName('#__extensions'))
->where('(' . $pkgDb->quoteName('element') . ' LIKE ' . $pkgDb->quote('pkg_mokosuite%')
. ' OR ' . $pkgDb->quoteName('element') . ' LIKE ' . $pkgDb->quote('pkg_mokojoom%') . ')');
$pkgDb->setQuery($pkgQuery);
foreach ($pkgDb->loadObjectList() ?: [] as $pkg) {
$m = json_decode($pkg->manifest_cache ?? '{}');
$mokoPackages[$pkg->element] = $m->version ?? '';
}
} catch (\Throwable $e) {}
$payload = json_encode([
'token' => $healthToken,
'domain' => $domain,
@@ -149,6 +165,7 @@ class DisplayController extends BaseController
'joomla_version' => (new \Joomla\CMS\Version())->getShortVersion(),
'php_version' => PHP_VERSION,
'timestamp' => $timestamp,
'moko_packages' => $mokoPackages,
], JSON_UNESCAPED_SLASHES);
// RSA sign the request
@@ -213,30 +213,46 @@ class DashboardModel extends BaseDatabaseModel
}
/**
* Get installed MokoSuiteClient component and modules with versions.
* Discover all installed MokoSuite ecosystem extensions.
*
* @return array Array of extension objects with name, element, type, version.
* Fuzzy-matches packages, components, modules, plugins, and libraries
* by element name containing "mokosuite", "mokosuiteclient", "mokojoom",
* or "moko" prefix patterns.
*
* @return array Extension objects with name, element, type, version, enabled, family.
*/
public function getMokoExtensions(): array
{
$db = $this->getDatabase();
$el = $db->quoteName('element');
// Fuzzy match: any extension whose element contains moko patterns
$patterns = [
$el . ' LIKE ' . $db->quote('pkg_mokosuite%'),
$el . ' LIKE ' . $db->quote('com_mokosuite%'),
$el . ' LIKE ' . $db->quote('mod_mokosuite%'),
$el . ' LIKE ' . $db->quote('mokosuite%'),
$el . ' LIKE ' . $db->quote('mokosuiteclient%'),
$el . ' LIKE ' . $db->quote('pkg_mokojoom%'),
$el . ' LIKE ' . $db->quote('com_mokojoom%'),
$el . ' LIKE ' . $db->quote('mod_mokojoom%'),
$el . ' LIKE ' . $db->quote('mokojoom%'),
$el . ' LIKE ' . $db->quote('plg_%_mokosuite%'),
$el . ' LIKE ' . $db->quote('plg_%_mokojoom%'),
];
$query = $db->getQuery(true)
->select([
$db->quoteName('extension_id'),
$db->quoteName('element'),
$db->quoteName('name'),
$db->quoteName('type'),
$db->quoteName('folder'),
$db->quoteName('enabled'),
$db->quoteName('manifest_cache'),
])
->from($db->quoteName('#__extensions'))
->where('('
// The component
. '(' . $db->quoteName('type') . ' = ' . $db->quote('component')
. ' AND ' . $db->quoteName('element') . ' = ' . $db->quote('com_mokosuiteclient') . ')'
// Admin modules
. ' OR (' . $db->quoteName('type') . ' = ' . $db->quote('module')
. ' AND ' . $db->quoteName('element') . ' LIKE ' . $db->quote('mod_mokosuiteclient%') . ')'
. ')')
->where('(' . implode(' OR ', $patterns) . ')')
->order($db->quoteName('type') . ' ASC, ' . $db->quoteName('element') . ' ASC');
$db->setQuery($query);
@@ -248,12 +264,27 @@ class DashboardModel extends BaseDatabaseModel
{
$manifest = json_decode($row->manifest_cache ?? '{}');
// Determine product family from element name
$family = 'mokosuite';
if (stripos($row->element, 'mokosuiteclient') !== false) {
$family = 'mokosuiteclient';
} elseif (stripos($row->element, 'mokosuitehq') !== false) {
$family = 'mokosuitehq';
} elseif (stripos($row->element, 'mokosuitecrm') !== false) {
$family = 'mokosuitecrm';
} elseif (stripos($row->element, 'mokojoom') !== false) {
$family = 'mokojoom';
}
$extensions[] = (object) [
'element' => $row->element,
'name' => $manifest->name ?? $row->name,
'type' => $row->type,
'version' => $manifest->version ?? '',
'enabled' => (int) $row->enabled,
'extension_id' => (int) $row->extension_id,
'element' => $row->element,
'name' => $manifest->name ?? $row->name,
'type' => $row->type,
'folder' => $row->folder ?? '',
'version' => $manifest->version ?? '',
'enabled' => (int) $row->enabled,
'family' => $family,
];
}