From 26faed3d9a3dbfe319626051a5fed775c68bdc0f Mon Sep 17 00:00:00 2001 From: Jim Elvers Date: Mon, 18 May 2026 14:55:35 +0200 Subject: [PATCH 1/2] feat: add `it_localizes_null_values_for_non_direct_descendants` test to `EntryTest` --- tests/Entries/EntryTest.php | 48 +++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tests/Entries/EntryTest.php b/tests/Entries/EntryTest.php index 81862cf1..975962c6 100644 --- a/tests/Entries/EntryTest.php +++ b/tests/Entries/EntryTest.php @@ -287,6 +287,54 @@ public function it_localizes_null_fields() $this->assertEquals('baz', $entry->descendants()->get('es')->foo ?? null); } + #[Test] + public function it_localizes_null_values_for_non_direct_descendants() + { + $this->setSites([ + 'en' => ['name' => 'English', 'locale' => 'en_US', 'url' => 'http://test.com/'], + 'nl' => ['name' => 'Dutch', 'locale' => 'nl_NL', 'url' => 'http://nl.test.com/'], + 'nl-BE' => ['name' => 'Flemish', 'locale' => 'nl_BE', 'url' => 'http://test.com/nl-be/'], + ]); + + $blueprint = Facades\Blueprint::makeFromFields(['foo' => ['type' => 'text', 'localizable' => true]])->setHandle('test'); + $blueprint->save(); + + BlueprintRepository::shouldReceive('in')->with('collections/pages')->andReturn(collect(['test' => $blueprint])); + + $collection = (new Collection) + ->handle('pages') + ->propagate(true) + ->sites(['en', 'nl', 'nl-BE']) + ->save(); + + /** @var Entry $originEntry */ + $originEntry = (new Entry) + ->id(1) + ->locale('en') + ->collection($collection) + ->blueprint('test') + ->data(['foo' => 'bar']); + $originEntry->save(); + + /** @var Entry $directLocalizationEntry */ + $directLocalizationEntry = $originEntry->in('nl'); + $directLocalizationEntry->toModel()->save(); + $indirectLocalizationEntry = $directLocalizationEntry->in('nl-BE'); + $indirectLocalizationEntry->origin($directLocalizationEntry); + + $this->assertSame($originEntry, $directLocalizationEntry->origin()); + $this->assertSame($directLocalizationEntry, $indirectLocalizationEntry->origin()); + + $indirectLocalizationEntry->data(['foo' => null]); + $indirectLocalizationEntry->toModel()->save(); + + $directLocalizationEntry = Entry::fromModel($directLocalizationEntry->model()->fresh()); + $indirectLocalizationEntry = Entry::fromModel($indirectLocalizationEntry->model()->fresh()); + + $this->assertEquals('bar', $directLocalizationEntry->value('foo')); + $this->assertEquals(null, $indirectLocalizationEntry->value('foo')); + } + #[Test] public function it_stores_and_retrieves_mapped_data_values() { From a4606491d9b966b2559692552a71b2cef3c430a2 Mon Sep 17 00:00:00 2001 From: Jim Elvers Date: Tue, 28 Apr 2026 11:19:33 +0200 Subject: [PATCH 2/2] fix: use closest origin that has field localized to determine if a field can be forgotten in `makeModelFromContract` method of `Entry` --- src/Entries/Entry.php | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/Entries/Entry.php b/src/Entries/Entry.php index f2c0d05e..37e78bf5 100644 --- a/src/Entries/Entry.php +++ b/src/Entries/Entry.php @@ -61,7 +61,7 @@ public static function makeModelFromContract(EntryContract $source) $date = $source->hasDate() ? $source->date() : null; - $origin = $source->origin(); + $directOrigin = $source->origin(); if ($source->hasOrigin()) { if ($blueprint = $source->blueprint()) { @@ -73,13 +73,23 @@ public static function makeModelFromContract(EntryContract $source) ->handle() ->all(); - $originData = $origin->data(); + $directOriginData = $directOrigin->data(); // remove any fields in entry data that are marked as localized but value is present, and does not match origin $localizedFields = []; foreach ($localizedBlueprintFields as $blueprintField) { if ($data->has($blueprintField)) { - if ($data->get($blueprintField) === $originData->get($blueprintField)) { + $fieldOrigin = $directOrigin; + $fieldOriginData = $directOriginData; + while ( + $fieldOrigin->hasOrigin() + && ! in_array($blueprintField, $fieldOriginData->get('__localized_fields') ?? []) + ) { + $fieldOrigin = $fieldOrigin->origin(); + $fieldOriginData = $fieldOrigin->data(); + } + + if ($data->get($blueprintField) === $fieldOriginData->get($blueprintField)) { $data->forget($blueprintField); } else { $localizedFields[] = $blueprintField; @@ -87,12 +97,12 @@ public static function makeModelFromContract(EntryContract $source) } } - $data = $originData->merge($data); + $data = $directOrigin->data()->merge($data); $data->put('__localized_fields', $localizedFields); if (! in_array('date', $localizedFields)) { - $date = $origin->hasDate() ? $origin->date() : null; + $date = $directOrigin->hasDate() ? $directOrigin->date() : null; } } } @@ -117,7 +127,7 @@ public static function makeModelFromContract(EntryContract $source) $attributes = [ ...$attributes, - 'origin_id' => $origin?->id(), + 'origin_id' => $directOrigin?->id(), 'site' => $source->locale(), 'slug' => $source->slug(), 'uri' => $source->uri() ?? $source->routableUri(),