Skip to content

Commit

Permalink
Issue #97 Fix duplicate language code with UrlNormalizer
Browse files Browse the repository at this point in the history
  • Loading branch information
mikehaertl committed Mar 22, 2017
1 parent 84c08d6 commit 2b14493
Show file tree
Hide file tree
Showing 2 changed files with 163 additions and 54 deletions.
44 changes: 29 additions & 15 deletions UrlManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use yii\base\InvalidConfigException;
use yii\web\Cookie;
use yii\web\UrlManager as BaseUrlManager;
use yii\web\UrlNormalizerRedirectException;

/**
* UrlManager
Expand Down Expand Up @@ -158,6 +159,7 @@ public function getDefaultLanguage()
public function parseRequest($request)
{
if ($this->enableLocaleUrls && $this->languages) {
$this->_request = $request;
$process = true;
if ($this->ignoreLanguageUrlPatterns) {
$pathInfo = $request->getPathInfo();
Expand All @@ -169,13 +171,17 @@ public function parseRequest($request)
}
}
if ($process && !$this->_processed) {
// If a normalizer is configured, let it do it's job
// Check if a normalizer wants to redirect
$normalized = false;
if (property_exists($this, 'normalizer') && $this->normalizer!==false) {
parent::parseRequest($request);
try {
parent::parseRequest($request);
} catch (UrlNormalizerRedirectException $e) {
$normalized = true;
}
}
// Still here, so parent::parseRequest() didn't throw a UrlNormalizerRedirectException.
$this->_processed = true;
$this->processLocaleUrl($request);
$this->processLocaleUrl($normalized);
}
}
return parent::parseRequest($request);
Expand Down Expand Up @@ -291,12 +297,11 @@ public function createUrl($params)
* If no parameter is found it will try to detect the language from persistent storage (session /
* cookie) or from browser settings.
*
* @var \yii\web\Request $request
* @param bool $normalized whether a UrlNormalizer tried to redirect
*/
protected function processLocaleUrl($request)
protected function processLocaleUrl($normalized)
{
$this->_request = $request;
$pathInfo = $request->getPathInfo();
$pathInfo = $this->_request->getPathInfo();
$parts = [];
foreach ($this->languages as $k => $v) {
$value = is_string($k) ? $k : $v;
Expand All @@ -310,7 +315,7 @@ protected function processLocaleUrl($request)
}
$pattern = implode('|', $parts);
if (preg_match("#^($pattern)\b(/?)#i", $pathInfo, $m)) {
$request->setPathInfo(mb_substr($pathInfo, mb_strlen($m[1].$m[2])));
$this->_request->setPathInfo(mb_substr($pathInfo, mb_strlen($m[1].$m[2])));
$code = $m[1];
if (isset($this->languages[$code])) {
// Replace alias with language code
Expand Down Expand Up @@ -351,7 +356,9 @@ protected function processLocaleUrl($request)

// "Reset" case: We called e.g. /fr/demo/page so the persisted language was set back to "fr".
// Now we can redirect to the URL without language prefix, if default prefixes are disabled.
if (!$this->enableDefaultLanguageUrlCode && $language===$this->_defaultLanguage) {
$reset = !$this->enableDefaultLanguageUrlCode && $language===$this->_defaultLanguage;

if ($reset || $normalized) {
$this->redirectToLanguage('');
}
} else {
Expand All @@ -360,12 +367,12 @@ protected function processLocaleUrl($request)
$language = Yii::$app->session->get($this->languageSessionKey);
$language!==null && Yii::trace("Found persisted language '$language' in session.", __METHOD__);
if ($language===null) {
$language = $request->getCookies()->getValue($this->languageCookieName);
$language = $this->_request->getCookies()->getValue($this->languageCookieName);
$language!==null && Yii::trace("Found persisted language '$language' in cookie.", __METHOD__);
}
}
if ($language===null && $this->enableLanguageDetection) {
foreach ($request->getAcceptableLanguages() as $acceptable) {
foreach ($this->_request->getAcceptableLanguages() as $acceptable) {
list($language,$country) = $this->matchCode($acceptable);
if ($language!==null) {
$language = $country===null ? $language : "$language-$country";
Expand All @@ -390,7 +397,10 @@ protected function processLocaleUrl($request)
if ($key && is_string($key)) {
$language = $key;
}
$this->redirectToLanguage($this->keepUppercaseLanguageCode ? $language : strtolower($language));
if (!$this->keepUppercaseLanguageCode) {
$language = strtolower($language);
}
$this->redirectToLanguage($language);
}
}

Expand Down Expand Up @@ -448,7 +458,12 @@ protected function matchCode($code)
*/
protected function redirectToLanguage($language)
{
$result = parent::parseRequest($this->_request);
try {
$result = parent::parseRequest($this->_request);
} catch (UrlNormalizerRedirectException $e) {
$route = is_array($e->url) ? $e->url[0] : $e->url;
$result = [$route, $this->_request->getQueryParams()];
}
if ($result === false) {
throw new \yii\web\NotFoundHttpException(Yii::t('yii', 'Page not found.'));
}
Expand All @@ -473,6 +488,5 @@ protected function redirectToLanguage($language)
} else {
Yii::$app->end();
}

}
}
173 changes: 134 additions & 39 deletions tests/RedirectTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ class RedirectTest extends TestCase
* // - a string with a URL that should be redirected to,
* // - `false` if there should be no redirect
* // - an array of individual request/session/cookie configurations
* // each indexed by the expected redirect URL (or `false`)
* // of this form:
* // [$to, 'request' => .., 'session' => ..., 'cookie' => ...]
* ],
* ]
* ```
Expand All @@ -40,47 +41,68 @@ class RedirectTest extends TestCase
'/site/page' => [

// Acceptable languages in request
'/de/site/page' => ['request' => ['acceptableLanguages' => ['de']]],
'/at/site/page' => ['request' => ['acceptableLanguages' => ['de-at', 'de']]],
'/wc/site/page' => ['request' => ['acceptableLanguages' => ['wc']]],
'/es-bo/site/page' => ['request' => ['acceptableLanguages' => ['es-BO', 'es', 'en']]],
'/es-bo/site/page' => ['request' => ['acceptableLanguages' => ['es-bo', 'es', 'en']]],
'/wc-at/site/page' => ['request' => ['acceptableLanguages' => ['wc-AT', 'de', 'en']]],
'/pt/site/page' => ['request' => ['acceptableLanguages' => ['pt-br']]],
'/alias/site/page' => ['request' => ['acceptableLanguages' => ['fr']]],
['/de/site/page', 'request' => ['acceptableLanguages' => ['de']]],
['/at/site/page', 'request' => ['acceptableLanguages' => ['de-at', 'de']]],
['/wc/site/page', 'request' => ['acceptableLanguages' => ['wc']]],
['/es-bo/site/page', 'request' => ['acceptableLanguages' => ['es-BO', 'es', 'en']]],
['/es-bo/site/page', 'request' => ['acceptableLanguages' => ['es-bo', 'es', 'en']]],
['/wc-at/site/page', 'request' => ['acceptableLanguages' => ['wc-AT', 'de', 'en']]],
['/pt/site/page', 'request' => ['acceptableLanguages' => ['pt-br']]],
['/alias/site/page', 'request' => ['acceptableLanguages' => ['fr']]],
// no redirect
false => ['request' => ['acceptableLanguages' => ['en']]], // default language
[false, 'request' => ['acceptableLanguages' => ['en']]], // default language

// Language in session
'/de/site/page' => ['session' => ['_language' => 'de']],
'/at/site/page' => ['session' => ['_language' => 'de-AT']],
'/wc/site/page' => ['session' => ['_language' => 'wc']],
'/es-bo/site/page' => ['session' => ['_language' => 'es-BO']],
'/wc-at/site/page' => ['session' => ['_language' => 'wc-AT']],
'/pt/site/page' => ['session' => ['_language' => 'pt']],
'/alias/site/page' => ['session' => ['_language' => 'fr']],
['/de/site/page', 'session' => ['_language' => 'de']],
['/at/site/page', 'session' => ['_language' => 'de-AT']],
['/wc/site/page', 'session' => ['_language' => 'wc']],
['/es-bo/site/page', 'session' => ['_language' => 'es-BO']],
['/wc-at/site/page', 'session' => ['_language' => 'wc-AT']],
['/pt/site/page', 'session' => ['_language' => 'pt']],
['/alias/site/page', 'session' => ['_language' => 'fr']],

// Language in cookie
'/de/site/page' => ['cookie' => ['_language' => 'de']],
'/at/site/page' => ['cookie' => ['_language' => 'de-AT']],
'/wc/site/page' => ['cookie' => ['_language' => 'wc']],
'/es-bo/site/page' => ['cookie' => ['_language' => 'es-BO']],
'/wc-at/site/page' => ['cookie' => ['_language' => 'wc-AT']],
'/pt/site/page' => ['cookie' => ['_language' => 'pt']],
'/alias/site/page' => ['cookie' => ['_language' => 'fr']],
['/de/site/page', 'cookie' => ['_language' => 'de']],
['/at/site/page', 'cookie' => ['_language' => 'de-AT']],
['/wc/site/page', 'cookie' => ['_language' => 'wc']],
['/es-bo/site/page', 'cookie' => ['_language' => 'es-BO']],
['/wc-at/site/page', 'cookie' => ['_language' => 'wc-AT']],
['/pt/site/page', 'cookie' => ['_language' => 'pt']],
['/alias/site/page', 'cookie' => ['_language' => 'fr']],
],

// Requests with other language in session, cookie or request headers
'/de/site/page' => [
[false, 'session' => ['_language' => 'wc']],
[false, 'cookie' => ['_language' => 'wc']],
[false, 'request' => ['acceptableLanguages' => ['en']]],
],
],
],

// Default language uses language code
[
'urlManager' => [
'languages' => ['en-US', 'en', 'de'],
'languages' => ['en-US', 'en', 'de', 'pt', 'at' => 'de-AT', 'alias' => 'fr', 'es-BO', 'wc-*'],
'enableDefaultLanguageUrlCode' => true,
],
'redirects' => [
'/' => '/en',
'/site/page' => '/en/site/page',
'/' => [
['/en'], // default language
['/de', 'session' => ['_language' => 'de']],
['/alias', 'cookie' => ['_language' => 'fr']],
],
'/site/page' => [
['/en/site/page', ], // default language
['/de/site/page', 'session' => ['_language' => 'de']],
['/alias/site/page', 'cookie' => ['_language' => 'fr']],
],
// Requests with other language in session, cookie or request headers
'/de/site/page' => [
[false, 'session' => ['_language' => 'wc']],
[false, 'cookie' => ['_language' => 'wc']],
[false, 'request' => ['acceptableLanguages' => ['en']]],
],
],
],

Expand All @@ -93,8 +115,8 @@ class RedirectTest extends TestCase
'redirects' => [
'/es-BO/site/page' => false,
'/site/page' => [
'/en-US/site/page' => ['session' => ['_language' => 'en-US']],
'/en-US/site/page' => ['cookie' => ['_language' => 'en-US']],
['/en-US/site/page', 'session' => ['_language' => 'en-US']],
['/en-US/site/page', 'cookie' => ['_language' => 'en-US']],
]
],
],
Expand Down Expand Up @@ -127,13 +149,21 @@ class RedirectTest extends TestCase
],
[
'urlManager' => [
'languages' => ['en-US', 'en', 'de'],
'languages' => ['en-US', 'en', 'de', 'pt', 'at' => 'de-AT', 'alias' => 'fr', 'es-BO', 'wc-*'],
'enableDefaultLanguageUrlCode' => true,
'suffix' => '/'
],
'redirects' => [
'/' => '/en/',
'/site/page/' => '/en/site/page/',
'/' => [
['/en/'], // default language
['/de/', 'session' => ['_language' => 'de']],
['/alias/', 'cookie' => ['_language' => 'fr']],
],
'/site/page/' => [
['/en/site/page/'], // default language
['/de/site/page/', 'session' => ['_language' => 'de']],
['/alias/site/page/', 'cookie' => ['_language' => 'fr']],
],
],
],

Expand All @@ -147,13 +177,17 @@ class RedirectTest extends TestCase
],
],
'redirects' => [
'' => '',
'/site/page' => '/site/page/',
'/site/page/' => false,

'/de' => '/de/', // normalizer
'/de/' => false,

'/de/site/login' => '/de/site/login/', // normalizer
'/de/site/login/' => false,

'/en/site/login' => '/en/site/login/', // normalizer
'/en/site/login' => '/site/login/', // normalizer
'/en/site/login/' => '/site/login/', // localeurls
],
],
Expand All @@ -165,14 +199,64 @@ class RedirectTest extends TestCase
],
],
'redirects' => [
'' => '',
'/site/page/' => '/site/page',
'/site/page' => false,

'/de/' => '/de', // normalizer
'/de' => false,

'/de/site/login/' => '/de/site/login', // normalizer
'/de/site/login' => false,

'/en/site/login/' => '/en/site/login', // normalizer
'/en/site/login' => '/site/login', // localeurls
'/en/site/login/' => '/site/login', // normalizer
'/en/site/login' => '/site/login', // localeurls
],
],
// Normalizer with default language code
[
'urlManager' => [
'languages' => ['en-US', 'en', 'de', 'pt', 'at' => 'de-AT', 'alias' => 'fr', 'es-BO', 'wc-*'],
'enableDefaultLanguageUrlCode' => true,
'suffix' => '/',
'normalizer' => [
'class' => '\yii\web\UrlNormalizer',
],
],
'redirects' => [
'' => [
['/en/'], // default language
['/de/', 'session' => ['_language' => 'de']],
['/alias/', 'cookie' => ['_language' => 'fr']],
],
'/site/page' => [
['/en/site/page/'], // default language
['/de/site/page/', 'session' => ['_language' => 'de']],
['/alias/site/page/', 'cookie' => ['_language' => 'fr']],
],
'/en/site/page' => '/en/site/page/',
],
],
[
'urlManager' => [
'languages' => ['en-US', 'en', 'de', 'pt', 'at' => 'de-AT', 'alias' => 'fr', 'es-BO', 'wc-*'],
'enableDefaultLanguageUrlCode' => true,
'normalizer' => [
'class' => '\yii\web\UrlNormalizer',
],
],
'redirects' => [
'/' => [
['/en'], // default language
['/de', 'session' => ['_language' => 'de']],
['/alias', 'cookie' => ['_language' => 'fr']],
],
'/site/page/' => [
['/en/site/page'], // default language
['/de/site/page', 'session' => ['_language' => 'de']],
['/alias/site/page', 'cookie' => ['_language' => 'fr']],
],
'/en/site/page/' => '/en/site/page',
],
],
];
Expand All @@ -183,7 +267,8 @@ public function testRedirects()
$urlManager = isset($config['urlManager']) ? $config['urlManager'] : [];
foreach ($config['redirects'] as $from => $to) {
if (is_array($to)) {
foreach ($to as $url => $params) {
foreach ($to as $params) {
$url = $params[0];
$request = isset($params['request']) ? $params['request'] : [];
$session = isset($params['session']) ? $params['session'] : [];
$cookie = isset($params['cookie']) ? $params['cookie'] : [];
Expand Down Expand Up @@ -217,10 +302,18 @@ public function performRedirectTest($from, $to, $urlManager, $request = [], $ses
if ($cookie!==null) {
$_COOKIE = $cookie;
}
$configMessage = print_r([
'from' => $from,
'to' => $to,
'urlManager' => $urlManager,
'request' => $request,
'session' => $session,
'cookie' => $cookie,
], true);
try {
$this->mockRequest($from, $request);
if ($to) {
$this->fail("No redirect for $from to $to with urlManager config:\n" . print_r($urlManager, true));
$this->fail("No redirect:\n$configMessage");
}
} catch (\yii\web\UrlNormalizerRedirectException $e) {
$url = $e->url;
Expand All @@ -231,9 +324,11 @@ public function performRedirectTest($from, $to, $urlManager, $request = [], $ses
}
$url += Yii::$app->request->getQueryParams();
}
$this->assertEquals($this->prepareUrl($to), Url::to($url, $e->scheme));
$message = "UrlNormalizerRedirectException:\n$configMessage";
$this->assertEquals($this->prepareUrl($to), Url::to($url, $e->scheme), $message);
} catch (\yii\base\Exception $e) {
$this->assertEquals($this->prepareUrl($to), $e->getMessage());
$message = "Redirection:\n$configMessage";
$this->assertEquals($this->prepareUrl($to), $e->getMessage(), $message);
}
}

Expand Down

0 comments on commit 2b14493

Please sign in to comment.