@@ -34,7 +34,7 @@ class NavigationManager implements INavigationManager {
3434 * negative to keep this group in front of everything else.
3535 * Apps that are not listed keep their own order.
3636 */
37- private const DEFAULT_APP_ORDER = [
37+ private const array DEFAULT_APP_ORDER = [
3838 // Basics
3939 'dashboard ' => -100 ,
4040 'files ' => -99 ,
@@ -54,15 +54,20 @@ class NavigationManager implements INavigationManager {
5454 'activity ' => -88 ,
5555 ];
5656
57+ protected ?string $ activeEntry = null ;
5758 /** @var array<string, NavigationEntryOutput> */
5859 protected array $ entries = [];
5960 /** @var list<callable(): NavigationEntry> */
6061 protected array $ closureEntries = [];
61- protected ?string $ activeEntry = null ;
62- protected array $ unreadCounters = [];
63- protected bool $ init = false ;
6462 /** User defined app order (cached for the `add` function) */
65- private ?array $ customAppOrder = null ;
63+ protected ?array $ customAppOrder = null ;
64+ /** @var array<string, int> */
65+ protected array $ unreadCounters = [];
66+
67+ /** true if the internal state has been initialized */
68+ protected bool $ initAppOrderDone = false ;
69+ /** true if all apps have been loaded by the App Manager */
70+ protected bool $ initSetupDone = false ;
6671 /** List of loaded app info */
6772 private array $ loadedAppInfo = [];
6873
@@ -80,11 +85,12 @@ public function __construct(
8085
8186 #[Override]
8287 public function add (array |callable $ entry ): void {
83- if ($ entry instanceof \Closure ) {
88+ if (is_callable ( $ entry) ) {
8489 $ this ->closureEntries [] = $ entry ;
8590 return ;
8691 }
87- $ this ->init ();
92+ // if needed initialize the internal state to allow setting app order and default app
93+ $ this ->initCustomAppOrder ();
8894
8995 $ id = $ entry ['id ' ];
9096
@@ -201,7 +207,7 @@ public function clear(bool $resetInit = true): void {
201207
202208 if ($ resetInit ) {
203209 $ this ->loadedAppInfo = [];
204- $ this ->init = false ;
210+ $ this ->initAppOrderDone = false ;
205211 }
206212 }
207213
@@ -219,11 +225,11 @@ public function getActiveEntry(): ?string {
219225 * Initialize the internal state.
220226 * This loads the default app mapping and user mapping for app ordering.
221227 */
222- private function init (): void {
223- if ($ this ->init ) {
228+ private function initCustomAppOrder (): void {
229+ if ($ this ->initAppOrderDone ) {
224230 return ;
225231 }
226- $ this ->init = true ;
232+ $ this ->initAppOrderDone = true ;
227233
228234 if ($ this ->customAppOrder === null ) {
229235 if ($ this ->userSession ->isLoggedIn ()) {
@@ -237,20 +243,22 @@ private function init(): void {
237243
238244 /**
239245 * Setup the navigation manager.
246+ *
240247 * @internal - This is only used by Nextcloud core to setup the navigation manager. It is not intended for use by apps.
241248 */
242249 public function setup (): void {
243- // Resolve app navigation closures
244- while ($ c = array_pop ($ this ->closureEntries )) {
245- $ this ->add ($ c ());
246- }
247-
248250 // Resolve dynamically added navigation entries via event listeners
249251 $ this ->eventDispatcher ->dispatchTyped (new LoadAdditionalEntriesEvent ());
252+
253+ // mark setup as done to allow performance optimizations
254+ $ this ->initSetupDone = true ;
250255 }
251256
252257 /**
253- * Resolve classic info.xml based navigation entires.
258+ * Resolve app navigation entries.
259+ *
260+ * This is called every time by any getter as some code for legacy reasons relies
261+ * on the navigation entries being available before the app loading is finished.
254262 * Some code relies on this to be available earlier then the app loading finished.
255263 * So we need to resolve the navigation entries here, even if not all apps are loaded yet.
256264 */
@@ -342,6 +350,19 @@ private function resolveAppNavigationEntries(): void {
342350 ));
343351 }
344352 }
353+
354+ // once all apps are loaded we can resolve the app navigation closures
355+ if ($ this ->initSetupDone ) {
356+ // This has to be done on every call,
357+ // as apps might add new navigation entries via closures at any time
358+ while ($ c = array_pop ($ this ->closureEntries )) {
359+ try {
360+ $ this ->add ($ c ());
361+ } catch (\Throwable $ e ) {
362+ $ this ->logger ->error ('Failed to add navigation entry from closure ' , ['exception ' => $ e ]);
363+ }
364+ }
365+ }
345366 }
346367
347368 private function isAdmin (): bool {
0 commit comments