@@ -122,14 +122,52 @@ public function menu($location = 'sidebar', $parent = null, int $maxDepth = 2):
122122 }
123123 if (!$ eligible ) return [];
124124
125- // 2) Build parent->children map among eligible routes
125+ // --- NEW: helper to promote parents to nearest eligible ancestor(s) ---
126+ $ resolveParents = function (array $ declared ) use ($ all , $ eligible ): array {
127+ $ result = [];
128+ $ queue = $ declared ;
129+ $ seen = [];
130+ while ($ queue ) {
131+ $ cur = array_shift ($ queue );
132+ if (!is_string ($ cur ) || $ cur === '' || isset ($ seen [$ cur ])) continue ;
133+ $ seen [$ cur ] = true ;
134+
135+ if (isset ($ eligible [$ cur ])) {
136+ // found an eligible ancestor for this branch
137+ $ result [$ cur ] = true ;
138+ continue ;
139+ }
140+
141+ // climb up if we know about this route in $all
142+ if (!isset ($ all [$ cur ])) continue ;
143+ $ pp = $ all [$ cur ]['parent ' ] ?? null ;
144+ if ($ pp === null ) continue ;
145+
146+ foreach (is_array ($ pp ) ? $ pp : [$ pp ] as $ up ) {
147+ if (is_string ($ up ) && $ up !== '' ) $ queue [] = $ up ;
148+ }
149+ }
150+ return array_keys ($ result );
151+ };
152+
153+ // 2) Build parent->children map among eligible routes (with promotion)
126154 $ children = []; // parentRoute => [childRoute...]
127155 foreach ($ eligible as $ r => $ _ ) $ children [$ r ] = [];
156+
157+ // keep a promoted-parents cache to avoid recomputing
158+ $ effParentCache = [];
159+
128160 foreach ($ eligible as $ child => $ node ) {
129- foreach ($ node ['parents ' ] as $ par ) {
130- if (isset ($ eligible [$ par ])) $ children [$ par ][] = $ child ;
161+ $ declared = $ node ['parents ' ];
162+ $ eff = $ effParentCache [$ child ] ?? $ resolveParents ($ declared );
163+ $ effParentCache [$ child ] = $ eff ;
164+
165+ foreach ($ eff as $ parEff ) {
166+ // only link to eligible parents
167+ if (isset ($ children [$ parEff ])) $ children [$ parEff ][] = $ child ;
131168 }
132169 }
170+
133171 $ sortKeys = function (array &$ arr ) { ksort ($ arr , SORT_NATURAL | SORT_FLAG_CASE ); };
134172 $ sortList = function (array &$ list ) { sort ($ list , SORT_NATURAL | SORT_FLAG_CASE ); };
135173 foreach ($ children as &$ lst ) $ sortList ($ lst );
@@ -138,28 +176,45 @@ public function menu($location = 'sidebar', $parent = null, int $maxDepth = 2):
138176 // 3) Determine start nodes
139177 $ starts = [];
140178 if ($ parent === null ) {
141- // global roots = nodes that have no eligible parent
179+ // global roots = nodes that have no eligible (promoted) parent
142180 foreach ($ eligible as $ route => $ node ) {
143- $ hasEligibleParent = false ;
144- foreach ($ node ['parents ' ] as $ p ) {
145- if (isset ($ eligible [$ p ])) { $ hasEligibleParent = true ; break ; }
146- }
147- if (!$ hasEligibleParent ) $ starts [] = $ route ;
181+ $ eff = $ effParentCache [$ route ] ?? $ resolveParents ($ node ['parents ' ]);
182+ if (empty ($ eff )) $ starts [] = $ route ;
148183 }
149184 $ sortList ($ starts );
150185 } else {
186+ // pick descendants under the requested parent, even if that parent itself is not eligible
151187 $ want = is_array ($ parent ) ? $ parent : [$ parent ];
152188 $ seen = [];
189+
190+ // If the requested parent is eligible, we can directly use $children
153191 foreach ($ want as $ p ) {
154192 if (isset ($ children [$ p ])) {
155- foreach ($ children [$ p ] as $ c ) { $ seen [$ c ] = true ; }
156- } else {
157- // if parent isn't itself eligible, include any eligible that declares it as parent
158- foreach ($ eligible as $ route => $ node ) {
159- if (in_array ($ p , $ node ['parents ' ], true )) $ seen [$ route ] = true ;
193+ foreach ($ children [$ p ] as $ c ) $ seen [$ c ] = true ;
194+ }
195+
196+ // Also include any eligible node that has $p in its ancestry chain (promoted)
197+ foreach ($ eligible as $ route => $ node ) {
198+ // Check if $p appears in ancestry by climbing from declared parents
199+ $ queue = $ node ['parents ' ];
200+ $ visited = [];
201+ $ found = false ;
202+ while ($ queue && !$ found ) {
203+ $ cur = array_shift ($ queue );
204+ if (isset ($ visited [$ cur ])) continue ;
205+ $ visited [$ cur ] = true ;
206+ if ($ cur === $ p ) { $ found = true ; break ; }
207+ if (!isset ($ all [$ cur ])) continue ;
208+ $ pp = $ all [$ cur ]['parent ' ] ?? null ;
209+ if ($ pp === null ) continue ;
210+ foreach (is_array ($ pp ) ? $ pp : [$ pp ] as $ up ) {
211+ if (is_string ($ up ) && $ up !== '' ) $ queue [] = $ up ;
212+ }
160213 }
214+ if ($ found ) $ seen [$ route ] = true ;
161215 }
162216 }
217+
163218 $ starts = array_keys ($ seen );
164219 $ sortList ($ starts );
165220 }
@@ -185,7 +240,6 @@ public function menu($location = 'sidebar', $parent = null, int $maxDepth = 2):
185240 if (isset ($ visited [$ cur ])) continue ;
186241 $ visited [$ cur ] = true ;
187242 $ out [$ cur ] = $ makeLeaf ($ cur );
188- // enqueue children (we want *all* descendants in flat mode)
189243 foreach ($ children [$ cur ] ?? [] as $ ch ) $ queue [] = $ ch ;
190244 }
191245 $ sortKeys ($ out );
@@ -195,7 +249,7 @@ public function menu($location = 'sidebar', $parent = null, int $maxDepth = 2):
195249 // 4b) Depth >= 2: build nested tree up to $maxDepth; deeper nodes are omitted
196250 $ buildTree = function (string $ route , int $ depth ) use (&$ buildTree , $ maxDepth , $ children , $ makeLeaf ): array {
197251 $ node = $ makeLeaf ($ route );
198- if ($ depth >= $ maxDepth ) return $ node ; // reached cap; omit deeper nodes
252+ if ($ depth >= $ maxDepth ) return $ node ;
199253 $ items = [];
200254 foreach ($ children [$ route ] ?? [] as $ ch ) {
201255 $ items [$ ch ] = $ buildTree ($ ch , $ depth + 1 );
@@ -215,7 +269,6 @@ public function menu($location = 'sidebar', $parent = null, int $maxDepth = 2):
215269 return $ result ;
216270 }
217271
218-
219272 /**
220273 * Create Crumbs
221274 *
0 commit comments