From bc57af98d9e9ce6af4e8b121ad4ed156c1f23662 Mon Sep 17 00:00:00 2001 From: Patrick Samson Date: Mon, 4 Apr 2016 12:28:40 -0400 Subject: [PATCH] Rewrite * Add .php_cs * Add Drivers * Implement new drivers * Add tests * Use --prefer-dist * Disable XDebug on travis * Bugfix * Update .travis.yml * Fix tests * Fix tests * Fix tests * Fix tests * Travis add cache * Optimize Travis cache * Removed constant from drivers * Travis prefer-source * Disable XDebug * Fix tests * Fix Travis * Travis re-enable XDebug * Travis fix * Added composer.lock and .gitattributes * Improved tests * Improved CookieDriver * Removed automatic middleware registration * Improved tests * Delete LocaleSwitcherTest.php * Delete composer.lock * Update .travis.yml * Update .travis.yml * Travis min version Laravel 5.2 * Test with Laravel 5.0 * 5.0 not working, using 5.1 * Fixed middleware under 5.1 * Try Laravel 5.0 and PHP 5.5.9 * PHPunit min version is 4.8 * Min version will be Laravel 5.1 * Fix Travis * Added RouteParameterDriver * Added storage tests * Fix tests * trying out Coveralls * Add vendor to travis cache * Fixed tests * Cleaned up travis * remove codeclimate * code style * Update .travis.yml * Update .travis.yml * bugfix * composer self-update * Finished the BrowserDriver and its tests * Fix tests * Improve Code coverage * Changed default settings * Add helpers * add more tests * cleanup and update readme * Add more tests covering locale switching * Add switch_locale() helper * update Readme * request() doesnt exist in Laravel 4.2 * fix tests * cleanup * Update README.md --- .codeclimate.yml | 17 -- .gitattributes | 8 + .gitignore | 2 +- .styleci.yml | 10 - .travis.yml | 34 ++- README.md | 29 +-- composer.json | 7 +- config/config.php | 56 ++++- phpunit.xml | 5 + src/{CurrentConfig.php => ConfigManager.php} | 32 +-- src/Contracts/DriverInterface.php | 12 + src/Drivers/BaseDriver.php | 26 +++ src/Drivers/BrowserDriver.php | 69 ++++++ src/Drivers/CookieDriver.php | 33 +++ src/Drivers/RequestDriver.php | 25 +++ src/Drivers/RouteParameterDriver.php | 28 +++ src/Drivers/SessionDriver.php | 30 +++ src/LocaleSwitcher.php | 208 +++++------------- ...hLocale.php => SwitchLocaleMiddleware.php} | 11 +- src/ServiceProvider.php | 18 +- src/helpers.php | 75 +++++++ tests/Drivers/BrowserDriverTest.php | 61 +++++ tests/Drivers/CookieDriverTest.php | 63 ++++++ tests/Drivers/RequestDriverTest.php | 51 +++++ tests/Drivers/SessionDriverTest.php | 59 +++++ tests/Integration/HelpersTest.php | 179 +++++++++++++++ tests/Integration/IntegrationTest.php | 141 ++++++++++++ tests/LocaleSwitcherTest.php | 157 ------------- 28 files changed, 1033 insertions(+), 413 deletions(-) delete mode 100644 .codeclimate.yml create mode 100644 .gitattributes delete mode 100644 .styleci.yml rename src/{CurrentConfig.php => ConfigManager.php} (61%) create mode 100644 src/Contracts/DriverInterface.php create mode 100644 src/Drivers/BaseDriver.php create mode 100644 src/Drivers/BrowserDriver.php create mode 100644 src/Drivers/CookieDriver.php create mode 100644 src/Drivers/RequestDriver.php create mode 100644 src/Drivers/RouteParameterDriver.php create mode 100644 src/Drivers/SessionDriver.php rename src/Middleware/{SwitchLocale.php => SwitchLocaleMiddleware.php} (68%) create mode 100644 src/helpers.php create mode 100644 tests/Drivers/BrowserDriverTest.php create mode 100644 tests/Drivers/CookieDriverTest.php create mode 100644 tests/Drivers/RequestDriverTest.php create mode 100644 tests/Drivers/SessionDriverTest.php create mode 100644 tests/Integration/HelpersTest.php create mode 100644 tests/Integration/IntegrationTest.php delete mode 100644 tests/LocaleSwitcherTest.php diff --git a/.codeclimate.yml b/.codeclimate.yml deleted file mode 100644 index dec2ef0..0000000 --- a/.codeclimate.yml +++ /dev/null @@ -1,17 +0,0 @@ -engines: - duplication: - enabled: true - config: - languages: - - php - fixme: - enabled: true - phpmd: - enabled: true -ratings: - paths: - - "**.php" -exclude_paths: - - config/* - - tests/* - - vendor/* diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f39cc23 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +* text=auto + +/tests export-ignore +.editorconfig export-ignore +.gitattributes export-ignore +.gitignore export-ignore +.travis.yml export-ignore +phpunit.xml export-ignore diff --git a/.gitignore b/.gitignore index dfd6caa..af1f417 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ +composer.lock /vendor -composer.lock \ No newline at end of file diff --git a/.styleci.yml b/.styleci.yml deleted file mode 100644 index 3db7a37..0000000 --- a/.styleci.yml +++ /dev/null @@ -1,10 +0,0 @@ -preset: laravel - -risky: false - -linting: true - -finder: - exclude: - - "tests" - - "config" diff --git a/.travis.yml b/.travis.yml index 16e328d..aae0043 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,21 +7,39 @@ php: - 7.0 - hhvm -addons: - code_climate: - repo_token: e9d8026b4a1ca0f1c3577c3772bbe9d8d337f0cb2ee50642d2047efa8a148327 +cache: + directories: + - vendor + - $HOME/.composer/cache + +env: + global: + - setup=basic + - COMPOSER_DISCARD_CHANGES=true + - COMPOSER_NO_INTERACTION=1 + - COMPOSER_DISABLE_XDEBUG_WARN=1 + +matrix: + include: + - php: 5.6 + env: setup=lowest + - php: 5.6 + env: setup=stable before_install: - - travis_retry composer self-update + - if [ -n "$GH_TOKEN" ]; then composer config github-oauth.github.com ${GH_TOKEN}; fi; + - composer self-update && composer -V install: - - travis_retry composer install --prefer-source --no-interaction + - if [[ $setup = 'basic' ]]; then travis_retry composer update --prefer-dist; fi + - if [[ $setup = 'stable' ]]; then travis_retry composer update --prefer-dist --prefer-stable; fi + - if [[ $setup = 'lowest' ]]; then travis_retry composer update --prefer-dist --prefer-stable --prefer-lowest; fi script: - - phpunit --coverage-clover build/logs/clover.xml + - vendor/bin/phpunit --coverage-clover build/logs/clover.xml -after_script: - - vendor/bin/test-reporter +after_success: + - travis_retry php vendor/bin/coveralls notifications: email: diff --git a/README.md b/README.md index 00f7332..06061ff 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ [![Latest Version on Packagist][ico-version]][link-packagist] [![Software License][ico-license]](LICENSE.md) [![Build Status][ico-travis]][link-travis] -[![Quality Score][ico-code-quality]][link-code-quality] [![Total Downloads][ico-downloads]][link-downloads] A Simple Laravel middleware to easily load and switch the user's locale. @@ -21,11 +20,18 @@ Then, add this to your Service Providers : Lykegenes\LocaleSwitcher\ServiceProvider::class, ``` -and add this Alias +and add this Alias [Optional] ``` php 'LocaleSwitcher' => Lykegenes\LocaleSwitcher\Facades\LocaleSwitcher::class, ``` +Finally, you will need to register this Middleware either in your `Kernel.php` or directly in your routes. +This way, you can control which routes will have automatic locale detection (Web pages) or not (your API). +Make sure this middleware is placed **after** any Session or Cookie related middlewares from the framework. +``` php +\Lykegenes\LocaleSwitcher\Middleware\SwitchLocaleMiddleware::class, +``` + Optionally, you can publish and edit the configuration file : ``` bash php artisan vendor:publish --provider="Lykegenes\LocaleSwitcher\ServiceProvider" --tag=config @@ -43,22 +49,21 @@ http://my-app.com/some/sub/route?locale=es&otherParam=value ``` This will store the locale in the user's session, and set it as the current locale everytime the user requests a page. -You can build the routes like so : +You can build the routes with the included helpers. The URLs will be generated using the current locale. ```php -$url = action('SomeController@someFunction', ['locale' => 'en']); -$url = route('someNamedRoute', ['locale' => 'en']); -$url = url('/some/url', ['locale' => 'en']); +$url = action_localized('SomeController@someFunction', ['your' => 'parameters']); +$url = route_localized('someNamedRoute', ['your' => 'parameters']); ``` -You can easily generate a dropdown using the [laravelcollective/html](https://github.com/LaravelCollective/html) package : +To build a URL to the same page, but with a different locale, use the `switch_locale()` helper. ```php -HTML::ul(LocaleSwitcher::getEnabledLocales()); +$url = switch_locale('fr'); // URL of the French version of the current page. ``` -## Testing -``` bash -composer test +You can easily generate a dropdown using the [laravelcollective/html](https://github.com/LaravelCollective/html) package : +```php +HTML::ul(LocaleSwitcher::getEnabledLocales()); ``` ## Credits @@ -73,12 +78,10 @@ The MIT License (MIT). Please see [License File](LICENSE.md) for more informatio [ico-version]: https://img.shields.io/packagist/v/lykegenes/laravel-locale-switcher.svg?style=flat-square [ico-license]: https://img.shields.io/packagist/l/lykegenes/laravel-locale-switcher.svg?style=flat-square [ico-travis]: https://img.shields.io/travis/Lykegenes/laravel-locale-switcher/master.svg?style=flat-square -[ico-code-quality]: https://img.shields.io/scrutinizer/g/lykegenes/laravel-locale-switcher.svg?style=flat-square [ico-downloads]: https://img.shields.io/packagist/dt/lykegenes/laravel-locale-switcher.svg?style=flat-square [link-packagist]: https://packagist.org/packages/lykegenes/laravel-locale-switcher [link-travis]: https://travis-ci.org/Lykegenes/laravel-locale-switcher -[link-code-quality]: https://scrutinizer-ci.com/g/lykegenes/laravel-locale-switcher [link-downloads]: https://packagist.org/packages/lykegenes/laravel-locale-switcher [link-author]: https://github.com/lykegenes [link-contributors]: ../../contributors diff --git a/composer.json b/composer.json index 1da378d..f8297ad 100644 --- a/composer.json +++ b/composer.json @@ -22,12 +22,15 @@ "require-dev": { "phpunit/phpunit": "~5.1", "mockery/mockery": "0.9.*", - "codeclimate/php-test-reporter": "^0.2.0" + "satooshi/php-coveralls": "~1.0", + "orchestra/testbench": "~3.1", + "illuminate/foundation": "^5.1.3" }, "autoload": { "psr-4": { "Lykegenes\\LocaleSwitcher\\": "src/" - } + }, + "files": ["src/helpers.php"] }, "scripts": { "test": "phpunit" diff --git a/config/config.php b/config/config.php index 3402151..b93b078 100644 --- a/config/config.php +++ b/config/config.php @@ -1,7 +1,7 @@ true, - /** + /* *-------------------------------------------------------------------------- - * LocaleSwitcher URL parameter key prefix + * LocaleSwitcher Source Drivers *-------------------------------------------------------------------------- * - * Sometimes you want to set the URL parameter key to be used by LocaleSwitcher - * to use to detect locale switching requests. - * - * By default, it is "locale", so the URL will be : - * http://my-app.com/some/page/?locale=en + * These are the drivers that will be used to determine the current Locale. + * + * The Drivers will be used in this order, and if no locale is found, + * the Application default locale will be used. + * + */ + 'source_drivers' => [ + Lykegenes\LocaleSwitcher\Drivers\RouteParameterDriver::class, // Laravel Route parameter + // Lykegenes\LocaleSwitcher\Drivers\RequestDriver::class, // URL query string + // Lykegenes\LocaleSwitcher\Drivers\CookieDriver::class, // Cookie + // Lykegenes\LocaleSwitcher\Drivers\SessionDriver::class, // Laravel Session + // Lykegenes\LocaleSwitcher\Drivers\BrowserDriver::class, // Browser Accept-Language header + ], + + /* + *-------------------------------------------------------------------------- + * LocaleSwitcher Store Driver + *-------------------------------------------------------------------------- + * + * This is the driver that will be used to store the locale for future requests. + * It is set to null by default in order to detect the locale on every request. + * Only one can be used! + * + * The included drivers are : + * Lykegenes\LocaleSwitcher\Drivers\SessionDriver::class, + * Lykegenes\LocaleSwitcher\Drivers\CookieDriver::class, + * + */ + 'store_driver' => null, + + /* + *-------------------------------------------------------------------------- + * LocaleSwitcher Driver Key + *-------------------------------------------------------------------------- + * + * This key will be used by all the included drivers to detect and store + * the locale across requests. * */ - 'URL_param_key' => 'locale', + 'default_key' => 'locale', - /** + /* *-------------------------------------------------------------------------- * Enabled Locales *-------------------------------------------------------------------------- @@ -39,4 +71,4 @@ 'fr' => 'Français', ], -); +]; diff --git a/phpunit.xml b/phpunit.xml index 9f816d8..74df34c 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -15,6 +15,11 @@ ./tests/ + + + ./tests/Integration + + ./src/ diff --git a/src/CurrentConfig.php b/src/ConfigManager.php similarity index 61% rename from src/CurrentConfig.php rename to src/ConfigManager.php index e31459f..da81f13 100644 --- a/src/CurrentConfig.php +++ b/src/ConfigManager.php @@ -2,7 +2,7 @@ namespace Lykegenes\LocaleSwitcher; -class CurrentConfig +class ConfigManager { /** * Check if this package is enabled or not. @@ -14,37 +14,39 @@ public static function isPackageEnabled() return config('locale-switcher.enabled'); } - /** - * Get the Http Get parameter used to switch locales. - * - * @return string - */ - public static function getUrlParamKey() + public static function getSourceDrivers() { - return config('locale-switcher.URL_param_key'); + return config('locale-switcher.source_drivers', []); + } + + public static function getStoreDriver() + { + return config('locale-switcher.store_driver'); + } + + public static function getDefaultKey() + { + return config('locale-switcher.default_key'); } /** * Determine if this locale is enabled or not. * Depends on the current settings used. * - * @param string $locale The locale shorthand to verify + * @param string $locale The locale shorthand to verify * * @return bool */ public static function isEnabledLocale($locale) { - if (config('locale-switcher.enabled_locales') !== null) { - return true; - } - - return array_key_exists($locale, config('locale-switcher.enabled_locales')); + return config('locale-switcher.enabled_locales') !== null + && array_key_exists($locale, config('locale-switcher.enabled_locales')); } /** * Get the localized name of the specified locale. * - * @param string $locale The locale shorthand to verify + * @param string $locale The locale shorthand to verify * * @return string */ diff --git a/src/Contracts/DriverInterface.php b/src/Contracts/DriverInterface.php new file mode 100644 index 0000000..9ebe058 --- /dev/null +++ b/src/Contracts/DriverInterface.php @@ -0,0 +1,12 @@ +request = $request; + $this->configManager = $configManager; + } + + public function has($key = 'Accept-Language') + { + return null !== $this->request->header($key, null); + } + + public function get($key = 'Accept-Language', $default = null) + { + $header = $this->request->header($key, $default); + + $locales = $this->parseAcceptLanguage($header); + + return $this->selectPreferredLocale($locales); + } + + protected function parseAcceptLanguage($acceptLanguage) + { + $locales = []; + $languages = explode(',', $acceptLanguage); + + foreach ($languages as $item) { + $split = explode(';', $item); + $locales[] = [ + 'locale' => $split[0], + 'q' => (array_key_exists(1, $split) && substr($split[1], 0, 2) === 'q=') ? (float) substr($split[1], 2) : 1.0, + ]; + } + + // Sort in place by desc priority ("q") + usort($locales, function ($a, $b) { + return ($a['q'] > $b['q']) ? -1 : 1; + }); + + return $locales; + } + + protected function selectPreferredLocale($locales) + { + foreach ($locales as $locale) { + if ($this->configManager->isEnabledLocale($locale['locale'])) { + return $locale['locale']; + } + } + } +} diff --git a/src/Drivers/CookieDriver.php b/src/Drivers/CookieDriver.php new file mode 100644 index 0000000..fb85835 --- /dev/null +++ b/src/Drivers/CookieDriver.php @@ -0,0 +1,33 @@ +request = $request; + $this->cookieJar = $cookieJar; + } + + public function has($key) + { + return $this->request->hasCookie($key); + } + + public function get($key, $default = null) + { + return $this->request->cookie($key, $default); + } + + public function store($key, $value) + { + return $this->cookieJar->queue(cookie($key, $value, 45000)); + } +} diff --git a/src/Drivers/RequestDriver.php b/src/Drivers/RequestDriver.php new file mode 100644 index 0000000..1d97a07 --- /dev/null +++ b/src/Drivers/RequestDriver.php @@ -0,0 +1,25 @@ +request = $request; + } + + public function has($key) + { + return $this->request->has($key); + } + + public function get($key, $default = null) + { + return $this->request->input($key, $default); + } +} diff --git a/src/Drivers/RouteParameterDriver.php b/src/Drivers/RouteParameterDriver.php new file mode 100644 index 0000000..9eb4f71 --- /dev/null +++ b/src/Drivers/RouteParameterDriver.php @@ -0,0 +1,28 @@ +currentroute = $request->route(); + } + + public function has($key) + { + return $this->currentroute->hasParameter($key); + } + + public function get($key, $default = null) + { + return $this->currentroute->getParameter($key, $default); + } +} diff --git a/src/Drivers/SessionDriver.php b/src/Drivers/SessionDriver.php new file mode 100644 index 0000000..2376a82 --- /dev/null +++ b/src/Drivers/SessionDriver.php @@ -0,0 +1,30 @@ +session = $session; + } + + public function has($key) + { + return $this->session->has($key); + } + + public function get($key, $default = null) + { + return $this->session->get($key, $default); + } + + public function store($key, $value) + { + return $this->session->put($key, $value); + } +} diff --git a/src/LocaleSwitcher.php b/src/LocaleSwitcher.php index 6c432a5..26414a4 100644 --- a/src/LocaleSwitcher.php +++ b/src/LocaleSwitcher.php @@ -2,201 +2,95 @@ namespace Lykegenes\LocaleSwitcher; -use Illuminate\Http\Request; -use Illuminate\Support\Facades\App; -use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Illuminate\Foundation\Application; +use Lykegenes\LocaleSwitcher\Drivers\BaseDriver; class LocaleSwitcher { + protected $app; /** * The current LocaleSwitcher config. * - * @var \Lykegenes\LocaleSwitcher\CurrentConfig + * @var \Lykegenes\LocaleSwitcher\ConfigManager */ - protected $currentConfig; + protected $config; /** - * The session used by the guard. + * The detected locale. * - * @var \Symfony\Component\HttpFoundation\Session\SessionInterface + * @var string|null */ - protected $session; - - /** - * The request instance. - * - * @var \Symfony\Component\HttpFoundation\Request - */ - protected $request; - - /** - * The request instance. - * - * @var \Symfony\Component\HttpFoundation\Request - */ - protected $localeWasSwitched = false; - - /** - * The name of the "created at" column. - * - * @var string - */ - const SESSION_KEY = 'locale'; - - /** - * The name of the "created at" column. - * - * @var string - */ - const REQUEST_KEY = 'locale'; - - /** - * The name of the "created at" column. - * - * @var string - */ - const COOKIE_KEY = 'locale'; + protected $locale = null; /** * Create a new locale switcher. - * - * @param \Symfony\Component\HttpFoundation\Session\SessionInterface $session - * @param \Symfony\Component\HttpFoundation\Request $request - * @return void - */ - public function __construct(SessionInterface $session, Request $request = null, CurrentConfig $currentConfig = null) - { - $this->session = $session; - $this->request = $request; - $this->currentConfig = $currentConfig; - } - - /** - * Get an array of ll the enabled locales. - * - * @return array - */ - public function getEnabledLocales() - { - return $this->currentConfig->getEnabledLocales(); - } - - /** - * Determine if the current user is authenticated. - * - * @return bool - */ - public function sessionHasLocale() - { - return $this->session->has(static::SESSION_KEY); - } - - /** - * Determine if the current user is authenticated. - * - * @return bool - */ - public function requestHasLocale() - { - return $this->request->has(static::REQUEST_KEY); - } - - /** - * Determine if the current user is authenticated. - * - * @return bool */ - public function cookieHasLocale() + public function __construct(Application $app, ConfigManager $config) { - return $this->request->hasCookie(static::COOKIE_KEY); + $this->app = $app; + $this->config = $config; } /** - * Determine if the current user is authenticated. + * Attempt to detected the new locale from the enabled drivers. * - * @return bool + * @return string|null The locale that should now be used. */ - public function setSessionLocale($locale) + public function detectLocale() { - return $this->session->put(static::SESSION_KEY, $locale); - } - - /** - * Determine if the current user is authenticated. - * - * @return bool - */ - public function getLocaleFromSession($default = null) - { - return $this->session->get(static::SESSION_KEY, $default); + $sourceDrivers = $this->config->getSourceDrivers(); + $key = $this->config->getDefaultKey(); + + if ( ! empty($sourceDrivers) && is_string($key)) { + foreach ($sourceDrivers as $driver) { + $driver = class_exists($driver) ? $this->app->make($driver) : null; + + if ($driver instanceof BaseDriver && $driver->has($key)) { + $newLocale = $driver->get($key); + if ($this->config->isEnabledLocale($newLocale)) { + $this->locale = $newLocale; + + return $newLocale; + } + } + } + } } - /** - * Determine if the current user is authenticated. - * - * @return bool - */ - public function getLocaleFromRequest($default = null) + public function storeLocale() { - return $this->request->input(static::REQUEST_KEY, $default); - } + $storeDriver = $this->config->getStoreDriver(); + $key = $this->config->getDefaultKey(); - /** - * Determine if the current user is authenticated. - * - * @return bool - */ - public function getLocaleFromCookie($default = null) - { - return $this->request->cookie(static::COOKIE_KEY, $default); - } + if ($this->locale !== null && ! empty($storeDriver)) { + $storeDriver = $this->app->make($storeDriver); - /** - * Determine if the current user is authenticated. - * - * @return bool - */ - public function localeWasSwitched() - { - return $this->localeWasSwitched; - } + if ($storeDriver instanceof BaseDriver) { + $storeDriver->store($key, $this->locale); - /** - * Switch locale in the current user's session. - * - * @param string $default The default locale to use - * @return string|null The locale that should now be used - */ - public function switchLocale($default = null) - { - // returns the first non-null value - $locale = $this->getLocaleFromRequest() - ?: $this->getLocaleFromCookie() - ?: $default; - - if ($locale !== null && $this->currentConfig->isEnabledLocale($locale)) { - $this->setSessionLocale($locale); - $this->localeWasSwitched = true; + return $this->locale; + } } - - return $locale; } /** - * Attempt to authenticate using HTTP Basic Auth. + * Attempt to detect and switch the current locale. * - * @param string $field - * @return \Symfony\Component\HttpFoundation\Response|null + * @return string|null The detected locale or null */ public function setAppLocale() { - $this->switchLocale(); + // Detect the current locale from enabled drivers + $this->detectLocale(); - if ($this->sessionHasLocale()) { - $locale = $this->session->get(static::SESSION_KEY); - App::setLocale($locale); + // Store the current locale for future requests + $this->storeLocale(); - return $locale; + // Set the locale for this current request + if ($this->locale !== null) { + $this->app->setLocale($this->locale); } + + return $this->locale; } }; diff --git a/src/Middleware/SwitchLocale.php b/src/Middleware/SwitchLocaleMiddleware.php similarity index 68% rename from src/Middleware/SwitchLocale.php rename to src/Middleware/SwitchLocaleMiddleware.php index 35263fa..ebe9d06 100644 --- a/src/Middleware/SwitchLocale.php +++ b/src/Middleware/SwitchLocaleMiddleware.php @@ -5,7 +5,7 @@ use Closure; use Lykegenes\LocaleSwitcher\LocaleSwitcher; -class SwitchLocale +class SwitchLocaleMiddleware { /** * The locale switcher instance. @@ -17,8 +17,7 @@ class SwitchLocale /** * Create a new middleware instance. * - * @param \Lykegenes\LaravelLocaleSwitcher\LocaleSwitcher $localeSwitcher - * @return void + * @param \Lykegenes\LaravelLocaleSwitcher\LocaleSwitcher $localeSwitcher */ public function __construct(LocaleSwitcher $localeSwitcher) { @@ -28,14 +27,14 @@ public function __construct(LocaleSwitcher $localeSwitcher) /** * Handle an incoming request. * - * @param \Illuminate\Http\Request $request\ - * @param \Closure $next + * @param \Illuminate\Http\Request $request\ + * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { $this->localeSwitcher->setAppLocale(); - return $this->localeSwitcher->localeWasSwitched() ? back()->withInput() : $next($request); + return $next($request); } } diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 5dabe05..1dd08ed 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -2,6 +2,9 @@ namespace Lykegenes\LocaleSwitcher; +/** + * @codeCoverageIgnore + */ class ServiceProvider extends \Illuminate\Support\ServiceProvider { /** @@ -13,8 +16,6 @@ class ServiceProvider extends \Illuminate\Support\ServiceProvider /** * Register the service provider. - * - * @return void */ public function register() { @@ -31,8 +32,6 @@ public function boot() if (! $enabled) { return; } - - $this->registerMiddleware('Lykegenes\LocaleSwitcher\Middleware\SwitchLocale'); } /** @@ -62,15 +61,4 @@ protected function publishConfig($configPath) { $this->publishes([$configPath => config_path('locale-switcher.php')], 'config'); } - - /** - * Register the Debugbar Middleware. - * - * @param string $middleware - */ - protected function registerMiddleware($middleware) - { - $kernel = $this->app['Illuminate\Contracts\Http\Kernel']; - $kernel->pushMiddleware($middleware); - } } diff --git a/src/helpers.php b/src/helpers.php new file mode 100644 index 0000000..91542fa --- /dev/null +++ b/src/helpers.php @@ -0,0 +1,75 @@ + app()->getLocale()]; + $parameters = array_merge($locale, $parameters); + + return route($name, $parameters, $absolute, $route); + } +} + +if ( ! function_exists('action_localized')) { + /** + * Generate a URL to a controller action. + * + * @param string $name + * @param array $parameters + * @param bool $absolute + * @return string + */ + function action_localized($name, $parameters = [], $absolute = true) + { + $locale = [ConfigManager::getDefaultKey() => app()->getLocale()]; + $parameters = array_merge($locale, $parameters); + + return action($name, $parameters, $absolute); + } +} + +if ( ! function_exists('switch_locale')) { + /** + * Generate a URL to a controller action. + * + * @param string $locale + * @return string + */ + function switch_locale($newLocale) + { + $route = app('request')->route(); + + $locale = [ConfigManager::getDefaultKey() => $newLocale]; + + // The new locale overwrites the current parameters + $parameters = array_merge($route->parameters(), $locale); + + if (null !== $route->getName()) { + // Use the same named route + return route($route->getName(), $parameters); + } elseif (null !== $route->getActionName()) { + // Closures are matched like Actions + if ($route->getActionName() !== 'Closure') { + // Use the same action + return action($route->getActionName(), $parameters); + } else { + // This is a Closure + // TODO : Can we do anything? + } + } + + // return the current URL + return app('request')->url(); + } +} diff --git a/tests/Drivers/BrowserDriverTest.php b/tests/Drivers/BrowserDriverTest.php new file mode 100644 index 0000000..c96cfe8 --- /dev/null +++ b/tests/Drivers/BrowserDriverTest.php @@ -0,0 +1,61 @@ +request = Mockery::mock('Illuminate\Http\Request'); + $this->configManager = Mockery::mock('Lykegenes\LocaleSwitcher\ConfigManager'); + + $this->browserDriver = new BrowserDriver($this->request, $this->configManager); + } + + public function tearDown() + { + parent::tearDown(); + + Mockery::close(); + $this->request = null; + } + + /** @test */ + public function it_detects_if_browser_sent_accept_language_header() + { + $this->request->shouldReceive('header')->atLeast()->once()->andReturn('en-US,en;q=0.8,fr-CA;q=0.5,fr;q=0.3'); + + $this->assertTrue($this->browserDriver->has('key')); + } + + /** @test */ + public function it_gets_locale_from_request() + { + $this->request->shouldReceive('header')->atLeast()->once()->andReturn('en-US,en;q=0.8,fr-CA;q=0.5,fr;q=0.3'); + $this->configManager->shouldReceive('isEnabledLocale')->atLeast()->once() + ->andReturnUsing(function ($locale) { + return $locale === 'fr'; + }); + + $locale = $this->browserDriver->get('Accept-Language'); + + $this->assertEquals('fr', $locale); + } +} diff --git a/tests/Drivers/CookieDriverTest.php b/tests/Drivers/CookieDriverTest.php new file mode 100644 index 0000000..227e3c7 --- /dev/null +++ b/tests/Drivers/CookieDriverTest.php @@ -0,0 +1,63 @@ +request = Mockery::mock('Illuminate\Http\Request'); + $this->cookieJar = Mockery::mock('Illuminate\Cookie\CookieJar'); + + $this->cookieDriver = new CookieDriver($this->request, $this->cookieJar); + } + + public function tearDown() + { + parent::tearDown(); + + Mockery::close(); + $this->request = null; + $this->cookieJar = null; + } + + /** @test */ + public function it_detects_if_cookie_has_locale() + { + $this->request->shouldReceive('hasCookie')->atLeast()->once()->andReturn(true); + + $this->assertTrue($this->cookieDriver->has('key')); + } + + /** @test */ + public function it_gets_locale_from_cookie() + { + $this->request->shouldReceive('cookie')->atLeast()->once()->andReturn('en'); + + $locale = $this->cookieDriver->get('key'); + + $this->assertEquals('en', $locale); + } + + /** @test */ + public function it_stores_locale_in_session() + { + $this->cookieJar->shouldReceive('queue')->atLeast()->once()->andReturn(true); + + $this->assertTrue($this->cookieDriver->store('key', 'en')); + } +} diff --git a/tests/Drivers/RequestDriverTest.php b/tests/Drivers/RequestDriverTest.php new file mode 100644 index 0000000..be1d7ba --- /dev/null +++ b/tests/Drivers/RequestDriverTest.php @@ -0,0 +1,51 @@ +request = Mockery::mock('Illuminate\Http\Request'); + + $this->requestDriver = new RequestDriver($this->request); + } + + public function tearDown() + { + parent::tearDown(); + + Mockery::close(); + $this->request = null; + } + + /** @test */ + public function it_detects_if_request_has_locale() + { + $this->request->shouldReceive('has')->atLeast()->once()->andReturn(true); + + $this->assertTrue($this->requestDriver->has('key')); + } + + /** @test */ + public function it_gets_locale_from_request() + { + $this->request->shouldReceive('input')->atLeast()->once()->andReturn('en'); + + $locale = $this->requestDriver->get('key'); + + $this->assertEquals('en', $locale); + } +} diff --git a/tests/Drivers/SessionDriverTest.php b/tests/Drivers/SessionDriverTest.php new file mode 100644 index 0000000..e01134f --- /dev/null +++ b/tests/Drivers/SessionDriverTest.php @@ -0,0 +1,59 @@ +session = Mockery::mock('Symfony\Component\HttpFoundation\Session\SessionInterface'); + + $this->sessionDriver = new SessionDriver($this->session); + } + + public function tearDown() + { + parent::tearDown(); + + Mockery::close(); + $this->session = null; + } + + /** @test */ + public function it_detects_if_session_has_locale() + { + $this->session->shouldReceive('has')->atLeast()->once()->andReturn(true); + + $this->assertTrue($this->sessionDriver->has('key')); + } + + /** @test */ + public function it_gets_locale_from_session() + { + $this->session->shouldReceive('get')->atLeast()->once()->andReturn('en'); + + $locale = $this->sessionDriver->get('key'); + + $this->assertEquals('en', $locale); + } + + /** @test */ + public function it_stores_locale_in_session() + { + $this->session->shouldReceive('put')->atLeast()->once()->andReturn(true); + + $this->assertTrue($this->sessionDriver->store('key', 'en')); + } +} diff --git a/tests/Integration/HelpersTest.php b/tests/Integration/HelpersTest.php new file mode 100644 index 0000000..d03b4f2 --- /dev/null +++ b/tests/Integration/HelpersTest.php @@ -0,0 +1,179 @@ +set('app.locale', 'en'); + + // Detect Locale from route parameter + $app['router']->get('{locale}/something', [ + 'as' => 'route-parameter', + 'middleware' => [ + \Lykegenes\LocaleSwitcher\Middleware\SwitchLocaleMiddleware::class, + \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, + ], + 'uses' => TestHelperController::$route_parameter, + ]); + + // Detect Locale using Query String + $app['router']->get('something', [ + 'as' => 'route-querystring', + 'middleware' => [ + \Lykegenes\LocaleSwitcher\Middleware\SwitchLocaleMiddleware::class, + \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, + ], + 'uses' => TestHelperController::$route_querystring, + ]); + + // Named switch locale route + $app['router']->get('named/{newLocale}', [ + 'as' => 'named-switch-locale', + 'middleware' => [ + \Lykegenes\LocaleSwitcher\Middleware\SwitchLocaleMiddleware::class, + \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, + ], + 'uses' => TestHelperController::$named_switch_locale, + ]); + + $app['router']->get('action/{newLocale}', [ + 'middleware' => [ + \Lykegenes\LocaleSwitcher\Middleware\SwitchLocaleMiddleware::class, + \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, + ], + 'uses' => TestHelperController::$action_switch_locale, + ]); + + $app['router']->get('closure/{newLocale}', [ + 'middleware' => [ + \Lykegenes\LocaleSwitcher\Middleware\SwitchLocaleMiddleware::class, + \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, + ], + function ($newLocale) { + return 'hello world, url is : '.switch_locale($newLocale); + }, + ]); + } + + /** + * @test + */ + public function testRouteLocalizedHelperWithRouteParams() + { + $this->assertEquals('http://localhost/en/something', route_localized('route-parameter')); + + $this->app['config']->set('app.locale', 'fr'); + $this->assertEquals('http://localhost/fr/something', route_localized('route-parameter')); + + $this->makeRequest('GET', route_localized('route-parameter')) + ->see('hello world, locale is : fr'); + } + + /** + * @test + */ + public function testRouteLocalizedHelperWithQueryString() + { + $this->assertEquals('http://localhost/something?locale=en', route_localized('route-querystring')); + + $this->app['config']->set('app.locale', 'fr'); + $this->assertEquals('http://localhost/something?locale=fr', route_localized('route-querystring')); + + $this->makeRequest('GET', route_localized('route-querystring')) + ->see('hello world, locale is : fr'); + } + + /** + * @test + */ + public function testActionLocalizedHelperWithRouteParams() + { + $this->assertEquals('http://localhost/en/something', action_localized(TestHelperController::$route_parameter)); + + $this->app['config']->set('app.locale', 'fr'); + $this->assertEquals('http://localhost/fr/something', action_localized(TestHelperController::$route_parameter)); + + $this->makeRequest('GET', action_localized(TestHelperController::$route_parameter)) + ->see('hello world, locale is : fr'); + } + + /** + * @test + */ + public function testActionLocalizedHelperWithQueryString() + { + $this->assertEquals('http://localhost/something?locale=en', action_localized(TestHelperController::$route_querystring)); + + $this->app['config']->set('app.locale', 'fr'); + $this->assertEquals('http://localhost/something?locale=fr', action_localized(TestHelperController::$route_querystring)); + + $this->makeRequest('GET', action_localized(TestHelperController::$route_querystring)) + ->see('hello world, locale is : fr'); + } + + /** + * @test + */ + public function testSwitchLocaleHelperWithQueryString() + { + // Query string has to be appended + $this->makeRequest('GET', route_localized('named-switch-locale', ['newLocale' => 'fr'])) + ->see('http://localhost/named/fr?locale=fr'); + + // Query String has to be appended + $this->makeRequest('GET', action_localized(TestHelperController::$action_switch_locale, ['newLocale' => 'fr'])) + ->see('http://localhost/action/fr?locale=fr'); + + // We can't match a Closure. Return the exact same urkl, without the query string + $this->makeRequest('GET', '/closure/fr') + ->see('http://localhost/closure/fr'); + } +} + +class TestHelperController extends \Illuminate\Routing\Controller +{ + public static $route_parameter = 'Lykegenes\LocaleSwitcher\TestCase\TestHelperController@route_parameter'; + public static $route_querystring = 'Lykegenes\LocaleSwitcher\TestCase\TestHelperController@route_querystring'; + public static $named_switch_locale = 'Lykegenes\LocaleSwitcher\TestCase\TestHelperController@named_switch_locale'; + public static $action_switch_locale = 'Lykegenes\LocaleSwitcher\TestCase\TestHelperController@action_switch_locale'; + + public function route_parameter() + { + return 'hello world, locale is : '.\App::getLocale(); + } + + public function route_querystring() + { + return 'hello world, locale is : '.\App::getLocale(); + } + + public function named_switch_locale($newLocale) + { + return 'hello world, url is : '.switch_locale($newLocale); + } + + public function action_switch_locale($newLocale) + { + return 'hello world, url is : '.switch_locale($newLocale); + } +} diff --git a/tests/Integration/IntegrationTest.php b/tests/Integration/IntegrationTest.php new file mode 100644 index 0000000..3f95cbf --- /dev/null +++ b/tests/Integration/IntegrationTest.php @@ -0,0 +1,141 @@ +set('app.locale', 'en'); + + $app['router']->get('locale', [ + 'middleware' => [ + \Lykegenes\LocaleSwitcher\Middleware\SwitchLocaleMiddleware::class, + \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, + ], + function () { + return 'hello world, locale is : '.\App::getLocale(); + }, + ]); + } + + /** + * @test + */ + public function testGetLocaleRoute() + { + $this->visit('locale') + ->see('hello world, locale is : en'); + } + + /** + * @test + */ + public function testDetectLocaleFromRequestUrl() + { + $this->app['config']->set('locale-switcher.source_drivers', [\Lykegenes\LocaleSwitcher\Drivers\RequestDriver::class]); + + $this->makeRequest('GET', 'locale', ['locale' => 'fr']) + ->see('hello world, locale is : fr'); + + // switch to new locale on subsequent request + $this->makeRequest('GET', 'locale', ['locale' => 'en']) + ->see('hello world, locale is : en'); + } + + /** + * @test + */ + public function testDetectLocaleFromSession() + { + $this->app['config']->set('locale-switcher.source_drivers', [\Lykegenes\LocaleSwitcher\Drivers\SessionDriver::class]); + + $this->withSession(['locale' => 'fr']) + ->visit('locale') + ->see('hello world, locale is : fr'); + } + + /** + * @test + */ + public function testDetectLocaleFromCookie() + { + $this->app['config']->set('locale-switcher.source_drivers', [\Lykegenes\LocaleSwitcher\Drivers\CookieDriver::class]); + + $this->makeRequest('GET', 'locale', [], ['locale' => 'fr']) + ->see('hello world, locale is : fr'); + } + + /** + * @test + */ + public function testDetectLocaleFromRouteParameter() + { + $this->app['config']->set('locale-switcher.source_drivers', [\Lykegenes\LocaleSwitcher\Drivers\RouteParameterDriver::class]); + + $this->app['router']->get('{locale}/parametertest', ['middleware' => \Lykegenes\LocaleSwitcher\Middleware\SwitchLocaleMiddleware::class, function () { + return 'hello world, locale is : '.\App::getLocale(); + }]); + + $this->visit('fr/parametertest') + ->see('hello world, locale is : fr'); + + // switch to new locale on subsequent request + $this->visit('en/parametertest') + ->see('hello world, locale is : en'); + } + + /** + * @test + */ + public function testStoreLocaleInSession() + { + $this->app['config']->set('locale-switcher.source_drivers', [\Lykegenes\LocaleSwitcher\Drivers\RequestDriver::class]); + $this->app['config']->set('locale-switcher.store_driver', \Lykegenes\LocaleSwitcher\Drivers\SessionDriver::class); + + $this->makeRequest('GET', 'locale', ['locale' => 'fr']) + ->see('hello world, locale is : fr') + ->assertSessionHas('locale', 'fr'); + + // switch to new locale on subsequent request + $this->makeRequest('GET', 'locale', ['locale' => 'en']) + ->see('hello world, locale is : en') + ->assertSessionHas('locale', 'en'); + } + + /** + * @test + */ + public function testStoreLocaleInCookie() + { + $this->app['config']->set('locale-switcher.source_drivers', [\Lykegenes\LocaleSwitcher\Drivers\RequestDriver::class]); + $this->app['config']->set('locale-switcher.store_driver', \Lykegenes\LocaleSwitcher\Drivers\CookieDriver::class); + + $this->makeRequest('GET', 'locale', ['locale' => 'fr']) + ->see('hello world, locale is : fr') + ->seeCookie('locale', 'fr'); + + // switch to new locale on subsequent request + $this->makeRequest('GET', 'locale', ['locale' => 'en']) + ->see('hello world, locale is : en') + ->seeCookie('locale', 'en'); + } +} diff --git a/tests/LocaleSwitcherTest.php b/tests/LocaleSwitcherTest.php deleted file mode 100644 index ea984b0..0000000 --- a/tests/LocaleSwitcherTest.php +++ /dev/null @@ -1,157 +0,0 @@ -request = Mockery::mock('Illuminate\Http\Request'); - $this->container = Mockery::mock('Illuminate\Contracts\Container\Container'); - $this->session = Mockery::mock('Symfony\Component\HttpFoundation\Session\SessionInterface'); - $this->currentConfig = Mockery::mock('Lykegenes\LocaleSwitcher\CurrentConfig'); - - $this->session->shouldReceive('put')->zeroOrMoreTimes(); - $this->request->shouldReceive('getSession')->zeroOrMoreTimes()->andReturn($this->session); - $this->currentConfig->shouldReceive('isEnabledLocale')->zeroOrMoreTimes()->andReturn(true); - - $this->localeSwitcher = new LocaleSwitcher($this->session, $this->request, $this->currentConfig); - } - - public function tearDown() - { - Mockery::close(); - $this->request = null; - $this->container = null; - $this->session = null; - } - - /** @test */ - public function it_uses_application_default_by_default() - { - $this->request->shouldReceive('input')->zeroOrMoreTimes()->andReturn(null); - $this->request->shouldReceive('cookie')->zeroOrMoreTimes()->andReturn(null); - - $newLocale = $this->localeSwitcher->switchLocale(); - - $this->assertFalse($this->localeSwitcher->localeWasSwitched()); - $this->assertNull($newLocale); - } - - /** @test */ - public function it_stores_locale_in_session() - { - $this->request->shouldReceive('input')->zeroOrMoreTimes()->andReturn(null); - $this->request->shouldReceive('cookie')->zeroOrMoreTimes()->andReturn(null); - $this->session->shouldReceive('has')->zeroOrMoreTimes()->andReturn(true); - $this->session->shouldReceive('get')->zeroOrMoreTimes()->andReturn('fr'); - - $newLocale = $this->localeSwitcher->switchLocale('fr'); - - $this->assertTrue($this->localeSwitcher->localeWasSwitched()); - $this->assertTrue($this->localeSwitcher->sessionHasLocale()); - $this->assertEquals('fr', $this->localeSwitcher->getLocaleFromSession()); - $this->assertNotEquals('', $newLocale); - $this->assertEquals('fr', $newLocale); - } - - /** @test */ - public function it_switches_locale_from_request() - { - $this->request->shouldReceive('input')->zeroOrMoreTimes()->andReturn('fr'); - $this->request->shouldReceive('cookie')->zeroOrMoreTimes()->andReturn(null); - $this->request->shouldReceive('has')->zeroOrMoreTimes()->andReturn(true); - - $newLocale = $this->localeSwitcher->switchLocale(); - - $this->assertTrue($this->localeSwitcher->localeWasSwitched()); - $this->assertTrue($this->localeSwitcher->requestHasLocale()); - $this->assertNotEquals('', $newLocale); - $this->assertEquals('fr', $newLocale); - } - - /** @test */ - public function it_switches_locale_from_cookie() - { - $this->request->shouldReceive('input')->zeroOrMoreTimes()->andReturn(null); - $this->request->shouldReceive('cookie')->zeroOrMoreTimes()->andReturn('fr'); - $this->request->shouldReceive('hasCookie')->zeroOrMoreTimes()->andReturn(true); - - $newLocale = $this->localeSwitcher->switchLocale(); - - $this->assertTrue($this->localeSwitcher->localeWasSwitched()); - $this->assertTrue($this->localeSwitcher->cookieHasLocale()); - $this->assertNotEquals('', $newLocale); - $this->assertEquals('fr', $newLocale); - } - - /** @test */ - public function it_uses_request_over_cookie() - { - $this->request->shouldReceive('input')->zeroOrMoreTimes()->andReturn('fr'); - $this->request->shouldReceive('cookie')->zeroOrMoreTimes()->andReturn('en'); - - $newLocale = $this->localeSwitcher->switchLocale(); - - $this->assertTrue($this->localeSwitcher->localeWasSwitched()); - $this->assertNotEquals('', $newLocale); - $this->assertNotEquals('en', $newLocale); - $this->assertEquals('fr', $newLocale); - } - - /** @test */ - public function it_sets_app_locale() - { - $this->request->shouldReceive('input')->zeroOrMoreTimes()->andReturn('fr'); - $this->session->shouldReceive('has')->zeroOrMoreTimes()->andReturn(true); - $this->session->shouldReceive('get')->zeroOrMoreTimes()->andReturn('fr'); - Illuminate\Support\Facades\App::shouldReceive('setLocale')->once(); - - $newLocale = $this->localeSwitcher->setAppLocale(); - - $this->assertTrue($this->localeSwitcher->localeWasSwitched()); - $this->assertNotEquals('', $newLocale); - $this->assertNotEquals('en', $newLocale); - $this->assertEquals('fr', $newLocale); - } - - /** @test */ - public function it_returns_enabled_locales() - { - $expected = [ - 'en' => 'English', - 'fr' => 'Français', - ]; - $this->currentConfig->shouldReceive('getEnabledLocales')->zeroOrMoreTimes()->andReturn($expected); - - $locales = $this->localeSwitcher->getEnabledLocales(); - - $this->assertEquals($expected, $locales); - } -}