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..a282f791db 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, }, }; 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/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; diff --git a/src/Http/Controllers/CP/Collections/EntriesController.php b/src/Http/Controllers/CP/Collections/EntriesController.php index 5535e11a30..2aa8457c22 100644 --- a/src/Http/Controllers/CP/Collections/EntriesController.php +++ b/src/Http/Controllers/CP/Collections/EntriesController.php @@ -233,27 +233,7 @@ 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); + $this->validateUniqueUri($entry, $tree ?? null, $entry->parent()?->id()); if ($entry->revisionsEnabled() && $entry->published()) { $saved = $entry @@ -302,10 +282,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 +325,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 +380,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; @@ -465,34 +442,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)) { 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/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) 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/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() { 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')); + } +}