520422db8c
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 6s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 8s
Universal: Auto Version Bump / Version Bump (push) Successful in 13s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 11s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 22s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 36s
Generic: Project CI / Lint & Validate (pull_request) Successful in 39s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 40s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 42s
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (push) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been cancelled
Platform: moko-platform CI / CI Summary (push) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Has been cancelled
Joomla: Extension CI / PHPStan Analysis (pull_request) Has been cancelled
Joomla: Extension CI / Build RC Pre-Release (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Has been cancelled
Platform: moko-platform CI / CI Summary (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
226 lines
7.9 KiB
PHP
226 lines
7.9 KiB
PHP
<?php
|
|
/**
|
|
* MokoSuiteClient Admin Sidebar Menu
|
|
*
|
|
* Each installed Moko component gets its own top-level collapsible section.
|
|
* com_mokosuitehq is always pinned first. com_mokosuiteclient 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_mokosuiteclient ──────────────────────────────────
|
|
$mokosuiteclientStaticViews = [
|
|
['icon' => 'icon-cogs', 'title' => 'Dashboard', 'link' => 'index.php?option=com_mokosuiteclient'],
|
|
['icon' => 'fa-solid fa-handshake-angle', 'title' => 'Helpdesk', 'link' => 'index.php?option=com_mokosuiteclient&view=tickets'],
|
|
['icon' => 'icon-puzzle-piece', 'title' => 'Extensions', 'link' => 'index.php?option=com_mokosuiteclient&view=extensions'],
|
|
['icon' => 'fa-solid fa-file-code', 'title' => '.htaccess Maker', 'link' => 'index.php?option=com_mokosuiteclient&view=htaccess'],
|
|
['icon' => 'icon-lock', 'title' => 'Privacy Guard', 'link' => 'index.php?option=com_mokosuiteclient&view=privacy'],
|
|
['icon' => 'icon-shield-alt', 'title' => 'WAF Log', 'link' => 'index.php?option=com_mokosuiteclient&view=waflog'],
|
|
['icon' => 'icon-database', 'title' => 'Database Tools', 'link' => 'index.php?option=com_mokosuiteclient&view=database'],
|
|
['icon' => 'icon-trash', 'title' => 'Cache Cleanup', 'link' => 'index.php?option=com_mokosuiteclient&view=cleanup'],
|
|
['icon' => 'icon-power-off', 'title' => 'Feature Plugins', 'link' => 'index.php?option=com_plugins&filter[folder]=system&filter[search]=mokosuiteclient'],
|
|
];
|
|
|
|
// ── 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_mokosuiteclient children with static views
|
|
if (isset($mokoComponents['com_mokosuiteclient']))
|
|
{
|
|
$mokoComponents['com_mokosuiteclient']['children'] = $mokosuiteclientStaticViews;
|
|
$mokoComponents['com_mokosuiteclient']['icon'] = 'icon-shield-alt';
|
|
}
|
|
else
|
|
{
|
|
// com_mokosuiteclient not in admin menu — add it manually
|
|
$mokoComponents['com_mokosuiteclient'] = [
|
|
'id' => 0,
|
|
'title' => 'MokoSuite',
|
|
'link' => 'index.php?option=com_mokosuiteclient',
|
|
'icon' => 'icon-shield-alt',
|
|
'element' => 'com_mokosuiteclient',
|
|
'children' => $mokosuiteclientStaticViews,
|
|
];
|
|
}
|
|
|
|
// ── Sort: HQ first, Client second, then alphabetical ─────────
|
|
$hq = null;
|
|
$client = null;
|
|
$rest = [];
|
|
|
|
foreach ($mokoComponents as $key => $comp)
|
|
{
|
|
// Shorten display titles:
|
|
// MokoSuiteClient → MokoSuite, MokoSuiteHQ → MokoHQ
|
|
// Everything else: MokoSuiteBackup → Backup, MokoSuiteOpenGraph → OpenGraph
|
|
if ($key === 'com_mokosuiteclient')
|
|
{
|
|
$comp['title'] = 'MokoSuite';
|
|
}
|
|
elseif ($key === 'com_mokosuitehq')
|
|
{
|
|
$comp['title'] = preg_replace('/^MokoSuite/i', 'Moko', $comp['title']);
|
|
}
|
|
else
|
|
{
|
|
$comp['title'] = preg_replace('/^MokoSuite\s*/i', '', $comp['title']);
|
|
}
|
|
|
|
if ($key === 'com_mokosuitehq')
|
|
{
|
|
$hq = $comp;
|
|
}
|
|
elseif ($key === 'com_mokosuiteclient')
|
|
{
|
|
$client = $comp;
|
|
}
|
|
else
|
|
{
|
|
$rest[$key] = $comp;
|
|
}
|
|
}
|
|
|
|
usort($rest, fn($a, $b) => strcasecmp($a['title'], $b['title']));
|
|
|
|
$sorted = [];
|
|
if ($hq !== null)
|
|
{
|
|
$sorted[] = $hq;
|
|
}
|
|
if ($client !== null)
|
|
{
|
|
$sorted[] = $client;
|
|
}
|
|
foreach ($rest as $comp)
|
|
{
|
|
$sorted[] = $comp;
|
|
}
|
|
?>
|
|
|
|
<style>
|
|
.sidebar-wrapper .mokosuiteclient-ext-child > a { padding-inline-start: 0.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_mokosuiteclient static children, also check the plugins filter link
|
|
if (!$compActive && $comp['element'] === 'com_mokosuiteclient' && $currentOption === 'com_plugins')
|
|
{
|
|
$compActive = true;
|
|
}
|
|
|
|
$hasChildren = !empty($comp['children']);
|
|
$liClass = 'item mokosuiteclient-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 mokosuiteclient-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>
|