Skip to content

Commit 6b418a1

Browse files
[6.x] Fix adding nested replicator sets (#13694)
Co-authored-by: Jason Varga <jason@pixelfear.com>
1 parent 29d4211 commit 6b418a1

4 files changed

Lines changed: 142 additions & 31 deletions

File tree

resources/js/components/fieldtypes/bard/BardFieldtype.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,8 @@ export default {
550550
551551
return this.fieldPathKeys
552552
.map((key, index) => {
553+
if (['attrs', 'values'].includes(key)) return;
554+
553555
if (Number.isInteger(parseInt(key))) {
554556
let setValues = data_get(this.publishContainer.values, this.fieldPathKeys.slice(0, index + 1).join('.'));
555557

resources/js/components/fieldtypes/replicator/Replicator.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,8 @@ export default {
265265
266266
return this.fieldPathKeys
267267
.map((key, index) => {
268+
if (['attrs', 'values'].includes(key)) return;
269+
268270
if (Number.isInteger(parseInt(key))) {
269271
let setValues = data_get(this.publishContainer.values, this.fieldPathKeys.slice(0, index + 1).join('.'));
270272

src/Http/Controllers/CP/Fieldtypes/ReplicatorSetController.php

Lines changed: 46 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -60,43 +60,58 @@ private function getReplicatorField(Blueprint $blueprint, string $field): Field
6060
$remainingFieldPathComponents = explode('.', $field);
6161

6262
$config = $blueprint->fields()->all()->get($remainingFieldPathComponents[0])->config();
63-
unset($remainingFieldPathComponents[0]);
6463

65-
foreach ($remainingFieldPathComponents as $index => $fieldPathComponent) {
66-
unset($remainingFieldPathComponents[$fieldPathComponent]);
67-
68-
if (isset($config['sets'])) {
69-
$config = collect($config['sets'])
70-
->flatMap(fn (array $setGroup): array => $setGroup['sets'] ?? [])
71-
->get($fieldPathComponent);
72-
}
73-
74-
if (isset($config['fields'])) {
75-
$fields = collect($config['fields'])
76-
->flatMap(function ($field): array {
77-
if (isset($field['import']) || (isset($field['field']) && is_string($field['field']))) {
78-
return (new Fields([$field]))
79-
->all()
80-
->mapWithKeys(fn (Field $field) => [$field->handle() => [
81-
'handle' => $field->handle(),
82-
'field' => $field->config(),
83-
]])
84-
->all();
85-
}
86-
87-
return [$field];
88-
});
89-
90-
if ($fieldConfig = $fields->firstWhere('handle', $remainingFieldPathComponents[$index])) {
91-
$config = $fieldConfig['field'];
92-
}
93-
}
94-
}
64+
$config = $this->getConfig($config, $remainingFieldPathComponents);
9565

9666
if (! isset($config['type'])) {
9767
throw new \Exception("Cannot find Replicator field [$field]");
9868
}
9969

10070
return new Field(Str::afterLast($field, '.'), $config);
10171
}
72+
73+
private function getConfig(array $config, array $remainingFieldPathComponents): array
74+
{
75+
$isReplicator = isset($config['type']) && in_array($config['type'], ['bard', 'replicator']);
76+
77+
if ($isReplicator) {
78+
$flattenedSets = collect($config['sets'])
79+
->flatMap(fn (array $setGroup): array => $setGroup['sets'] ?? [])
80+
->all();
81+
82+
if (count($remainingFieldPathComponents) === 1) {
83+
return $config;
84+
}
85+
86+
array_shift($remainingFieldPathComponents);
87+
88+
return $this->getConfig($flattenedSets, $remainingFieldPathComponents);
89+
}
90+
91+
$fields = $this->resolveFields($config[$remainingFieldPathComponents[0]]['fields']);
92+
93+
array_shift($remainingFieldPathComponents);
94+
95+
return $this->getConfig($fields[$remainingFieldPathComponents[0]]['field'], $remainingFieldPathComponents);
96+
}
97+
98+
private function resolveFields(array $fields): array
99+
{
100+
return collect($fields)
101+
->flatMap(function ($field): array {
102+
if (isset($field['import']) || (isset($field['field']) && is_string($field['field']))) {
103+
return (new Fields([$field]))
104+
->all()
105+
->map(fn (Field $field) => [
106+
'handle' => $field->handle(),
107+
'field' => $field->config(),
108+
])
109+
->all();
110+
}
111+
112+
return [$field];
113+
})
114+
->keyBy('handle')
115+
->all();
116+
}
102117
}

tests/Fieldtypes/ReplicatorTest.php

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,98 @@ public function it_can_return_set_defaults()
840840
], $response->json('new'));
841841
}
842842

843+
/**
844+
* We're purposefully naming the sets the same as its nested field to replicate the reported issue.
845+
*
846+
* @see https://github.com/statamic/cms/issues/13687
847+
*/
848+
#[Test]
849+
public function it_can_return_set_defaults_for_nested_sets()
850+
{
851+
$this->partialMock(RowId::class, function (MockInterface $mock) {
852+
$mock->shouldReceive('generate')->andReturn('random-string-1', 'random-string-2');
853+
});
854+
855+
$cards = Fieldset::make('cards')->setContents(['fields' => [
856+
['handle' => 'cards', 'field' => ['type' => 'replicator', 'sets' => [
857+
'card' => [
858+
'sets' => [
859+
'card' => [
860+
'fields' => [
861+
['handle' => 'text_field', 'field' => ['type' => 'text', 'default' => 'the default']],
862+
],
863+
],
864+
],
865+
],
866+
]]],
867+
]]);
868+
869+
$article = Fieldset::make('article')->setContents(['fields' => [
870+
['handle' => 'bard_field', 'field' => ['type' => 'bard', 'sets' => [
871+
'cards' => [
872+
'sets' => [
873+
'cards' => [
874+
'fields' => [
875+
['import' => 'cards'],
876+
],
877+
],
878+
],
879+
],
880+
]]],
881+
]]);
882+
883+
$pageBuilder = Fieldset::make('page_builder')->setContents(['fields' => [
884+
['handle' => 'page_builder', 'field' => ['type' => 'replicator', 'sets' => [
885+
'replicator_set_group' => [
886+
'sets' => [
887+
'article' => [
888+
'fields' => [
889+
['handle' => 'text_field', 'field' => ['type' => 'text', 'default' => 'the default']],
890+
['import' => 'article'],
891+
],
892+
],
893+
],
894+
],
895+
]]],
896+
]]);
897+
898+
Fieldset::shouldReceive('find')->with('cards')->andReturn($cards);
899+
Fieldset::shouldReceive('find')->with('article')->andReturn($article);
900+
Fieldset::shouldReceive('find')->with('page_builder')->andReturn($pageBuilder);
901+
902+
$blueprint = Facades\Blueprint::make()->setHandle('default')->setNamespace('collections.pages');
903+
$blueprint->setContents([
904+
'sections' => [
905+
'main' => [
906+
'fields' => [
907+
['import' => 'page_builder'],
908+
],
909+
],
910+
],
911+
]);
912+
913+
Facades\Blueprint::partialMock();
914+
Facades\Blueprint::shouldReceive('find')->with('collections.pages.default')->andReturn($blueprint);
915+
916+
$response = $this
917+
->actingAs(tap(Facades\User::make()->makeSuper())->save())
918+
->postJson(cp_route('replicator-fieldtype.set'), [
919+
'blueprint' => 'collections.pages.default',
920+
'field' => 'page_builder.article.bard_field.cards.cards',
921+
'set' => 'card',
922+
])
923+
->assertOk();
924+
925+
$this->assertEquals([
926+
'text_field' => 'the default',
927+
], $response->json('defaults'));
928+
929+
$this->assertEquals([
930+
'_' => '_',
931+
'text_field' => null,
932+
], $response->json('new'));
933+
}
934+
843935
public static function groupedSetsProvider()
844936
{
845937
return [

0 commit comments

Comments
 (0)