From 15e8dcc28434b008372a97f25a99449e79b138e9 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 25 Feb 2025 15:43:41 +0000 Subject: [PATCH 1/9] Stop ensuring the "parent" field on structured collections --- src/Entries/Collection.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/Entries/Collection.php b/src/Entries/Collection.php index 70e5c562d4..5eae3c180f 100644 --- a/src/Entries/Collection.php +++ b/src/Entries/Collection.php @@ -384,16 +384,6 @@ public function ensureEntryBlueprintFields($blueprint) $blueprint->ensureField('date', ['type' => 'date', 'required' => true, 'default' => 'now'], 'sidebar'); } - if ($this->hasStructure() && ! $this->orderable()) { - $blueprint->ensureField('parent', [ - 'type' => 'entries', - 'collections' => [$this->handle()], - 'max_items' => 1, - 'listable' => false, - 'localizable' => true, - ], 'sidebar'); - } - foreach ($this->taxonomies() as $taxonomy) { if ($blueprint->hasField($taxonomy->handle())) { continue; From ed1c0d50003fb36afddb14d279d39b5980c913bc Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 25 Feb 2025 15:44:03 +0000 Subject: [PATCH 2/9] Remove "Parent" fields when updating --- src/Providers/ExtensionServiceProvider.php | 1 + src/UpdateScripts/RemoveParentField.php | 30 +++++++ tests/UpdateScripts/RemoveParentFieldTest.php | 82 +++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 src/UpdateScripts/RemoveParentField.php create mode 100644 tests/UpdateScripts/RemoveParentFieldTest.php diff --git a/src/Providers/ExtensionServiceProvider.php b/src/Providers/ExtensionServiceProvider.php index 3712cde802..6883830cd2 100644 --- a/src/Providers/ExtensionServiceProvider.php +++ b/src/Providers/ExtensionServiceProvider.php @@ -246,6 +246,7 @@ class ExtensionServiceProvider extends ServiceProvider Updates\AddSitePermissions::class, Updates\UseClassBasedStatamicUniqueRules::class, Updates\MigrateSitesConfigToYaml::class, + Updates\RemoveParentField::class, ]; public function register() diff --git a/src/UpdateScripts/RemoveParentField.php b/src/UpdateScripts/RemoveParentField.php new file mode 100644 index 0000000000..c13e9a196d --- /dev/null +++ b/src/UpdateScripts/RemoveParentField.php @@ -0,0 +1,30 @@ +isUpdatingTo('6.0.0'); + } + + public function update() + { + Collection::all()->each(function ($collection) { + $collection->entryBlueprints()->each(function ($blueprint) use ($collection) { + if ($collection->hasStructure() && $blueprint->hasField('parent')) { + $blueprint->removeField('parent')->save(); + + $this->console->line(sprintf( + 'Parent field removed from the %s collection\'s %s blueprint.', + $collection->handle(), + $blueprint->handle() + )); + } + }); + }); + } +} diff --git a/tests/UpdateScripts/RemoveParentFieldTest.php b/tests/UpdateScripts/RemoveParentFieldTest.php new file mode 100644 index 0000000000..d0cdb2f1e7 --- /dev/null +++ b/tests/UpdateScripts/RemoveParentFieldTest.php @@ -0,0 +1,82 @@ +assertUpdateScriptRegistered(RemoveParentField::class); + } + + #[Test] + public function it_removes_parent_field_from_structured_collection_blueprint() + { + $collection = tap(Collection::make('test')->structureContents(['tree' => []]))->save(); + + $blueprint = $collection->entryBlueprint(); + + $blueprint->setContents(['tabs' => [ + 'main' => ['sections' => [ + ['fields' => [ + ['handle' => 'title', 'field' => ['type' => 'text']], + ]], + ]], + 'sidebar' => ['sections' => [ + ['fields' => [ + ['handle' => 'slug', 'field' => ['type' => 'slug']], + ['handle' => 'parent', 'field' => ['type' => 'entries', 'collections' => ['test'], 'max_items' => 1]], + ]], + ]], + ]]); + + $blueprint->save(); + + $this->runUpdateScript(RemoveParentField::class); + + $blueprint = Blueprint::find($blueprint->fullyQualifiedHandle()); + + $this->assertFalse($blueprint->hasField('parent')); + } + + #[Test] + public function it_does_not_remove_parent_field_from_unstructured_collection_blueprint() + { + $collection = tap(Collection::make('test'))->save(); + + $blueprint = $collection->entryBlueprint(); + + $blueprint->setContents(['tabs' => [ + 'main' => ['sections' => [ + ['fields' => [ + ['handle' => 'title', 'field' => ['type' => 'text']], + ]], + ]], + 'sidebar' => ['sections' => [ + ['fields' => [ + ['handle' => 'slug', 'field' => ['type' => 'slug']], + ['handle' => 'parent', 'field' => ['type' => 'entries', 'collections' => ['test'], 'max_items' => 1]], + ]], + ]], + ]]); + + $blueprint->save(); + + $this->runUpdateScript(RemoveParentField::class); + + $blueprint = Blueprint::find($blueprint->fullyQualifiedHandle()); + + $this->assertTrue($blueprint->hasField('parent')); + } +} From fc7df62c1cda92c5877e43ff46f48a2f7ba7ccfa Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 25 Feb 2025 16:34:26 +0000 Subject: [PATCH 3/9] Change how the parent is handled when creating entries We still need this flow for the "Create Child Page" option in the tree branch dropdown. --- resources/js/components/entries/BaseCreateForm.vue | 2 ++ resources/js/components/entries/PublishForm.vue | 2 ++ resources/views/entries/create.blade.php | 1 + src/Http/Controllers/CP/Collections/EntriesController.php | 7 ++----- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/resources/js/components/entries/BaseCreateForm.vue b/resources/js/components/entries/BaseCreateForm.vue index de54611122..215ecf7413 100644 --- a/resources/js/components/entries/BaseCreateForm.vue +++ b/resources/js/components/entries/BaseCreateForm.vue @@ -18,6 +18,7 @@ :revisions-enabled="revisions" :breadcrumbs="breadcrumbs" :initial-site="site" + :parent="parent" :can-manage-publish-state="canManagePublishState" :create-another-url="createAnotherUrl" :initial-listing-url="listingUrl" @@ -41,6 +42,7 @@ export default { 'revisions', 'breadcrumbs', 'site', + 'parent', 'canManagePublishState', 'createAnotherUrl', 'listingUrl', diff --git a/resources/js/components/entries/PublishForm.vue b/resources/js/components/entries/PublishForm.vue index 28e13ad46f..456cf6f90c 100644 --- a/resources/js/components/entries/PublishForm.vue +++ b/resources/js/components/entries/PublishForm.vue @@ -401,6 +401,7 @@ export default { collectionHasRoutes: Boolean, previewTargets: Array, autosaveInterval: Number, + parent: String, }, data() { @@ -620,6 +621,7 @@ export default { ...{ _blueprint: this.fieldset.handle, _localized: this.localizedFields, + _parent: this.parent, // todo: don't pass this via values }, }; diff --git a/resources/views/entries/create.blade.php b/resources/views/entries/create.blade.php index 434afdad9e..a7ba940aea 100644 --- a/resources/views/entries/create.blade.php +++ b/resources/views/entries/create.blade.php @@ -17,6 +17,7 @@ :revisions="{{ Statamic\Support\Str::bool($revisionsEnabled) }}" :breadcrumbs="{{ $breadcrumbs->toJson() }}" site="{{ $locale }}" + parent="{{ $parent }}" create-another-url="{{ cp_route('collections.entries.create', [$collection, $locale, 'blueprint' => $blueprint['handle'], 'parent' => $values['parent'] ?? null]) }}" listing-url="{{ cp_route('collections.show', $collection) }}" :can-manage-publish-state="{{ Statamic\Support\Str::bool($canManagePublishState) }}" diff --git a/src/Http/Controllers/CP/Collections/EntriesController.php b/src/Http/Controllers/CP/Collections/EntriesController.php index 5535e11a30..1bb56aa889 100644 --- a/src/Http/Controllers/CP/Collections/EntriesController.php +++ b/src/Http/Controllers/CP/Collections/EntriesController.php @@ -302,10 +302,6 @@ public function create(Request $request, $collection, $site) $values = Entry::make()->collection($collection)->values()->all(); - if ($collection->hasStructure() && $request->parent) { - $values['parent'] = $request->parent; - } - $fields = $blueprint ->fields() ->addValues($values) @@ -349,6 +345,7 @@ public function create(Request $request, $collection, $site) 'canManagePublishState' => User::current()->can('publish '.$collection->handle().' entries'), 'previewTargets' => $collection->previewTargets()->all(), 'autosaveInterval' => $collection->autosaveInterval(), + 'parent' => $collection->hasStructure() ? $request->parent : null, ]; if ($request->wantsJson()) { @@ -403,7 +400,7 @@ public function store(Request $request, $collection, $site) } if ($structure && ! $collection->orderable()) { - $parent = $values['parent'] ?? null; + $parent = $request->_parent; $entry->afterSave(function ($entry) use ($parent, $tree) { if ($parent && optional($tree->find($parent))->isRoot()) { $parent = null; From 90f5b8018ff4e49deb0d28f1b6715fb8fa39c6a7 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 25 Feb 2025 16:35:00 +0000 Subject: [PATCH 4/9] Remove some unused code from the entry update flow --- .../CP/Collections/EntriesController.php | 47 ------------------- 1 file changed, 47 deletions(-) diff --git a/src/Http/Controllers/CP/Collections/EntriesController.php b/src/Http/Controllers/CP/Collections/EntriesController.php index 1bb56aa889..b8a8d7604a 100644 --- a/src/Http/Controllers/CP/Collections/EntriesController.php +++ b/src/Http/Controllers/CP/Collections/EntriesController.php @@ -233,25 +233,6 @@ public function update(Request $request, $collection, $entry) $tree = $entry->structure()->in($entry->locale()); } - $parent = $values->get('parent'); - - if ($structure && ! $collection->orderable()) { - $this->validateParent($entry, $tree, $parent); - - if (! $entry->revisionsEnabled()) { - $entry->afterSave(function ($entry) use ($parent, $tree) { - if ($parent && optional($tree->find($parent))->isRoot()) { - $parent = null; - } - - $tree - ->move($entry->id(), $parent) - ->save(); - }); - - $entry->remove('parent'); - } - } $this->validateUniqueUri($entry, $tree ?? null, $parent ?? null); @@ -462,34 +443,6 @@ protected function extractAssetsFromValues($values) ->values(); } - private function validateParent($entry, $tree, $parent) - { - if ($entry->id() == $parent) { - throw ValidationException::withMessages(['parent' => __('statamic::validation.parent_cannot_be_itself')]); - } - - // If there's no parent selected, the entry will be at end of the top level, which is fine. - // If the entry being edited is not the root, then we don't have anything to worry about. - // If the parent is the root, that's fine, and is handled during the tree update later. - if (! $parent || ! $entry->page()->isRoot()) { - $maxDepth = $entry->collection()->structure()->maxDepth(); - - // If a parent is selected, validate that it doesn't exceed the max depth of the structure. - if ($parent && $maxDepth && Entry::find($parent)->page()->depth() >= $maxDepth) { - throw ValidationException::withMessages(['parent' => __('statamic::validation.parent_exceeds_max_depth')]); - } - - return; - } - - // There will always be a next page since we couldn't have got this far with a single page. - $nextTopLevelPage = $tree->pages()->all()->skip(1)->first(); - - if ($nextTopLevelPage->id() === $parent || $nextTopLevelPage->pages()->all()->count() > 0) { - throw ValidationException::withMessages(['parent' => __('statamic::validation.parent_causes_root_children')]); - } - } - private function validateUniqueUri($entry, $tree, $parent) { if (! $uri = $this->entryUri($entry, $tree, $parent)) { From 9c512a055c34a6db1fcedab160e6d61fd3071aba Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 25 Feb 2025 18:58:22 +0000 Subject: [PATCH 5/9] Remove parent entry handling in Revisable --- src/Revisions/Revisable.php | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/Revisions/Revisable.php b/src/Revisions/Revisable.php index 2c1dfd0efc..8316a2afce 100644 --- a/src/Revisions/Revisable.php +++ b/src/Revisions/Revisable.php @@ -72,12 +72,6 @@ public function publishWorkingCopy($options = []) { $item = $this->fromWorkingCopy(); - if ($item instanceof Entry) { - $parent = $item->get('parent'); - - $item->remove('parent'); - } - $saved = $item ->published(true) ->updateLastModified($user = $options['user'] ?? false) @@ -87,18 +81,6 @@ public function publishWorkingCopy($options = []) return false; } - if ($item instanceof Entry && $item->collection()->hasStructure() && $parent) { - $tree = $item->collection()->structure()->in($item->locale()); - - if (optional($tree->find($parent))->isRoot()) { - $parent = null; - } - - $tree - ->move($this->id(), $parent) - ->save(); - } - $item ->makeRevision() ->user($user) From faada06ddd78fa7f626a41489c61e95237f6a2d6 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Wed, 26 Feb 2025 15:35:09 +0000 Subject: [PATCH 6/9] Remove max depth test from UpdateEntryTest --- tests/Feature/Entries/UpdateEntryTest.php | 32 ----------------------- 1 file changed, 32 deletions(-) diff --git a/tests/Feature/Entries/UpdateEntryTest.php b/tests/Feature/Entries/UpdateEntryTest.php index 4531ade84d..d0b4020db9 100644 --- a/tests/Feature/Entries/UpdateEntryTest.php +++ b/tests/Feature/Entries/UpdateEntryTest.php @@ -437,38 +437,6 @@ public function user_without_permission_to_manage_publish_state_cannot_change_pu $this->markTestIncomplete(); } - #[Test] - public function validates_max_depth() - { - [$user, $collection] = $this->seedUserAndCollection(); - - $structure = (new CollectionStructure)->maxDepth(2)->expectsRoot(true); - $collection->structure($structure)->save(); - - EntryFactory::collection('test')->id('home')->slug('home')->data(['title' => 'Home', 'foo' => 'bar'])->create(); - EntryFactory::collection('test')->id('about')->slug('about')->data(['title' => 'About', 'foo' => 'baz'])->create(); - EntryFactory::collection('test')->id('team')->slug('team')->data(['title' => 'Team'])->create(); - - $entry = EntryFactory::collection($collection) - ->id('existing-entry') - ->slug('existing-entry') - ->data(['title' => 'Existing Entry', 'foo' => 'bar']) - ->create(); - - $collection->structure()->in('en')->tree([ - ['entry' => 'home'], - ['entry' => 'about', 'children' => [ - ['entry' => 'team'], - ]], - ['entry' => 'existing-entry'], - ])->save(); - - $this - ->actingAs($user) - ->update($entry, ['title' => 'Existing Entry', 'slug' => 'existing-entry', 'parent' => ['team']]) // This would make it 3 levels deep, so it should fail. - ->assertUnprocessable(); - } - #[Test] public function does_not_validate_max_depth_when_collection_max_depth_is_null() { From 126879e79dc1cdd632266eb8f3b77c6215a55634 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Wed, 26 Feb 2025 15:50:48 +0000 Subject: [PATCH 7/9] Pint --- src/Http/Controllers/CP/Collections/EntriesController.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Http/Controllers/CP/Collections/EntriesController.php b/src/Http/Controllers/CP/Collections/EntriesController.php index b8a8d7604a..4e625995de 100644 --- a/src/Http/Controllers/CP/Collections/EntriesController.php +++ b/src/Http/Controllers/CP/Collections/EntriesController.php @@ -233,7 +233,6 @@ public function update(Request $request, $collection, $entry) $tree = $entry->structure()->in($entry->locale()); } - $this->validateUniqueUri($entry, $tree ?? null, $parent ?? null); if ($entry->revisionsEnabled() && $entry->published()) { From 0aa179bb51b7219647878ec99a70fd869e5e870e Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Wed, 26 Feb 2025 16:06:53 +0000 Subject: [PATCH 8/9] remove comment --- resources/js/components/entries/PublishForm.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/js/components/entries/PublishForm.vue b/resources/js/components/entries/PublishForm.vue index 456cf6f90c..a282f791db 100644 --- a/resources/js/components/entries/PublishForm.vue +++ b/resources/js/components/entries/PublishForm.vue @@ -621,7 +621,7 @@ export default { ...{ _blueprint: this.fieldset.handle, _localized: this.localizedFields, - _parent: this.parent, // todo: don't pass this via values + _parent: this.parent, }, }; From 083b173b0e1f271fd30dcef7cc809d9343de3053 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Wed, 26 Feb 2025 16:19:23 +0000 Subject: [PATCH 9/9] Pass the current parent when validating unique URIs --- src/Http/Controllers/CP/Collections/EntriesController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Controllers/CP/Collections/EntriesController.php b/src/Http/Controllers/CP/Collections/EntriesController.php index 4e625995de..2aa8457c22 100644 --- a/src/Http/Controllers/CP/Collections/EntriesController.php +++ b/src/Http/Controllers/CP/Collections/EntriesController.php @@ -233,7 +233,7 @@ public function update(Request $request, $collection, $entry) $tree = $entry->structure()->in($entry->locale()); } - $this->validateUniqueUri($entry, $tree ?? null, $parent ?? null); + $this->validateUniqueUri($entry, $tree ?? null, $entry->parent()?->id()); if ($entry->revisionsEnabled() && $entry->published()) { $saved = $entry