Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
c33ed4d
add to base field
edalzell Dec 12, 2024
4aff3fa
tiny refactor to prep
edalzell Dec 12, 2024
8817d35
update the contract and implement in entry
edalzell Dec 12, 2024
42b23b7
better
edalzell Dec 12, 2024
cac88a7
get the non-revisable fields
edalzell Dec 13, 2024
59c9267
save the non-revisable data
edalzell Dec 13, 2024
83108ea
tidier
edalzell Dec 13, 2024
4f5106a
tidy
edalzell Dec 13, 2024
668de50
move this so all revisable data can use it
edalzell Dec 13, 2024
34b3dc5
first test
edalzell Dec 13, 2024
9a32871
Field test
edalzell Dec 13, 2024
34e8b23
fix tests
edalzell Dec 13, 2024
b7eefe1
tidy
edalzell Dec 13, 2024
bc3645a
test revision attributes
edalzell Dec 13, 2024
d7cb176
test makeFromRevision
edalzell Dec 13, 2024
5b26aa1
support terms
edalzell Dec 13, 2024
9b5c6e2
term controller
edalzell Dec 13, 2024
06028e1
entry revision tests
edalzell Dec 20, 2024
e6543e3
lint
edalzell Dec 20, 2024
3c4df11
Merge branch 'statamic:5.x' into non-revisable-fields
edalzell Jan 3, 2025
710f61f
prep for revisions
edalzell Jan 3, 2025
1f89ced
get base test going
edalzell Jan 3, 2025
5e845ff
add test and pass
edalzell Jan 4, 2025
5c149ad
lint
edalzell Jan 4, 2025
bc25912
Merge branch 'statamic:5.x' into non-revisable-fields
edalzell Jan 6, 2025
75dbadb
Merge branch '5.x' into non-revisable-fields
edalzell Jan 27, 2025
2149c64
whoops need to return all data to the front end
edalzell Jan 28, 2025
0e2ea62
remove debugging
edalzell Jan 28, 2025
17067fa
Discard changes to src/Http/Controllers/CP/Taxonomies/TermsController…
edalzell Jan 28, 2025
0473902
Discard changes to src/Taxonomies/LocalizedTerm.php
edalzell Jan 28, 2025
dcb5fdd
Merge branch '5.x' into non-revisable-fields
edalzell Feb 14, 2025
4392357
remove term stuff
edalzell Feb 14, 2025
2ac126d
Merge branch '5.x' into non-revisable-fields
edalzell Jun 3, 2025
eee0405
Merge branch '5.x' into non-revisable-fields
edalzell Oct 1, 2025
c9ddfcd
Merge branch '5.x' into non-revisable-fields
edalzell Oct 1, 2025
96b2540
Computed fields are always revisable cuz they shouldn’t be saved
edalzell Oct 1, 2025
63f722c
Add test
edalzell Oct 1, 2025
72939c8
Merge branch '5.x' into non-revisable-fields
edalzell Mar 25, 2026
0b914cd
Merge branch '6.x' into non-revisable-fields
edalzell Mar 25, 2026
1d55d8c
get from container now that these have query builders
edalzell Mar 25, 2026
a26fcd4
fix tests
edalzell Mar 25, 2026
6f9ee06
remove debugging
edalzell Mar 25, 2026
2b77d78
fix test
edalzell Mar 25, 2026
f29b6db
Merge branch '6.x' into non-revisable-fields
edalzell May 6, 2026
e9e36e0
Merge branch '6.x' into non-revisable-fields
edalzell Jun 9, 2026
690ab94
Merge remote-tracking branch 'upstream/6.x' into non-revisable-fields
edalzell Aug 20, 2026
16a940b
Merge branch '6.x' into non-revisable-fields
edalzell Sep 2, 2026
24d08bd
Fully replace data when making an entry from a revision, keeping only…
edalzell Sep 2, 2026
0225065
Save non-revisable fields from raw data and skip the save when there …
edalzell Sep 2, 2026
527fa91
Add revisable instructions text and hide the toggle for computed fields
edalzell Sep 2, 2026
05c2f6e
Merge remote-tracking branch 'origin/6.x' into non-revisable-fields
jasonvarga Sep 14, 2026
1c4dde1
Remove non-revisable fields from a localization when they're unsynced
edalzell Sep 14, 2026
a2fbefa
Skip the non-revisable write-through when the revision is cancelled
edalzell Sep 14, 2026
5cdadd2
Touch updated_at when writing non-revisable fields
edalzell Sep 14, 2026
db0882a
Default an explicit null revisable to true
edalzell Sep 14, 2026
36a52e9
Clarify that revisable only applies to top level fields
edalzell Sep 14, 2026
02778dd
Merge branch '6.x' into non-revisable-fields
edalzell Sep 17, 2026
aa90445
Let the publish array take revisable from the field config
edalzell Oct 26, 2026
f95e67f
Derive un-localized non-revisable fields from the entry data
edalzell Oct 26, 2026
d171d5f
Trim non-revisable coverage in the entry revisions tests
edalzell Oct 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lang/en/messages.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
'fields_instructions_position_instructions' => 'Show instructions above or below the field.',
'fields_listable_instructions' => 'Control the listing column visibility.',
'fields_replicator_preview_instructions' => 'Control preview visibility in Replicator/Bard sets.',
'fields_revisable_instructions' => 'Include this field in revisions. When disabled, changes are saved straight to the published entry instead of a working copy. Only applies to top level fields in collection entries. The date, slug and published fields are unaffected.',
'fields_sortable_instructions' => 'Control if the field should be sortable in listing views.',
'fields_visibility_instructions' => 'Control field visibility on publish forms.',
'fieldset_import_fieldset_instructions' => 'The fieldset to be imported.',
Expand Down
4 changes: 2 additions & 2 deletions src/Entries/Entry.php
Original file line number Diff line number Diff line change
Expand Up @@ -762,7 +762,7 @@ protected function revisionAttributes()
'slug' => $this->slug(),
'published' => $this->published(),
'date' => $this->collection()->dated() && (! $this->hasOrigin() || $this->hasExplicitDate()) ? $this->date()->timestamp : null,
'data' => $this->data()->except(['updated_by', 'updated_at'])->all(),
'data' => $this->data()->except(['updated_by', 'updated_at', ...$this->nonRevisableFields()])->all(),
];
}

