diff --git a/e107_handlers/admin_ui.php b/e107_handlers/admin_ui.php index 68d6d934b..440dd0e70 100755 --- a/e107_handlers/admin_ui.php +++ b/e107_handlers/admin_ui.php @@ -1618,6 +1618,7 @@ class e_admin_dispatcher * * @return string|array */ + public function renderMenu($debug = false) { @@ -1629,10 +1630,12 @@ class e_admin_dispatcher // First loop: Build $var without permissions checks foreach($adminMenu as $key => $val) { - $parentKey = ''; + + // $parentKey = ''; $tmp = explode('/', trim($key, '/'), 2); // mode/action + $isSubItem = !empty($val['group']); if($isSubItem) @@ -1640,6 +1643,8 @@ class e_admin_dispatcher $parentKey = $val['group'] ?? ''; } + + if(isset($val['selected']) && $val['selected']) { $selected = $val['selected'] === true ? $key : $val['selected']; @@ -1650,7 +1655,7 @@ class e_admin_dispatcher if($isSubItem) { - if(!isset($var[$parentKey])) + if(empty($var[$parentKey])) { $var[$parentKey] = [ 'text' => 'Unknown', @@ -1658,21 +1663,43 @@ class e_admin_dispatcher 'link_id' => str_replace('/', '-', $parentKey) ]; } - $subKey = str_replace($parentKey . '/', '', $key); - $var[$parentKey]['sub'][$subKey] = $processedItem; + + // Use full key for sub-items to match $adminMenu + $subKey = $key; + if(!is_array($var[$parentKey])) + { + $var[$parentKey] = []; + } + + if(!isset($var[$parentKey]['sub'][$subKey])) + { + $var[$parentKey]['sub'][$subKey] = $processedItem; + } + } else { - $var[$key] = $processedItem; + if(!isset($var[$key])) + { + $var[$key] = $processedItem; + } + } + + } + if(!$selected) { $request = $this->getRequest(); $selected = $request->getMode() . '/' . $request->getAction(); } + // Apply permissions restrictions + $var = $this->restrictMenuAccess($var, $adminMenu); + + // Second loop: Handle links and collapse attributes without permissions checks foreach($var as $key => &$item) { @@ -1705,8 +1732,6 @@ class e_admin_dispatcher } } - // Apply permissions restrictions - $var = $this->restrictMenuAccess($var, $adminMenu); if(empty($var)) { @@ -1753,7 +1778,13 @@ class e_admin_dispatcher { // Check top-level item permissions $val = $adminMenu[$key] ?? []; - if((isset($val['perm']) && $val['perm'] !== '' && !$this->hasPerms($val['perm'])) || !$this->hasModeAccess(explode('/', trim($key, '/'), 2)[0]) || !$this->hasRouteAccess($key)) + + // Handle single-segment keys (e.g., 'treatment') by using the key as the mode + $mode = strpos($key, '/') !== false ? explode('/', trim($key, '/'), 2)[0] : $key; + + // Default to true for hasPerms if perm is unset or empty + $hasPerms = isset($val['perm']) && $val['perm'] !== '' ? $this->hasPerms($val['perm']) : true; + if(!$hasPerms || !$this->hasModeAccess($mode) || !$this->hasRouteAccess($key)) { unset($var[$key]); continue; @@ -1766,7 +1797,17 @@ class e_admin_dispatcher foreach($item['sub'] as $subKey => &$subItem) { $subVal = $adminMenu[$subKey] ?? []; - if(isset($subVal['perm']) && $this->hasPerms($subVal['perm']) && $this->hasRouteAccess($subKey) && $this->hasRouteAccess($parentKey)) + // Log permissions check for sub-item only when removed + if(!isset($subVal['group']) || $subVal['group'] !== $parentKey) + { + unset($item['sub'][$subKey]); + // fwrite(STDOUT, "B. restrictMenuAccess: removing subKey=$subKey, parent=$parentKey, group=" . ($subVal['group'] ?? 'none') . ", perm=" . ($subVal['perm'] ?? 'none') . ", hasPerms=" . var_export(isset($subVal['perm']) && $subVal['perm'] !== '' ? $this->hasPerms($subVal['perm']) : true, true) . ", hasModeAccess=" . var_export($this->hasModeAccess($subMode ?? $subKey), true) . ", hasRouteAccess=" . var_export($this->hasRouteAccess($subKey), true) . ", parentRouteAccess=" . var_export($this->hasRouteAccess($parentKey), true) . "\n"); + continue; + } + $subMode = strpos($subKey, '/') !== false ? explode('/', trim($subKey, '/'), 2)[0] : $subKey; + // Default to true for hasPerms if perm is unset or empty + $hasPerms = isset($subVal['perm']) && $subVal['perm'] !== '' ? $this->hasPerms($subVal['perm']) : true; + if($hasPerms && $this->hasModeAccess($subMode) && $this->hasRouteAccess($subKey) && $this->hasRouteAccess($parentKey)) { $hasValidSubItems = true; } @@ -1780,14 +1821,12 @@ class e_admin_dispatcher if(!$hasValidSubItems || empty($item['sub'])) { unset($var[$key]); - continue; } } } return $var; } - /** * @param $val * @param $key diff --git a/e107_tests/tests/unit/e_admin_dispatcherTest.php b/e107_tests/tests/unit/e_admin_dispatcherTest.php index 2b2d58c1c..8b77cfada 100644 --- a/e107_tests/tests/unit/e_admin_dispatcherTest.php +++ b/e107_tests/tests/unit/e_admin_dispatcherTest.php @@ -38,7 +38,7 @@ class e_admin_dispatcherTest extends \Codeception\Test\Unit ->willReturnCallback(function ($perm) { - return $perm === 'Y'; + return $perm === 'P'; }); $this->dp->expects($this->any()) @@ -86,19 +86,19 @@ class e_admin_dispatcherTest extends \Codeception\Test\Unit $adminMenu = [ 'main/list' => ['caption' => 'Manage', 'perm' => '0'], - 'main/create' => ['caption' => 'LAN_CREATE', 'perm' => 'Y'], + 'main/create' => ['caption' => 'LAN_CREATE', 'perm' => 'P'], 'main/prefs' => ['caption' => 'Settings', 'perm' => 'J', 'icon' => 'fa-cog'], 'main/custom' => ['caption' => 'Custom Pages', 'perm' => '0', 'icon' => 'fa-asterisk'], - 'main/custom1' => ['group' => 'main/custom', 'caption' => 'Custom Page 1', 'perm' => 'Y'], + 'main/custom1' => ['group' => 'main/custom', 'caption' => 'Custom Page 1', 'perm' => 'P'], 'main/custom2' => ['group' => 'main/custom', 'caption' => 'Custom Page 2', 'perm' => '0'], - 'other/custom' => ['caption' => 'Other Pages', 'perm' => 'Y', 'icon' => 'fa-asterisk'], // should be ignored since no access to sub-items. 'other/custom1' => ['group' => 'other/custom', 'caption' => 'Other Page 1', 'perm' => '0'], + 'other/custom' => ['caption' => 'Other Pages', 'perm' => 'P', 'icon' => 'fa-asterisk'], // should be ignored since no access to sub-items. 'other/custom1' => ['group' => 'other/custom', 'caption' => 'Other Page 1', 'perm' => '0'], 'other/custom2' => ['group' => 'other/custom', 'caption' => 'Other Page 2', 'perm' => '0'], - 'misc/custom' => ['caption' => 'Misc Pages', 'perm' => 'Y', 'icon' => 'fa-asterisk'], + 'misc/custom' => ['caption' => 'Misc Pages', 'perm' => 'P', 'icon' => 'fa-asterisk'], 'misc/custom1' => ['group' => 'misc/custom', 'caption' => 'misc Page 1', 'perm' => '0'], - 'misc/custom2' => ['group' => 'misc/custom', 'caption' => 'misc Page 2', 'perm' => 'Y'], + 'misc/custom2' => ['group' => 'misc/custom', 'caption' => 'misc Page 2', 'perm' => 'P'], ]; // Use real setMenuData @@ -139,24 +139,24 @@ class e_admin_dispatcherTest extends \Codeception\Test\Unit $adminMenu = [ 'main/list' => ['caption' => 'Manage', 'perm' => '0'], - 'main/create' => ['caption' => 'LAN_CREATE', 'perm' => 'Y'], - 'main/prefs' => ['caption' => 'Settings', 'perm' => 'Y', 'icon' => 'fa-cog'], + 'main/create' => ['caption' => 'LAN_CREATE', 'perm' => 'P'], + 'main/prefs' => ['caption' => 'Settings', 'perm' => 'P', 'icon' => 'fa-cog'], 'main/custom' => ['caption' => 'Custom Pages', 'perm' => '0', 'icon' => 'fa-asterisk'], - 'main/custom1' => ['group' => 'main/custom', 'caption' => 'Custom Page 1', 'perm' => 'Y'], - 'main/custom2' => ['group' => 'main/custom', 'caption' => 'Custom Page 2', 'perm' => 'Y'], + 'main/custom1' => ['group' => 'main/custom', 'caption' => 'Custom Page 1', 'perm' => 'P'], + 'main/custom2' => ['group' => 'main/custom', 'caption' => 'Custom Page 2', 'perm' => 'P'], - 'other/custom' => ['caption' => 'Other Pages', 'perm' => 'Y', 'icon' => 'fa-asterisk'], - 'other/custom1' => ['group' => 'other/custom', 'caption' => 'Other Page 1', 'perm' => 'Y'], - 'other/custom2' => ['group' => 'other/custom', 'caption' => 'Other Page 2', 'perm' => 'Y'], + 'other/custom' => ['caption' => 'Other Pages', 'perm' => 'P', 'icon' => 'fa-asterisk'], + 'other/custom1' => ['group' => 'other/custom', 'caption' => 'Other Page 1', 'perm' => 'P'], + 'other/custom2' => ['group' => 'other/custom', 'caption' => 'Other Page 2', 'perm' => 'P'], - 'misc/custom' => ['caption' => 'Misc Pages', 'perm' => 'Y', 'icon' => 'fa-asterisk'], - 'misc/custom1' => ['group' => 'misc/custom', 'caption' => 'misc Page 1', 'perm' => 'Y'], - 'misc/custom2' => ['group' => 'misc/custom', 'caption' => 'misc Page 2', 'perm' => 'Y'], + 'misc/custom' => ['caption' => 'Misc Pages', 'perm' => 'P', 'icon' => 'fa-asterisk'], + 'misc/custom1' => ['group' => 'misc/custom', 'caption' => 'misc Page 1', 'perm' => 'P'], + 'misc/custom2' => ['group' => 'misc/custom', 'caption' => 'misc Page 2', 'perm' => 'P'], - 'cat/custom' => ['caption' => 'Category Pages', 'perm' => 'Y', 'icon' => 'fa-asterisk'], - 'cat/custom1' => ['group' => 'cat/custom', 'caption' => 'Category Page 1', 'perm' => 'Y'], - 'cat/custom2' => ['group' => 'cat/custom', 'caption' => 'Category Page 2', 'perm' => 'Y'], + 'cat/custom' => ['caption' => 'Category Pages', 'perm' => 'P', 'icon' => 'fa-asterisk'], + 'cat/custom1' => ['group' => 'cat/custom', 'caption' => 'Category Page 1', 'perm' => 'P'], + 'cat/custom2' => ['group' => 'cat/custom', 'caption' => 'Category Page 2', 'perm' => 'P'], 'treatment' => array('caption'=> "Treatment", 'perm' => 'P', 'icon'=>'fas-syringe'), 'treatment/day' => array('group'=>'treatment', 'caption'=> "Treatment Today (Status)", 'perm' => 'P', 'icon'=>'fas-syringe'), @@ -208,6 +208,7 @@ class e_admin_dispatcherTest extends \Codeception\Test\Unit $result = $this->dp->renderMenu(true); + $this::assertNotEmpty($result, 'Render menu result is empty'); $this::assertArrayNotHasKey('main/list', $result, 'Main list menu item should NOT be present'); $this::assertNotEmpty($result['main/create'], 'Main create menu item should be present'); @@ -216,7 +217,10 @@ class e_admin_dispatcherTest extends \Codeception\Test\Unit $this::assertArrayNotHasKey('other/custom', $result, 'Other custom group should NOT be present'); $this::assertArrayNotHasKey('cat/custom', $result, 'Category group should NOT be present'); + $this::assertArrayHasKey('treatment', $result, 'Treatment group should be present '); // This is failing. - // $this::assertArrayHasKey('treatment/day', $result, 'Treatment day sub-item should be present'); // This is failing. + $this::assertArrayHasKey('schedule/week', $result['treatment']['sub'], 'Treatment Today (Status) should be present '); // This is failing. + + $this::assertArrayHasKey('treatment/day', $result['treatment']['sub'], 'Treatment Today (Scheduled) should be present'); // This is failing. } } \ No newline at end of file diff --git a/e107_themes/bootstrap3/css/modern-dark-2.css b/e107_themes/bootstrap3/css/modern-dark-2.css index d2d063477..85222c644 100644 --- a/e107_themes/bootstrap3/css/modern-dark-2.css +++ b/e107_themes/bootstrap3/css/modern-dark-2.css @@ -1516,7 +1516,7 @@ html { height: auto !important; } body { margin-top: 51px; padding: 0 !important; background: #2f2f2f } .navbar-brand { padding: -10px 17px !important; +10px 16px !important; }