From 971de0ed54bec1a19b59e9671616cf3506796ad4 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Mon, 7 Jul 2025 18:53:04 -0400 Subject: [PATCH 1/6] Allow `->children(true)` to assume conventional nested URLs for active state. --- src/CP/Navigation/NavItem.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/CP/Navigation/NavItem.php b/src/CP/Navigation/NavItem.php index 7e860638293..af800fde206 100644 --- a/src/CP/Navigation/NavItem.php +++ b/src/CP/Navigation/NavItem.php @@ -20,6 +20,7 @@ class NavItem protected $url; protected $icon; protected $children; + protected $hasImplicitChildren; protected $isChild; protected $wasOriginallyChild; protected $authorization; @@ -232,6 +233,14 @@ public function children($items = null, $generateNewIds = true) return $this->children; } + if ($items === true) { + $this->hasImplicitChildren = true; + + return $this; + } elseif ($items) { + $this->hasImplicitChildren = false; + } + if (is_callable($items)) { $this->children = $items; @@ -292,6 +301,14 @@ protected function currentUrlIsRestfulDescendant(): bool ]); } + /** + * Check if this nav item has implicit children by assuming nested URL conventions. + */ + protected function hasImplicitChildren(): bool + { + return (bool) $this->hasImplicitChildren; + } + /** * Check if this nav item was ever a child before user preferences were applied. */ @@ -393,6 +410,7 @@ public function isActive() if ($this->currentUrlIsNotExplicitlyReferencedInNav()) { switch (true) { case $this->currentUrlIsRestfulDescendant(): + case $this->hasImplicitChildren(): case $this->wasOriginallyChild(): return $this->isActiveByPattern($this->active); } From 29669349b09af5c02f0e7139dbd4ef0494dd6668 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Mon, 7 Jul 2025 18:54:28 -0400 Subject: [PATCH 2/6] Updates page has implicit children. --- src/CP/Navigation/CoreNav.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/CP/Navigation/CoreNav.php b/src/CP/Navigation/CoreNav.php index a69e8798a89..35944718e46 100644 --- a/src/CP/Navigation/CoreNav.php +++ b/src/CP/Navigation/CoreNav.php @@ -194,7 +194,8 @@ protected function makeToolsSection() ->route('updater') ->icon('loading-bar') ->view('statamic::nav.updates') - ->can('view updates'); + ->can('view updates') + ->children(true); Nav::tools('Addons') ->route('addons.index') From 07873f75bacfc2b81dd4804ab1bd87b428ea5e2e Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Tue, 8 Jul 2025 09:56:33 -0400 Subject: [PATCH 3/6] Revert "Updates page has implicit children." This reverts commit 29669349b09af5c02f0e7139dbd4ef0494dd6668. --- src/CP/Navigation/CoreNav.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/CP/Navigation/CoreNav.php b/src/CP/Navigation/CoreNav.php index 35944718e46..a69e8798a89 100644 --- a/src/CP/Navigation/CoreNav.php +++ b/src/CP/Navigation/CoreNav.php @@ -194,8 +194,7 @@ protected function makeToolsSection() ->route('updater') ->icon('loading-bar') ->view('statamic::nav.updates') - ->can('view updates') - ->children(true); + ->can('view updates'); Nav::tools('Addons') ->route('addons.index') From 312cdae690e9fe22d1ba4b43298fe4f12020faf4 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Tue, 8 Jul 2025 09:56:44 -0400 Subject: [PATCH 4/6] Revert "Allow `->children(true)` to assume conventional nested URLs for active state." This reverts commit 971de0ed54bec1a19b59e9671616cf3506796ad4. --- src/CP/Navigation/NavItem.php | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/CP/Navigation/NavItem.php b/src/CP/Navigation/NavItem.php index af800fde206..7e860638293 100644 --- a/src/CP/Navigation/NavItem.php +++ b/src/CP/Navigation/NavItem.php @@ -20,7 +20,6 @@ class NavItem protected $url; protected $icon; protected $children; - protected $hasImplicitChildren; protected $isChild; protected $wasOriginallyChild; protected $authorization; @@ -233,14 +232,6 @@ public function children($items = null, $generateNewIds = true) return $this->children; } - if ($items === true) { - $this->hasImplicitChildren = true; - - return $this; - } elseif ($items) { - $this->hasImplicitChildren = false; - } - if (is_callable($items)) { $this->children = $items; @@ -301,14 +292,6 @@ protected function currentUrlIsRestfulDescendant(): bool ]); } - /** - * Check if this nav item has implicit children by assuming nested URL conventions. - */ - protected function hasImplicitChildren(): bool - { - return (bool) $this->hasImplicitChildren; - } - /** * Check if this nav item was ever a child before user preferences were applied. */ @@ -410,7 +393,6 @@ public function isActive() if ($this->currentUrlIsNotExplicitlyReferencedInNav()) { switch (true) { case $this->currentUrlIsRestfulDescendant(): - case $this->hasImplicitChildren(): case $this->wasOriginallyChild(): return $this->isActiveByPattern($this->active); } From ceaf3b85147642bb1fcc7997b087a050205ff0bc Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Tue, 8 Jul 2025 10:05:05 -0400 Subject: [PATCH 5/6] Add failing test coverage. --- tests/CP/Navigation/ActiveNavItemTest.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/CP/Navigation/ActiveNavItemTest.php b/tests/CP/Navigation/ActiveNavItemTest.php index ab560f7ddf1..d59d9f4ac0d 100644 --- a/tests/CP/Navigation/ActiveNavItemTest.php +++ b/tests/CP/Navigation/ActiveNavItemTest.php @@ -536,6 +536,25 @@ public function it_properly_handles_various_edge_cases_when_checking_is_active_o $this->assertFalse($externalSecure->isActive()); } + #[Test] + public function active_nav_descendant_url_still_functions_properly_when_parent_item_has_no_children() + { + Facades\CP\Nav::extend(function ($nav) { + $nav->tools('Schopify')->url('/cp/totally-custom-url'); + }); + + $this + ->prepareNavCaches() + ->get('http://localhost/cp/totally-custom-url/deeper/descendant') + ->assertStatus(200); + + $toolsItems = $this->build()->get('Tools'); + + $this->assertTrue($this->getItemByDisplay($toolsItems, 'Schopify')->isActive()); + $this->assertFalse($this->getItemByDisplay($toolsItems, 'Addons')->isActive()); + $this->assertFalse($this->getItemByDisplay($toolsItems, 'Utilities')->isActive()); + } + #[Test] public function active_nav_check_still_functions_properly_when_custom_nav_extension_hijacks_a_core_item_child() { From 406060aa281a7f8e494a4af68cb3753993c398c4 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Tue, 8 Jul 2025 10:27:57 -0400 Subject: [PATCH 6/6] If item has no children, always treat nested URLs as children for active state. --- src/CP/Navigation/NavItem.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/CP/Navigation/NavItem.php b/src/CP/Navigation/NavItem.php index 7e860638293..dfb95673911 100644 --- a/src/CP/Navigation/NavItem.php +++ b/src/CP/Navigation/NavItem.php @@ -292,6 +292,14 @@ protected function currentUrlIsRestfulDescendant(): bool ]); } + /** + * Check if we should assume nested URL conventions for active state on children. + */ + protected function doesntHaveExplicitChildren(): bool + { + return (bool) ! $this->children; + } + /** * Check if this nav item was ever a child before user preferences were applied. */ @@ -393,6 +401,7 @@ public function isActive() if ($this->currentUrlIsNotExplicitlyReferencedInNav()) { switch (true) { case $this->currentUrlIsRestfulDescendant(): + case $this->doesntHaveExplicitChildren(): case $this->wasOriginallyChild(): return $this->isActiveByPattern($this->active); }