From 49c6f2c92da8be517080b726aa8f81b1b20fec8f Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Wed, 3 Jun 2026 09:04:34 +0100 Subject: [PATCH] Use closest origin that has field localized --- src/Entries/Entry.php | 24 +++++++++++++------ tests/Entries/EntryTest.php | 48 +++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/src/Entries/Entry.php b/src/Entries/Entry.php index 4e6e4063..3d0adf5b 100644 --- a/src/Entries/Entry.php +++ b/src/Entries/Entry.php @@ -62,7 +62,7 @@ public static function makeModelFromContract(EntryContract $source) $date = $source->hasDate() ? $source->date() : null; - $origin = $source->origin(); + $directOrigin = $source->origin(); if ($template = $source->get('template', $source->template)) { $data->put('template', $template); @@ -82,13 +82,23 @@ public static function makeModelFromContract(EntryContract $source) $data->forget('template'); } - $originData = $origin->data(); + $directOriginData = $directOrigin->data(); - // remove any fields in entry data that are marked as localized but value is present, and matches origin + // 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; @@ -96,12 +106,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; } } } @@ -126,7 +136,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(), diff --git a/tests/Entries/EntryTest.php b/tests/Entries/EntryTest.php index e53063ec..b0294266 100644 --- a/tests/Entries/EntryTest.php +++ b/tests/Entries/EntryTest.php @@ -537,4 +537,52 @@ public function it_build_stache_associations_when_taxonomy_driver_is_not_eloquen $this->assertCount(2, $taxonomyStore->store('test')->index('associations')->items()); } + + #[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')); + } }