Expand All @@ -778,7 +778,7 @@ public function makeFromRevision($revision)

$entry
->published($attrs['published'])
->data($attrs['data'])
->data(collect($attrs['data'])->merge($this->data()->only($this->nonRevisableFields())))
->slug($attrs['slug']);

if ($this->collection()->dated()) {
Expand Down
24 changes: 24 additions & 0 deletions src/Fields/Field.php
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,19 @@ public function isFilterable()
return (bool) $this->get('filterable');
}

public function isRevisable()
{
if ($this->visibility() === 'computed') {
return true;
}

if (is_null($this->get('revisable'))) {
return true;
}

return (bool) $this->get('revisable');
}

public function shouldBeDuplicated()
{
if (is_null($this->get('duplicate'))) {
Expand Down Expand Up @@ -623,6 +636,17 @@ public static function commonFieldOptions(): Fields
'width' => 50,

],
'revisable' => [
Comment thread
edalzell marked this conversation as resolved.
'display' => __('Revisable'),
'instructions' => __('statamic::messages.fields_revisable_instructions'),
Comment thread
edalzell marked this conversation as resolved.
'type' => 'toggle',
'validate' => 'boolean',
'default' => true,
'unless' => [
'visibility' => 'equals computed',
],
'width' => 50,
],
])->map(fn ($field, $handle) => compact('handle', 'field'))->values()->all();

return new ConfigFields($fields);
Expand Down
24 changes: 24 additions & 0 deletions src/Http/Controllers/CP/Collections/EntriesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,11 @@ public function update(Request $request, $collection, $entry)
->user(User::current())
->save();

// have to save in case there are non-revisable fields
if ($saved) {
$this->saveNonRevisableFields($entry);
}

// catch any changes through RevisionSaving event
$entry = $entry->fromWorkingCopy();
} else {
Expand Down Expand Up @@ -531,4 +536,23 @@ protected function ensureCollectionIsAvailableOnSite($collection, $site)
return redirect()->back()->with('error', __('Collection is not available on site ":handle".', ['handle' => $site->handle]));
}
}

private function saveNonRevisableFields(EntryContract $entry): void
{
$fresh = $entry->fresh();
$handles = collect($entry->nonRevisableFields());
$values = $entry->data()->only($handles->all());

// A localization's data only holds the localized handles, so a non-revisable
// field missing from it has been synced back to the origin and must be removed.
$removed = $handles->diff($values->keys())->filter(fn ($handle) => $fresh->has($handle));

if ($values->isEmpty() && $removed->isEmpty()) {
return;
}

$removed->each(fn ($handle) => $fresh->remove($handle));

$fresh->merge($values)->updateLastModified(User::current())->save();
}
}
10 changes: 10 additions & 0 deletions src/Revisions/Revisable.php
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,16 @@ public function revisionsEnabled()
return config('statamic.revisions.enabled') && Statamic::pro();
}

public function nonRevisableFields(): array
{
return $this->blueprint()
->fields()
->all()
->reject(fn ($field) => $field->isRevisable())
->keys()
->all();
}

abstract protected function revisionKey();

abstract protected function revisionAttributes();
Expand Down
49 changes: 48 additions & 1 deletion tests/Feature/Entries/EntryRevisionsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,10 @@ 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();

Expand All @@ -216,6 +219,7 @@ public function it_publishes_an_entry()
'blueprint' => 'test',
'title' => 'Title',
'foo' => 'bar',
'bar' => 'foo',
])->create();

tap($entry->makeWorkingCopy(), function ($copy) {
Expand All @@ -238,6 +242,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());
Expand Down Expand Up @@ -535,6 +540,48 @@ public function it_restores_an_unpublished_entrys_contents_to_another_revision()
$this->assertCount(1, $entry->revisions());
}

#[Test]
public function it_keeps_non_revisable_fields_when_restoring_an_unpublished_entrys_contents()
{
$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();

tap((new Revision)
->key('collections/blog/en/123')
->date(Carbon::createFromTimestamp('1553546421', config('app.timezone')))
->attributes([
'published' => true,
'slug' => 'existing-slug',
'data' => ['foo' => 'existing foo'],
]))->save();

$entry = EntryFactory::id('123')
->slug('test')
->collection('blog')
->published(false)
->data([
'blueprint' => 'test',
'foo' => 'bar',
'bar' => 'not tracked by revisions',
'stale' => 'not in the revision',
])->create();

$this
->actingAs($user)
->restore($entry, ['revision' => '1553546421'])
->assertOk()
->assertSessionHas('success');

$entry = Entry::find($entry->id());
$this->assertEquals('existing foo', $entry->get('foo'));
$this->assertEquals('not tracked by revisions', $entry->get('bar'));
$this->assertFalse($entry->has('stale'));
}

#[Test]
public function localized_entry_with_inherited_localizable_date_keeps_following_its_origin_when_reconstructed_from_revision()
{
Expand Down
Loading
Loading