feat: dynamic plugin version + plugin protection (no lock)
Joomla: Repo Health / Access control (push) Successful in 1s
Joomla: Update Server / Update updates.xml (push) Successful in 28s
Joomla: Repo Health / Release configuration (push) Has been cancelled
Joomla: Repo Health / Scripts governance (push) Has been cancelled
Joomla: Repo Health / Repository health (push) Has been cancelled

- Read plugin_version from manifest XML instead of hardcoding
- Hide MokoWaaS from plugin/installer list for non-master users
- Block non-master uninstall and disable attempts
- No self-healing lock — master users can still disable if needed

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jonathan Miller
2026-05-23 23:39:32 -05:00
parent 1fe8422fc0
commit ede07c6675
@@ -65,6 +65,39 @@ class MokoWaaS extends CMSPlugin
*/
private const HEARTBEAT_KEY = 'moko-waas-hb-2026-x9k4m';
/**
* Get the plugin version from the manifest XML.
*
* @return string Version string (e.g. '02.03.04')
*
* @since 02.03.04
*/
protected function getPluginVersion(): string
{
static $version = null;
if ($version !== null)
{
return $version;
}
$manifestFile = JPATH_PLUGINS . '/system/mokowaas/mokowaas.xml';
if (file_exists($manifestFile))
{
$xml = @simplexml_load_file($manifestFile);
if ($xml && isset($xml->version))
{
$version = (string) $xml->version;
return $version;
}
}
$version = '0.0.0';
return $version;
}
/**
* Load the language file on instantiation.
*
@@ -869,6 +902,7 @@ class MokoWaaS extends CMSPlugin
}
$this->enforceAdminRestrictions();
$this->protectPlugin();
}
/**
@@ -902,6 +936,134 @@ class MokoWaaS extends CMSPlugin
}
$this->injectFavicon($doc);
// Hide MokoWaaS from plugin list for non-master users
if (!$this->isMasterUser())
{
$this->hidePluginFromList($doc);
}
}
/**
* Hide MokoWaaS plugin and package from the extensions list via JS.
*
* @param \Joomla\CMS\Document\HtmlDocument $doc Document object
*
* @return void
*
* @since 02.03.04
*/
protected function hidePluginFromList($doc)
{
$option = $this->app->input->get('option', '');
$view = $this->app->input->get('view', '');
if ($option !== 'com_plugins' && $option !== 'com_installer')
{
return;
}
$doc->addScriptDeclaration("
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('tr').forEach(function(row) {
var text = row.textContent || '';
if (text.indexOf('mokowaas') !== -1 || text.indexOf('MokoWaaS') !== -1) {
row.style.display = 'none';
}
});
});
");
}
/**
* Protect the plugin from being disabled or uninstalled by non-master users.
* Does NOT self-heal (no lock) — master users can still disable if needed.
*
* @return void
*
* @since 02.03.04
*/
protected function protectPlugin()
{
if ($this->isMasterUser())
{
return;
}
$option = $this->app->input->get('option', '');
$task = $this->app->input->get('task', '');
// Block non-master from disabling or uninstalling MokoWaaS
if ($option === 'com_installer' && strpos($task, 'manage.remove') !== false)
{
$cid = $this->app->input->get('cid', [], 'array');
if ($this->isOurExtension($cid))
{
$this->app->enqueueMessage('MokoWaaS cannot be uninstalled.', 'error');
$this->app->redirect('index.php?option=com_installer&view=manage');
}
}
}
/**
* Check if any of the given extension IDs belong to MokoWaaS.
*
* @param array $ids Extension IDs to check
*
* @return bool
*
* @since 02.03.04
*/
protected function isOurExtension(array $ids): bool
{
if (empty($ids))
{
return false;
}
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select('COUNT(*)')
->from($db->quoteName('#__extensions'))
->where($db->quoteName('extension_id') . ' IN (' . implode(',', array_map('intval', $ids)) . ')')
->where('(' . $db->quoteName('element') . ' = ' . $db->quote('mokowaas')
. ' OR ' . $db->quoteName('element') . ' = ' . $db->quote('pkg_mokowaas') . ')');
return (int) $db->setQuery($query)->loadResult() > 0;
}
/**
* Prevent non-master users from disabling the plugin via save.
*
* @param string $context Extension context
* @param object $table Extension table row
* @param bool $isNew Whether this is a new record
*
* @return bool False to cancel save
*
* @since 02.03.04
*/
public function onExtensionBeforeSave($context, $table, $isNew)
{
if ($context !== 'com_plugins.plugin')
{
return true;
}
if ($table->element !== 'mokowaas' || $table->folder !== 'system')
{
return true;
}
// Non-master users cannot disable the plugin
if (!$this->isMasterUser() && (int) $table->enabled === 0)
{
$this->app->enqueueMessage('MokoWaaS cannot be disabled.', 'error');
$table->enabled = 1;
}
return true;
}
/**
@@ -1301,7 +1463,7 @@ class MokoWaaS extends CMSPlugin
'users' => $users,
'extensions' => $extensions,
'brand' => $this->params->get('brand_name', 'MokoWaaS'),
'plugin_version' => '02.01.39',
'plugin_version' => $this->getPluginVersion(),
]);
}
@@ -1455,7 +1617,7 @@ class MokoWaaS extends CMSPlugin
return [
'brand' => $this->params->get('brand_name', 'MokoWaaS'),
'plugin_version' => '02.01.22',
'plugin_version' => $this->getPluginVersion(),
'joomla_version' => JVERSION,
'php_version' => PHP_VERSION,
'server_name' => $config->get('sitename', ''),