From ed9b77311ab6cab8ea7392bf4004704147d32419 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joachim=20R=C3=BCtter?= Date: Thu, 25 Jun 2026 22:36:59 +0000 Subject: [PATCH] Pass active query scope handles to relationship index scopes The entries/relationship fieldtype index applied each configured query scope with the raw request params, which never included the list of active scope handles. A scope registered under multiple aliases couldn't tell which one was in effect. Merge the configured handles into the params as queryScopes, the same way the asset browser does. --- src/Fieldtypes/Relationship.php | 7 +++- .../Fieldtypes/RelationshipFieldtypeTest.php | 42 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/Fieldtypes/Relationship.php b/src/Fieldtypes/Relationship.php index 1ced3557ea8..33e7c134083 100644 --- a/src/Fieldtypes/Relationship.php +++ b/src/Fieldtypes/Relationship.php @@ -366,7 +366,12 @@ public function toQueryableValue($value) protected function applyIndexQueryScopes($query, $params) { - collect(Arr::wrap($this->config('query_scopes'))) + $handles = Arr::wrap($this->config('query_scopes')); + + // Pass the active handles along (like the asset browser) so an aliased scope knows which is in effect. + $params = array_merge($params, ['queryScopes' => $handles]); + + collect($handles) ->map(fn ($handle) => Scope::find($handle)) ->filter() ->each(fn ($scope) => $scope->apply($query, $params)); diff --git a/tests/Feature/Fieldtypes/RelationshipFieldtypeTest.php b/tests/Feature/Fieldtypes/RelationshipFieldtypeTest.php index ff273834710..1c73066127c 100644 --- a/tests/Feature/Fieldtypes/RelationshipFieldtypeTest.php +++ b/tests/Feature/Fieldtypes/RelationshipFieldtypeTest.php @@ -31,6 +31,10 @@ public function setUp(): void $this->collection = Collection::make('test')->save(); app('statamic.scopes')[StartsWithC::handle()] = StartsWithC::class; + + // Register one scope class under an alias that differs from its handle, so the + // test can prove the configured alias is what reaches the scope. + app('statamic.scopes')['fruit_filter'] = AliasedScope::class; } #[Test] @@ -64,6 +68,34 @@ public function it_filters_entries_by_query_scopes() $this->assertNotContains('Banana', $titles); } + #[Test] + public function it_passes_the_active_scope_handles_to_aliased_scopes() + { + Entry::make()->collection('test')->slug('apple')->data(['title' => 'Apple'])->save(); + Entry::make()->collection('test')->slug('carrot')->data(['title' => 'Carrot'])->save(); + Entry::make()->collection('test')->slug('cherry')->data(['title' => 'Cherry'])->save(); + Entry::make()->collection('test')->slug('banana')->data(['title' => 'Banana'])->save(); + + $this->setTestRoles(['test' => ['access cp', 'view test entries']]); + $user = User::make()->assignRole('test')->save(); + + $config = base64_encode(json_encode([ + 'type' => 'entries', + 'collections' => ['test'], + 'query_scopes' => ['fruit_filter'], + ])); + + $response = $this + ->actingAs($user) + ->get("/cp/fieldtypes/relationship?config={$config}") + ->assertOk(); + + $titles = collect($response->json('data'))->pluck('title')->all(); + + // The scope only filters when its alias is present in the queryScopes param. + $this->assertEqualsCanonicalizing(['Carrot', 'Cherry'], $titles); + } + #[Test] public function it_limits_access_to_entries_from_collections_the_user_can_view() { @@ -716,3 +748,13 @@ public function apply($query, $params) $query->where('title', 'like', 'C%'); } } + +class AliasedScope extends Scope +{ + public function apply($query, $params) + { + if (in_array('fruit_filter', $params['queryScopes'] ?? [])) { + $query->where('title', 'like', 'C%'); + } + } +}