diff --git a/src/Extension/FluentExtension.php b/src/Extension/FluentExtension.php index ff4f6820..0877f43a 100644 --- a/src/Extension/FluentExtension.php +++ b/src/Extension/FluentExtension.php @@ -48,6 +48,7 @@ * * @template T of DataObject * @extends DataExtension + * @property DataObject|$this $owner */ class FluentExtension extends DataExtension { @@ -582,11 +583,11 @@ public function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null) // Apply substitutions $localisedPredicate = str_replace($conditionSearch, $conditionReplace, $predicate); - + if (empty($localisedPredicate)) { continue; } - + $where[$index] = [ $localisedPredicate => $parameters ]; @@ -968,16 +969,27 @@ protected function getRecordLocale() /** * Returns the source locale that will display the content for this record + * Source locale for frontend context is used by default as this is the most common use case, + * but you can optionally specify CMS context as well + * Passing null will fall back to whatever context is currently in use in global fluent state + * + * @param bool|null $isFrontend * * @return Locale|null */ - public function getSourceLocale() + public function getSourceLocale(?bool $isFrontend = true): ?Locale { - $sourceLocale = $this->owner->getField('SourceLocale'); - if ($sourceLocale) { - return Locale::getByLocale($sourceLocale); + $currentLocale = FluentState::singleton()->getLocale(); + + // We do not have any locales set up yet, so there is no source locale to find + if (!$currentLocale) { + return null; } - return Locale::getDefault(); + + $owner = $this->owner; + $localeInformation = $owner->LocaleInformation($currentLocale); + + return $localeInformation->getSourceLocale($isFrontend); } /** diff --git a/src/Model/RecordLocale.php b/src/Model/RecordLocale.php index 4374727c..a66e7945 100644 --- a/src/Model/RecordLocale.php +++ b/src/Model/RecordLocale.php @@ -360,23 +360,43 @@ public function getStagesDiffer(): bool /** * Get the locale which is the source of content for this record + * Source locale for frontend context is used by default as this is the most common use case, + * but you can optionally specify CMS context as well + * Passing null will fall back to whatever context is currently in use in global fluent state * + * @param bool|null $isFrontend * @return Locale|null */ - public function getSourceLocale(): ?Locale + public function getSourceLocale(?bool $isFrontend = true): ?Locale { + $isFrontend ??= FluentState::singleton()->getIsFrontend(); + /** @var DataObject|FluentExtension $record */ $record = $this->getOriginalRecord(); + $config = $record->config(); + + $inheritanceMode = $isFrontend + ? $config->get('frontend_publish_required') + : $config->get('cms_localisation_required'); + // This model has localised data in the current locale so the current locale is also the source locale if ($record->existsInLocale($this->getLocale())) { return $this->getLocaleObject(); } + // This model requires localisation so fallback of any kind is not allowed + // hence the content can't come from another locale + // We don't have a source locale for such case + if ($inheritanceMode === FluentExtension::INHERITANCE_MODE_EXACT) { + return null; + } + foreach ($this->getLocaleObject()->Fallbacks() as $fallback) { if (!$record->existsInLocale($fallback->Locale)) { continue; } + // We found a locale to fall back to, so this will be our source locale return $fallback; } diff --git a/tests/php/Extension/LocaleInheritanceTest.php b/tests/php/Extension/LocaleInheritanceTest.php new file mode 100644 index 00000000..2b617887 --- /dev/null +++ b/tests/php/Extension/LocaleInheritanceTest.php @@ -0,0 +1,189 @@ + [ + FluentSiteTreeExtension::class, + ], + ]; + + /** + * @param string $cmsInheritanceMode + * @param string $frontendInheritanceMode + * @param bool $frontendContext + * @param string $locale + * @param string|null $expected + * @return void + * @throws ValidationException + * @dataProvider sourceLocaleCasesProvider + */ + public function testGetSourceLocale( + string $cmsInheritanceMode, + string $frontendInheritanceMode, + bool $frontendContext, + string $locale, + ?string $expected + ): void { + Page::config() + ->set('cms_localisation_required', $cmsInheritanceMode) + ->set('frontend_publish_required', $frontendInheritanceMode); + + Versioned::withVersionedMode(function () use ($frontendContext, $locale, $expected): void { + // Make sure we have the correct stage set + Versioned::set_stage(Versioned::DRAFT); + + // Create the page in the default locale + FluentState::singleton()->withState( + function (FluentState $state) use ($frontendContext, $locale, $expected): void { + $state + ->setLocale('en_US') + ->setIsFrontend($frontendContext); + + /** @var Page|FluentExtension $page */ + $page = Page::create(); + $page->Title = 'Page title'; + $page->URLSegment = 'test-page'; + $page->write(); + + $localeInformation = $page->LocaleInformation($locale); + $sourceLocaleObject = $localeInformation->getSourceLocale($frontendContext); + $sourceLocale = $sourceLocaleObject?->Locale; + $this->assertEquals( + $expected, + $sourceLocale, + 'We expect a specific source locale (locale information)' + ); + + if (!$sourceLocale) { + return; + } + + // Re-fetch the page in the target locale + $state->setLocale($locale); + + /** @var Page|FluentExtension $page */ + $page = Page::get()->byID($page->ID); + + $this->assertNotNull($page, 'We expect the page to be available in this locale'); + $sourceLocaleObject = $page->getSourceLocale($frontendContext); + $this->assertEquals( + $expected, + $sourceLocaleObject->Locale, + 'We expect a specific source locale (page shorthand method)' + ); + } + ); + }); + } + + public function sourceLocaleCasesProvider(): array + { + return [ + 'default locale, cms with any mode, frontend with any mode, frontend context' => [ + FluentExtension::INHERITANCE_MODE_ANY, + FluentExtension::INHERITANCE_MODE_ANY, + true, + 'en_US', + 'en_US', + ], + 'default locale, cms with exact mode, frontend with any mode, frontend context' => [ + FluentExtension::INHERITANCE_MODE_EXACT, + FluentExtension::INHERITANCE_MODE_ANY, + true, + 'en_US', + 'en_US', + ], + 'default locale, cms with any mode, frontend with exact mode, frontend context' => [ + FluentExtension::INHERITANCE_MODE_ANY, + FluentExtension::INHERITANCE_MODE_EXACT, + true, + 'en_US', + 'en_US', + ], + 'fallback locale, cms with any mode, frontend with any mode, frontend context' => [ + FluentExtension::INHERITANCE_MODE_ANY, + FluentExtension::INHERITANCE_MODE_ANY, + true, + 'de_DE', + 'en_US', + ], + 'fallback locale, cms with exact mode, frontend with any mode, frontend context' => [ + FluentExtension::INHERITANCE_MODE_EXACT, + FluentExtension::INHERITANCE_MODE_ANY, + true, + 'de_DE', + 'en_US', + ], + 'fallback locale, cms with any mode, frontend with exact mode, frontend context' => [ + FluentExtension::INHERITANCE_MODE_ANY, + FluentExtension::INHERITANCE_MODE_EXACT, + true, + 'de_DE', + null, + ], + 'fallback locale, cms with any mode, frontend with fallback mode, frontend context' => [ + FluentExtension::INHERITANCE_MODE_ANY, + FluentExtension::INHERITANCE_MODE_FALLBACK, + true, + 'de_DE', + 'en_US', + ], + 'fallback locale, cms with any mode, frontend with exact mode, cms context' => [ + FluentExtension::INHERITANCE_MODE_ANY, + FluentExtension::INHERITANCE_MODE_EXACT, + false, + 'de_DE', + 'en_US', + ], + 'no fallback locale, cms with any mode, frontend with any mode, frontend context' => [ + FluentExtension::INHERITANCE_MODE_ANY, + FluentExtension::INHERITANCE_MODE_ANY, + true, + 'es_ES', + null, + ], + 'no fallback locale, cms with exact mode, frontend with any mode, frontend context' => [ + FluentExtension::INHERITANCE_MODE_EXACT, + FluentExtension::INHERITANCE_MODE_ANY, + true, + 'es_ES', + null, + ], + 'no fallback locale, cms with any mode, frontend with exact mode, frontend context' => [ + FluentExtension::INHERITANCE_MODE_ANY, + FluentExtension::INHERITANCE_MODE_EXACT, + true, + 'es_ES', + null, + ], + 'no fallback locale, cms with any mode, frontend with fallback mode, frontend context' => [ + FluentExtension::INHERITANCE_MODE_ANY, + FluentExtension::INHERITANCE_MODE_FALLBACK, + true, + 'es_ES', + null, + ], + 'no fallback locale, cms with any mode, frontend with exact mode, cms context' => [ + FluentExtension::INHERITANCE_MODE_ANY, + FluentExtension::INHERITANCE_MODE_EXACT, + false, + 'es_ES', + null, + ], + ]; + } +} diff --git a/tests/php/Extension/LocaleInheritanceTest.yml b/tests/php/Extension/LocaleInheritanceTest.yml new file mode 100644 index 00000000..4f9fca82 --- /dev/null +++ b/tests/php/Extension/LocaleInheritanceTest.yml @@ -0,0 +1,13 @@ +TractorCow\Fluent\Model\Locale: + default: + Title: US English + Locale: en_US + IsGlobalDefault: true + german: + Title: German + Locale: de_DE + Fallbacks: + - =>TractorCow\Fluent\Model\Locale.default + spanish: + Title: Spanish + Locale: es_ES