diff --git a/resources/js/components/blueprints/ImportField.vue b/resources/js/components/blueprints/ImportField.vue index babeb9da327..590fab51e3e 100644 --- a/resources/js/components/blueprints/ImportField.vue +++ b/resources/js/components/blueprints/ImportField.vue @@ -11,6 +11,7 @@ + - +
+ +
@@ -76,6 +82,14 @@ export default { type: Boolean, default: false, }, + excludeFieldset: { + type: String, + default: null, + }, + withCommandPalette: { + type: Boolean, + default: false, + }, }, data() { diff --git a/resources/js/components/fields/ImportSettings.vue b/resources/js/components/fields/ImportSettings.vue index 3f4dcc2f815..1bcc72dcea1 100644 --- a/resources/js/components/fields/ImportSettings.vue +++ b/resources/js/components/fields/ImportSettings.vue @@ -16,16 +16,36 @@ + + + + + + diff --git a/src/Fields/FieldTransformer.php b/src/Fields/FieldTransformer.php index 156e5441a65..ab992143676 100644 --- a/src/Fields/FieldTransformer.php +++ b/src/Fields/FieldTransformer.php @@ -21,6 +21,9 @@ private static function importTabField(array $submitted) return array_filter([ 'import' => $submitted['fieldset'], 'prefix' => $submitted['prefix'] ?? null, + 'section_behavior' => ($submitted['section_behavior'] ?? 'preserve') === 'flatten' + ? 'flatten' + : null, ]); } @@ -179,11 +182,17 @@ private static function inlineFieldToVue($field): array private static function importFieldToVue($field): array { - return [ + $import = [ 'type' => 'import', 'fieldset' => $field['import'], 'prefix' => $field['prefix'] ?? null, ]; + + if (isset($field['section_behavior'])) { + $import['section_behavior'] = $field['section_behavior']; + } + + return $import; } public static function fieldsetFields() diff --git a/src/Fields/Fieldset.php b/src/Fields/Fieldset.php index cf01cabb96a..8ddd4467d6e 100644 --- a/src/Fields/Fieldset.php +++ b/src/Fields/Fieldset.php @@ -2,6 +2,7 @@ namespace Statamic\Fields; +use Illuminate\Support\Collection as IlluminateCollection; use Statamic\CommandPalette\Category; use Statamic\CommandPalette\Link; use Statamic\Events\FieldsetCreated; @@ -62,18 +63,24 @@ public function initialPath($path = null) return $this; } + /** + * Canonical storage: either top-level `fields` (flat) or non-empty `sections`, never both. + * Hand-edited YAML or mixed input is normalized on save. + */ public function setContents(array $contents) { - $fields = Arr::get($contents, 'fields', []); - - // Support legacy syntax - if (! empty($fields) && array_keys($fields)[0] !== 0) { - $fields = collect($fields)->map(function ($field, $handle) { - return compact('handle', 'field'); - })->values()->all(); + if (array_key_exists('sections', $contents)) { + $contents['sections'] = $this->normalizeSections(Arr::get($contents, 'sections', [])); } - $contents['fields'] = $fields; + $usesSections = array_key_exists('sections', $contents) && ! empty($contents['sections']); + + if ($usesSections) { + unset($contents['fields']); + } else { + unset($contents['sections']); + $contents['fields'] = $this->normalizeFields(Arr::get($contents, 'fields', [])); + } $this->contents = $contents; @@ -100,11 +107,23 @@ public function validateRecursion() public function fields(): Fields { - $fields = Arr::get($this->contents, 'fields', []); + $fields = $this->hasSections() + ? $this->sections()->flatMap(fn ($section) => Arr::get($section, 'fields', []))->values()->all() + : Arr::get($this->contents, 'fields', []); return new Fields($fields); } + public function sections(): IlluminateCollection + { + return collect(Arr::get($this->contents, 'sections', [])); + } + + public function hasSections(): bool + { + return $this->sections()->isNotEmpty(); + } + public function field(string $handle): ?Field { return $this->fields()->get($handle); @@ -159,9 +178,12 @@ public function importedBy(): array })->values(); $fieldsets = \Statamic\Facades\Fieldset::all() - ->filter(fn (Fieldset $fieldset) => isset($fieldset->contents()['fields'])) ->filter(function (Fieldset $fieldset) { - return collect($fieldset->contents()['fields']) + $fields = $fieldset->hasSections() + ? $fieldset->sections()->flatMap(fn ($section) => Arr::get($section, 'fields', []))->all() + : Arr::get($fieldset->contents(), 'fields', []); + + return collect($fields) ->filter(fn ($field) => $this->fieldImportsFieldset($field)) ->isNotEmpty(); }) @@ -311,4 +333,25 @@ public static function __callStatic($method, $parameters) { return Facades\Fieldset::{$method}(...$parameters); } + + private function normalizeFields(array $fields): array + { + // Support legacy syntax where handles are array keys. + if (! empty($fields) && array_keys($fields)[0] !== 0) { + $fields = collect($fields)->map(function ($field, $handle) { + return compact('handle', 'field'); + })->values()->all(); + } + + return $fields; + } + + private function normalizeSections(array $sections): array + { + return collect($sections)->map(function ($section) { + $section['fields'] = $this->normalizeFields(Arr::get($section, 'fields', [])); + + return $section; + })->all(); + } } diff --git a/src/Fields/Tab.php b/src/Fields/Tab.php index bbc50f2a2bc..0c67dfaabc1 100644 --- a/src/Fields/Tab.php +++ b/src/Fields/Tab.php @@ -2,6 +2,7 @@ namespace Statamic\Fields; +use Statamic\Facades\Fieldset as FieldsetRepository; use Statamic\Support\Arr; use Statamic\Support\Str; @@ -81,7 +82,7 @@ public function toPublishArray() 'display' => $this->display(), 'instructions' => $this->instructions(), 'handle' => $this->handle, - 'sections' => $this->sections()->map->toPublishArray()->all(), + 'sections' => $this->expandedSections(), ]; } @@ -94,4 +95,89 @@ public function instructions() { return Arr::get($this->contents, 'instructions'); } + + private function expandedSections(): array + { + return $this->sections()->reduce(function ($carry, Section $section) { + $fields = Arr::get($section->contents(), 'fields', []); + + if (empty($fields)) { + return $carry->push($section->toPublishArray()); + } + + $sectionContents = $section->contents(); + $sectionMeta = Arr::except($sectionContents, ['fields']); + $bufferedFields = []; + + foreach ($fields as $field) { + if (! $this->isSectionedFieldsetImport($field)) { + $bufferedFields[] = $field; + + continue; + } + + if (! empty($bufferedFields)) { + $carry->push((new Section($sectionMeta + ['fields' => $bufferedFields]))->toPublishArray()); + $bufferedFields = []; + } + + $importedSections = FieldsetRepository::find($field['import'])->sections(); + + foreach ($importedSections as $importedSection) { + $carry->push($this->toImportedPublishSection($importedSection, $field)); + } + } + + if (! empty($bufferedFields)) { + $carry->push((new Section($sectionMeta + ['fields' => $bufferedFields]))->toPublishArray()); + } + + return $carry; + }, collect())->all(); + } + + private function isSectionedFieldsetImport(array $field): bool + { + if (! isset($field['import'])) { + return false; + } + + if (($field['section_behavior'] ?? 'preserve') === 'flatten') { + return false; + } + + $fieldset = FieldsetRepository::find($field['import']); + + return $fieldset && $fieldset->hasSections(); + } + + private function toImportedPublishSection(array $section, array $import): array + { + $fields = (new Fields(Arr::get($section, 'fields', [])))->all(); + + if ($overrides = $import['config'] ?? null) { + $fields = $fields->map(function ($field, $handle) use ($overrides) { + return $field->setConfig(array_merge($field->config(), $overrides[$handle] ?? [])); + }); + } + + if ($prefix = Arr::get($import, 'prefix')) { + $fields = $fields->mapWithKeys(function ($field) use ($prefix) { + $field = clone $field; + $handle = $prefix.$field->handle(); + $prefix = $prefix.$field->prefix(); + + return [$handle => $field->setHandle($handle)->setPrefix($prefix)]; + }); + } + + return Arr::removeNullValues([ + 'display' => Arr::get($section, 'display'), + 'instructions' => Arr::get($section, 'instructions'), + 'collapsible' => ($collapsible = Arr::get($section, 'collapsible')) ?: null, + 'collapsed' => ($collapsible && Arr::get($section, 'collapsed')) ?: null, + ]) + [ + 'fields' => $fields->map->toPublishArray()->values()->all(), + ]; + } } diff --git a/src/Http/Controllers/CP/Fields/FieldsetController.php b/src/Http/Controllers/CP/Fields/FieldsetController.php index 0254a72f06f..7027ff80e42 100644 --- a/src/Http/Controllers/CP/Fields/FieldsetController.php +++ b/src/Http/Controllers/CP/Fields/FieldsetController.php @@ -112,9 +112,7 @@ public function edit($fieldset) $vue = [ 'title' => $fieldset->title(), 'handle' => $fieldset->handle(), - 'fields' => collect(Arr::get($fieldset->contents(), 'fields'))->map(function ($field, $i) { - return array_merge(FieldTransformer::toVue($field), ['_id' => $i]); - })->all(), + 'sections' => $this->sectionsToVue($fieldset), ]; return Inertia::render('fieldsets/Edit', [ @@ -130,15 +128,30 @@ public function update(Request $request, $fieldset) $request->validate([ 'title' => 'required', + 'sections' => 'array', 'fields' => 'array', ]); - $fieldset->setContents(array_merge($fieldset->contents(), [ - 'title' => $request->title, - 'fields' => collect($request->fields)->map(function ($field) { - return FieldTransformer::fromVue($field); - })->all(), - ])); + $base = array_merge( + Arr::except($fieldset->contents(), ['fields', 'sections']), + ['title' => $request->title], + ); + + if ($request->has('sections')) { + $sections = $this->sectionsFromVueRequest($request->sections); + + $fieldset->setContents( + $this->shouldStoreAsFlatFields($sections) + ? $base + ['fields' => Arr::get($sections, '0.fields', [])] + : $base + ['sections' => $sections] + ); + } else { + $fieldset->setContents($base + [ + 'fields' => collect($request->fields) + ->map(fn ($field) => FieldTransformer::fromVue($field)) + ->all(), + ]); + } $fieldset->validateRecursion(); @@ -217,4 +230,58 @@ private function groupKey(Fieldset $fieldset): string return __('My Fieldsets'); } + + private function sectionsFromVueRequest(array $sections): array + { + return collect($sections)->map(function ($section) { + return Arr::removeNullValues([ + 'display' => Arr::get($section, 'display'), + 'instructions' => Arr::get($section, 'instructions'), + 'collapsible' => ($collapsible = Arr::get($section, 'collapsible')) ?: null, + 'collapsed' => ($collapsible && Arr::get($section, 'collapsed')) ?: null, + 'fields' => collect(Arr::get($section, 'fields', [])) + ->map(fn ($field) => FieldTransformer::fromVue($field)) + ->all(), + ]); + })->all(); + } + + private function sectionsToVue(Fieldset $fieldset): array + { + $sections = $fieldset->hasSections() + ? Arr::get($fieldset->contents(), 'sections', []) + : [[ + 'display' => __('Fields'), + 'fields' => Arr::get($fieldset->contents(), 'fields', []), + ]]; + + return collect($sections)->map(function ($section, $sectionIndex) { + return Arr::removeNullValues([ + '_id' => "section-{$sectionIndex}", + 'display' => Arr::get($section, 'display'), + 'instructions' => Arr::get($section, 'instructions'), + 'collapsible' => Arr::get($section, 'collapsible'), + 'collapsed' => Arr::get($section, 'collapsed'), + ]) + [ + 'fields' => collect(Arr::get($section, 'fields', []))->map(function ($field, $fieldIndex) use ($sectionIndex) { + return array_merge(FieldTransformer::toVue($field), ['_id' => "section-{$sectionIndex}-{$fieldIndex}"]); + })->all(), + ]; + })->all(); + } + + private function shouldStoreAsFlatFields(array $sections): bool + { + if (count($sections) !== 1) { + return false; + } + + $section = $sections[0]; + $display = Arr::get($section, 'display'); + + return in_array($display, [null, '', __('Fields')], true) + && Arr::get($section, 'instructions') === null + && Arr::get($section, 'collapsible') !== true + && Arr::get($section, 'collapsed') !== true; + } } diff --git a/src/Http/Controllers/CP/Fields/ManagesFields.php b/src/Http/Controllers/CP/Fields/ManagesFields.php index 7df69f95c9f..98394fb9ff6 100644 --- a/src/Http/Controllers/CP/Fields/ManagesFields.php +++ b/src/Http/Controllers/CP/Fields/ManagesFields.php @@ -21,10 +21,16 @@ private function fieldProps() private function fieldsets() { return Fieldset::all()->mapWithKeys(function ($fieldset) { + $fields = $fieldset->hasSections() + ? $fieldset->sections()->flatMap(fn ($section) => Arr::get($section, 'fields', [])) + : collect(Arr::get($fieldset->contents(), 'fields', [])); + return [$fieldset->handle() => [ 'handle' => $fieldset->handle(), 'title' => $fieldset->title(), - 'fields' => collect(Arr::get($fieldset->contents(), 'fields'))->map(function ($field) { + 'has_sections' => $fieldset->hasSections(), + 'sections_count' => $fieldset->hasSections() ? $fieldset->sections()->count() : 0, + 'fields' => $fields->map(function ($field) { return FieldTransformer::toVue($field); })->sortBy('config.display')->values()->all(), ]]; diff --git a/tests/Feature/Fieldsets/EditFieldsetTest.php b/tests/Feature/Fieldsets/EditFieldsetTest.php index df82c1882d0..24d88b92f13 100644 --- a/tests/Feature/Fieldsets/EditFieldsetTest.php +++ b/tests/Feature/Fieldsets/EditFieldsetTest.php @@ -43,12 +43,46 @@ public function it_provides_the_fieldset() { $this->withoutExceptionHandling(); $user = Facades\User::make()->makeSuper()->save(); - $fieldset = (new Fieldset)->setHandle('test')->setContents(['title' => 'Test'])->save(); + $fieldset = (new Fieldset)->setHandle('test')->setContents([ + 'title' => 'Test', + 'fields' => [ + ['handle' => 'title', 'field' => ['type' => 'text']], + ], + ])->save(); + + $this + ->actingAs($user) + ->get($fieldset->editUrl()) + ->assertStatus(200) + ->assertInertia(fn ($page) => $page + ->where('initialFieldset.handle', $fieldset->handle()) + ->where('initialFieldset.sections.0.fields.0.handle', 'title') + ); + } + + #[Test] + public function it_provides_sectioned_fieldsets() + { + $user = Facades\User::make()->makeSuper()->save(); + $fieldset = (new Fieldset)->setHandle('test')->setContents([ + 'title' => 'Test', + 'sections' => [ + [ + 'display' => 'SEO', + 'fields' => [ + ['handle' => 'meta_title', 'field' => ['type' => 'text']], + ], + ], + ], + ])->save(); $this ->actingAs($user) ->get($fieldset->editUrl()) ->assertStatus(200) - ->assertInertia(fn ($page) => $page->where('initialFieldset.handle', $fieldset->handle())); + ->assertInertia(fn ($page) => $page + ->where('initialFieldset.sections.0.display', 'SEO') + ->where('initialFieldset.sections.0.fields.0.handle', 'meta_title') + ); } } diff --git a/tests/Feature/Fieldsets/UpdateFieldsetTest.php b/tests/Feature/Fieldsets/UpdateFieldsetTest.php index 619a75c10bf..ff9e694bd4c 100644 --- a/tests/Feature/Fieldsets/UpdateFieldsetTest.php +++ b/tests/Feature/Fieldsets/UpdateFieldsetTest.php @@ -105,6 +105,231 @@ public function fieldset_gets_saved() ], Facades\Fieldset::find('test')->contents()); } + #[Test] + public function fieldset_gets_saved_with_sections() + { + $user = tap(Facades\User::make()->makeSuper())->save(); + (new Fieldset)->setHandle('seo_defaults')->setContents([ + 'title' => 'SEO Defaults', + 'fields' => [ + ['handle' => 'canonical_url', 'field' => ['type' => 'text']], + ], + ])->save(); + + $fieldset = (new Fieldset)->setHandle('test')->setContents([ + 'title' => 'Test', + 'foo' => 'bar', + 'fields' => [ + ['handle' => 'legacy', 'field' => ['type' => 'text']], + ], + ])->save(); + + $this + ->actingAs($user) + ->submit($fieldset, [ + 'title' => 'Updated title', + 'sections' => [ + [ + '_id' => 'section-1', + 'display' => 'SEO', + 'instructions' => 'SEO fields', + 'collapsible' => true, + 'collapsed' => true, + 'fields' => [ + [ + '_id' => 'section-1-field-1', + 'handle' => 'meta_title', + 'type' => 'inline', + 'config' => [ + 'type' => 'text', + 'display' => 'Meta title', + ], + ], + [ + '_id' => 'section-1-field-2', + 'type' => 'import', + 'fieldset' => 'seo_defaults', + 'prefix' => 'seo_', + ], + ], + ], + ], + ]) + ->assertStatus(204); + + $this->assertEquals([ + 'title' => 'Updated title', + 'foo' => 'bar', + 'sections' => [ + [ + 'display' => 'SEO', + 'instructions' => 'SEO fields', + 'collapsible' => true, + 'collapsed' => true, + 'fields' => [ + [ + 'handle' => 'meta_title', + 'field' => [ + 'type' => 'text', + 'display' => 'Meta title', + ], + ], + [ + 'import' => 'seo_defaults', + 'prefix' => 'seo_', + ], + ], + ], + ], + ], Facades\Fieldset::find('test')->contents()); + } + + #[Test] + public function fieldset_sections_are_removed_when_updating_with_flat_fields() + { + $user = tap(Facades\User::make()->makeSuper())->save(); + $fieldset = (new Fieldset)->setHandle('test')->setContents([ + 'title' => 'Test', + 'sections' => [ + [ + 'display' => 'SEO', + 'fields' => [ + ['handle' => 'legacy', 'field' => ['type' => 'text']], + ], + ], + ], + ])->save(); + + $this + ->actingAs($user) + ->submit($fieldset, [ + 'title' => 'Updated title', + 'fields' => [ + [ + '_id' => 'flat-1', + 'handle' => 'meta_title', + 'type' => 'inline', + 'config' => [ + 'type' => 'text', + 'display' => 'Meta title', + ], + ], + ], + ]) + ->assertStatus(204); + + $this->assertEquals([ + 'title' => 'Updated title', + 'fields' => [ + [ + 'handle' => 'meta_title', + 'field' => [ + 'type' => 'text', + 'display' => 'Meta title', + ], + ], + ], + ], Facades\Fieldset::find('test')->contents()); + } + + #[Test] + public function import_section_behavior_is_saved_when_flattening_sections() + { + $user = tap(Facades\User::make()->makeSuper())->save(); + (new Fieldset)->setHandle('seo_defaults')->setContents([ + 'title' => 'SEO Defaults', + 'fields' => [ + ['handle' => 'canonical_url', 'field' => ['type' => 'text']], + ], + ])->save(); + + $fieldset = (new Fieldset)->setHandle('test')->setContents([ + 'title' => 'Test', + 'fields' => [], + ])->save(); + + $this + ->actingAs($user) + ->submit($fieldset, [ + 'sections' => [ + [ + '_id' => 'section-1', + 'display' => 'Main', + 'fields' => [ + [ + '_id' => 'section-1-field-1', + 'type' => 'import', + 'fieldset' => 'seo_defaults', + 'section_behavior' => 'flatten', + ], + ], + ], + ], + ]) + ->assertStatus(204); + + $this->assertEquals([ + 'title' => 'Updated', + 'sections' => [ + [ + 'display' => 'Main', + 'fields' => [ + [ + 'import' => 'seo_defaults', + 'section_behavior' => 'flatten', + ], + ], + ], + ], + ], Facades\Fieldset::find('test')->contents()); + } + + #[Test] + public function single_default_section_is_collapsed_back_to_flat_fields() + { + $user = tap(Facades\User::make()->makeSuper())->save(); + $fieldset = (new Fieldset)->setHandle('test')->setContents([ + 'title' => 'Test', + 'fields' => [], + ])->save(); + + $this + ->actingAs($user) + ->submit($fieldset, [ + 'sections' => [ + [ + '_id' => 'section-1', + 'display' => 'Fields', + 'fields' => [ + [ + '_id' => 'section-1-field-1', + 'handle' => 'meta_title', + 'type' => 'inline', + 'config' => [ + 'type' => 'text', + 'display' => 'Meta title', + ], + ], + ], + ], + ], + ]) + ->assertStatus(204); + + $this->assertEquals([ + 'title' => 'Updated', + 'fields' => [ + [ + 'handle' => 'meta_title', + 'field' => [ + 'type' => 'text', + 'display' => 'Meta title', + ], + ], + ], + ], Facades\Fieldset::find('test')->contents()); + } + #[Test] public function title_is_required() { diff --git a/tests/Fields/FieldsetTest.php b/tests/Fields/FieldsetTest.php index ff8fb006d08..559edcbee9f 100644 --- a/tests/Fields/FieldsetTest.php +++ b/tests/Fields/FieldsetTest.php @@ -117,6 +117,107 @@ public function it_gets_fields() $this->assertEquals(['text', 'textarea'], $fields->map->type()->values()->all()); } + #[Test] + public function it_gets_fields_from_sections() + { + $fieldset = new Fieldset; + + $fieldset->setContents([ + 'sections' => [ + [ + 'display' => 'First section', + 'fields' => [ + [ + 'handle' => 'one', + 'field' => ['type' => 'text'], + ], + ], + ], + [ + 'display' => 'Second section', + 'fields' => [ + [ + 'handle' => 'two', + 'field' => ['type' => 'textarea'], + ], + ], + ], + ], + ]); + + $fields = $fieldset->fields(); + + $this->assertTrue($fieldset->hasSections()); + $this->assertCount(2, $fieldset->sections()); + $this->assertInstanceOf(Fields::class, $fields); + $this->assertEveryItemIsInstanceOf(Field::class, $fields = $fields->all()); + $this->assertEquals(['one', 'two'], $fields->map->handle()->values()->all()); + $this->assertEquals(['text', 'textarea'], $fields->map->type()->values()->all()); + } + + #[Test] + public function it_drops_empty_sections_when_storing_flat_fields() + { + $fieldset = new Fieldset; + + $fieldset->setContents([ + 'title' => 'Test', + 'sections' => [], + 'fields' => [ + ['handle' => 'one', 'field' => ['type' => 'text']], + ], + ]); + + $this->assertArrayNotHasKey('sections', $fieldset->contents()); + $this->assertEquals('one', $fieldset->fields()->all()->first()->handle()); + } + + #[Test] + public function it_drops_top_level_fields_when_storing_sections() + { + $fieldset = new Fieldset; + + $fieldset->setContents([ + 'sections' => [ + [ + 'display' => 'A', + 'fields' => [ + ['handle' => 'one', 'field' => ['type' => 'text']], + ], + ], + ], + 'fields' => [ + ['handle' => 'stale', 'field' => ['type' => 'text']], + ], + ]); + + $this->assertArrayNotHasKey('fields', $fieldset->contents()); + $this->assertEquals(['one'], $fieldset->fields()->all()->keys()->all()); + } + + #[Test] + public function it_normalizes_legacy_section_fields_syntax() + { + $fieldset = new Fieldset; + + $fieldset->setContents([ + 'sections' => [ + [ + 'display' => 'First section', + 'fields' => [ + 'one' => ['type' => 'text'], + 'two' => ['type' => 'textarea'], + ], + ], + ], + ]); + + $fields = $fieldset->fields()->all(); + + $this->assertEquals(['one', 'two'], $fields->map->handle()->values()->all()); + $this->assertEquals(['text', 'textarea'], $fields->map->type()->values()->all()); + } + #[Test] public function it_gets_fields_using_legacy_syntax() { @@ -445,6 +546,32 @@ public function gets_fieldsets_importing_single_field_from_fieldset() $this->assertEquals($fieldsetA->handle(), $importedBy['fieldsets']->first()->handle()); } + #[Test] + public function gets_fieldsets_importing_fieldset_inside_sections() + { + $fieldset = Fieldset::make('seo')->setContents(['fields' => [ + ['handle' => 'meta_title', 'field' => ['type' => 'text']], + ]])->save(); + + $fieldsetA = Fieldset::make('one') + ->setContents([ + 'sections' => [ + [ + 'display' => 'SEO', + 'fields' => [ + ['import' => 'seo'], + ], + ], + ], + ]) + ->save(); + + $importedBy = $fieldset->importedBy(); + + $this->assertCount(1, $importedBy['fieldsets']); + $this->assertEquals($fieldsetA->handle(), $importedBy['fieldsets']->first()->handle()); + } + #[Test] public function it_saves_through_the_repository() { diff --git a/tests/Fields/TabTest.php b/tests/Fields/TabTest.php index 69094f5abb1..c26e642a433 100644 --- a/tests/Fields/TabTest.php +++ b/tests/Fields/TabTest.php @@ -4,8 +4,10 @@ use Facades\Statamic\Fields\FieldRepository; use PHPUnit\Framework\Attributes\Test; +use Statamic\Facades\Fieldset as FieldsetRepository; use Statamic\Fields\Field; use Statamic\Fields\Fields; +use Statamic\Fields\Fieldset; use Statamic\Fields\Tab; use Tests\TestCase; @@ -194,4 +196,121 @@ public function converts_to_array_suitable_for_rendering_fields_in_publish_compo ], ], $tab->toPublishArray()); } + + #[Test] + public function it_expands_sectioned_fieldset_imports_into_publish_sections() + { + FieldsetRepository::shouldReceive('find') + ->with('seo') + ->andReturn((new Fieldset)->setHandle('seo')->setContents([ + 'sections' => [ + [ + 'display' => 'SEO', + 'fields' => [ + ['handle' => 'meta_title', 'field' => ['type' => 'text']], + ], + ], + [ + 'display' => 'Social', + 'fields' => [ + ['handle' => 'og_title', 'field' => ['type' => 'text']], + ], + ], + ], + ])); + + $tab = (new Tab('main'))->setContents([ + 'sections' => [ + [ + 'display' => 'Main', + 'fields' => [ + ['handle' => 'title', 'field' => ['type' => 'text']], + ['import' => 'seo'], + ['handle' => 'summary', 'field' => ['type' => 'textarea']], + ], + ], + ], + ]); + + $publish = $tab->toPublishArray(); + + $this->assertCount(4, $publish['sections']); + $this->assertEquals('Main', $publish['sections'][0]['display']); + $this->assertEquals(['title'], collect($publish['sections'][0]['fields'])->pluck('handle')->all()); + $this->assertEquals('SEO', $publish['sections'][1]['display']); + $this->assertEquals(['meta_title'], collect($publish['sections'][1]['fields'])->pluck('handle')->all()); + $this->assertEquals('Social', $publish['sections'][2]['display']); + $this->assertEquals(['og_title'], collect($publish['sections'][2]['fields'])->pluck('handle')->all()); + $this->assertEquals('Main', $publish['sections'][3]['display']); + $this->assertEquals(['summary'], collect($publish['sections'][3]['fields'])->pluck('handle')->all()); + } + + #[Test] + public function it_applies_prefixes_to_fields_inside_imported_fieldset_sections() + { + FieldsetRepository::shouldReceive('find') + ->with('seo') + ->andReturn((new Fieldset)->setHandle('seo')->setContents([ + 'sections' => [ + [ + 'display' => 'SEO', + 'fields' => [ + ['handle' => 'meta_title', 'field' => ['type' => 'text']], + ], + ], + ], + ])); + + $tab = (new Tab('main'))->setContents([ + 'sections' => [ + [ + 'fields' => [ + ['import' => 'seo', 'prefix' => 'seo_'], + ], + ], + ], + ]); + + $publish = $tab->toPublishArray(); + $field = $publish['sections'][0]['fields'][0]; + + $this->assertEquals('seo_meta_title', $field['handle']); + $this->assertEquals('seo_', $field['prefix']); + } + + #[Test] + public function it_can_flatten_imported_fieldset_sections_in_place() + { + FieldsetRepository::shouldReceive('find') + ->with('seo') + ->andReturn((new Fieldset)->setHandle('seo')->setContents([ + 'sections' => [ + [ + 'display' => 'SEO', + 'fields' => [ + ['handle' => 'meta_title', 'field' => ['type' => 'text']], + ], + ], + ], + ])); + + $tab = (new Tab('main'))->setContents([ + 'sections' => [ + [ + 'display' => 'Main', + 'fields' => [ + ['handle' => 'title', 'field' => ['type' => 'text']], + ['import' => 'seo', 'section_behavior' => 'flatten'], + ['handle' => 'summary', 'field' => ['type' => 'textarea']], + ], + ], + ], + ]); + + $publish = $tab->toPublishArray(); + + $this->assertCount(1, $publish['sections']); + $this->assertEquals('Main', $publish['sections'][0]['display']); + $this->assertEquals(['title', 'meta_title', 'summary'], collect($publish['sections'][0]['fields'])->pluck('handle')->all()); + } }