Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[6.x] Remove "Parent" field from entries #11506

Merged
merged 9 commits into from
Feb 26, 2025
2 changes: 2 additions & 0 deletions resources/js/components/entries/BaseCreateForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -41,6 +42,7 @@ export default {
'revisions',
'breadcrumbs',
'site',
'parent',
'canManagePublishState',
'createAnotherUrl',
'listingUrl',
Expand Down
2 changes: 2 additions & 0 deletions resources/js/components/entries/PublishForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,7 @@ export default {
collectionHasRoutes: Boolean,
previewTargets: Array,
autosaveInterval: Number,
parent: String,
},

data() {
Expand Down Expand Up @@ -620,6 +621,7 @@ export default {
...{
_blueprint: this.fieldset.handle,
_localized: this.localizedFields,
_parent: this.parent,
},
};

Expand Down
1 change: 1 addition & 0 deletions resources/views/entries/create.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) }}"
Expand Down
10 changes: 0 additions & 10 deletions src/Entries/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
57 changes: 3 additions & 54 deletions src/Http/Controllers/CP/Collections/EntriesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)) {
Expand Down
1 change: 1 addition & 0 deletions src/Providers/ExtensionServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ class ExtensionServiceProvider extends ServiceProvider
Updates\AddSitePermissions::class,
Updates\UseClassBasedStatamicUniqueRules::class,
Updates\MigrateSitesConfigToYaml::class,
Updates\RemoveParentField::class,
];

public function register()
Expand Down
18 changes: 0 additions & 18 deletions src/Revisions/Revisable.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
30 changes: 30 additions & 0 deletions src/UpdateScripts/RemoveParentField.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace Statamic\UpdateScripts;

use Statamic\Facades\Collection;

class RemoveParentField extends UpdateScript
{
public function shouldUpdate($newVersion, $oldVersion)
{
return $this->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 <comment>%s</comment> collection\'s <info>%s</info> blueprint.',
$collection->handle(),
$blueprint->handle()
));
}
});
});
}
}
32 changes: 0 additions & 32 deletions tests/Feature/Entries/UpdateEntryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
82 changes: 82 additions & 0 deletions tests/UpdateScripts/RemoveParentFieldTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

namespace Tests\UpdateScripts;

use PHPUnit\Framework\Attributes\Test;
use Statamic\Facades\Blueprint;
use Statamic\Facades\Collection;
use Statamic\UpdateScripts\RemoveParentField;
use Tests\PreventSavingStacheItemsToDisk;
use Tests\TestCase;
use Tests\UpdateScripts\Concerns\RunsUpdateScripts;

class RemoveParentFieldTest extends TestCase
{
use PreventSavingStacheItemsToDisk, RunsUpdateScripts;

#[Test]
public function it_is_registered()
{
$this->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'));
}
}
Loading