diff --git a/src/CP/Navigation/CoreNav.php b/src/CP/Navigation/CoreNav.php index 036e622cd1a..595a05694a4 100644 --- a/src/CP/Navigation/CoreNav.php +++ b/src/CP/Navigation/CoreNav.php @@ -70,7 +70,7 @@ protected function makeContentSection() Nav::content('Collections') ->route('collections.index') ->icon('collections') - ->can('index', Collection::class) + ->can('index', [Collection::class, Site::selected()]) ->extra([ 'breadcrumbs' => [ 'create_label' => 'Create Collection', @@ -103,7 +103,7 @@ protected function makeContentSection() Nav::content('Navigation') ->route('navigation.index') ->icon('navigation') - ->can('index', NavContract::class) + ->can('index', [NavContract::class, Site::selected()]) ->extra([ 'breadcrumbs' => [ 'create_label' => 'Create Navigation', @@ -181,7 +181,7 @@ protected function makeContentSection() Nav::content('Globals') ->route('globals.index') ->icon('globals') - ->can('index', GlobalSet::class) + ->can('index', [GlobalSet::class, Site::selected()]) ->extra([ 'breadcrumbs' => [ 'create_label' => 'Create Global Set', diff --git a/src/Policies/CollectionPolicy.php b/src/Policies/CollectionPolicy.php index 094096051e6..b72e2e32dfc 100644 --- a/src/Policies/CollectionPolicy.php +++ b/src/Policies/CollectionPolicy.php @@ -4,6 +4,7 @@ use Statamic\Facades\Collection; use Statamic\Facades\User; +use Statamic\Sites\Site; class CollectionPolicy { @@ -18,7 +19,7 @@ public function before($user) } } - public function index($user) + public function index($user, ?Site $site = null) { $user = User::fromUser($user); @@ -26,9 +27,10 @@ public function index($user) return true; } - return ! Collection::all()->filter(function ($collection) use ($user) { - return $this->view($user, $collection); - })->isEmpty(); + return Collection::all() + ->filter(fn ($collection) => $this->view($user, $collection)) + ->filter(fn ($collection) => ! $site || $collection->sites()->contains($site->handle())) + ->isNotEmpty(); } public function create($user) diff --git a/src/Policies/GlobalSetPolicy.php b/src/Policies/GlobalSetPolicy.php index 857857e9bdc..438ae06d8ce 100644 --- a/src/Policies/GlobalSetPolicy.php +++ b/src/Policies/GlobalSetPolicy.php @@ -4,6 +4,7 @@ use Statamic\Facades\GlobalSet; use Statamic\Facades\User; +use Statamic\Sites\Site; class GlobalSetPolicy { @@ -18,7 +19,7 @@ public function before($user) } } - public function index($user) + public function index($user, ?Site $site = null) { $user = User::fromUser($user); @@ -26,9 +27,10 @@ public function index($user) return true; } - return ! GlobalSet::all()->filter(function ($set) use ($user) { - return $this->view($user, $set); - })->isEmpty(); + return GlobalSet::all() + ->filter(fn ($set) => $this->view($user, $set)) + ->filter(fn ($set) => ! $site || $set->existsIn($site->handle())) + ->isNotEmpty(); } public function create($user) diff --git a/src/Policies/NavPolicy.php b/src/Policies/NavPolicy.php index 02d55bdd82c..af19ae621e2 100644 --- a/src/Policies/NavPolicy.php +++ b/src/Policies/NavPolicy.php @@ -4,6 +4,7 @@ use Statamic\Facades\Nav; use Statamic\Facades\User; +use Statamic\Sites\Site; class NavPolicy { @@ -18,7 +19,7 @@ public function before($user) } } - public function index($user) + public function index($user, ?Site $site = null) { $user = User::fromUser($user); @@ -26,9 +27,10 @@ public function index($user) return true; } - return ! Nav::all()->filter(function ($nav) use ($user) { - return $this->view($user, $nav); - })->isEmpty(); + return Nav::all() + ->filter(fn ($nav) => $this->view($user, $nav)) + ->filter(fn ($nav) => ! $site || $nav->existsIn($site->handle())) + ->isNotEmpty(); } public function create($user) diff --git a/tests/CP/Navigation/CoreNavTest.php b/tests/CP/Navigation/CoreNavTest.php index 46f4cbaf4b4..b3f80062355 100644 --- a/tests/CP/Navigation/CoreNavTest.php +++ b/tests/CP/Navigation/CoreNavTest.php @@ -221,6 +221,84 @@ public function it_doesnt_build_globals_children_from_sites_that_the_user_is_not $this->assertEqualsCanonicalizing($expected, $actual); } + #[Test] + public function it_doesnt_build_collections_item_when_the_user_cant_view_any_collections_in_the_selected_site() + { + $this->setSites([ + 'en' => ['url' => '/', 'locale' => 'en_US', 'name' => 'English'], + 'fr' => ['url' => '/', 'locale' => 'fr_FR', 'name' => 'French'], + ]); + + Facades\Collection::make('only_english')->sites(['en'])->save(); + + $this->setTestRoles(['test' => [ + 'access cp', + 'view only_english entries', + 'access en site', + 'access fr site', + ]]); + + $this->actingAs(tap(User::make()->assignRole('test'))->save()); + + Facades\Site::setSelected('en'); + $this->assertContains('Collections', $this->build()->get('Content', collect())->map->display()->all()); + + Facades\Site::setSelected('fr'); + $this->assertNotContains('Collections', $this->build()->get('Content', collect())->map->display()->all()); + } + + #[Test] + public function it_doesnt_build_navigation_item_when_the_user_cant_view_any_navs_in_the_selected_site() + { + $this->setSites([ + 'en' => ['url' => '/', 'locale' => 'en_US', 'name' => 'English'], + 'fr' => ['url' => '/', 'locale' => 'fr_FR', 'name' => 'French'], + ]); + + tap(Facades\Nav::make()->handle('only_english'))->save()->makeTree('en')->save(); + + $this->setTestRoles(['test' => [ + 'access cp', + 'view only_english nav', + 'access en site', + 'access fr site', + ]]); + + $this->actingAs(tap(User::make()->assignRole('test'))->save()); + + Facades\Site::setSelected('en'); + $this->assertContains('Navigation', $this->build()->get('Content', collect())->map->display()->all()); + + Facades\Site::setSelected('fr'); + $this->assertNotContains('Navigation', $this->build()->get('Content', collect())->map->display()->all()); + } + + #[Test] + public function it_doesnt_build_globals_item_when_the_user_cant_view_any_globals_in_the_selected_site() + { + $this->setSites([ + 'en' => ['url' => '/', 'locale' => 'en_US', 'name' => 'English'], + 'fr' => ['url' => '/', 'locale' => 'fr_FR', 'name' => 'French'], + ]); + + Facades\GlobalSet::make('only_english')->sites(['en'])->save(); + + $this->setTestRoles(['test' => [ + 'access cp', + 'edit only_english globals', + 'access en site', + 'access fr site', + ]]); + + $this->actingAs(tap(User::make()->assignRole('test'))->save()); + + Facades\Site::setSelected('en'); + $this->assertContains('Globals', $this->build()->get('Content', collect())->map->display()->all()); + + Facades\Site::setSelected('fr'); + $this->assertNotContains('Globals', $this->build()->get('Content', collect())->map->display()->all()); + } + #[Test] public function it_builds_the_nav_when_a_form_has_no_title() { diff --git a/tests/Policies/CollectionPolicyTest.php b/tests/Policies/CollectionPolicyTest.php index 833ed9d59c6..2bdd3d00342 100644 --- a/tests/Policies/CollectionPolicyTest.php +++ b/tests/Policies/CollectionPolicyTest.php @@ -5,6 +5,7 @@ use PHPUnit\Framework\Attributes\Test; use Statamic\Contracts\Entries\Collection as CollectionContract; use Statamic\Facades\Collection; +use Statamic\Facades\Site; class CollectionPolicyTest extends PolicyTestCase { @@ -45,6 +46,23 @@ public function index_is_allowed_if_any_collection_is_viewable_with_site_permiss $this->assertFalse($userWithDePermission->can('index', CollectionContract::class)); } + #[Test] + public function index_is_allowed_for_a_site_if_any_collection_is_viewable_in_that_site() + { + $this->withSites(['en', 'fr']); + + $user = $this->userWithPermissions([ + 'view test entries', + 'access en site', + 'access fr site', + ]); + + Collection::make('test')->sites(['en'])->save(); + + $this->assertTrue($user->can('index', [CollectionContract::class, Site::get('en')])); + $this->assertFalse($user->can('index', [CollectionContract::class, Site::get('fr')])); + } + #[Test] public function collections_are_viewable_with_view_permissions() { diff --git a/tests/Policies/GlobalSetPolicyTest.php b/tests/Policies/GlobalSetPolicyTest.php index b053f4df899..6e4eb831622 100644 --- a/tests/Policies/GlobalSetPolicyTest.php +++ b/tests/Policies/GlobalSetPolicyTest.php @@ -5,6 +5,7 @@ use PHPUnit\Framework\Attributes\Test; use Statamic\Contracts\Globals\GlobalSet; use Statamic\Facades\GlobalSet as GlobalSets; +use Statamic\Facades\Site; class GlobalSetPolicyTest extends PolicyTestCase { @@ -49,6 +50,24 @@ public function index_is_allowed_if_any_set_is_viewable_with_site_permissions() $this->assertFalse($userWithDePermission->can('index', GlobalSet::class)); } + #[Test] + public function index_is_allowed_for_a_site_if_any_set_is_viewable_in_that_site() + { + $this->withSites(['en', 'fr']); + + $user = $this->userWithPermissions([ + 'edit test globals', + 'access en site', + 'access fr site', + ]); + + $global = GlobalSets::make('test')->sites(['en' => null])->save(); + $global->in('en')->save(); + + $this->assertTrue($user->can('index', [GlobalSet::class, Site::get('en')])); + $this->assertFalse($user->can('index', [GlobalSet::class, Site::get('fr')])); + } + #[Test] public function globals_are_viewable_with_edit_permissions() { diff --git a/tests/Policies/NavPolicyTest.php b/tests/Policies/NavPolicyTest.php index 00c1e6be80d..1b428915cc8 100644 --- a/tests/Policies/NavPolicyTest.php +++ b/tests/Policies/NavPolicyTest.php @@ -5,6 +5,7 @@ use PHPUnit\Framework\Attributes\Test; use Statamic\Contracts\Structures\Nav as NavContract; use Statamic\Facades\Nav; +use Statamic\Facades\Site; class NavPolicyTest extends PolicyTestCase { @@ -47,6 +48,24 @@ public function index_is_allowed_if_any_nav_is_viewable_with_site_permissions() $this->assertFalse($userWithDePermission->can('index', NavContract::class)); } + #[Test] + public function index_is_allowed_for_a_site_if_any_nav_is_viewable_in_that_site() + { + $this->withSites(['en', 'fr']); + + $user = $this->userWithPermissions([ + 'view test nav', + 'access en site', + 'access fr site', + ]); + + $nav = tap(Nav::make('test'))->save(); + $nav->makeTree('en')->save(); + + $this->assertTrue($user->can('index', [NavContract::class, Site::get('en')])); + $this->assertFalse($user->can('index', [NavContract::class, Site::get('fr')])); + } + #[Test] public function navs_are_viewable_with_view_permissions() {