6aee7353b9
Each installed Moko component now renders as its own top-level collapsible section instead of being nested under a single MokoSuite parent. com_mokosuitehq is pinned first, com_mokosuite uses static views as children, all others auto-discover from #__menu.
202 lines
7.3 KiB
PHP
202 lines
7.3 KiB
PHP
<?php
|
|
/**
|
|
* MokoSuite Admin Sidebar Menu
|
|
*
|
|
* Each installed Moko component gets its own top-level collapsible section.
|
|
* com_mokosuitehq is always pinned first. com_mokosuite uses static views
|
|
* as children. All other components auto-discover their submenu items.
|
|
*/
|
|
|
|
defined('_JEXEC') or die;
|
|
|
|
use Joomla\CMS\Factory;
|
|
use Joomla\CMS\Language\Text;
|
|
use Joomla\CMS\Router\Route;
|
|
|
|
$app = Factory::getApplication();
|
|
$currentOption = $app->getInput()->get('option', '');
|
|
$currentView = $app->getInput()->get('view', '');
|
|
|
|
// ── Static views for com_mokosuite ──────────────────────────────────
|
|
$mokosuiteStaticViews = [
|
|
['icon' => 'icon-cogs', 'title' => 'Dashboard', 'link' => 'index.php?option=com_mokosuite'],
|
|
['icon' => 'fa-solid fa-handshake-angle', 'title' => 'Helpdesk', 'link' => 'index.php?option=com_mokosuite&view=tickets'],
|
|
['icon' => 'icon-puzzle-piece', 'title' => 'Extensions', 'link' => 'index.php?option=com_mokosuite&view=extensions'],
|
|
['icon' => 'fa-solid fa-file-code', 'title' => '.htaccess Maker', 'link' => 'index.php?option=com_mokosuite&view=htaccess'],
|
|
['icon' => 'icon-lock', 'title' => 'Privacy Guard', 'link' => 'index.php?option=com_mokosuite&view=privacy'],
|
|
['icon' => 'icon-shield-alt', 'title' => 'WAF Log', 'link' => 'index.php?option=com_mokosuite&view=waflog'],
|
|
['icon' => 'icon-database', 'title' => 'Database Tools', 'link' => 'index.php?option=com_mokosuite&view=database'],
|
|
['icon' => 'icon-trash', 'title' => 'Cache Cleanup', 'link' => 'index.php?option=com_mokosuite&view=cleanup'],
|
|
['icon' => 'icon-power-off', 'title' => 'Feature Plugins', 'link' => 'index.php?option=com_plugins&filter[folder]=system&filter[search]=mokosuite'],
|
|
];
|
|
|
|
// ── Auto-discover all Moko components from #__menu ──────────────────
|
|
$mokoComponents = [];
|
|
|
|
try
|
|
{
|
|
$db = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
|
|
|
|
$db->setQuery(
|
|
"SELECT m.id, m.title, m.link, m.level, m.parent_id, m.img, e.element"
|
|
. " FROM " . $db->quoteName('#__menu') . " m"
|
|
. " LEFT JOIN " . $db->quoteName('#__extensions') . " e ON m.component_id = e.extension_id"
|
|
. " WHERE m.client_id = 1 AND m.level >= 1 AND m.published = 1"
|
|
. " AND e.element LIKE 'com_moko%'"
|
|
. " AND e.enabled = 1"
|
|
. " ORDER BY e.element, m.level, m.lft"
|
|
);
|
|
$menuItems = $db->loadObjectList() ?: [];
|
|
|
|
// Load language files for discovered components
|
|
$lang = Factory::getLanguage();
|
|
$loadedLangs = [];
|
|
foreach ($menuItems as $m)
|
|
{
|
|
if (!isset($loadedLangs[$m->element]))
|
|
{
|
|
$lang->load($m->element . '.sys', JPATH_ADMINISTRATOR);
|
|
$lang->load($m->element, JPATH_ADMINISTRATOR);
|
|
$loadedLangs[$m->element] = true;
|
|
}
|
|
}
|
|
|
|
// Group: level 1 = component parent, level 2 = children
|
|
foreach ($menuItems as $m)
|
|
{
|
|
if ((int) $m->level === 1)
|
|
{
|
|
$mokoComponents[$m->element] = [
|
|
'id' => $m->id,
|
|
'title' => Text::_($m->title),
|
|
'link' => $m->link,
|
|
'icon' => str_replace('class:', 'icon-', $m->img ?: 'class:puzzle-piece'),
|
|
'element' => $m->element,
|
|
'children' => [],
|
|
];
|
|
}
|
|
elseif ((int) $m->level === 2 && isset($mokoComponents[$m->element]))
|
|
{
|
|
$mokoComponents[$m->element]['children'][] = [
|
|
'title' => Text::_($m->title),
|
|
'link' => $m->link,
|
|
'icon' => str_replace('class:', 'icon-', $m->img ?: 'class:cog'),
|
|
];
|
|
}
|
|
}
|
|
}
|
|
catch (\Throwable $e)
|
|
{
|
|
// Silent — menu works without auto-discovered components
|
|
}
|
|
|
|
// Override com_mokosuite children with static views
|
|
if (isset($mokoComponents['com_mokosuite']))
|
|
{
|
|
$mokoComponents['com_mokosuite']['children'] = $mokosuiteStaticViews;
|
|
$mokoComponents['com_mokosuite']['icon'] = 'icon-shield-alt';
|
|
}
|
|
else
|
|
{
|
|
// com_mokosuite not in admin menu — add it manually
|
|
$mokoComponents['com_mokosuite'] = [
|
|
'id' => 0,
|
|
'title' => 'MokoSuite',
|
|
'link' => 'index.php?option=com_mokosuite',
|
|
'icon' => 'icon-shield-alt',
|
|
'element' => 'com_mokosuite',
|
|
'children' => $mokosuiteStaticViews,
|
|
];
|
|
}
|
|
|
|
// ── Sort: com_mokosuitehq first, then alphabetical by title ─────────
|
|
$hq = null;
|
|
$rest = [];
|
|
|
|
foreach ($mokoComponents as $key => $comp)
|
|
{
|
|
if ($key === 'com_mokosuitehq')
|
|
{
|
|
$hq = $comp;
|
|
}
|
|
else
|
|
{
|
|
$rest[$key] = $comp;
|
|
}
|
|
}
|
|
|
|
usort($rest, fn($a, $b) => strcasecmp($a['title'], $b['title']));
|
|
|
|
$sorted = [];
|
|
if ($hq !== null)
|
|
{
|
|
$sorted[] = $hq;
|
|
}
|
|
foreach ($rest as $comp)
|
|
{
|
|
$sorted[] = $comp;
|
|
}
|
|
?>
|
|
|
|
<style>
|
|
.sidebar-wrapper .mokosuite-ext-item > a { padding-inline-start: 1.5rem; }
|
|
.sidebar-wrapper .mokosuite-ext-child > a { padding-inline-start: 2.5rem; }
|
|
</style>
|
|
|
|
<ul class="nav flex-column main-nav">
|
|
<?php foreach ($sorted as $comp): ?>
|
|
<?php
|
|
$compParsed = [];
|
|
parse_str(parse_url($comp['link'], PHP_URL_QUERY) ?? '', $compParsed);
|
|
$compOption = $compParsed['option'] ?? '';
|
|
$compActive = ($compOption === $currentOption);
|
|
|
|
// For com_mokosuite static children, also check the plugins filter link
|
|
if (!$compActive && $comp['element'] === 'com_mokosuite' && $currentOption === 'com_plugins')
|
|
{
|
|
$compActive = true;
|
|
}
|
|
|
|
$hasChildren = !empty($comp['children']);
|
|
$liClass = 'item mokosuite-ext-item' . ($hasChildren ? ' parent item-level-1' : '') . ($compActive ? ' mm-active' : '');
|
|
$aClass = ($hasChildren ? 'has-arrow' : 'no-dropdown') . ($compActive ? ' mm-active' : '');
|
|
$childCollapse = 'collapse-level-1 mm-collapse' . ($compActive ? ' mm-show' : '');
|
|
?>
|
|
<li class="<?php echo $liClass; ?>">
|
|
<a class="<?php echo $aClass; ?>" href="<?php echo $hasChildren ? '#' : Route::_($comp['link']); ?>"<?php echo ($compActive && !$hasChildren) ? ' aria-current="page"' : ''; ?>>
|
|
<span class="<?php echo $comp['icon']; ?>" aria-hidden="true" style="display:inline-block!important;width:1.25em;text-align:center;margin-inline-end:0.4em;"></span>
|
|
<span class="sidebar-item-title"><?php echo $comp['title']; ?></span>
|
|
</a>
|
|
<?php if ($hasChildren): ?>
|
|
<ul class="<?php echo $childCollapse; ?>" style="padding-inline-start:0.5rem;">
|
|
<?php foreach ($comp['children'] as $child): ?>
|
|
<?php
|
|
$childParsed = [];
|
|
parse_str(parse_url($child['link'], PHP_URL_QUERY) ?? '', $childParsed);
|
|
$childOption = $childParsed['option'] ?? '';
|
|
$childView = $childParsed['view'] ?? '';
|
|
|
|
$childActive = false;
|
|
if ($childOption === $currentOption)
|
|
{
|
|
$childActive = empty($childView)
|
|
? ($currentView === '' || $currentView === 'dashboard')
|
|
: ($currentView === $childView);
|
|
}
|
|
|
|
$childLiClass = 'item mokosuite-ext-child' . ($childActive ? ' mm-active' : '');
|
|
$childAClass = 'no-dropdown' . ($childActive ? ' mm-active' : '');
|
|
?>
|
|
<li class="<?php echo $childLiClass; ?>">
|
|
<a class="<?php echo $childAClass; ?>" href="<?php echo Route::_($child['link']); ?>"<?php echo $childActive ? ' aria-current="page"' : ''; ?>>
|
|
<span class="<?php echo $child['icon']; ?>" aria-hidden="true" style="display:inline-block!important;width:1.25em;text-align:center;margin-inline-end:0.4em;"></span>
|
|
<span class="sidebar-item-title"><?php echo $child['title']; ?></span>
|
|
</a>
|
|
</li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
<?php endif; ?>
|
|
</li>
|
|
<?php endforeach; ?>
|
|
</ul>
|