cache ??= cache(); } public function getMenuForRoles(array $roles): array { $roles = array_map(fn($r)=>strtolower(trim((string)$r)), $roles); $cacheKey = 'navbar_' . md5(json_encode($roles)); if ($menu = $this->cache->get($cacheKey)) { return $menu; } // Which items this user can see $allowedIds = $this->mapModel->getNavItemIdsForRoles($roles); if (empty($allowedIds)) return []; // Load all enabled items, then filter. The builder controls this order. $rows = $this->navModel->where('is_enabled', 1) ->orderBy('sort_order', 'ASC') ->orderBy('label', 'ASC') ->orderBy('id', 'ASC') ->findAll(); // index by id $byId = []; foreach ($rows as $r) { if (in_array($r['id'], $allowedIds, true)) { $r['children'] = []; $byId[$r['id']] = $r; } } // Build tree $tree = []; foreach ($byId as $id => &$node) { $pid = $node['menu_parent_id']; if ($pid && isset($byId[$pid])) { $byId[$pid]['children'][] = &$node; } else { $tree[] = &$node; } } unset($node); $this->sortTreeByOrder($tree); $this->cache->save($cacheKey, $tree, 300); // 5 minutes return $tree; } private function sortTreeByOrder(array &$nodes): void { usort($nodes, function ($a, $b) { $order = ((int) ($a['sort_order'] ?? 0)) <=> ((int) ($b['sort_order'] ?? 0)); if ($order !== 0) { return $order; } $label = strnatcasecmp( $this->labelSortKey((string) ($a['label'] ?? '')), $this->labelSortKey((string) ($b['label'] ?? '')) ); if ($label !== 0) { return $label; } return ((int) ($a['id'] ?? 0)) <=> ((int) ($b['id'] ?? 0)); }); foreach ($nodes as &$node) { if (!empty($node['children']) && is_array($node['children'])) { $this->sortTreeByOrder($node['children']); } } unset($node); } private function labelSortKey(string $label): string { $label = trim(preg_replace('/\s+/', ' ', $label) ?? ''); $label = preg_replace('/^[^[:alnum:]]+/u', '', $label) ?? $label; $label = preg_replace('/^(the|an|a)\s+/iu', '', $label) ?? $label; return function_exists('mb_strtolower') ? mb_strtolower($label, 'UTF-8') : strtolower($label); } public function clearCache(): void { // simplest: flush; or if you have a tagged cache, clear only keys cache()->clean(); } }