From c33ed4d708c0086d8513b086d08d553eaafc3c6d Mon Sep 17 00:00:00 2001 From: edalzell Date: Thu, 12 Dec 2024 15:11:24 -0800 Subject: [PATCH 01/23] add to base field --- src/Fields/Field.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Fields/Field.php b/src/Fields/Field.php index b7b9f4f17e..75b765169e 100644 --- a/src/Fields/Field.php +++ b/src/Fields/Field.php @@ -248,6 +248,11 @@ public function isFilterable() return (bool) $this->get('filterable'); } + public function isRevisable() + { + return (bool) $this->get('revisable', true); + } + public function shouldBeDuplicated() { if (is_null($this->get('duplicate'))) { @@ -269,6 +274,7 @@ public function toPublishArray() 'visibility' => $this->visibility(), 'read_only' => $this->visibility() === 'read_only', // Deprecated: Addon fieldtypes should now reference new `visibility` state. 'always_save' => $this->alwaysSave(), + 'revisable' => $this->isRevisable(), ]); } @@ -567,6 +573,13 @@ public static function commonFieldOptions(): Fields 'validate' => 'boolean', 'default' => true, ], + 'revisable' => [ + 'display' => __('Revisable'), + 'instructions' => __('statamic::messages.fields_revisable_instructions'), + 'type' => 'toggle', + 'validate' => 'boolean', + 'default' => true, + ], ])->map(fn ($field, $handle) => compact('handle', 'field'))->values()->all(); return new ConfigFields($fields); From 4aff3fa536f099366e9df9c1671eb92acdc2fa24 Mon Sep 17 00:00:00 2001 From: edalzell Date: Thu, 12 Dec 2024 15:23:44 -0800 Subject: [PATCH 02/23] tiny refactor to prep --- src/Entries/Entry.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Entries/Entry.php b/src/Entries/Entry.php index 06a18e7c0e..3b19d1db01 100644 --- a/src/Entries/Entry.php +++ b/src/Entries/Entry.php @@ -668,12 +668,14 @@ protected function revisionKey() protected function revisionAttributes() { + $nonRevisableFields = ['updated_by', 'updated_at']; + return [ 'id' => $this->id(), 'slug' => $this->slug(), 'published' => $this->published(), 'date' => $this->collection()->dated() ? $this->date()->timestamp : null, - 'data' => $this->data()->except(['updated_by', 'updated_at'])->all(), + 'data' => $this->data()->except($nonRevisableFields)->all(), ]; } From 8817d35cb1ee80efd679ec9c9a82b9852ca187dc Mon Sep 17 00:00:00 2001 From: edalzell Date: Thu, 12 Dec 2024 15:28:26 -0800 Subject: [PATCH 03/23] update the contract and implement in entry --- src/Entries/Entry.php | 7 ++++++- src/Revisions/Revisable.php | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Entries/Entry.php b/src/Entries/Entry.php index 3b19d1db01..5b57d84d34 100644 --- a/src/Entries/Entry.php +++ b/src/Entries/Entry.php @@ -666,6 +666,11 @@ protected function revisionKey() ]); } + protected function nonRevisableFields(): array + { + return []; + } + protected function revisionAttributes() { $nonRevisableFields = ['updated_by', 'updated_at']; @@ -675,7 +680,7 @@ protected function revisionAttributes() 'slug' => $this->slug(), 'published' => $this->published(), 'date' => $this->collection()->dated() ? $this->date()->timestamp : null, - 'data' => $this->data()->except($nonRevisableFields)->all(), + 'data' => $this->data()->except(array_merge($nonRevisableFields, $this->nonRevisableFields()))->all(), ]; } diff --git a/src/Revisions/Revisable.php b/src/Revisions/Revisable.php index 2c1dfd0efc..7a94eb34f3 100644 --- a/src/Revisions/Revisable.php +++ b/src/Revisions/Revisable.php @@ -177,6 +177,8 @@ public function revisionsEnabled() return config('statamic.revisions.enabled') && Statamic::pro(); } + abstract protected function nonRevisableFields(): array; + abstract protected function revisionKey(); abstract protected function revisionAttributes(); From 42b23b7182ebd5370ecfc592715fe9ca113d0995 Mon Sep 17 00:00:00 2001 From: edalzell Date: Thu, 12 Dec 2024 15:29:11 -0800 Subject: [PATCH 04/23] better --- src/Entries/Entry.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Entries/Entry.php b/src/Entries/Entry.php index 5b57d84d34..2f56dee2da 100644 --- a/src/Entries/Entry.php +++ b/src/Entries/Entry.php @@ -673,14 +673,14 @@ protected function nonRevisableFields(): array protected function revisionAttributes() { - $nonRevisableFields = ['updated_by', 'updated_at']; + $nonRevisableFields = ['updated_by', 'updated_at', ...$this->nonRevisableFields()]; return [ 'id' => $this->id(), 'slug' => $this->slug(), 'published' => $this->published(), 'date' => $this->collection()->dated() ? $this->date()->timestamp : null, - 'data' => $this->data()->except(array_merge($nonRevisableFields, $this->nonRevisableFields()))->all(), + 'data' => $this->data()->except($nonRevisableFields)->all(), ]; } From cac88a783db914a4dad6edfc56daf4f03e47215d Mon Sep 17 00:00:00 2001 From: edalzell Date: Thu, 12 Dec 2024 16:16:33 -0800 Subject: [PATCH 05/23] get the non-revisable fields --- src/Entries/Entry.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Entries/Entry.php b/src/Entries/Entry.php index 2f56dee2da..791fee7249 100644 --- a/src/Entries/Entry.php +++ b/src/Entries/Entry.php @@ -668,7 +668,12 @@ protected function revisionKey() protected function nonRevisableFields(): array { - return []; + return $this->blueprint() + ->fields() + ->all() + ->reject(fn ($field) => $field->isRevisable()) + ->keys() + ->all(); } protected function revisionAttributes() From 59c926752f2549f0f31df22ebaf7473f2f8c9594 Mon Sep 17 00:00:00 2001 From: edalzell Date: Thu, 12 Dec 2024 16:16:49 -0800 Subject: [PATCH 06/23] save the non-revisable data --- src/Entries/Entry.php | 2 +- src/Http/Controllers/CP/Collections/EntriesController.php | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Entries/Entry.php b/src/Entries/Entry.php index 791fee7249..3b6de95e3f 100644 --- a/src/Entries/Entry.php +++ b/src/Entries/Entry.php @@ -701,7 +701,7 @@ public function makeFromRevision($revision) $entry ->published($attrs['published']) - ->data($attrs['data']) + ->data(array_merge($this->data()->all(), $attrs['data'])) ->slug($attrs['slug']); if ($this->collection()->dated() && ($date = Arr::get($attrs, 'date'))) { diff --git a/src/Http/Controllers/CP/Collections/EntriesController.php b/src/Http/Controllers/CP/Collections/EntriesController.php index 08921a71e1..37067e27c3 100644 --- a/src/Http/Controllers/CP/Collections/EntriesController.php +++ b/src/Http/Controllers/CP/Collections/EntriesController.php @@ -262,7 +262,8 @@ public function update(Request $request, $collection, $entry) ->save(); // catch any changes through RevisionSaving event - $entry = $entry->fromWorkingCopy(); + // have to save in case there are non-revisable fields + $entry = tap($entry->fromWorkingCopy())->save(); } else { if (! $entry->revisionsEnabled() && User::current()->can('publish', $entry)) { $entry->published($request->published); From 83108eabadb56eb42018decd898eb2db8d43eced Mon Sep 17 00:00:00 2001 From: edalzell Date: Thu, 12 Dec 2024 16:17:39 -0800 Subject: [PATCH 07/23] tidier --- src/Entries/Entry.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Entries/Entry.php b/src/Entries/Entry.php index 3b6de95e3f..d6ae278196 100644 --- a/src/Entries/Entry.php +++ b/src/Entries/Entry.php @@ -701,7 +701,7 @@ public function makeFromRevision($revision) $entry ->published($attrs['published']) - ->data(array_merge($this->data()->all(), $attrs['data'])) + ->data($this->data()->merge($attrs['data'])) ->slug($attrs['slug']); if ($this->collection()->dated() && ($date = Arr::get($attrs, 'date'))) { From 4f5106a982d74e3648357e48d87e34eff6a72daa Mon Sep 17 00:00:00 2001 From: edalzell Date: Thu, 12 Dec 2024 16:19:08 -0800 Subject: [PATCH 08/23] tidy --- src/Entries/Entry.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Entries/Entry.php b/src/Entries/Entry.php index d6ae278196..9864a07ba3 100644 --- a/src/Entries/Entry.php +++ b/src/Entries/Entry.php @@ -678,14 +678,12 @@ protected function nonRevisableFields(): array protected function revisionAttributes() { - $nonRevisableFields = ['updated_by', 'updated_at', ...$this->nonRevisableFields()]; - return [ 'id' => $this->id(), 'slug' => $this->slug(), 'published' => $this->published(), 'date' => $this->collection()->dated() ? $this->date()->timestamp : null, - 'data' => $this->data()->except($nonRevisableFields)->all(), + 'data' => $this->data()->except(['updated_by', 'updated_at', ...$this->nonRevisableFields()])->all(), ]; } From 668de505d5f52db11ba4b1b85e08ebfb3ed5ebaf Mon Sep 17 00:00:00 2001 From: edalzell Date: Thu, 12 Dec 2024 16:23:06 -0800 Subject: [PATCH 09/23] move this so all revisable data can use it --- src/Entries/Entry.php | 10 ---------- src/Revisions/Revisable.php | 10 +++++++++- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/Entries/Entry.php b/src/Entries/Entry.php index 9864a07ba3..2b58343161 100644 --- a/src/Entries/Entry.php +++ b/src/Entries/Entry.php @@ -666,16 +666,6 @@ protected function revisionKey() ]); } - protected function nonRevisableFields(): array - { - return $this->blueprint() - ->fields() - ->all() - ->reject(fn ($field) => $field->isRevisable()) - ->keys() - ->all(); - } - protected function revisionAttributes() { return [ diff --git a/src/Revisions/Revisable.php b/src/Revisions/Revisable.php index 7a94eb34f3..a4dd7742db 100644 --- a/src/Revisions/Revisable.php +++ b/src/Revisions/Revisable.php @@ -177,7 +177,15 @@ public function revisionsEnabled() return config('statamic.revisions.enabled') && Statamic::pro(); } - abstract protected function nonRevisableFields(): array; + protected function nonRevisableFields(): array + { + return $this->blueprint() + ->fields() + ->all() + ->reject(fn ($field) => $field->isRevisable()) + ->keys() + ->all(); + } abstract protected function revisionKey(); From 34b3dc592038f1597e39d6d1e82a362dd0014a00 Mon Sep 17 00:00:00 2001 From: edalzell Date: Fri, 13 Dec 2024 10:24:51 -0800 Subject: [PATCH 10/23] first test --- src/Revisions/Revisable.php | 2 +- tests/Revisions/RevisableTest.php | 41 +++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 tests/Revisions/RevisableTest.php diff --git a/src/Revisions/Revisable.php b/src/Revisions/Revisable.php index a4dd7742db..f64bed5ecc 100644 --- a/src/Revisions/Revisable.php +++ b/src/Revisions/Revisable.php @@ -177,7 +177,7 @@ public function revisionsEnabled() return config('statamic.revisions.enabled') && Statamic::pro(); } - protected function nonRevisableFields(): array + public function nonRevisableFields(): array { return $this->blueprint() ->fields() diff --git a/tests/Revisions/RevisableTest.php b/tests/Revisions/RevisableTest.php new file mode 100644 index 0000000000..5bde76e1c0 --- /dev/null +++ b/tests/Revisions/RevisableTest.php @@ -0,0 +1,41 @@ +repo = (new RevisionRepository); + } + + #[Test] + public function it_gets_revisions_and_excludes_working_copies() + { + $blueprint = Blueprint::makeFromFields([ + 'revisable' => ['type' => 'text'], + 'non_revisable' => ['type' => 'text', 'revisable' => false], + ]); + BlueprintRepository::shouldReceive('in')->with('collections/blog')->andReturn(collect(['blog' => $blueprint])); + Collection::make('blog')->save(); + + $entry = (new Entry)->collection('blog')->id('123'); + + $this->assertEquals(['non_revisable'], $entry->nonRevisableFields()); + } +} From 9a328711ceb94de51348f36a02888f77b5289865 Mon Sep 17 00:00:00 2001 From: edalzell Date: Fri, 13 Dec 2024 10:32:14 -0800 Subject: [PATCH 11/23] Field test --- tests/Fields/FieldTest.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/Fields/FieldTest.php b/tests/Fields/FieldTest.php index 62c9221748..d3c0f5c05a 100644 --- a/tests/Fields/FieldTest.php +++ b/tests/Fields/FieldTest.php @@ -342,6 +342,7 @@ public function preProcess($data) 'visibility' => 'visible', 'replicator_preview' => true, 'duplicate' => true, + 'revisable' => true, 'type' => 'example', 'validate' => 'required', 'foo' => 'bar', @@ -647,4 +648,20 @@ public function it_gets_and_sets_the_form() $this->assertEquals($field, $return); $this->assertEquals($form, $field->form()); } + + #[Test] + public function it_defaults_to_revisable() + { + $field = new Field('test', ['type' => 'text']); + + $this->assertTrue($field->isRevisable()); + } + + #[Test] + public function it_gets_revisable() + { + $field = new Field('test', ['type' => 'text', 'revisable' => false]); + + $this->assertFalse($field->isRevisable()); + } } From 34e8b2399026a04b271320c55af152f0c65cc2fa Mon Sep 17 00:00:00 2001 From: edalzell Date: Fri, 13 Dec 2024 10:34:52 -0800 Subject: [PATCH 12/23] fix tests --- tests/Fields/BlueprintTest.php | 4 ++++ tests/Fields/FieldsTest.php | 4 ++++ tests/Fields/SectionTest.php | 2 ++ tests/Fields/TabTest.php | 2 ++ tests/Fieldtypes/NestedFieldsTest.php | 2 ++ tests/Fieldtypes/SetsTest.php | 3 +++ 6 files changed, 17 insertions(+) diff --git a/tests/Fields/BlueprintTest.php b/tests/Fields/BlueprintTest.php index a13c952be8..60e59d5139 100644 --- a/tests/Fields/BlueprintTest.php +++ b/tests/Fields/BlueprintTest.php @@ -436,6 +436,7 @@ public function converts_to_array_suitable_for_rendering_fields_in_publish_compo 'visibility' => 'visible', 'replicator_preview' => true, 'duplicate' => true, + 'revisable' => true, 'type' => 'text', 'validate' => 'required|min:2', 'input_type' => 'text', @@ -474,6 +475,7 @@ public function converts_to_array_suitable_for_rendering_fields_in_publish_compo 'visibility' => 'visible', 'replicator_preview' => true, 'duplicate' => true, + 'revisable' => true, 'type' => 'textarea', 'placeholder' => null, 'validate' => 'min:2', @@ -563,6 +565,7 @@ public function converts_to_array_suitable_for_rendering_prefixed_conditional_fi 'visibility' => 'visible', 'replicator_preview' => true, 'duplicate' => true, + 'revisable' => true, 'type' => 'text', 'input_type' => 'text', 'placeholder' => null, @@ -589,6 +592,7 @@ public function converts_to_array_suitable_for_rendering_prefixed_conditional_fi 'visibility' => 'visible', 'replicator_preview' => true, 'duplicate' => true, + 'revisable' => true, 'type' => 'text', 'input_type' => 'text', 'placeholder' => null, diff --git a/tests/Fields/FieldsTest.php b/tests/Fields/FieldsTest.php index 057bc08d2a..fddcdcd290 100644 --- a/tests/Fields/FieldsTest.php +++ b/tests/Fields/FieldsTest.php @@ -434,6 +434,7 @@ public function converts_to_array_suitable_for_rendering_fields_in_publish_compo 'sortable' => true, 'replicator_preview' => true, 'duplicate' => true, + 'revisable' => true, ], [ 'handle' => 'two', @@ -457,6 +458,7 @@ public function converts_to_array_suitable_for_rendering_fields_in_publish_compo 'sortable' => true, 'replicator_preview' => true, 'duplicate' => true, + 'revisable' => true, ], ], $fields->toPublishArray()); } @@ -522,6 +524,7 @@ public function converts_to_array_suitable_for_rendering_prefixed_conditional_fi 'sortable' => true, 'replicator_preview' => true, 'duplicate' => true, + 'revisable' => true, ], [ 'handle' => 'nested_deeper_two', @@ -548,6 +551,7 @@ public function converts_to_array_suitable_for_rendering_prefixed_conditional_fi 'sortable' => true, 'replicator_preview' => true, 'duplicate' => true, + 'revisable' => true, ], ], $fields->toPublishArray()); } diff --git a/tests/Fields/SectionTest.php b/tests/Fields/SectionTest.php index 0e09f08b90..0afb84b986 100644 --- a/tests/Fields/SectionTest.php +++ b/tests/Fields/SectionTest.php @@ -125,6 +125,7 @@ public function converts_to_array_suitable_for_rendering_fields_in_publish_compo 'visibility' => 'visible', 'replicator_preview' => true, 'duplicate' => true, + 'revisable' => true, 'type' => 'text', 'validate' => 'required|min:2', 'input_type' => 'text', @@ -152,6 +153,7 @@ public function converts_to_array_suitable_for_rendering_fields_in_publish_compo 'visibility' => 'visible', 'replicator_preview' => true, 'duplicate' => true, + 'revisable' => true, 'type' => 'textarea', 'validate' => 'min:2', 'placeholder' => null, diff --git a/tests/Fields/TabTest.php b/tests/Fields/TabTest.php index ad0d34d3bc..f6f1b0c98c 100644 --- a/tests/Fields/TabTest.php +++ b/tests/Fields/TabTest.php @@ -150,6 +150,7 @@ public function converts_to_array_suitable_for_rendering_fields_in_publish_compo 'visibility' => 'visible', 'replicator_preview' => true, 'duplicate' => true, + 'revisable' => true, 'type' => 'text', 'validate' => 'required|min:2', 'input_type' => 'text', @@ -177,6 +178,7 @@ public function converts_to_array_suitable_for_rendering_fields_in_publish_compo 'visibility' => 'visible', 'replicator_preview' => true, 'duplicate' => true, + 'revisable' => true, 'type' => 'textarea', 'validate' => 'min:2', 'placeholder' => null, diff --git a/tests/Fieldtypes/NestedFieldsTest.php b/tests/Fieldtypes/NestedFieldsTest.php index d35817067a..5f20838d86 100644 --- a/tests/Fieldtypes/NestedFieldsTest.php +++ b/tests/Fieldtypes/NestedFieldsTest.php @@ -23,6 +23,7 @@ public function it_preprocesses_each_value_when_used_for_config() ->andReturn(new class extends Fieldtype { protected $component = 'assets'; + protected $configFields = [ 'max_files' => ['type' => 'integer'], 'container' => ['type' => 'plain'], @@ -84,6 +85,7 @@ public function preProcess($data) 'visibility' => 'visible', 'replicator_preview' => true, 'duplicate' => true, + 'revisable' => true, 'type' => 'assets', 'max_files' => 2, 'container' => 'main', diff --git a/tests/Fieldtypes/SetsTest.php b/tests/Fieldtypes/SetsTest.php index e57b713568..f0e29b94cd 100644 --- a/tests/Fieldtypes/SetsTest.php +++ b/tests/Fieldtypes/SetsTest.php @@ -227,6 +227,7 @@ public function it_preprocesses_for_config_with_groups() 'visibility' => 'visible', 'replicator_preview' => true, 'duplicate' => true, + 'revisable' => true, 'type' => 'text', 'input_type' => 'text', 'placeholder' => null, @@ -264,6 +265,7 @@ public function it_preprocesses_for_config_with_groups() 'visibility' => 'visible', 'replicator_preview' => true, 'duplicate' => true, + 'revisable' => true, 'type' => 'text', 'input_type' => 'text', 'placeholder' => null, @@ -324,6 +326,7 @@ public function it_preprocesses_for_config_without_groups() 'visibility' => 'visible', 'replicator_preview' => true, 'duplicate' => true, + 'revisable' => true, 'type' => 'text', 'input_type' => 'text', 'placeholder' => null, From b7eefe1de196eec2578b51afe0204231286bf091 Mon Sep 17 00:00:00 2001 From: edalzell Date: Fri, 13 Dec 2024 10:41:56 -0800 Subject: [PATCH 13/23] tidy --- tests/Revisions/RevisableTest.php | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/tests/Revisions/RevisableTest.php b/tests/Revisions/RevisableTest.php index 5bde76e1c0..a12fc71798 100644 --- a/tests/Revisions/RevisableTest.php +++ b/tests/Revisions/RevisableTest.php @@ -7,7 +7,6 @@ use Statamic\Entries\Entry; use Statamic\Facades\Blueprint; use Statamic\Facades\Collection; -use Statamic\Revisions\RevisionRepository; use Tests\PreventSavingStacheItemsToDisk; use Tests\TestCase; @@ -15,17 +14,8 @@ class RevisableTest extends TestCase { use PreventSavingStacheItemsToDisk; - private $entry; - - protected function setUp(): void - { - parent::setUp(); - - // $this->repo = (new RevisionRepository); - } - #[Test] - public function it_gets_revisions_and_excludes_working_copies() + public function it_gets_non_revisable_fields() { $blueprint = Blueprint::makeFromFields([ 'revisable' => ['type' => 'text'], From bc3645abec1a5d9242ec947960874a5668ccd955 Mon Sep 17 00:00:00 2001 From: edalzell Date: Fri, 13 Dec 2024 10:47:34 -0800 Subject: [PATCH 14/23] test revision attributes --- tests/Revisions/RevisableTest.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/Revisions/RevisableTest.php b/tests/Revisions/RevisableTest.php index a12fc71798..8487aa9230 100644 --- a/tests/Revisions/RevisableTest.php +++ b/tests/Revisions/RevisableTest.php @@ -28,4 +28,22 @@ public function it_gets_non_revisable_fields() $this->assertEquals(['non_revisable'], $entry->nonRevisableFields()); } + + #[Test] + public function it_sets_proper_revision_attributes() + { + $blueprint = Blueprint::makeFromFields([ + 'revisable' => ['type' => 'text'], + 'non_revisable' => ['type' => 'text', 'revisable' => false], + ]); + BlueprintRepository::shouldReceive('in')->with('collections/blog')->andReturn(collect(['blog' => $blueprint])); + Collection::make('blog')->save(); + + $entry = (new Entry)->collection('blog')->id('123'); + $entry + ->set('revisable', 'see me') + ->set('non_revisable', "don't see me"); + + $this->assertEquals(['revisable' => 'see me'], $entry->makeRevision()->attributes()['data']); + } } From d7cb17677ec8d95c4b8ad42b9517b2a2f581e35e Mon Sep 17 00:00:00 2001 From: edalzell Date: Fri, 13 Dec 2024 10:59:30 -0800 Subject: [PATCH 15/23] test makeFromRevision --- tests/Revisions/RevisableTest.php | 37 +++++++++++++++++++ .../__fixtures__/123/1553546421.yaml | 8 +++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/tests/Revisions/RevisableTest.php b/tests/Revisions/RevisableTest.php index 8487aa9230..d14fe5ee10 100644 --- a/tests/Revisions/RevisableTest.php +++ b/tests/Revisions/RevisableTest.php @@ -7,6 +7,7 @@ use Statamic\Entries\Entry; use Statamic\Facades\Blueprint; use Statamic\Facades\Collection; +use Statamic\Revisions\RevisionRepository; use Tests\PreventSavingStacheItemsToDisk; use Tests\TestCase; @@ -14,6 +15,15 @@ class RevisableTest extends TestCase { use PreventSavingStacheItemsToDisk; + private $repo; + + protected function setUp(): void + { + parent::setUp(); + config(['statamic.revisions.path' => __DIR__.'/__fixtures__']); + $this->repo = (new RevisionRepository); + } + #[Test] public function it_gets_non_revisable_fields() { @@ -46,4 +56,31 @@ public function it_sets_proper_revision_attributes() $this->assertEquals(['revisable' => 'see me'], $entry->makeRevision()->attributes()['data']); } + + #[Test] + public function it_can_make_entry_from_revision() + { + $blueprint = Blueprint::makeFromFields([ + 'revisable' => ['type' => 'text'], + 'non_revisable' => ['type' => 'text', 'revisable' => false], + ]); + + BlueprintRepository::shouldReceive('in')->with('collections/blog')->andReturn(collect(['blog' => $blueprint])); + Collection::make('blog')->save(); + + $entry = (new Entry)->collection('blog')->id('123'); + $entry->set('revisable', 'override me')->set('non_revisable', "don't override me"); + + $revision = $this->repo->whereKey('123')->first(); + + $revisedEntry = $entry->makeFromRevision($revision); + + $this->assertEquals( + collect([ + "revisable" => "overridden", + "non_revisable" => "don't override me", + ]), + $entry->makeFromRevision($revision)->data() + ); + } } diff --git a/tests/Revisions/__fixtures__/123/1553546421.yaml b/tests/Revisions/__fixtures__/123/1553546421.yaml index 833a259984..cb828ee7eb 100644 --- a/tests/Revisions/__fixtures__/123/1553546421.yaml +++ b/tests/Revisions/__fixtures__/123/1553546421.yaml @@ -1,2 +1,8 @@ date: 1553546421 -attributes: {} +attributes: + id: 123 + slug: the-slug + published: true + date: 1633590000 + data: + revisable: 'overridden' From 5b26aa10f4abe43669f0a2df333960066ddfb9db Mon Sep 17 00:00:00 2001 From: edalzell Date: Fri, 13 Dec 2024 11:27:56 -0800 Subject: [PATCH 16/23] support terms --- src/Taxonomies/LocalizedTerm.php | 10 +++---- tests/Revisions/RevisableTest.php | 45 ++++++++++++++++++++++++++----- 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/src/Taxonomies/LocalizedTerm.php b/src/Taxonomies/LocalizedTerm.php index f40b394ae0..97613a52ea 100644 --- a/src/Taxonomies/LocalizedTerm.php +++ b/src/Taxonomies/LocalizedTerm.php @@ -244,23 +244,23 @@ protected function revisionAttributes() 'id' => $this->id(), 'slug' => $this->slug(), 'published' => $this->published(), - 'data' => $this->data()->except(['updated_by', 'updated_at'])->all(), + 'data' => $this->data()->except(['updated_by', 'updated_at', ...$this->nonRevisableFields()])->all(), ]; } public function makeFromRevision($revision) { - $entry = clone $this; + $term = clone $this; if (! $revision) { - return $entry; + return $term; } $attrs = $revision->attributes(); - return $entry + return $term ->published($attrs['published']) - ->data($attrs['data']) + ->data($this->data()->merge($attrs['data'])) ->slug($attrs['slug']); } diff --git a/tests/Revisions/RevisableTest.php b/tests/Revisions/RevisableTest.php index d14fe5ee10..2d422f0cdb 100644 --- a/tests/Revisions/RevisableTest.php +++ b/tests/Revisions/RevisableTest.php @@ -7,7 +7,10 @@ use Statamic\Entries\Entry; use Statamic\Facades\Blueprint; use Statamic\Facades\Collection; +use Statamic\Facades\Taxonomy; use Statamic\Revisions\RevisionRepository; +use Statamic\Taxonomies\LocalizedTerm; +use Statamic\Taxonomies\Term; use Tests\PreventSavingStacheItemsToDisk; use Tests\TestCase; @@ -47,14 +50,21 @@ public function it_sets_proper_revision_attributes() 'non_revisable' => ['type' => 'text', 'revisable' => false], ]); BlueprintRepository::shouldReceive('in')->with('collections/blog')->andReturn(collect(['blog' => $blueprint])); + BlueprintRepository::shouldReceive('in')->with('taxonomies/tags')->andReturn(collect(['tags' => $blueprint])); Collection::make('blog')->save(); + Taxonomy::make('tags')->save(); - $entry = (new Entry)->collection('blog')->id('123'); - $entry + $entry = (new Entry) + ->collection('blog') + ->id('123') ->set('revisable', 'see me') ->set('non_revisable', "don't see me"); + $term = (new Term)->taxonomy('tags')->slug('foo')->data(['revisable' => 'see me', 'non_revisable' => "don't see me"]); + $localizedTerm = (new LocalizedTerm($term, 'en')); + $this->assertEquals(['revisable' => 'see me'], $entry->makeRevision()->attributes()['data']); + $this->assertEquals(['revisable' => 'see me'], $localizedTerm->makeRevision()->attributes()['data']); } #[Test] @@ -73,14 +83,37 @@ public function it_can_make_entry_from_revision() $revision = $this->repo->whereKey('123')->first(); - $revisedEntry = $entry->makeFromRevision($revision); - $this->assertEquals( collect([ - "revisable" => "overridden", - "non_revisable" => "don't override me", + 'revisable' => 'overridden', + 'non_revisable' => "don't override me", ]), $entry->makeFromRevision($revision)->data() ); } + + #[Test] + public function it_can_make_term_from_revision() + { + $blueprint = Blueprint::makeFromFields([ + 'revisable' => ['type' => 'text'], + 'non_revisable' => ['type' => 'text', 'revisable' => false], + ]); + + BlueprintRepository::shouldReceive('in')->with('taxonomies/tags')->andReturn(collect(['tags' => $blueprint])); + Taxonomy::make('tags')->save(); + + $term = (new Term)->taxonomy('tags')->slug('foo')->data(['revisable' => 'override me', 'non_revisable' => "don't override me"]); + $localizedTerm = (new LocalizedTerm($term, 'en')); + + $revision = $this->repo->whereKey('123')->first(); + + $this->assertEquals( + collect([ + 'revisable' => 'overridden', + 'non_revisable' => "don't override me", + ]), + $localizedTerm->makeFromRevision($revision)->data() + ); + } } From 9b5c6e2c4359d9567ccecba2416db15b9be820ac Mon Sep 17 00:00:00 2001 From: edalzell Date: Fri, 13 Dec 2024 11:29:58 -0800 Subject: [PATCH 17/23] term controller --- src/Http/Controllers/CP/Taxonomies/TermsController.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Http/Controllers/CP/Taxonomies/TermsController.php b/src/Http/Controllers/CP/Taxonomies/TermsController.php index 9482380aae..5674504985 100644 --- a/src/Http/Controllers/CP/Taxonomies/TermsController.php +++ b/src/Http/Controllers/CP/Taxonomies/TermsController.php @@ -200,6 +200,9 @@ public function update(Request $request, $taxonomy, $term, $site) ->makeWorkingCopy() ->user(User::current()) ->save(); + + // have to save in case there are non-revisable fields + $term = tap($term->fromWorkingCopy())->save(); } else { if (! $term->revisionsEnabled()) { $term->published($request->published); From 06028e1f60f8d536cb06a0d312ac1693145c6c18 Mon Sep 17 00:00:00 2001 From: edalzell Date: Fri, 20 Dec 2024 14:41:10 -0800 Subject: [PATCH 18/23] entry revision tests --- tests/Feature/Entries/EntryRevisionsTest.php | 52 ++++++++++++++++++-- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/tests/Feature/Entries/EntryRevisionsTest.php b/tests/Feature/Entries/EntryRevisionsTest.php index e50009df67..066a70834b 100644 --- a/tests/Feature/Entries/EntryRevisionsTest.php +++ b/tests/Feature/Entries/EntryRevisionsTest.php @@ -45,7 +45,16 @@ public function it_gets_revisions() { $now = Carbon::parse('2017-02-03'); Carbon::setTestNow($now); - $this->setTestBlueprint('test', ['foo' => ['type' => 'text']]); + $this->setTestBlueprint( + 'test', + [ + 'foo' => ['type' => 'text'], + 'bar' => [ + 'type' => 'text', + 'revisable' => false, + ], + ] + ); $this->setTestRoles(['test' => ['access cp', 'publish blog entries']]); $user = User::make()->id('user-1')->assignRole('test')->save(); @@ -58,6 +67,7 @@ public function it_gets_revisions() 'blueprint' => 'test', 'title' => 'Original title', 'foo' => 'bar', + 'bar' => 'foo', ])->create(); tap($entry->makeRevision(), function ($copy) { @@ -85,6 +95,7 @@ public function it_gets_revisions() ->assertJsonPath('0.revisions.0.message', 'Revision one') ->assertJsonPath('0.revisions.0.attributes.data.title', 'Original title') ->assertJsonPath('0.revisions.0.attributes.item_url', 'http://localhost/cp/collections/blog/entries/1/revisions/'.Carbon::parse('2017-02-01')->timestamp) + ->assertJsonPath('0.revisions.0.attributes.data.bar', null) ->assertJsonPath('1.revisions.0.action', 'revision') ->assertJsonPath('1.revisions.0.message', false) @@ -102,7 +113,16 @@ public function it_publishes_an_entry() { $now = Carbon::parse('2017-02-03'); Carbon::setTestNow($now); - $this->setTestBlueprint('test', ['foo' => ['type' => 'text']]); + $this->setTestBlueprint( + 'test', + [ + 'foo' => ['type' => 'text'], + 'bar' => [ + 'type' => 'text', + 'revisable' => false, + ], + ] + ); $this->setTestRoles(['test' => ['access cp', 'publish blog entries']]); $user = User::make()->id('user-1')->assignRole('test')->save(); @@ -115,6 +135,7 @@ public function it_publishes_an_entry() 'blueprint' => 'test', 'title' => 'Title', 'foo' => 'bar', + 'bar' => 'foo' ])->create(); tap($entry->makeWorkingCopy(), function ($copy) { @@ -137,6 +158,7 @@ public function it_publishes_an_entry() 'blueprint' => 'test', 'title' => 'Title', 'foo' => 'foo modified in working copy', + 'bar' => 'foo', 'updated_at' => $now->timestamp, 'updated_by' => $user->id(), ], $entry->data()->all()); @@ -166,7 +188,16 @@ public function it_unpublishes_an_entry() { $now = Carbon::parse('2017-02-03'); Carbon::setTestNow($now); - $this->setTestBlueprint('test', ['foo' => ['type' => 'text']]); + $this->setTestBlueprint( + 'test', + [ + 'foo' => ['type' => 'text'], + 'bar' => [ + 'type' => 'text', + 'revisable' => false, + ], + ] + ); $this->setTestRoles(['test' => ['access cp', 'publish blog entries']]); $user = User::make()->id('user-1')->assignRole('test')->save(); @@ -179,6 +210,7 @@ public function it_unpublishes_an_entry() 'blueprint' => 'test', 'title' => 'Title', 'foo' => 'bar', + 'bar' => 'foo' ])->create(); $this->assertTrue($entry->published()); @@ -194,6 +226,7 @@ public function it_unpublishes_an_entry() 'blueprint' => 'test', 'title' => 'Title', 'foo' => 'bar', + 'bar' => 'foo', 'updated_at' => $now->timestamp, 'updated_by' => $user->id(), ], $entry->data()->all()); @@ -219,7 +252,16 @@ public function it_unpublishes_an_entry() #[Test] public function it_creates_a_revision() { - $this->setTestBlueprint('test', ['foo' => ['type' => 'text']]); + $this->setTestBlueprint( + 'test', + [ + 'foo' => ['type' => 'text'], + 'bar' => [ + 'type' => 'text', + 'revisable' => false, + ], + ] + ); $this->setTestRoles(['test' => ['access cp', 'edit blog entries']]); $user = User::make()->id('user-1')->assignRole('test')->save(); @@ -232,6 +274,7 @@ public function it_creates_a_revision() 'blueprint' => 'test', 'title' => 'Title', 'foo' => 'bar', + 'bar' => 'foo' ])->create(); tap($entry->makeWorkingCopy(), function ($copy) { @@ -253,6 +296,7 @@ public function it_creates_a_revision() 'blueprint' => 'test', 'title' => 'Title', 'foo' => 'bar', + 'bar' => 'foo' ], $entry->data()->all()); $this->assertFalse($entry->published()); $this->assertCount(1, $entry->revisions()); From e6543e3eb04c61bb7632646d00ab3ed30c065328 Mon Sep 17 00:00:00 2001 From: edalzell Date: Fri, 20 Dec 2024 14:46:59 -0800 Subject: [PATCH 19/23] lint --- tests/Feature/Entries/EntryRevisionsTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Feature/Entries/EntryRevisionsTest.php b/tests/Feature/Entries/EntryRevisionsTest.php index 066a70834b..5b9b60ef39 100644 --- a/tests/Feature/Entries/EntryRevisionsTest.php +++ b/tests/Feature/Entries/EntryRevisionsTest.php @@ -135,7 +135,7 @@ public function it_publishes_an_entry() 'blueprint' => 'test', 'title' => 'Title', 'foo' => 'bar', - 'bar' => 'foo' + 'bar' => 'foo', ])->create(); tap($entry->makeWorkingCopy(), function ($copy) { @@ -210,7 +210,7 @@ public function it_unpublishes_an_entry() 'blueprint' => 'test', 'title' => 'Title', 'foo' => 'bar', - 'bar' => 'foo' + 'bar' => 'foo', ])->create(); $this->assertTrue($entry->published()); @@ -274,7 +274,7 @@ public function it_creates_a_revision() 'blueprint' => 'test', 'title' => 'Title', 'foo' => 'bar', - 'bar' => 'foo' + 'bar' => 'foo', ])->create(); tap($entry->makeWorkingCopy(), function ($copy) { @@ -296,7 +296,7 @@ public function it_creates_a_revision() 'blueprint' => 'test', 'title' => 'Title', 'foo' => 'bar', - 'bar' => 'foo' + 'bar' => 'foo', ], $entry->data()->all()); $this->assertFalse($entry->published()); $this->assertCount(1, $entry->revisions()); From 710f61fb013999c1fd2a61e75696e8e8a94c8a01 Mon Sep 17 00:00:00 2001 From: edalzell Date: Fri, 3 Jan 2025 14:32:35 -0800 Subject: [PATCH 20/23] prep for revisions --- tests/Feature/Entries/UpdateEntryTest.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/Feature/Entries/UpdateEntryTest.php b/tests/Feature/Entries/UpdateEntryTest.php index 4531ade84d..e7a480fe56 100644 --- a/tests/Feature/Entries/UpdateEntryTest.php +++ b/tests/Feature/Entries/UpdateEntryTest.php @@ -11,6 +11,7 @@ use Statamic\Facades\Blueprint; use Statamic\Facades\Collection; use Statamic\Facades\Entry; +use Statamic\Facades\Folder; use Statamic\Facades\Role; use Statamic\Facades\User; use Statamic\Structures\CollectionStructure; @@ -23,6 +24,24 @@ class UpdateEntryTest extends TestCase use FakesRoles; use PreventSavingStacheItemsToDisk; + public function setUp(): void + { + parent::setUp(); + + $this->dir = __DIR__.'/tmp'; + + config([ + 'statamic.revisions.path' => $this->dir, + 'statamic.revisions.enable' => true, + ]); + } + + public function tearDown(): void + { + Folder::delete($this->dir); + parent::tearDown(); + } + #[Test] public function it_denies_access_if_you_dont_have_edit_permission() { From 1f89cedad72de08fa1e28a09f8c673a59ed1028e Mon Sep 17 00:00:00 2001 From: edalzell Date: Fri, 3 Jan 2025 14:44:04 -0800 Subject: [PATCH 21/23] get base test going --- tests/Feature/Entries/UpdateEntryTest.php | 44 +++++++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/tests/Feature/Entries/UpdateEntryTest.php b/tests/Feature/Entries/UpdateEntryTest.php index e7a480fe56..f36833bfa8 100644 --- a/tests/Feature/Entries/UpdateEntryTest.php +++ b/tests/Feature/Entries/UpdateEntryTest.php @@ -435,7 +435,45 @@ public function it_can_validate_against_published_value() #[Test] public function published_entry_gets_saved_to_working_copy() { - $this->markTestIncomplete(); + [$user, $collection] = $this->seedUserAndCollection(); + + $this->seedBlueprintFields($collection, [ + 'revisable' => ['type' => 'text'], + 'non_revisable' => ['type' => 'text', 'revisable' => false], + ]); + + $entry = EntryFactory::id('1') + ->slug('test') + ->collection('test') + ->data([ + 'title' => 'revisable test', + ])->create(); + + $this + ->actingAs($user) + ->update($entry, [ + 'revisable' => 'revise me', + 'non_revisable' => 'no revisions for you', + ]) + ->assertOk(); + + // $entry = Entry::find($entry->id()); + // $this->assertEquals('test', $entry->slug()); + // $this->assertEquals([ + // 'blueprint' => 'test', + // 'title' => 'Original title', + // 'foo' => 'bar', + // 'updated_at' => $originalTimestamp, + // ], $entry->data()); + + // $workingCopy = $entry->fromWorkingCopy(); + // $this->assertEquals('updated-slug', $workingCopy->slug()); + // $this->assertEquals([ + // 'blueprint' => 'test', + // 'title' => 'Updated title', + // 'foo' => 'updated foo', + // ], $workingCopy->data()); + } #[Test] @@ -520,7 +558,7 @@ public function does_not_validate_max_depth_when_collection_max_depth_is_null() ->assertOk(); } - private function seedUserAndCollection() + private function seedUserAndCollection(bool $enableRevisions = false) { $this->setTestRoles(['test' => [ 'access cp', @@ -529,7 +567,7 @@ private function seedUserAndCollection() 'access fr site', ]]); $user = tap(User::make()->assignRole('test'))->save(); - $collection = tap(Collection::make('test'))->save(); + $collection = tap(Collection::make('test')->revisionsEnabled($enableRevisions))->save(); return [$user, $collection]; } From 5e845ff2ca01ee8fd248c7f41d97d1fa0d89a524 Mon Sep 17 00:00:00 2001 From: edalzell Date: Fri, 3 Jan 2025 16:18:26 -0800 Subject: [PATCH 22/23] add test and pass --- .../CP/Collections/EntriesController.php | 18 ++++++++- tests/Feature/Entries/UpdateEntryTest.php | 39 +++++++++---------- 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/src/Http/Controllers/CP/Collections/EntriesController.php b/src/Http/Controllers/CP/Collections/EntriesController.php index 69138c8cc0..c38cf802be 100644 --- a/src/Http/Controllers/CP/Collections/EntriesController.php +++ b/src/Http/Controllers/CP/Collections/EntriesController.php @@ -6,6 +6,7 @@ use Illuminate\Validation\ValidationException; use Statamic\Contracts\Entries\Entry as EntryContract; use Statamic\CP\Breadcrumbs; +use Statamic\Entries\Entry as EntriesEntry; use Statamic\Exceptions\BlueprintNotFoundException; use Statamic\Facades\Action; use Statamic\Facades\Asset; @@ -13,6 +14,7 @@ use Statamic\Facades\Site; use Statamic\Facades\Stache; use Statamic\Facades\User; +use Statamic\Fields\Field; use Statamic\Hooks\CP\EntriesIndexQuery; use Statamic\Http\Controllers\CP\CpController; use Statamic\Http\Requests\FilteredRequest; @@ -263,7 +265,7 @@ public function update(Request $request, $collection, $entry) // catch any changes through RevisionSaving event // have to save in case there are non-revisable fields - $entry = tap($entry->fromWorkingCopy())->save(); + $entry = $this->saveNonRevisableFields($entry); } else { if (! $entry->revisionsEnabled() && User::current()->can('publish', $entry)) { $entry->published($request->published); @@ -564,4 +566,18 @@ protected function ensureCollectionIsAvailableOnSite($collection, $site) return redirect()->back()->with('error', __('Collection is not available on site ":handle".', ['handle' => $site->handle])); } } + + private function saveNonRevisableFields(EntriesEntry $entry): EntriesEntry + { + /** @var EntriesEntry */ + $savedVersion = $entry->fresh(); + + $entry->blueprint()->fields()->all() + ->reject(fn (Field $field) => $field->isRevisable()) + ->each(fn ($ignore, string $fieldHandle) => $savedVersion->set($fieldHandle, $entry->{$fieldHandle})); + + $savedVersion->save(); + + return $savedVersion; + } } diff --git a/tests/Feature/Entries/UpdateEntryTest.php b/tests/Feature/Entries/UpdateEntryTest.php index f36833bfa8..5db854a3c2 100644 --- a/tests/Feature/Entries/UpdateEntryTest.php +++ b/tests/Feature/Entries/UpdateEntryTest.php @@ -31,8 +31,9 @@ public function setUp(): void $this->dir = __DIR__.'/tmp'; config([ + 'statamic.editions.pro' => true, 'statamic.revisions.path' => $this->dir, - 'statamic.revisions.enable' => true, + 'statamic.revisions.enabled' => true, ]); } @@ -435,7 +436,7 @@ public function it_can_validate_against_published_value() #[Test] public function published_entry_gets_saved_to_working_copy() { - [$user, $collection] = $this->seedUserAndCollection(); + [$user, $collection] = $this->seedUserAndCollection(true); $this->seedBlueprintFields($collection, [ 'revisable' => ['type' => 'text'], @@ -445,9 +446,8 @@ public function published_entry_gets_saved_to_working_copy() $entry = EntryFactory::id('1') ->slug('test') ->collection('test') - ->data([ - 'title' => 'revisable test', - ])->create(); + ->data(['title' => 'Revisable Test', 'published' => true]) + ->create(); $this ->actingAs($user) @@ -457,23 +457,20 @@ public function published_entry_gets_saved_to_working_copy() ]) ->assertOk(); - // $entry = Entry::find($entry->id()); - // $this->assertEquals('test', $entry->slug()); - // $this->assertEquals([ - // 'blueprint' => 'test', - // 'title' => 'Original title', - // 'foo' => 'bar', - // 'updated_at' => $originalTimestamp, - // ], $entry->data()); - - // $workingCopy = $entry->fromWorkingCopy(); - // $this->assertEquals('updated-slug', $workingCopy->slug()); - // $this->assertEquals([ - // 'blueprint' => 'test', - // 'title' => 'Updated title', - // 'foo' => 'updated foo', - // ], $workingCopy->data()); + $entry = Entry::find($entry->id()); + $this->assertEquals('no revisions for you', $entry->non_revisable); + $this->assertEquals('Revisable Test', $entry->title); + $this->assertEquals('test', $entry->slug()); + $this->assertNull($entry->revisable); + $workingCopy = $entry->fromWorkingCopy(); + $this->assertEquals('updated-entry', $workingCopy->slug()); + $this->assertEquals([ + 'title' => 'Updated entry', + 'revisable' => 'revise me', + 'non_revisable' => 'no revisions for you', + 'published' => true + ], $workingCopy->data()->all()); } #[Test] From 5c149ad576c28daffc7b0afbd51c182c482d99ea Mon Sep 17 00:00:00 2001 From: edalzell Date: Fri, 3 Jan 2025 16:32:09 -0800 Subject: [PATCH 23/23] lint --- tests/Feature/Entries/UpdateEntryTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Feature/Entries/UpdateEntryTest.php b/tests/Feature/Entries/UpdateEntryTest.php index 5db854a3c2..5962858368 100644 --- a/tests/Feature/Entries/UpdateEntryTest.php +++ b/tests/Feature/Entries/UpdateEntryTest.php @@ -469,7 +469,7 @@ public function published_entry_gets_saved_to_working_copy() 'title' => 'Updated entry', 'revisable' => 'revise me', 'non_revisable' => 'no revisions for you', - 'published' => true + 'published' => true, ], $workingCopy->data()->all()); }