From 0a374ac8d599f13b6bdbc836d87825637593275e Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Tue, 23 Jun 2026 14:26:49 -0500 Subject: [PATCH] feat: fuzzy detection of all MokoSuite/MokoJoom ecosystem packages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- .../src/Controller/DisplayController.php | 17 ++++++ .../admin/src/Model/DashboardModel.php | 61 ++++++++++++++----- 2 files changed, 63 insertions(+), 15 deletions(-) diff --git a/source/packages/com_mokosuiteclient/admin/src/Controller/DisplayController.php b/source/packages/com_mokosuiteclient/admin/src/Controller/DisplayController.php index 31a2dfbf..a6e288f6 100644 --- a/source/packages/com_mokosuiteclient/admin/src/Controller/DisplayController.php +++ b/source/packages/com_mokosuiteclient/admin/src/Controller/DisplayController.php @@ -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 diff --git a/source/packages/com_mokosuiteclient/admin/src/Model/DashboardModel.php b/source/packages/com_mokosuiteclient/admin/src/Model/DashboardModel.php index 48c28a7d..5fc4f9b0 100644 --- a/source/packages/com_mokosuiteclient/admin/src/Model/DashboardModel.php +++ b/source/packages/com_mokosuiteclient/admin/src/Model/DashboardModel.php @@ -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, ]; }