diff --git a/src/Model/RecordLocale.php b/src/Model/RecordLocale.php index 4374727c..b21fa432 100644 --- a/src/Model/RecordLocale.php +++ b/src/Model/RecordLocale.php @@ -4,8 +4,10 @@ use SilverStripe\Control\Director; use SilverStripe\i18n\i18n; +use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\DataObject; use SilverStripe\Versioned\Versioned; +use SilverStripe\View\ArrayData; use SilverStripe\View\ViewableData; use TractorCow\Fluent\Extension\FluentExtension; use TractorCow\Fluent\Extension\FluentFilteredExtension; @@ -360,26 +362,55 @@ 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, + * but you can optionally specify CMS context as well + * Passing null will fall back to whatever context is currently in use * + * @param bool|null $isFrontend * @return Locale|null */ - public function getSourceLocale(): ?Locale + public function getSourceLocale(?bool $isFrontend = true): ?Locale { + $isFrontendFromState = FluentState::singleton()->getIsFrontend(); + $isFrontend ??= $isFrontendFromState; + /** @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 this 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; } + // Noting here that we have another case here that isn't handled correctly + // In case this model allows fallback to any locale even bypassing the fallback rules, + // so we just need to find the most recently updated locale + // as that's the one which is going to be providing content + // This is what we call base record fallback + // It's technically possible to figure out which locale the content is coming from but + // displaying this information may actually be confusing so just displaying no source might be more helpful + return null; } } 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