diff --git a/src/SlmLocale/Strategy/UriPathStrategy.php b/src/SlmLocale/Strategy/UriPathStrategy.php index 1142d9c..694c637 100644 --- a/src/SlmLocale/Strategy/UriPathStrategy.php +++ b/src/SlmLocale/Strategy/UriPathStrategy.php @@ -43,14 +43,116 @@ namespace SlmLocale\Strategy; use SlmLocale\LocaleEvent; +use Zend\ServiceManager\ServiceManager; +use Zend\ServiceManager\ServiceManagerAwareInterface; +use Zend\Stdlib\RequestInterface; -class UriPathStrategy extends AbstractStrategy +class UriPathStrategy extends AbstractStrategy implements ServiceManagerAwareInterface { + const REDIRECT_STATUS_CODE = 302; + + protected $redirectWhenFound = true; + + protected $serviceManager; + + public function setOptions(array $options = array()) + { + if (array_key_exists('redirect_when_found', $options)) { + $this->redirectWhenFound = filter_var($options['redirect_when_found'], FILTER_VALIDATE_BOOLEAN); + } + } + + /** + * Set service manager instance + * + * @param ServiceManager $locator + */ + public function setServiceManager(ServiceManager $serviceManager) + { + $this->serviceManager = $serviceManager; + } + public function detect(LocaleEvent $event) { + if (!method_exists($event->getRequest(), 'getUri')) { + return; + } + + $router = $this->serviceManager->get('router'); + $existingBaseUrl = null; + if (method_exists($router, 'getBaseUrl')) { + $existingBaseUrl = $router->getBaseUrl(); + } + + $locale = $this->detectLocaleInRequest($event->getRequest(), $existingBaseUrl); + + if (!strlen($locale)) { + return; + } + + if (!$event->hasSupported() || !in_array($locale, $event->getSupported())) { + return; + } + + return $locale; } public function found(LocaleEvent $event) { + if (!method_exists($event->getRequest(), 'getUri')) { + return; + } + + $locale = $event->getLocale(); + if (null === $locale) { + return; + } + + $router = $this->serviceManager->get('router'); + $existingBaseUrl = null; + if (method_exists($router, 'getBaseUrl')) { + $existingBaseUrl = $router->getBaseUrl(); + $router->setBaseUrl($existingBaseUrl . '/' . $locale); + } + + $uri = $event->getRequest()->getUri(); + if ($locale == $this->detectLocaleInRequest($event->getRequest(), $existingBaseUrl)) { + if ($router->getBaseUrl() == $uri->getPath()) { + $response = $event->getResponse(); + $response->setStatusCode(self::REDIRECT_STATUS_CODE); + $response->getHeaders()->addHeaderLine('Location', $uri->toString() . '/'); + $response->send(); + } + + return; + } + + + $uri->setPath('/' . $locale . $uri->getPath()); + + if (!$this->redirectWhenFound) { + return; + } + + $response = $event->getResponse(); + $response->setStatusCode(self::REDIRECT_STATUS_CODE); + $response->getHeaders()->addHeaderLine('Location', $uri->toString()); + $response->send(); + } + + protected function detectLocaleInRequest(RequestInterface $request, $baseurl = null) + { + $uri = $request->getUri(); + $path = $uri->getPath(); + + if ($baseurl) { + $path = substr($path, strlen($baseurl)); + } + + $parts = explode("/", trim($path, '/')); + $locale = array_shift($parts); + + return $locale; } -} \ No newline at end of file + +} diff --git a/tests/SlmLocaleTest/Strategy/UriPathStrategyTest.php b/tests/SlmLocaleTest/Strategy/UriPathStrategyTest.php new file mode 100644 index 0000000..a14755a --- /dev/null +++ b/tests/SlmLocaleTest/Strategy/UriPathStrategyTest.php @@ -0,0 +1,322 @@ + + * @copyright 2012 Jurian Sluiman. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @link http://juriansluiman.nl + */ +namespace SlmLocaleTest\Locale; + +use PHPUnit_Framework_TestCase as TestCase; +use SlmLocale\LocaleEvent; +use SlmLocale\Strategy\UriPathStrategy; +use Zend\Console\Response as ConsoleResponse; +use Zend\Console\Request as ConsoleRequest; +use Zend\Http\PhpEnvironment\Response as HttpResponse; +use Zend\Http\PhpEnvironment\Request as HttpRequest; +use Zend\Mvc\Router\Http\TreeRouteStack as HttpRouter; +use Zend\Mvc\Router\Console\SimpleRouteStack as ConsoleRouter; +use Zend\ServiceManager\ServiceManager; + +class UriPathStrategyTest extends TestCase +{ + public function setup() + { + $this->strategy = new UriPathStrategy; + $this->strategy->setServiceManager($this->getServiceLocator()); + + $this->event = new LocaleEvent(); + $this->event->setSupported(array('nl', 'de', 'en')); + } + + /** + * Asserts that when the request instance does not has the getUri method detection is not possible as is the case for + * console requests. + */ + public function testDetect_ConsoleRequestReturnsNull() + { + $this->event->setRequest(new ConsoleRequest); + $this->event->setResponse(new ConsoleResponse); + + $locale = $this->strategy->detect($this->event); + + $this->assertNull($locale); + } + + /** + * Asserts that no locale is detected with the default options + */ + public function testDetect_NullByDefault() + { + $this->event->setRequest(new HttpRequest); + $this->event->setResponse(new HttpResponse); + + $this->assertNull($this->strategy->detect($this->event)); + } + + /** + * Asserts that the first segment of a path is detected as locale with the default options + */ + public function testDetect_FirstPathSegmentAsLocale() + { + $request = new HttpRequest; + $request->setUri('http://example.com/en/deep/path/'); + + $this->event->setRequest($request); + $this->event->setResponse(new HttpResponse); + + $this->assertEquals('en', $this->strategy->detect($this->event)); + } + + public function testDetect_NullForUnsupported() + { + $request = new HttpRequest; + $request->setUri('http://example.com/fr'); + + $this->event->setRequest($request); + $this->event->setResponse(new HttpResponse); + + $this->assertNull($this->strategy->detect($this->event)); + } + + public function testDetect_PreExistingBaseUrlInRouterDetectsCorrectPathPartAsLocale() + { + $serviceManager = $this->getServiceLocator(); + $serviceManager->get('router')->setBaseUrl('/some/seep/installation/path'); + $this->strategy->setServiceManager($serviceManager); + + $this->event->setLocale('en'); + + $request = new HttpRequest; + $request->setUri('http://example.com/some/deep/installation/path/en/some/deep/path'); + + $this->event->setRequest($request); + $this->event->setResponse(new HttpResponse); + + $this->assertEquals('en', $this->strategy->detect($this->event)); + } + + /** + * @runInSeparateProcess + * 'cause headers will be send (warning https://github.com/sebastianbergmann/phpunit/issues/254) + */ + public function testFound_RedirectByDefault() + { + $this->event->setLocale('en'); + + $request = new HttpRequest; + $request->setUri('http://username:password@example.com:8080/some/deep/path/some.file?withsomeparam=true'); + + $this->event->setRequest($request); + $this->event->setResponse(new HttpResponse); + + $locale = $this->strategy->found($this->event); + + $this->assertEquals($this->event->getResponse()->getStatusCode(), 302); + $this->assertContains($this->event->getResponse()->getHeaders()->toString(), + "Location: http://username:password@example.com:8080/en/some/deep/path/some.file?withsomeparam=true\r\n"); + } + + /** + * @runInSeparateProcess + * 'cause headers will be send (warning https://github.com/sebastianbergmann/phpunit/issues/254) + */ + public function testFound_ShouldRespectDisabledRedirectWhenFoundOption() + { + $this->strategy->setOptions(array('redirect_when_found' => false)); + $this->strategy->setServiceManager($this->getServiceLocator()); + + $this->event->setLocale('en'); + $this->event->setSupported(array('nl', 'de', 'en')); + + $request = new HttpRequest; + $request->setUri('http://example.com/'); + + $this->event->setRequest($request); + $this->event->setResponse(new HttpResponse); + + $locale = $this->strategy->found($this->event); + + $this->assertNotEquals($this->event->getResponse()->getStatusCode(), 302); + $this->assertEquals($this->event->getResponse()->getHeaders()->toString(), ""); + } + + /** + * @runInSeparateProcess + * 'cause headers will be send (warning https://github.com/sebastianbergmann/phpunit/issues/254) + */ + public function testFound_WithDisabledRedirectWhenFoundOptionLocaleShouldStillBeDirectedAnywayWhenPathContainsNothingFurther() + { + $this->strategy->setOptions(array('redirect_when_found' => false)); + $this->strategy->setServiceManager($this->getServiceLocator()); + + $this->event->setLocale('en'); + $this->event->setSupported(array('nl', 'de', 'en')); + + $request = new HttpRequest; + $request->setUri('http://example.com/en'); + + $this->event->setRequest($request); + $this->event->setResponse(new HttpResponse); + + $locale = $this->strategy->found($this->event); + + $this->assertEquals($this->event->getResponse()->getStatusCode(), 302); + $this->assertContains($this->event->getResponse()->getHeaders()->toString(), "Location: http://example.com/en/\r\n"); + } + + /** + * @runInSeparateProcess + * 'cause headers will be send (warning https://github.com/sebastianbergmann/phpunit/issues/254) + */ + public function testFound_WithDisabledRedirectWhenFoundOptionLocaleShouldStillBeDirectedAnyway() + { + $this->strategy->setOptions(array('redirect_when_found' => false)); + $this->strategy->setServiceManager($this->getServiceLocator()); + + $this->event->setLocale('en'); + $this->event->setSupported(array('nl', 'de', 'en')); + + $request = new HttpRequest; + $request->setUri('http://example.com/en/something.ext'); + + $this->event->setRequest($request); + $this->event->setResponse(new HttpResponse); + + $locale = $this->strategy->found($this->event); + + $this->assertNotEquals($this->event->getResponse()->getStatusCode(), 302); + $this->assertEquals($this->event->getResponse()->getHeaders()->toString(), ""); + } + + /** + * @runInSeparateProcess + * 'cause headers will be send (warning https://github.com/sebastianbergmann/phpunit/issues/254) + */ + public function testFound_SetsBaseUrlInRouter() + { + $serviceManager = $this->getServiceLocator(); + $this->strategy->setServiceManager($serviceManager); + + $this->event->setLocale('en'); + + $request = new HttpRequest; + $request->setUri('http://example.com/en'); + + $this->event->setRequest($request); + $this->event->setResponse(new HttpResponse); + + $locale = $this->strategy->found($this->event); + + $this->assertEquals($serviceManager->get('router')->getBaseUrl(), '/en'); + } + + /** + * @runInSeparateProcess + * 'cause headers will be send (warning https://github.com/sebastianbergmann/phpunit/issues/254) + */ + public function testFound_PrependToPreExistingBaseUrlInRouter() + { + $serviceManager = $this->getServiceLocator(); + $serviceManager->get('router')->setBaseUrl('/some/seep/installation/path'); + $this->strategy->setServiceManager($serviceManager); + + $this->event->setLocale('en'); + + $request = new HttpRequest; + $request->setUri('http://example.com/some/seep/installation/path/en'); + + $this->event->setRequest($request); + $this->event->setResponse(new HttpResponse); + + $locale = $this->strategy->found($this->event); + + $this->assertEquals($serviceManager->get('router')->getBaseUrl(), '/some/seep/installation/path/en'); + } + + /** + * @runInSeparateProcess + * 'cause headers will be send (warning https://github.com/sebastianbergmann/phpunit/issues/254) + */ + public function testFound_RedirectWhenAtLocaleUrlButMissingTrailingSlash() + { + $serviceManager = $this->getServiceLocator(); + $this->strategy->setServiceManager($serviceManager); + + $this->event->setLocale('en'); + + $request = new HttpRequest; + $request->setUri('http://example.com/en'); + + $this->event->setRequest($request); + $this->event->setResponse(new HttpResponse); + + $locale = $this->strategy->found($this->event); + + $this->assertEquals($this->event->getResponse()->getStatusCode(), 302); + } + + /** + * @runInSeparateProcess + * 'cause headers will be send (warning https://github.com/sebastianbergmann/phpunit/issues/254) + */ + public function testFound_DoesNotRedirectWhenAtLocaleUrl() + { + $serviceManager = $this->getServiceLocator(); + $this->strategy->setServiceManager($serviceManager); + + $this->event->setLocale('en'); + + $request = new HttpRequest; + $request->setUri('http://example.com/en/'); + + $this->event->setRequest($request); + $this->event->setResponse(new HttpResponse); + + $locale = $this->strategy->found($this->event); + + $this->assertNotEquals($this->event->getResponse()->getStatusCode(), 302); + } + + protected function getServiceLocator($withConsoleRouter = false) + { + $serviceLocator = new ServiceManager; + $serviceLocator->setService('router', $withConsoleRouter ? new ConsoleRouter : new HttpRouter); + + return $serviceLocator; + } + +}