Skip to content

Commit a289729

Browse files
Merge pull request #63524 from nextcloud/followup/63134/allow-to-hide-navigation-closures
fix(navigation): Allow apps to hide registered navigation entries
2 parents 911ce69 + 4f0a150 commit a289729

5 files changed

Lines changed: 49 additions & 5 deletions

File tree

build/psalm-baseline.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4080,6 +4080,11 @@
40804080
<code><![CDATA[DEFAULT_TTL]]></code>
40814081
</AmbiguousConstantInheritance>
40824082
</file>
4083+
<file src="ocs/v1.php">
4084+
<InternalMethod>
4085+
<code><![CDATA[setup]]></code>
4086+
</InternalMethod>
4087+
</file>
40834088
<file src="public.php">
40844089
<DeprecatedMethod>
40854090
<code><![CDATA[getAppValue]]></code>

lib/private/NavigationManager.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class NavigationManager implements INavigationManager {
5757
protected ?string $activeEntry = null;
5858
/** @var array<string, NavigationEntryOutput> */
5959
protected array $entries = [];
60-
/** @var list<callable(): NavigationEntry> */
60+
/** @var list<callable(): ?NavigationEntry> */
6161
protected array $closureEntries = [];
6262
/** User defined app order (cached for the `add` function) */
6363
protected ?array $customAppOrder = null;
@@ -357,7 +357,12 @@ private function resolveAppNavigationEntries(): void {
357357
// as apps might add new navigation entries via closures at any time
358358
while ($c = array_pop($this->closureEntries)) {
359359
try {
360-
$this->add($c());
360+
$entry = $c();
361+
if ($entry === null) {
362+
$this->logger->debug('Closure of navigation entry returned null, skipping');
363+
continue;
364+
}
365+
$this->add($entry);
361366
} catch (\Throwable $e) {
362367
$this->logger->error('Failed to add navigation entry from closure', ['exception' => $e]);
363368
}

lib/public/INavigationManager.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@ interface INavigationManager {
8080
/**
8181
* Creates a new navigation entry
8282
*
83-
* @param NavigationEntry|callable():NavigationEntry $entry If a menu entry (type = 'link') is added, you shall also set app to the app that
84-
* added the entry. The use of a closure is preferred, because it will avoid loading
85-
* the routing of your app, unless required.
83+
* @param NavigationEntry|callable():?NavigationEntry $entry If a menu entry (type = 'link') is added, you shall also set app to the app that
84+
* added the entry. The use of a closure is preferred, because it will avoid loading
85+
* the routing of your app, unless required.
8686
* @return void
8787
* @since 6.0.0
8888
*/

ocs/v1.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@
7979
$appManager->loadApps(['core']);
8080
}
8181

82+
// All apps are now loaded to handle the request
83+
Server::get(\OC\NavigationManager::class)->setup();
84+
Server::get(\OCP\EventDispatcher\IEventDispatcher::class)->dispatchTyped(new \OCP\App\Events\AppsLoadedEvent());
85+
8286
Server::get(Router::class)->match('/ocsapp' . $request->getRawPathInfo());
8387
} catch (MaxDelayReached $ex) {
8488
ApiHelper::respond(Http::STATUS_TOO_MANY_REQUESTS, $ex->getMessage());

tests/lib/NavigationManagerTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,36 @@ public function testAddClosure(array $entry, array $expectedEntry): void {
173173
$this->assertEmpty($this->navigationManager->getAll('all'), 'Expected no navigation entry exists after clear()');
174174
}
175175

176+
/**
177+
* Entry points that never call setup() (e.g. the OCS dispatch in ocs/v1.php)
178+
* must not silently lose closure-registered entries such as an app's nav
179+
* link: getAll() should only resolve what it can, not resolve nothing.
180+
*/
181+
public function testGetAllDoesNotResolveClosureBeforeSetup(): void {
182+
$numberOfCalls = 0;
183+
$this->navigationManager->add(function () use (&$numberOfCalls) {
184+
$numberOfCalls++;
185+
186+
return [
187+
'id' => 'entry id',
188+
'name' => 'link text',
189+
'order' => 1,
190+
'href' => 'url',
191+
];
192+
});
193+
194+
$navigationEntries = $this->navigationManager->getAll('all');
195+
196+
$this->assertEquals(0, $numberOfCalls, 'Expected that the closure is not called by getAll() before setup()');
197+
$this->assertEmpty($navigationEntries, 'Expected no navigation entry exists before setup()');
198+
199+
$this->navigationManager->setup();
200+
$navigationEntries = $this->navigationManager->getAll('all');
201+
202+
$this->assertEquals(1, $numberOfCalls, 'Expected that the closure is called by getAll() once setup() has run');
203+
$this->assertArrayHasKey('entry id', $navigationEntries);
204+
}
205+
176206
public function testAddClosureAfterSetup(): void {
177207
$this->navigationManager->setup();
178208
$this->assertEmpty($this->navigationManager->getAll('all'), 'Expected no navigation entry exists');

0 commit comments

Comments
 (0)