diff --git a/src/Menu/Matcher/Voter/AdminVoter.php b/src/Menu/Matcher/Voter/AdminVoter.php index 7876475abf..a326127b41 100644 --- a/src/Menu/Matcher/Voter/AdminVoter.php +++ b/src/Menu/Matcher/Voter/AdminVoter.php @@ -32,32 +32,43 @@ public function __construct( public function matchItem(ItemInterface $item): ?bool { - $admin = $item->getExtra('admin'); - $request = $this->requestStack->getMainRequest(); + if (null === $request) { + return null; + } - if ($admin instanceof AdminInterface + $admin = $item->getExtra('admin'); + if ( + $admin instanceof AdminInterface && $admin->hasRoute('list') && $admin->hasAccess('list') - && null !== $request + && $this->match($admin, $request->get('_sonata_admin')) ) { - $requestCode = $request->get('_sonata_admin'); - - if ($admin->getCode() === $requestCode) { - return true; - } - - foreach ($admin->getChildren() as $child) { - if ($child->getBaseCodeRoute() === $requestCode) { - return true; - } - } + return true; } $route = $item->getExtra('route'); - if (null !== $route && null !== $request && $route === $request->get('_route')) { + if (null !== $route && $route === $request->get('_route')) { return true; } return null; } + + /** + * @param AdminInterface $admin + */ + private function match(AdminInterface $admin, mixed $requestCode): bool + { + if ($admin->getBaseCodeRoute() === $requestCode) { + return true; + } + + foreach ($admin->getChildren() as $child) { + if ($this->match($child, $requestCode)) { + return true; + } + } + + return false; + } } diff --git a/tests/Menu/Matcher/Voter/AdminVoterTest.php b/tests/Menu/Matcher/Voter/AdminVoterTest.php index 841c4999fc..9f4ee5e177 100644 --- a/tests/Menu/Matcher/Voter/AdminVoterTest.php +++ b/tests/Menu/Matcher/Voter/AdminVoterTest.php @@ -62,6 +62,8 @@ public function provideMatchingCases(): iterable yield 'has admin' => [$this->getAdmin('_sonata_admin', true, true), '_sonata_admin', null, true]; yield 'has child admin' => [$this->getChildAdmin('_sonata_admin', '_sonata_child_admin', true, true), '_sonata_admin|_sonata_child_admin', null, true]; yield 'has bad child admin' => [$this->getChildAdmin('_sonata_admin', '_sonata_child_admin', true, true), '_sonata_admin|_sonata_child_admin_unexpected', null, null]; + yield 'has nested child admin' => [$this->getNestedChildAdmin('_sonata_admin', '_sonata_child_admin', '_sonata_nested_child_admin', true, true), '_sonata_admin|_sonata_child_admin|_sonata_nested_child_admin', null, true]; + yield 'has bad nested child admin' => [$this->getNestedChildAdmin('_sonata_admin', '_sonata_child_admin', '_sonata_nested_child_admin', true, true), '_sonata_admin|_sonata_child_admin|_sonata_nested_child_admin_unexpected', null, null]; yield 'direct link' => ['admin_post', null, 'admin_post', true]; yield 'no direct link' => ['admin_post', null, 'admin_blog', null]; } @@ -81,7 +83,7 @@ private function getAdmin(string $code, bool $list = false, bool $granted = fals ->with('list') ->willReturn($granted); $admin - ->method('getCode') + ->method('getBaseCodeRoute') ->willReturn($code); $admin ->method('getChildren') @@ -109,7 +111,7 @@ private function getChildAdmin( ->with('list') ->willReturn($granted); $parentAdmin - ->method('getCode') + ->method('getBaseCodeRoute') ->willReturn($parentCode); $childAdmin = $this->createMock(AdminInterface::class); @@ -123,4 +125,48 @@ private function getChildAdmin( return $parentAdmin; } + + /** + * @return AdminInterface + */ + private function getNestedChildAdmin( + string $grandParentCode, + string $parentCode, + string $childCode, + bool $list = false, + bool $granted = false + ): AdminInterface { + $grandParentAdmin = $this->createMock(AdminInterface::class); + $grandParentAdmin + ->method('hasRoute') + ->with('list') + ->willReturn($list); + $grandParentAdmin + ->method('hasAccess') + ->with('list') + ->willReturn($granted); + $grandParentAdmin + ->method('getBaseCodeRoute') + ->willReturn($grandParentCode); + + $parentAdmin = $this->createMock(AdminInterface::class); + $parentAdmin + ->method('getBaseCodeRoute') + ->willReturn(sprintf('%s|%s', $grandParentCode, $parentCode)); + + $grandParentAdmin + ->method('getChildren') + ->willReturn([$parentAdmin]); + + $childAdmin = $this->createMock(AdminInterface::class); + $childAdmin + ->method('getBaseCodeRoute') + ->willReturn(sprintf('%s|%s|%s', $grandParentCode, $parentCode, $childCode)); + + $parentAdmin + ->method('getChildren') + ->willReturn([$childAdmin]); + + return $grandParentAdmin; + } }