Skip to content

Commit 15118e7

Browse files
authored
[5.x] Support query scopes in navigations (#13509)
1 parent 37973e3 commit 15118e7

9 files changed

Lines changed: 139 additions & 1 deletion

File tree

resources/js/components/navigation/View.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@
131131
ref="selector"
132132
:site="site"
133133
:collections="collections"
134+
:query-scopes="entryQueryScopes"
134135
:max-items="maxPagesSelection"
135136
:can-select-across-sites="canSelectAcrossSites"
136137
@selected="entriesSelected"
@@ -208,7 +209,8 @@ export default {
208209
sites: { type: Array, required: true },
209210
blueprint: { type: Object, required: true },
210211
canEdit: { type: Boolean, required: true },
211-
canSelectAcrossSites: { type: Boolean, required: true }
212+
canSelectAcrossSites: { type: Boolean, required: true },
213+
entryQueryScopes: { type: Array, default: () => [] },
212214
},
213215
214216
data() {

resources/js/components/structures/PageSelector.vue

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ export default {
2929
site: String,
3030
collections: Array,
3131
canSelectAcrossSites: Boolean,
32+
queryScopes: {
33+
type: Array,
34+
default: () => []
35+
},
3236
maxItems: {
3337
type: Number,
3438
required: false,
@@ -41,6 +45,7 @@ export default {
4145
type: 'entries',
4246
collections: this.collections,
4347
select_across_sites: this.canSelectAcrossSites,
48+
query_scopes: this.queryScopes,
4449
},
4550
columns: [
4651
{ label: __('Title'), field: 'title' },

resources/views/navigation/show.blade.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
site="{{ $site }}"
1515
:sites="{{ json_encode($sites) }}"
1616
:collections="{{ json_encode($collections) }}"
17+
:entry-query-scopes='@json($collections_query_scopes)'
1718
:max-depth="{{ $nav->maxDepth() ?? 'Infinity' }}"
1819
:expects-root="{{ $str::bool($expectsRoot) }}"
1920
:blueprint="{{ json_encode($blueprint) }}"

src/Http/Controllers/CP/Navigation/NavigationController.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
use Statamic\Contracts\Structures\Nav as NavContract;
77
use Statamic\Facades\Blueprint;
88
use Statamic\Facades\Nav;
9+
use Statamic\Facades\Scope;
910
use Statamic\Facades\Site;
1011
use Statamic\Facades\User;
1112
use Statamic\Http\Controllers\CP\CpController;
13+
use Statamic\Query\Scopes\Filter;
1214
use Statamic\Rules\Handle;
1315
use Statamic\Support\Arr;
1416

@@ -46,6 +48,7 @@ public function edit($nav)
4648
'title' => $nav->title(),
4749
'handle' => $nav->handle(),
4850
'collections' => $nav->collections()->map->handle()->all(),
51+
'collections_query_scopes' => $nav->collectionsQueryScopes(),
4952
'root' => $nav->expectsRoot(),
5053
'sites' => $nav->trees()->keys()->all(),
5154
'max_depth' => $nav->maxDepth(),
@@ -86,6 +89,7 @@ public function show(Request $request, $nav)
8689
'nav' => $nav,
8790
'expectsRoot' => $nav->expectsRoot(),
8891
'collections' => $nav->collections()->map->handle()->all(),
92+
'collections_query_scopes' => $nav->collectionsQueryScopes(),
8993
'sites' => $this->getAuthorizedTreesForNav($nav)->map(function ($tree) {
9094
return [
9195
'handle' => $tree->locale(),
@@ -120,6 +124,7 @@ public function update(Request $request, $nav)
120124
->title($values['title'])
121125
->expectsRoot($values['root'])
122126
->collections($values['collections'])
127+
->collectionsQueryScopes(Arr::get($values, 'collections_query_scopes', []))
123128
->maxDepth($values['max_depth']);
124129

125130
$existingSites = $nav->trees()->keys()->all();
@@ -212,6 +217,16 @@ public function editFormBlueprint($nav)
212217
'type' => 'collections',
213218
'mode' => 'select',
214219
],
220+
'collections_query_scopes' => [
221+
'display' => __('Query Scopes'),
222+
'instructions' => __('statamic::fieldtypes.entries.config.query_scopes'),
223+
'type' => 'taggable',
224+
'options' => Scope::all()
225+
->reject(fn ($scope) => $scope instanceof Filter)
226+
->map->handle()
227+
->values()
228+
->all(),
229+
],
215230
'root' => [
216231
'display' => __('Expect a root page'),
217232
'instructions' => __('statamic::messages.expect_root_instructions'),

src/Stache/Stores/NavigationStore.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public function makeItemFromFile($path, $contents)
3636
->title($data['title'] ?? null)
3737
->maxDepth($data['max_depth'] ?? null)
3838
->collections($data['collections'] ?? null)
39+
->collectionsQueryScopes($data['collections_query_scopes'] ?? [])
3940
->expectsRoot($data['root'] ?? false)
4041
->canSelectAcrossSites($data['select_across_sites'] ?? false)
4142
->initialPath($path);

src/Structures/Nav.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@
1919
use Statamic\Facades\Collection;
2020
use Statamic\Facades\Site;
2121
use Statamic\Facades\Stache;
22+
use Statamic\Support\Str;
2223

2324
class Nav extends Structure implements Contract
2425
{
2526
use ExistsAsFile;
2627

2728
protected $collections;
2829
protected $canSelectAcrossSites = false;
30+
protected $collectionsQueryScopes = [];
2931
private $blueprintCache;
3032

3133
public function save()
@@ -77,6 +79,7 @@ public function fileData()
7779
return [
7880
'title' => $this->title,
7981
'collections' => $this->collections,
82+
'collections_query_scopes' => empty($this->collectionsQueryScopes) ? null : $this->collectionsQueryScopes,
8083
'select_across_sites' => $this->canSelectAcrossSites ? true : null,
8184
'max_depth' => $this->maxDepth,
8285
'root' => $this->expectsRoot ?: null,
@@ -165,4 +168,23 @@ public function canSelectAcrossSites($canSelect = null)
165168
->fluentlyGetOrSet('canSelectAcrossSites')
166169
->args(func_get_args());
167170
}
171+
172+
public function collectionsQueryScopes($scopes = null)
173+
{
174+
return $this
175+
->fluentlyGetOrSet('collectionsQueryScopes')
176+
->setter(function ($scopes) {
177+
if (empty($scopes)) {
178+
return [];
179+
}
180+
181+
return collect($scopes)
182+
->filter()
183+
->map(fn ($scope) => Str::snake($scope))
184+
->unique()
185+
->values()
186+
->all();
187+
})
188+
->args(func_get_args());
189+
}
168190
}

tests/Data/Structures/NavTest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,29 @@ public function collections_can_be_get_and_set()
216216
$this->assertEquals([$collectionOne, $collectionTwo], $collections->all());
217217
}
218218

219+
#[Test]
220+
public function collections_query_scopes_can_be_get_and_set()
221+
{
222+
$nav = $this->structure();
223+
224+
$this->assertEquals([], $nav->collectionsQueryScopes());
225+
226+
$return = $nav->collectionsQueryScopes(['scope_one', 'scope_two']);
227+
228+
$this->assertSame($nav, $return);
229+
$this->assertEquals(['scope_one', 'scope_two'], $nav->collectionsQueryScopes());
230+
}
231+
232+
#[Test]
233+
public function collections_query_scopes_are_normalized()
234+
{
235+
$nav = $this->structure();
236+
237+
$nav->collectionsQueryScopes(['ScopeOne', 'scope_two', '', null, 'ScopeOne']);
238+
239+
$this->assertEquals(['scope_one', 'scope_two'], $nav->collectionsQueryScopes());
240+
}
241+
219242
#[Test]
220243
public function it_has_cp_urls()
221244
{
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
namespace Tests\Feature\Fieldtypes;
4+
5+
use PHPUnit\Framework\Attributes\Test;
6+
use Statamic\Facades\Collection;
7+
use Statamic\Facades\Entry;
8+
use Statamic\Facades\User;
9+
use Statamic\Query\Scopes\Scope;
10+
use Tests\FakesRoles;
11+
use Tests\PreventSavingStacheItemsToDisk;
12+
use Tests\TestCase;
13+
14+
class RelationshipFieldtypeTest extends TestCase
15+
{
16+
use FakesRoles;
17+
use PreventSavingStacheItemsToDisk;
18+
19+
private $collection;
20+
21+
public function setUp(): void
22+
{
23+
parent::setUp();
24+
25+
$this->collection = Collection::make('test')->save();
26+
27+
app('statamic.scopes')[StartsWithC::handle()] = StartsWithC::class;
28+
}
29+
30+
#[Test]
31+
public function it_filters_entries_by_query_scopes()
32+
{
33+
Entry::make()->collection('test')->slug('apple')->data(['title' => 'Apple'])->save();
34+
Entry::make()->collection('test')->slug('carrot')->data(['title' => 'Carrot'])->save();
35+
Entry::make()->collection('test')->slug('cherry')->data(['title' => 'Cherry'])->save();
36+
Entry::make()->collection('test')->slug('banana')->data(['title' => 'Banana'])->save();
37+
38+
$this->setTestRoles(['test' => ['access cp']]);
39+
$user = User::make()->assignRole('test')->save();
40+
41+
$config = base64_encode(json_encode([
42+
'type' => 'entries',
43+
'collections' => ['test'],
44+
'query_scopes' => ['starts_with_c'],
45+
]));
46+
47+
$response = $this
48+
->actingAs($user)
49+
->get("/cp/fieldtypes/relationship?config={$config}&collections[0]=test")
50+
->assertOk();
51+
52+
$titles = collect($response->json('data'))->pluck('title')->all();
53+
54+
$this->assertCount(2, $titles);
55+
$this->assertContains('Carrot', $titles);
56+
$this->assertContains('Cherry', $titles);
57+
$this->assertNotContains('Apple', $titles);
58+
$this->assertNotContains('Banana', $titles);
59+
}
60+
}
61+
62+
class StartsWithC extends Scope
63+
{
64+
public function apply($query, $params)
65+
{
66+
$query->where('title', 'like', 'C%');
67+
}
68+
}

tests/Feature/Navigation/MocksStructures.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ private function createNav($handle)
1919
$s->shouldReceive('editUrl')->andReturn('/nav-edit-url');
2020
$s->shouldReceive('deleteUrl')->andReturn('/nav-delete-url');
2121
$s->shouldReceive('collections')->andReturn(collect());
22+
$s->shouldReceive('collectionsQueryScopes')->andReturn([]);
2223
$s->shouldReceive('expectsRoot')->andReturnFalse();
2324
$s->shouldReceive('maxDepth')->andReturnNull();
2425
$s->shouldReceive('canSelectAcrossSites')->andReturnFalse();

0 commit comments

Comments
 (0)