refactor: remove branding/identity from core plugin (#154)

Remove 8 branding methods and 4 call sites (-428 lines):
- enforceAtumBranding(), hexToHsl(), injectFavicon()
- enforceLoginSupportUrls(), loadLanguageOverrides()
- getPlaceholders(), parseLanguageFile(), setTemplateParam()

MokoWaaS is an admin tools suite, not a branding layer.
Core plugin docblock updated to reflect new purpose.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jonathan Miller
2026-06-04 13:50:12 -05:00
parent 524523b8c6
commit 8ea6df020b
@@ -24,7 +24,7 @@
* REPO: https://github.com/mokoconsulting-tech/mokowaas
* VERSION: 02.33.01
* PATH: /src/Extension/MokoWaaS.php
* NOTE: Handles Joomla system events for rebranding functionality
* NOTE: Core system plugin for MokoWaaS admin tools suite
*/
namespace Moko\Plugin\System\MokoWaaS\Extension;
@@ -42,10 +42,9 @@ use Joomla\CMS\User\UserHelper;
use Psr\Container\ContainerInterface;
/**
* MokoWaaS Brand System Plugin
* MokoWaaS Core System Plugin
*
* This plugin rebrands the Joomla system interface with MokoWaaS identity.
* It applies language overrides and ensures consistent branding across the platform.
* This plugin provides core coordination for the MokoWaaS admin tools suite.
*
* @since 01.04.00
*/
@@ -187,18 +186,14 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
$this->handleMokoApi($mokoAction);
}
// Admin-only core controls (branding, emergency access, master user)
// Admin-only core controls (emergency access, master user)
// NOTE: enforceHttps, enforceDevMode, enforceAdminSessionTimeout,
// enforceUploadRestrictions are now in feature plugins
if ($this->app->isClient('administrator'))
{
$this->handleEmergencyAccess();
$this->enforceMasterUser();
$this->enforceLoginSupportUrls();
$this->enforceAtumBranding();
}
$this->loadLanguageOverrides();
}
/**
@@ -698,102 +693,6 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
return in_array($clientIp, $allowedIps, true);
}
/**
* Build the placeholder → value map from plugin params.
*
* @return array Associative array of placeholder => replacement value
*
* @since 02.01.08
*/
protected function getPlaceholders()
{
return [
'{{BRAND_NAME}}' => self::BRAND_NAME,
'{{COMPANY_NAME}}' => self::COMPANY_NAME,
'{{SUPPORT_URL}}' => self::SUPPORT_URL,
];
}
/**
* Load language override templates and inject resolved strings into Joomla.
*
* Reads the override template shipped with the plugin, replaces
* {{BRAND_NAME}}, {{COMPANY_NAME}} and {{SUPPORT_URL}} with the
* values from plugin params, then injects the resolved strings into
* the active Language object.
*
* @return void
*
* @since 02.01.08
*/
protected function loadLanguageOverrides()
{
$language = $this->app->getLanguage();
$tag = $language->getTag();
$pluginPath = JPATH_PLUGINS . '/system/mokowaas';
$isAdmin = $this->app->isClient('administrator');
$overridePath = $isAdmin
? $pluginPath . '/administrator/language/overrides/' . $tag . '.override.ini'
: $pluginPath . '/language/overrides/' . $tag . '.override.ini';
if (!file_exists($overridePath))
{
return;
}
$strings = $this->parseLanguageFile($overridePath);
$placeholders = $this->getPlaceholders();
foreach ($strings as $key => $value)
{
$language->_strings[$key] = str_replace(
array_keys($placeholders),
array_values($placeholders),
$value
);
}
}
/**
* Parse a language INI file and return the raw strings (with placeholders).
*
* @param string $filePath The path to the language file
*
* @return array Array of language strings (key => raw value)
*
* @since 02.01.08
*/
protected function parseLanguageFile($filePath)
{
$strings = [];
if (!file_exists($filePath))
{
return $strings;
}
$content = file_get_contents($filePath);
$lines = explode("\n", $content);
foreach ($lines as $line)
{
$line = trim($line);
if ($line === '' || $line[0] === ';')
{
continue;
}
if (preg_match('/^([A-Z0-9_]+)="(.+)"$/i', $line, $matches))
{
$strings[strtoupper($matches[1])] = $matches[2];
}
}
return $strings;
}
/**
* Event triggered after an extension's config is saved.
*
@@ -1044,7 +943,6 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
return;
}
$this->injectFavicon($doc);
$this->redirectHelpMenu($doc);
// Hide MokoWaaS from plugin list for non-master users
@@ -4419,115 +4317,7 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
* @since 02.01.08
*/
/**
* 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
}
}
/**
* Enforce login support module URLs on admin requests.
*
* Checks the mod_loginsupport module params and corrects them if
* they have been changed away from the expected values.
*
* @return void
*
* @since 02.01.08
*/
protected function enforceLoginSupportUrls()
{
$expected = [
'forum_url' => 'https://mokoconsulting.tech/support',
'documentation_url' => 'https://mokoconsulting.tech/kb',
'news_url' => 'https://mokoconsulting.tech/news',
];
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select([$db->quoteName('id'), $db->quoteName('params')])
->from($db->quoteName('#__modules'))
->where($db->quoteName('module') . ' = '
. $db->quote('mod_loginsupport'));
$db->setQuery($query);
$modules = $db->loadObjectList();
if (empty($modules))
{
return;
}
foreach ($modules as $module)
{
$params = new \Joomla\Registry\Registry(
$module->params ?: '{}'
);
$needsFix = false;
foreach ($expected as $key => $url)
{
if ($params->get($key) !== $url)
{
$params->set($key, $url);
$needsFix = true;
}
}
if ($needsFix)
{
$update = $db->getQuery(true)
->update($db->quoteName('#__modules'))
->set($db->quoteName('params') . ' = '
. $db->quote($params->toString()))
->where($db->quoteName('id') . ' = '
. (int) $module->id);
$db->setQuery($update);
$db->execute();
}
}
}
// ------------------------------------------------------------------
// Tenant Restrictions (called from onAfterRoute)
@@ -4584,224 +4374,6 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
return $this->masterNames;
}
// ------------------------------------------------------------------
// Atum Template Branding (called from onAfterInitialise)
// ------------------------------------------------------------------
/**
* Enforce Atum admin template branding params.
*
* Sets logoBrandLarge, logoBrandSmall, loginLogo, and alt text
* in the Atum template style params. Uses the plugin's media
* folder as the image source. Only writes to DB when values
* have drifted.
*
* @return void
*
* @since 02.01.08
*/
protected function enforceAtumBranding()
{
$mediaBase = 'media/plg_system_mokowaas/';
// Logo params
$expected = [
'logoBrandLarge' => $mediaBase . 'logo.png',
'logoBrandSmall' => $mediaBase . 'favicon_256.png',
'loginLogo' => $mediaBase . 'logo.png',
'logoBrandLargeAlt' => '',
'logoBrandSmallAlt' => '',
'loginLogoAlt' => '',
'emptyLogoBrandLargeAlt' => '1',
'emptyLogoBrandSmallAlt' => '1',
'emptyLoginLogoAlt' => '1',
];
// Hardcoded color scheme
$primary = self::COLOR_PRIMARY;
$sidebar = self::COLOR_SIDEBAR;
$link = self::COLOR_LINK;
if (!empty($primary))
{
// Convert hex to HSL for Atum's hue param
$hsl = $this->hexToHsl($primary);
if ($hsl)
{
$expected['hue'] = sprintf(
'hsl(%d, %d%%, %d%%)',
$hsl[0], $hsl[1], $hsl[2]
);
}
$expected['special-color'] = $primary;
}
if (!empty($sidebar))
{
$expected['header-color'] = $sidebar;
}
if (!empty($link))
{
$expected['link-color'] = $link;
}
$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('atum'))
->where($db->quoteName('client_id') . ' = 1');
$db->setQuery($query);
$styles = $db->loadObjectList();
if (empty($styles))
{
return;
}
foreach ($styles as $style)
{
$params = new \Joomla\Registry\Registry(
$style->params ?: '{}'
);
$needsFix = false;
foreach ($expected as $key => $value)
{
if ($params->get($key) !== $value)
{
$params->set($key, $value);
$needsFix = true;
}
}
if ($needsFix)
{
$update = $db->getQuery(true)
->update($db->quoteName('#__template_styles'))
->set($db->quoteName('params') . ' = '
. $db->quote($params->toString()))
->where($db->quoteName('id') . ' = '
. (int) $style->id);
$db->setQuery($update);
$db->execute();
}
}
}
/**
* Convert a hex color to HSL values.
*
* @param string $hex Hex color (e.g., "#1a2744")
*
* @return array|null [hue, saturation%, lightness%] or null
*
* @since 02.01.08
*/
protected function hexToHsl($hex)
{
$hex = ltrim($hex, '#');
if (strlen($hex) !== 6)
{
return null;
}
$r = hexdec(substr($hex, 0, 2)) / 255;
$g = hexdec(substr($hex, 2, 2)) / 255;
$b = hexdec(substr($hex, 4, 2)) / 255;
$max = max($r, $g, $b);
$min = min($r, $g, $b);
$l = ($max + $min) / 2;
if ($max === $min)
{
return [0, 0, (int) round($l * 100)];
}
$d = $max - $min;
$s = $l > 0.5
? $d / (2 - $max - $min)
: $d / ($max + $min);
if ($max === $r)
{
$h = ($g - $b) / $d + ($g < $b ? 6 : 0);
}
elseif ($max === $g)
{
$h = ($b - $r) / $d + 2;
}
else
{
$h = ($r - $g) / $d + 4;
}
$h = $h / 6;
return [
(int) round($h * 360),
(int) round($s * 100),
(int) round($l * 100),
];
}
// ------------------------------------------------------------------
// Visual Branding (called from onBeforeCompileHead)
// ------------------------------------------------------------------
/**
* Replace the default favicon with a custom one.
*
* @param \Joomla\CMS\Document\HtmlDocument $doc
*
* @return void
*
* @since 02.01.08
*/
protected function injectFavicon($doc)
{
$mediaBase = 'media/plg_system_mokowaas/';
$root = Uri::root();
// Remove all existing favicon/icon links
foreach ($doc->_links as $href => $attrs)
{
if (isset($attrs['relation'])
&& strpos($attrs['relation'], 'icon') !== false)
{
unset($doc->_links[$href]);
}
}
// SVG favicon (modern browsers, preferred)
$doc->addHeadLink(
$root . $mediaBase . 'favicon.svg',
'icon',
'rel',
['type' => 'image/svg+xml']
);
// ICO fallback (legacy browsers)
$doc->addHeadLink(
$root . $mediaBase . 'favicon.ico',
'alternate icon',
'rel',
['type' => 'image/vnd.microsoft.icon']
);
// PNG for Apple/Android
$doc->addHeadLink(
$root . $mediaBase . 'favicon_256.png',
'apple-touch-icon',
'rel',
['sizes' => '256x256']
);
}
}