Skip to content

Commit 80e3bcd

Browse files
committed
fix(NavigationManager): only resolve navigations of booted apps
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
1 parent 5467d41 commit 80e3bcd

2 files changed

Lines changed: 40 additions & 16 deletions

File tree

lib/private/NavigationManager.php

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class NavigationManager implements INavigationManager {
3939
private ?array $customAppOrder = null;
4040
/** List of loaded app info */
4141
private array $loadedAppInfo = [];
42+
private bool $additionalEntriesLoaded = false;
4243

4344
public function __construct(
4445
protected IAppManager $appManager,
@@ -206,35 +207,50 @@ private function init(): void {
206207
* Resolve the app navigation entries from closures and info.xml files.
207208
*/
208209
private function resolveAppNavigationEntries(): void {
209-
// Resolve app navigation closures
210-
while ($c = array_pop($this->closureEntries)) {
211-
$this->add($c());
212-
}
210+
$this->resolveAppInfoEntries();
211+
212+
// we do not really know the current bootstrapping state
213+
// but we know that the files app is always enabled and loaded when "filesystem" is loaded thus the server is ready or close-to-ready.
214+
if ($this->appManager->isAppLoaded('files')) {
215+
// Resolve app navigation closures
216+
while ($c = array_pop($this->closureEntries)) {
217+
$this->add($c());
218+
}
213219

214-
// Resolve dynamically added navigation entries via event listeners
215-
if ($this->loadedAppInfo === []) {
216-
$this->eventDispatcher->dispatchTyped(new LoadAdditionalEntriesEvent());
220+
// Resolve dynamically added navigation entries via event listeners
221+
if (!$this->additionalEntriesLoaded) {
222+
$this->additionalEntriesLoaded = true;
223+
$this->eventDispatcher->dispatchTyped(new LoadAdditionalEntriesEvent());
224+
}
217225
}
226+
}
218227

219-
// Resolve classic info.xml based navigation entries
228+
/**
229+
* Resolve classic info.xml based navigation entires
230+
*/
231+
private function resolveAppInfoEntries(): void {
220232
if ($this->userSession->isLoggedIn()) {
221233
$user = $this->userSession->getUser();
222234
$apps = $this->appManager->getEnabledAppsForUser($user);
223235
} else {
224236
$apps = $this->appManager->getEnabledApps();
225237
}
226238

227-
foreach ($apps as $app) {
228-
// skip already loaded apps
229-
if (in_array($app, $this->loadedAppInfo)) {
230-
continue;
231-
}
239+
$appsToLoad = array_diff($apps, $this->loadedAppInfo);
240+
$appsToLoad = array_filter($appsToLoad, $this->appManager->isAppLoaded(...));
241+
if ($appsToLoad === []) {
242+
return;
243+
}
232244

245+
foreach ($appsToLoad as $app) {
233246
// load plugins and collections from info.xml
234247
$info = $this->appManager->getAppInfo($app);
235248
if (!isset($info['navigations']['navigation'])) {
249+
// this app does not have any navigation entries, skip it
250+
$this->loadedAppInfo[] = $app;
236251
continue;
237252
}
253+
238254
foreach ($info['navigations']['navigation'] as $key => $nav) {
239255
$nav['type'] = $nav['type'] ?? 'link';
240256
if (!isset($nav['name'])) {
@@ -250,8 +266,11 @@ private function resolveAppNavigationEntries(): void {
250266
}
251267
$id = $nav['id'] ?? $app . ($key === 0 ? '' : $key);
252268
$order = $nav['order'] ?? 100;
253-
$type = $nav['type'];
254-
$route = !empty($nav['route']) ? $this->urlGenerator->linkToRoute($nav['route']) : '';
269+
$type = $nav['type'] ?? 'link';
270+
$route = $nav['route'] ?? '';
271+
if ($route !== '') {
272+
$route = $this->urlGenerator->linkToRoute($route);
273+
}
255274
$icon = $nav['icon'] ?? null;
256275
if ($icon !== null) {
257276
try {

tests/lib/NavigationManagerTest.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,10 @@ public function testWithAppManager($expected, $navigation, $isAdmin = false): vo
233233
->method('getAppInfo')
234234
->with('test')
235235
->willReturn($navigation);
236+
$this->appManager->expects($this->any())
237+
->method('isAppLoaded')
238+
->with('test')
239+
->willReturn(true);
236240
$this->urlGenerator->expects($this->any())
237241
->method('imagePath')
238242
->willReturnCallback(function ($appName, $file) {
@@ -259,7 +263,7 @@ public function testWithAppManager($expected, $navigation, $isAdmin = false): vo
259263
$this->groupManager->expects($this->any())->method('isAdmin')->willReturn($isAdmin);
260264

261265
$this->navigationManager->clear();
262-
$this->dispatcher->expects($this->once())
266+
$this->dispatcher->expects($this->atLeastOnce())
263267
->method('dispatchTyped')
264268
->willReturnCallback(function ($event): void {
265269
$this->assertInstanceOf(LoadAdditionalEntriesEvent::class, $event);
@@ -428,6 +432,7 @@ function (string $userId, string $appName, string $key, mixed $default = '') use
428432
->with('theming')
429433
->willReturn(true);
430434
$this->appManager->expects($this->once())->method('getAppInfo')->with('test')->willReturn($navigation);
435+
$this->appManager->expects($this->any())->method('isAppLoaded')->with('test')->willReturn(true);
431436
$this->appManager->expects($this->once())->method('getAppIcon')->with('test')->willReturn('/apps/test/img/app.svg');
432437
$this->l10nFac->expects($this->any())->method('get')->willReturn($l);
433438
$this->urlGenerator->expects($this->any())->method('imagePath')->willReturnCallback(function ($appName, $file) {

0 commit comments

Comments
 (0)