4850928e6d
Universal: PR Check / Branch Policy (pull_request) Failing after 2s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 27s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 12s
Universal: PR Check / Validate PR (pull_request) Failing after 11s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Project CI / Lint & Validate (pull_request) Successful in 16s
Universal: PR Check / Secret Scan (pull_request) Successful in 18s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 16s
Universal: Build & Release / Promote to RC (pull_request) Failing after 15s
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 56s
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
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: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
Components use item-level-1, sub-items use item-level-2 to align with Atum template's native sidebar indentation and styling. Claude-Session: https://claude.ai/code/session_01Jo2JpjCwfHAh2HHRSjczKq
306 lines
13 KiB
PHP
306 lines
13 KiB
PHP
<?php
|
|
/**
|
|
* MokoSuiteClient Admin Sidebar Menu
|
|
*
|
|
* Single "MokoSuite" top-level item with all Moko ecosystem components
|
|
* as collapsible children underneath.
|
|
*/
|
|
|
|
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 (ACL-gated) ──────────────────────
|
|
$user = $app->getIdentity();
|
|
$allViews = [
|
|
['icon' => 'icon-cogs', 'title' => 'Dashboard', 'link' => 'index.php?option=com_mokosuiteclient', 'acl' => 'mokosuiteclient.dashboard'],
|
|
['icon' => 'icon-puzzle-piece', 'title' => 'Extensions', 'link' => 'index.php?option=com_mokosuiteclient&view=extensions', 'acl' => 'mokosuiteclient.extensions'],
|
|
['icon' => 'fa-solid fa-file-code', 'title' => '.htaccess Maker', 'link' => 'index.php?option=com_mokosuiteclient&view=htaccess', 'acl' => 'mokosuiteclient.htaccess'],
|
|
['icon' => 'icon-shield-alt', 'title' => 'WAF Log', 'link' => 'index.php?option=com_mokosuiteclient&view=waflog', 'acl' => 'mokosuiteclient.security.waflog'],
|
|
['icon' => 'icon-lock', 'title' => 'Privacy Guard', 'link' => 'index.php?option=com_mokosuiteclient&view=privacy', 'acl' => 'core.admin'],
|
|
['icon' => 'fa-solid fa-code', 'title' => 'Snippets', 'link' => 'index.php?option=com_mokosuiteclient&view=snippets', 'acl' => 'mokosuiteclient.snippets.manage'],
|
|
['icon' => 'fa-solid fa-file-lines', 'title' => 'Templates', 'link' => 'index.php?option=com_mokosuiteclient&view=templates', 'acl' => 'mokosuiteclient.templates.manage'],
|
|
['icon' => 'fa-solid fa-right-left', 'title' => 'Replacements', 'link' => 'index.php?option=com_mokosuiteclient&view=replacements','acl' => 'mokosuiteclient.replacements.manage'],
|
|
['icon' => 'fa-solid fa-shuffle', 'title' => 'Conditions', 'link' => 'index.php?option=com_mokosuiteclient&view=conditions', 'acl' => 'mokosuiteclient.conditions.manage'],
|
|
['icon' => 'icon-database', 'title' => 'Database Tools', 'link' => 'index.php?option=com_mokosuiteclient&view=database', 'acl' => 'core.admin'],
|
|
['icon' => 'icon-trash', 'title' => 'Cache Cleanup', 'link' => 'index.php?option=com_mokosuiteclient&view=cleanup', 'acl' => 'mokosuiteclient.cache'],
|
|
['icon' => 'icon-power-off', 'title' => 'Feature Plugins', 'link' => 'index.php?option=com_plugins&filter[folder]=system&filter[search]=mokosuiteclient', 'acl' => 'core.admin'],
|
|
];
|
|
$isSuper = $user->authorise('core.admin', 'com_mokosuiteclient');
|
|
$mokosuiteclientStaticViews = array_filter($allViews, function ($v) use ($user, $isSuper) {
|
|
return $isSuper || $user->authorise($v['acl'], 'com_mokosuiteclient');
|
|
});
|
|
|
|
// ── Icon overrides (canonical icons per component) ───────────────────
|
|
$iconOverrides = [
|
|
'com_mokosuiteclient' => 'icon-shield-alt',
|
|
'com_mokosuitehq' => 'icon-tachometer-alt',
|
|
'com_mokosuitebackup' => 'icon-archive',
|
|
'com_mokosuitecrm' => 'icon-address-book',
|
|
'com_mokosuiteerp' => 'icon-briefcase',
|
|
'com_mokosuiteshop' => 'icon-shopping-cart',
|
|
'com_mokosuitepos' => 'icon-calculator',
|
|
'com_mokosuitemrp' => 'icon-cogs',
|
|
'com_mokosuitehrm' => 'icon-id-badge',
|
|
'com_mokosuiterestaurant' => 'icon-utensils',
|
|
'com_mokosuitechild' => 'icon-child',
|
|
'com_mokosuitenpo' => 'icon-heart',
|
|
'com_mokosuitefield' => 'icon-wrench',
|
|
'com_mokosuitecreate' => 'icon-paint-brush',
|
|
'com_mokosuiteforms' => 'icon-list-alt',
|
|
'com_mokosuitecommunity' => 'icon-comments',
|
|
'com_mokosuitecross' => 'icon-share-alt',
|
|
'com_mokosuiteopengraph' => 'icon-globe',
|
|
'com_mokosuitestorelocator' => 'icon-map-marker-alt',
|
|
];
|
|
|
|
$childIconMap = [
|
|
'dashboard' => 'icon-tachometer-alt',
|
|
'contacts' => 'icon-address-book',
|
|
'deals' => 'icon-handshake',
|
|
'activities' => 'icon-clock',
|
|
'tickets' => 'icon-life-ring',
|
|
'helpdesk' => 'icon-life-ring',
|
|
'products' => 'icon-box',
|
|
'orders' => 'icon-shopping-cart',
|
|
'invoices' => 'icon-file-invoice',
|
|
'inventory' => 'icon-warehouse',
|
|
'settings' => 'icon-cog',
|
|
'config' => 'icon-cog',
|
|
'options' => 'icon-sliders-h',
|
|
'reports' => 'icon-chart-bar',
|
|
'analytics' => 'icon-chart-line',
|
|
'locations' => 'icon-map-marker-alt',
|
|
'stores' => 'icon-store',
|
|
'categories' => 'icon-folder',
|
|
'forms' => 'icon-list-alt',
|
|
'submissions' => 'icon-inbox',
|
|
'emails' => 'icon-envelope',
|
|
'campaigns' => 'icon-bullhorn',
|
|
'users' => 'icon-users',
|
|
'members' => 'icon-id-card',
|
|
'employees' => 'icon-id-badge',
|
|
'donors' => 'icon-hand-holding-heart',
|
|
'donations' => 'icon-donate',
|
|
'events' => 'icon-calendar',
|
|
'tasks' => 'icon-tasks',
|
|
'projects' => 'icon-project-diagram',
|
|
'templates' => 'icon-file-alt',
|
|
'announcements'=> 'icon-bullhorn',
|
|
'plugins' => 'icon-plug',
|
|
'import' => 'icon-upload',
|
|
'export' => 'icon-download',
|
|
'log' => 'icon-list',
|
|
'backup' => 'icon-archive',
|
|
'channels' => 'icon-share-alt',
|
|
'posts' => 'icon-paper-plane',
|
|
'schedule' => 'icon-calendar-alt',
|
|
];
|
|
|
|
// ── 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() ?: [];
|
|
|
|
$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);
|
|
$compLangPath = JPATH_ADMINISTRATOR . '/components/' . $m->element;
|
|
if (is_dir($compLangPath . '/language'))
|
|
{
|
|
$lang->load($m->element . '.sys', $compLangPath);
|
|
$lang->load($m->element, $compLangPath);
|
|
}
|
|
$loadedLangs[$m->element] = true;
|
|
}
|
|
}
|
|
|
|
foreach ($menuItems as $m)
|
|
{
|
|
if ((int) $m->level === 1)
|
|
{
|
|
$icon = $iconOverrides[$m->element]
|
|
?? str_replace('class:', 'icon-', $m->img ?: 'class:puzzle-piece');
|
|
$mokoComponents[$m->element] = [
|
|
'id' => $m->id,
|
|
'title' => Text::_($m->title),
|
|
'link' => $m->link,
|
|
'icon' => $icon,
|
|
'element' => $m->element,
|
|
'children' => [],
|
|
];
|
|
}
|
|
elseif ((int) $m->level === 2 && isset($mokoComponents[$m->element]))
|
|
{
|
|
$childIcon = str_replace('class:', 'icon-', $m->img ?: '');
|
|
if ($childIcon === '' || $childIcon === 'icon-cog' || $childIcon === 'icon-cogs')
|
|
{
|
|
$parsed = [];
|
|
parse_str(parse_url($m->link, PHP_URL_QUERY) ?? '', $parsed);
|
|
$viewKey = strtolower($parsed['view'] ?? '');
|
|
$childIcon = $childIconMap[$viewKey] ?? '';
|
|
}
|
|
$mokoComponents[$m->element]['children'][] = [
|
|
'title' => Text::_($m->title),
|
|
'link' => $m->link,
|
|
'icon' => $childIcon ?: 'icon-angle-right',
|
|
];
|
|
}
|
|
}
|
|
}
|
|
catch (\Throwable $e) {}
|
|
|
|
// 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
|
|
{
|
|
$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)
|
|
{
|
|
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;
|
|
|
|
// Is ANY Moko component active?
|
|
$anyActive = false;
|
|
foreach ($sorted as $comp)
|
|
{
|
|
$p = [];
|
|
parse_str(parse_url($comp['link'], PHP_URL_QUERY) ?? '', $p);
|
|
if (($p['option'] ?? '') === $currentOption) { $anyActive = true; break; }
|
|
}
|
|
if ($currentOption === 'com_plugins') $anyActive = true;
|
|
|
|
$iconStyle = 'display:inline-block!important;width:1.25em;text-align:center;margin-inline-end:0.4em;';
|
|
?>
|
|
|
|
<style>
|
|
.sidebar-wrapper { padding-right: 0.5rem; }
|
|
</style>
|
|
|
|
<ul class="nav flex-column main-nav">
|
|
<li class="item parent item-level-1 mokosuiteclient-ext-item<?php echo $anyActive ? ' mm-active' : ''; ?>">
|
|
<a class="has-arrow<?php echo $anyActive ? ' mm-active' : ''; ?>" href="#">
|
|
<span class="icon-shield-alt" aria-hidden="true" style="<?php echo $iconStyle; ?>"></span>
|
|
<span class="sidebar-item-title">MokoSuite</span>
|
|
</a>
|
|
<ul class="collapse-level-1 mm-collapse<?php echo $anyActive ? ' mm-show' : ''; ?>" style="padding-inline-start:0.5rem;">
|
|
<?php foreach ($sorted as $comp): ?>
|
|
<?php
|
|
$compParsed = [];
|
|
parse_str(parse_url($comp['link'], PHP_URL_QUERY) ?? '', $compParsed);
|
|
$compOption = $compParsed['option'] ?? '';
|
|
$compActive = ($compOption === $currentOption);
|
|
if (!$compActive && $comp['element'] === 'com_mokosuiteclient' && $currentOption === 'com_plugins')
|
|
{
|
|
$compActive = true;
|
|
}
|
|
|
|
$hasChildren = !empty($comp['children']);
|
|
?>
|
|
<?php if ($hasChildren): ?>
|
|
<li class="item parent item-level-1 mokosuiteclient-ext-item<?php echo $compActive ? ' mm-active' : ''; ?>">
|
|
<a class="has-arrow<?php echo $compActive ? ' mm-active' : ''; ?>" href="#">
|
|
<span class="<?php echo htmlspecialchars($comp['icon'], ENT_QUOTES, 'UTF-8'); ?>" aria-hidden="true" style="<?php echo $iconStyle; ?>"></span>
|
|
<span class="sidebar-item-title"><?php echo htmlspecialchars($comp['title'], ENT_QUOTES, 'UTF-8'); ?></span>
|
|
</a>
|
|
<ul class="collapse-level-2 mm-collapse<?php echo $compActive ? ' mm-show' : ''; ?>" 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);
|
|
}
|
|
?>
|
|
<li class="item item-level-2 mokosuiteclient-ext-child<?php echo $childActive ? ' mm-active' : ''; ?>">
|
|
<a class="no-dropdown<?php echo $childActive ? ' mm-active' : ''; ?>" href="<?php echo Route::_($child['link']); ?>"<?php echo $childActive ? ' aria-current="page"' : ''; ?>>
|
|
<span class="<?php echo htmlspecialchars($child['icon'], ENT_QUOTES, 'UTF-8'); ?>" aria-hidden="true" style="<?php echo $iconStyle; ?>"></span>
|
|
<span class="sidebar-item-title"><?php echo htmlspecialchars($child['title'], ENT_QUOTES, 'UTF-8'); ?></span>
|
|
</a>
|
|
</li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
</li>
|
|
<?php else: ?>
|
|
<li class="item item-level-1 mokosuiteclient-ext-item<?php echo $compActive ? ' mm-active' : ''; ?>">
|
|
<a class="no-dropdown<?php echo $compActive ? ' mm-active' : ''; ?>" href="<?php echo Route::_($comp['link']); ?>"<?php echo $compActive ? ' aria-current="page"' : ''; ?>>
|
|
<span class="<?php echo htmlspecialchars($comp['icon'], ENT_QUOTES, 'UTF-8'); ?>" aria-hidden="true" style="<?php echo $iconStyle; ?>"></span>
|
|
<span class="sidebar-item-title"><?php echo htmlspecialchars($comp['title'], ENT_QUOTES, 'UTF-8'); ?></span>
|
|
</a>
|
|
</li>
|
|
<?php endif; ?>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
</li>
|
|
</ul>
|