fix(install): enable ticket/offline plugins, consolidate monitor into core
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 10s

- enablePlugin() handles empty element columns with fallback match by name
- Retire monitor plugin: migrate config (base_url, signing_key) to core
- Runtime heartbeat moved from monitor plugin to core plugin
- DisplayController heartbeat reads from core plugin params
- ensureAdminModule() direct DB update for existing modules (fixes
  checked_out blocking, cpanel access level 6→3, menu ordering -1)
- Add missing ticket automation language strings (IMAP, autoclose)
- Remove monitor from cpanel plugin grid
This commit is contained in:
Jonathan Miller
2026-06-20 10:33:01 -05:00
parent c1696bce41
commit 72134bba28
8 changed files with 475 additions and 55 deletions
+154 -38
View File
@@ -78,7 +78,6 @@ class Pkg_MokosuiteclientInstallerScript
$this->enablePlugin('system', 'mokosuiteclient_devtools');
$this->enablePlugin('system', 'mokosuiteclient_offline');
$this->enablePlugin('system', 'mokosuiteclient_dbip');
$this->enablePlugin('system', 'mokosuiteclient_monitor');
$this->enablePlugin('webservices', 'mokosuiteclient');
$this->enablePlugin('task', 'mokosuiteclientdemo');
$this->enablePlugin('task', 'mokosuiteclientsync');
@@ -87,6 +86,9 @@ class Pkg_MokosuiteclientInstallerScript
// Migrate params from core plugin to feature plugins (one-time)
$this->migrateFeatureParams();
// Migrate monitor params into core plugin before monitor is retired
$this->migrateMonitorParams();
// Set up cpanel module on the admin dashboard
$this->setupCpanelModule();
@@ -469,6 +471,31 @@ class Pkg_MokosuiteclientInstallerScript
->where($db->quoteName('element') . ' = ' . $db->quote($element));
$db->setQuery($query);
$db->execute();
if ($db->getAffectedRows() > 0)
{
return;
}
// Row may exist with empty element (DEFAULT '' from preflight ALTER).
// Fix the element value and enable in one pass.
$manifestName = 'plg_' . $group . '_' . $element;
$fix = $db->getQuery(true)
->update($db->quoteName('#__extensions'))
->set($db->quoteName('element') . ' = ' . $db->quote($element))
->set($db->quoteName('enabled') . ' = 1')
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' . $db->quote($group))
->where('(' . $db->quoteName('element') . ' = ' . $db->quote('')
. ' OR ' . $db->quoteName('element') . ' IS NULL)')
->where($db->quoteName('name') . ' = ' . $db->quote($manifestName));
$db->setQuery($fix);
$db->execute();
if ($db->getAffectedRows() > 0)
{
Log::add('Fixed empty element for plugin ' . $group . '/' . $element, Log::NOTICE, 'mokosuiteclient');
}
}
catch (\Throwable $e)
{
@@ -801,7 +828,7 @@ class Pkg_MokosuiteclientInstallerScript
{
$db = Factory::getDbo();
// Get health token from core plugin
// All heartbeat config now lives in the core plugin params
$query = $db->getQuery(true)
->select($db->quoteName('params'))
->from($db->quoteName('#__extensions'))
@@ -816,20 +843,17 @@ class Pkg_MokosuiteclientInstallerScript
return;
}
// Get base URL and signing key from monitor plugin
$query = $db->getQuery(true)
->select($db->quoteName('params'))
->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' = ' . $db->quote('mokosuiteclient_monitor'))
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' . $db->quote('system'));
$monitorParams = json_decode((string) $db->setQuery($query)->loadResult());
$baseUrl = rtrim($monitorParams->base_url ?? '', '/');
if (($coreParams->heartbeat_enabled ?? '1') === '0')
{
return;
}
// Fall back to manifest XML default if not yet saved in params
$baseUrl = rtrim($coreParams->monitor_base_url ?? '', '/');
// Fall back to manifest XML default
if (empty($baseUrl))
{
$manifestFile = JPATH_PLUGINS . '/system/mokosuiteclient_monitor/mokosuiteclient_monitor.xml';
$manifestFile = JPATH_PLUGINS . '/system/mokosuiteclient/mokosuiteclient.xml';
if (is_file($manifestFile))
{
@@ -837,7 +861,7 @@ class Pkg_MokosuiteclientInstallerScript
if ($xml)
{
foreach ($xml->xpath('//field[@name="base_url"]') as $field)
foreach ($xml->xpath('//field[@name="monitor_base_url"]') as $field)
{
$baseUrl = rtrim((string) $field['default'], '/');
break;
@@ -867,12 +891,12 @@ class Pkg_MokosuiteclientInstallerScript
$headers = ['Content-Type: application/json'];
// RSA sign the request — fall back to manifest XML default
$signingKeyB64 = $monitorParams->signing_key ?? '';
$signingKeyB64 = $coreParams->monitor_signing_key ?? '';
// Fall back to manifest XML default
if (empty($signingKeyB64))
{
$manifestFile = JPATH_PLUGINS . '/system/mokosuiteclient_monitor/mokosuiteclient_monitor.xml';
$manifestFile = JPATH_PLUGINS . '/system/mokosuiteclient/mokosuiteclient.xml';
if (is_file($manifestFile))
{
@@ -880,7 +904,7 @@ class Pkg_MokosuiteclientInstallerScript
if ($xml)
{
foreach ($xml->xpath('//field[@name="signing_key"]') as $field)
foreach ($xml->xpath('//field[@name="monitor_signing_key"]') as $field)
{
$signingKeyB64 = (string) $field['default'];
break;
@@ -945,12 +969,12 @@ class Pkg_MokosuiteclientInstallerScript
*/
private function setupCpanelModule(): void
{
$this->ensureAdminModule('mod_mokosuiteclient_cpanel', 'MokoSuiteClient', 'top', 6, 0, '{"show_health":"1","show_plugins":"1"}');
$this->ensureAdminModule('mod_mokosuiteclient_cpanel', 'MokoSuiteClient', 'top', 3, 0, '{"show_health":"1","show_plugins":"1"}');
}
private function setupAdminMenuModule(): void
{
$this->ensureAdminModule('mod_mokosuiteclient_menu', 'MokoSuiteClient Menu', 'menu', 3, 0);
$this->ensureAdminModule('mod_mokosuiteclient_menu', 'MokoSuiteClient Menu', 'menu', 3, -1);
}
private function setupCacheModule(): void
@@ -989,28 +1013,55 @@ class Pkg_MokosuiteclientInstallerScript
);
$moduleId = (int) $db->loadResult();
// Build save data — Joomla's ModuleModel expects this format
$data = [
'title' => $title,
'module' => $element,
'position' => $position,
'published' => 1,
'access' => $access,
'ordering' => $ordering,
'showtitle' => 0,
'client_id' => 1,
'language' => '*',
'params' => $params,
'assignment' => 0, // 0 = all pages
];
if ($moduleId > 0)
{
$data['id'] = $moduleId;
// Module exists — ensure it stays published with correct position
$db->setQuery(
$db->getQuery(true)
->update($db->quoteName('#__modules'))
->set($db->quoteName('published') . ' = 1')
->set($db->quoteName('position') . ' = ' . $db->quote($position))
->set($db->quoteName('ordering') . ' = ' . (int) $ordering)
->set($db->quoteName('access') . ' = ' . (int) $access)
->set($db->quoteName('checked_out') . ' = NULL')
->set($db->quoteName('checked_out_time') . ' = NULL')
->where($db->quoteName('id') . ' = ' . $moduleId)
)->execute();
// Ensure module-menu mapping exists (0 = all pages)
$db->setQuery(
$db->getQuery(true)
->select('COUNT(*)')
->from($db->quoteName('#__modules_menu'))
->where($db->quoteName('moduleid') . ' = ' . $moduleId)
);
if ((int) $db->loadResult() === 0)
{
$db->setQuery(
"INSERT IGNORE INTO " . $db->quoteName('#__modules_menu')
. " (moduleid, menuid) VALUES (" . $moduleId . ", 0)"
)->execute();
}
return;
}
// Use Joomla's ModuleModel to handle save + menu assignment
\Joomla\CMS\MVC\Factory\MVCFactory::class;
// Module doesn't exist — create via ModuleModel
$data = [
'title' => $title,
'module' => $element,
'position' => $position,
'published' => 1,
'access' => $access,
'ordering' => $ordering,
'showtitle' => 0,
'client_id' => 1,
'language' => '*',
'params' => $params,
'assignment' => 0,
];
$app = Factory::getApplication();
/** @var \Joomla\Component\Modules\Administrator\Model\ModuleModel $model */
@@ -1494,6 +1545,71 @@ class Pkg_MokosuiteclientInstallerScript
}
}
/**
* Migrate monitor plugin params (base_url, signing_key) into the core plugin.
* The monitor plugin is retired but its config must survive in the core plugin.
*/
private function migrateMonitorParams(): void
{
try
{
$db = Factory::getDbo();
// Read core plugin params
$query = $db->getQuery(true)
->select($db->quoteName('params'))
->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' = ' . $db->quote('mokosuiteclient'))
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' . $db->quote('system'));
$coreParams = json_decode((string) $db->setQuery($query)->loadResult(), true) ?: [];
if (!empty($coreParams['_monitor_migrated']))
{
return;
}
// Read monitor plugin params (may already be gone)
$query = $db->getQuery(true)
->select($db->quoteName('params'))
->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' = ' . $db->quote('mokosuiteclient_monitor'))
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' . $db->quote('system'));
$monitorJson = (string) $db->setQuery($query)->loadResult();
$monitorParams = json_decode($monitorJson, true) ?: [];
$keyMap = [
'base_url' => 'monitor_base_url',
'signing_key' => 'monitor_signing_key',
'heartbeat_enabled' => 'heartbeat_enabled',
];
foreach ($keyMap as $old => $new)
{
if (!empty($monitorParams[$old]) && empty($coreParams[$new]))
{
$coreParams[$new] = $monitorParams[$old];
}
}
$coreParams['_monitor_migrated'] = 1;
$db->setQuery(
$db->getQuery(true)
->update($db->quoteName('#__extensions'))
->set($db->quoteName('params') . ' = ' . $db->quote(json_encode($coreParams)))
->where($db->quoteName('element') . ' = ' . $db->quote('mokosuiteclient'))
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
)->execute();
}
catch (\Throwable $e)
{
Log::add('Monitor param migration error: ' . $e->getMessage(), Log::WARNING, 'mokosuiteclient');
}
}
/**
* Warn after install/update if no license key (dlid) is configured on the update site.
*/