From 21676a2a21b24c2489a34344790709d954411bb3 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Wed, 25 Feb 2026 10:51:40 +0000 Subject: [PATCH 1/5] Add failing test --- tests/Fieldtypes/ReplicatorTest.php | 66 +++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/tests/Fieldtypes/ReplicatorTest.php b/tests/Fieldtypes/ReplicatorTest.php index 7d2a2b097f5..06d12b95766 100644 --- a/tests/Fieldtypes/ReplicatorTest.php +++ b/tests/Fieldtypes/ReplicatorTest.php @@ -1105,6 +1105,72 @@ public function it_can_return_set_defaults_for_replicator_inside_grid() ], $response->json('new')); } + #[Test] + public function it_can_return_set_defaults_for_replicator_inside_custom_fieldtype() + { + $this->partialMock(RowId::class, function (MockInterface $mock) { + $mock->shouldReceive('generate')->andReturn('random-string-1', 'random-string-2'); + }); + + $blueprint = Facades\Blueprint::make()->setHandle('default')->setNamespace('collections.pages'); + $blueprint->setContents([ + 'sections' => [ + 'main' => [ + 'fields' => [ + [ + 'handle' => 'stuff', + 'field' => [ + 'type' => 'custom_fieldtype', + 'fields' => [ + [ + 'handle' => 'content_blocks', + 'field' => [ + 'type' => 'replicator', + 'sets' => [ + 'text' => [ + 'fields' => [ + [ + 'handle' => 'body', + 'field' => [ + 'type' => 'textarea', + 'default' => 'the default', + ], + ], + ], + ], + ], + ], + ], + ], + ], + ], + ], + ], + ], + ]); + + Facades\Blueprint::partialMock(); + Facades\Blueprint::shouldReceive('find')->with('collections.pages.default')->andReturn($blueprint); + + $response = $this + ->actingAs(tap(Facades\User::make()->makeSuper())->save()) + ->postJson(cp_route('replicator-fieldtype.set'), [ + 'blueprint' => 'collections.pages.default', + 'field' => 'stuff.content_blocks', + 'set' => 'text', + ]) + ->assertOk(); + + $this->assertEquals([ + 'body' => 'the default', + ], $response->json('defaults')); + + $this->assertEquals([ + '_' => '_', + 'body' => null, + ], $response->json('new')); + } + #[Test] public function it_can_return_set_defaults_when_sets_are_stored_in_legacy_format() { From 14c492758a178a42775e43a1fbacbf9233670b26 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Wed, 25 Feb 2026 10:52:56 +0000 Subject: [PATCH 2/5] Loosen Group/Grid check --- src/Http/Controllers/CP/Fieldtypes/ReplicatorSetController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Controllers/CP/Fieldtypes/ReplicatorSetController.php b/src/Http/Controllers/CP/Fieldtypes/ReplicatorSetController.php index b4f4c56e702..c3e9f008b19 100644 --- a/src/Http/Controllers/CP/Fieldtypes/ReplicatorSetController.php +++ b/src/Http/Controllers/CP/Fieldtypes/ReplicatorSetController.php @@ -71,7 +71,7 @@ private function getReplicatorField(Blueprint $blueprint, string $field): Field private function getConfig(array $config, array $remainingFieldPathComponents): array { - $isGroupOrGrid = isset($config['type']) && in_array($config['type'], ['group', 'grid']); + $isGroupOrGrid = isset($config['fields']); $isReplicator = isset($config['type']) && in_array($config['type'], ['bard', 'replicator']); if ($isReplicator) { From 312f43c8daa44ad80e87180709344fc2ff46a4aa Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Fri, 11 Sep 2026 08:29:18 +0100 Subject: [PATCH 3/5] send encrypted token payload in custom fieldtype test Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_011yxrHxZC18iQiRgJgDjccK --- tests/Fieldtypes/ReplicatorTest.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/Fieldtypes/ReplicatorTest.php b/tests/Fieldtypes/ReplicatorTest.php index 46d7ab29b23..8d1784a3bd2 100644 --- a/tests/Fieldtypes/ReplicatorTest.php +++ b/tests/Fieldtypes/ReplicatorTest.php @@ -1173,10 +1173,15 @@ public function it_can_return_set_defaults_for_replicator_inside_custom_fieldtyp Facades\Blueprint::partialMock(); Facades\Blueprint::shouldReceive('find')->with('collections.pages.default')->andReturn($blueprint); + $user = tap(Facades\User::make()->makeSuper())->save(); + $response = $this - ->actingAs(tap(Facades\User::make()->makeSuper())->save()) + ->actingAs($user) ->postJson(cp_route('replicator-fieldtype.set'), [ - 'blueprint' => 'collections.pages.default', + 'token' => encrypt([ + 'fqh' => 'collections.pages.default', + 'user_id' => $user->id(), + ]), 'field' => 'stuff.content_blocks', 'set' => 'text', ]) From 0c21e8d45738c91eec4e1fb9d8316ee0b2d55a09 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Fri, 11 Sep 2026 08:29:38 +0100 Subject: [PATCH 4/5] guard nested field check against replicator sets maps Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_011yxrHxZC18iQiRgJgDjccK --- .../CP/Fieldtypes/ReplicatorSetController.php | 2 +- tests/Fieldtypes/ReplicatorTest.php | 75 +++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/src/Http/Controllers/CP/Fieldtypes/ReplicatorSetController.php b/src/Http/Controllers/CP/Fieldtypes/ReplicatorSetController.php index 96273ac984b..3a11ee8776b 100644 --- a/src/Http/Controllers/CP/Fieldtypes/ReplicatorSetController.php +++ b/src/Http/Controllers/CP/Fieldtypes/ReplicatorSetController.php @@ -85,7 +85,7 @@ private function getReplicatorField(Blueprint $blueprint, string $field): Field private function getConfig(array $config, array $remainingFieldPathComponents): array { - $isGroupOrGrid = isset($config['fields']); + $isGroupOrGrid = isset($config['type']) && isset($config['fields']); $isReplicator = isset($config['type']) && in_array($config['type'], ['bard', 'replicator']); if ($isReplicator) { diff --git a/tests/Fieldtypes/ReplicatorTest.php b/tests/Fieldtypes/ReplicatorTest.php index 8d1784a3bd2..68adc590c5a 100644 --- a/tests/Fieldtypes/ReplicatorTest.php +++ b/tests/Fieldtypes/ReplicatorTest.php @@ -1197,6 +1197,81 @@ public function it_can_return_set_defaults_for_replicator_inside_custom_fieldtyp ], $response->json('new')); } + #[Test] + public function it_can_return_set_defaults_for_replicator_inside_a_set_handled_fields() + { + $this->partialMock(RowId::class, function (MockInterface $mock) { + $mock->shouldReceive('generate')->andReturn('random-string-1', 'random-string-2'); + }); + + $blueprint = Facades\Blueprint::make()->setHandle('default')->setNamespace('collections.pages'); + $blueprint->setContents([ + 'sections' => [ + 'main' => [ + 'fields' => [ + [ + 'handle' => 'form_builder', + 'field' => [ + 'type' => 'replicator', + 'sets' => [ + 'fields' => [ + 'fields' => [ + [ + 'handle' => 'options', + 'field' => [ + 'type' => 'replicator', + 'sets' => [ + 'option' => [ + 'fields' => [ + [ + 'handle' => 'label', + 'field' => [ + 'type' => 'text', + 'default' => 'the default', + ], + ], + ], + ], + ], + ], + ], + ], + ], + ], + ], + ], + ], + ], + ], + ]); + + Facades\Blueprint::partialMock(); + Facades\Blueprint::shouldReceive('find')->with('collections.pages.default')->andReturn($blueprint); + + $user = tap(Facades\User::make()->makeSuper())->save(); + + $response = $this + ->actingAs($user) + ->postJson(cp_route('replicator-fieldtype.set'), [ + 'token' => encrypt([ + 'fqh' => 'collections.pages.default', + 'user_id' => $user->id(), + ]), + 'field' => 'form_builder.fields.options', + 'set' => 'option', + ]) + ->assertOk(); + + $this->assertEquals([ + 'label' => 'the default', + ], $response->json('defaults')); + + $this->assertEquals([ + '_' => '_', + 'label' => null, + ], $response->json('new')); + } + #[Test] public function it_can_return_set_defaults_when_sets_are_stored_in_legacy_format() { From e48cce55dcfac791c7fa584e3819abcc35535690 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Fri, 11 Sep 2026 08:29:47 +0100 Subject: [PATCH 5/5] rename `$isGroupOrGrid` to `$hasNestedFields` Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_011yxrHxZC18iQiRgJgDjccK --- .../Controllers/CP/Fieldtypes/ReplicatorSetController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Http/Controllers/CP/Fieldtypes/ReplicatorSetController.php b/src/Http/Controllers/CP/Fieldtypes/ReplicatorSetController.php index 3a11ee8776b..a119c26ba60 100644 --- a/src/Http/Controllers/CP/Fieldtypes/ReplicatorSetController.php +++ b/src/Http/Controllers/CP/Fieldtypes/ReplicatorSetController.php @@ -85,7 +85,7 @@ private function getReplicatorField(Blueprint $blueprint, string $field): Field private function getConfig(array $config, array $remainingFieldPathComponents): array { - $isGroupOrGrid = isset($config['type']) && isset($config['fields']); + $hasNestedFields = isset($config['type']) && isset($config['fields']); $isReplicator = isset($config['type']) && in_array($config['type'], ['bard', 'replicator']); if ($isReplicator) { @@ -100,7 +100,7 @@ private function getConfig(array $config, array $remainingFieldPathComponents): return $this->getConfig($flattenedSets, $remainingFieldPathComponents); } - if ($isGroupOrGrid) { + if ($hasNestedFields) { array_shift($remainingFieldPathComponents); $fields = $this->resolveFields($config['fields'] ?? []);