diff --git a/config/localizer.php b/config/localizer.php index ffe07b3..cfab0b4 100644 --- a/config/localizer.php +++ b/config/localizer.php @@ -30,6 +30,15 @@ CodeZero\Localizer\Detectors\AppDetector::class, ], + /** + * Add any of the above detector class names here to make it trusted. + * When a trusted detector returns a locale, it will be used + * as the app locale, regardless if it's a supported locale or not. + */ + 'trusted-detectors' => [ + // + ], + /** * The stores to store the first matching locale in. */ diff --git a/src/Localizer.php b/src/Localizer.php index bc60768..58bd5ae 100644 --- a/src/Localizer.php +++ b/src/Localizer.php @@ -14,7 +14,7 @@ class Localizer protected $locales; /** - * \CoderZero\Localizer\Detectors\Detector instances. + * \CoderZero\Localizer\Detectors\Detector class names or instances. * * @var \Illuminate\Support\Collection|array */ @@ -27,18 +27,27 @@ class Localizer */ protected $stores; + /** + * \CoderZero\Localizer\Detectors\Detector class names. + * + * @var \Illuminate\Support\Collection|array + */ + protected $trustedDetectors; + /** * Create a new Localizer instance. * * @param \Illuminate\Support\Collection|array $locales * @param \Illuminate\Support\Collection|array $detectors * @param \Illuminate\Support\Collection|array $stores + * @param \Illuminate\Support\Collection|array $trustedDetectors */ - public function __construct($locales, $detectors, $stores = []) + public function __construct($locales, $detectors, $stores = [], $trustedDetectors = []) { $this->setSupportedLocales($locales); $this->detectors = $detectors; $this->stores = $stores; + $this->trustedDetectors = $trustedDetectors; } /** @@ -52,7 +61,7 @@ public function detect() $locales = (array) $this->getInstance($detector)->detect(); foreach ($locales as $locale) { - if ($this->isSupportedLocale($locale)) { + if ($locale && ($this->isSupportedLocale($locale) || $this->isTrustedDetector($detector))) { return $locale; } } @@ -105,6 +114,28 @@ protected function isSupportedLocale($locale) return in_array($locale, $this->locales); } + /** + * Check if the given Detector class is trusted. + * + * @param \CodeZero\Localizer\Detectors\Detector|string $detector + * + * @return bool + */ + protected function isTrustedDetector($detector) + { + if (is_string($detector)) { + return in_array($detector, $this->trustedDetectors); + } + + foreach ($this->trustedDetectors as $trustedDetector) { + if ($detector instanceof $trustedDetector) { + return true; + } + } + + return false; + } + /** * Get the class from Laravel's IOC container if it is a string. * diff --git a/src/LocalizerServiceProvider.php b/src/LocalizerServiceProvider.php index 68fe8e4..82a3372 100644 --- a/src/LocalizerServiceProvider.php +++ b/src/LocalizerServiceProvider.php @@ -70,8 +70,9 @@ protected function registerLocalizer() $locales = $app['config']->get("{$this->name}.supported-locales"); $detectors = $app['config']->get("{$this->name}.detectors"); $stores = $app['config']->get("{$this->name}.stores"); + $trustedDetectors = $app['config']->get("{$this->name}.trusted-detectors"); - return new Localizer($locales, $detectors, $stores); + return new Localizer($locales, $detectors, $stores, $trustedDetectors); }); } diff --git a/tests/Feature/SetLocaleTest.php b/tests/Feature/SetLocaleTest.php index b4770d0..a595c9f 100644 --- a/tests/Feature/SetLocaleTest.php +++ b/tests/Feature/SetLocaleTest.php @@ -383,6 +383,31 @@ public function it_defaults_to_the_current_app_locale() $this->assertEquals('en', $response->original); } + /** @test */ + public function trusted_detectors_ignore_supported_locales_and_may_set_any_locale() + { + $this->setSupportedLocales(['en']); + $this->setAppLocale('en'); + + $routeAction = ['locale' => 'nl']; + + Config::set('localizer.trusted-detectors', [ + \CodeZero\Localizer\Detectors\RouteActionDetector::class, + ]); + + Route::group($routeAction, function () { + Route::get('some/route', function () { + return App::getLocale(); + })->middleware(['web', SetLocale::class]); + }); + + $response = $this->get('some/route'); + + $response->assertSessionHas($this->sessionKey, 'nl'); + $response->assertCookie($this->cookieName, 'nl'); + $this->assertEquals('nl', $response->original); + } + /** * Set the current app locale. * diff --git a/tests/Unit/LocalizerTest.php b/tests/Unit/LocalizerTest.php index 6180860..f2bcd17 100644 --- a/tests/Unit/LocalizerTest.php +++ b/tests/Unit/LocalizerTest.php @@ -39,6 +39,42 @@ public function it_returns_the_best_match_if_an_array_of_locales_is_detected() $this->assertEquals('nl', $localizer->detect()); } + /** @test */ + public function trusted_detectors_ignore_supported_locales_and_may_set_any_locale() + { + $supportedLocales = ['en']; + $detectors = [ + Mockery::mock(Detector::class)->allows()->detect()->andReturns('nl')->getMock(), + ]; + $trustedDetectors = [ + Detector::class, + ]; + + $localizer = new Localizer($supportedLocales, $detectors, [], $trustedDetectors); + + $this->assertEquals('nl', $localizer->detect()); + } + + /** @test */ + public function empty_locales_from_trusted_detectors_are_ignored() + { + $supportedLocales = ['en']; + $detectors = [ + Mockery::mock(Detector::class)->allows()->detect()->andReturns(false)->getMock(), + Mockery::mock(Detector::class)->allows()->detect()->andReturns(null)->getMock(), + Mockery::mock(Detector::class)->allows()->detect()->andReturns([])->getMock(), + Mockery::mock(Detector::class)->allows()->detect()->andReturns('')->getMock(), + Mockery::mock(Detector::class)->allows()->detect()->andReturns('en')->getMock(), + ]; + $trustedDetectors = [ + Detector::class, + ]; + + $localizer = new Localizer($supportedLocales, $detectors, [], $trustedDetectors); + + $this->assertEquals('en', $localizer->detect()); + } + /** @test */ public function it_returns_false_if_no_supported_locale_could_be_detected() {