diff --git a/config/module.config.php b/config/module.config.php index fbdc30f9..6b392a26 100644 --- a/config/module.config.php +++ b/config/module.config.php @@ -16,14 +16,17 @@ ), ), 'controllers' => array( - 'invokables' => array( - 'zfcuser' => 'ZfcUser\Controller\UserController', + 'factories' => array( + 'zfcuser' => 'ZfcUser\Factory\Controller\UserControllerFactory', ), ), 'service_manager' => array( 'aliases' => array( 'zfcuser_zend_db_adapter' => 'Zend\Db\Adapter\Adapter', ), + 'factories' => array( + 'zfcuser_redirect_callback' => 'ZfcUser\Factory\Controller\RedirectCallbackFactory' + ) ), 'router' => array( 'routes' => array( diff --git a/src/ZfcUser/Controller/RedirectCallback.php b/src/ZfcUser/Controller/RedirectCallback.php new file mode 100644 index 00000000..cf2dd1e5 --- /dev/null +++ b/src/ZfcUser/Controller/RedirectCallback.php @@ -0,0 +1,116 @@ +router = $router; + $this->application = $application; + $this->options = $options; + } + + /** + * @return Response + */ + public function __invoke() + { + $routeMatch = $this->application->getMvcEvent()->getRouteMatch(); + $redirect = $this->getRedirect($routeMatch->getMatchedRouteName(), $this->getRedirectRouteFromRequest()); + + $response = $this->application->getResponse(); + $response->getHeaders()->addHeaderLine('Location', $redirect); + $response->setStatusCode(302); + return $response; + } + + /** + * Return the redirect from param. + * First checks GET then POST + * @return string + */ + private function getRedirectRouteFromRequest() + { + $request = $this->application->getRequest(); + $redirect = $request->getQuery('redirect'); + if ($redirect && $this->routeExists($redirect)) { + return $redirect; + } + + $redirect = $request->getPost('redirect'); + if ($redirect && $this->routeExists($redirect)) { + return $redirect; + } + + return false; + } + + /** + * @param $route + * @return bool + */ + private function routeExists($route) + { + try { + $this->router->assemble(array(), array('name' => $route)); + } catch (Exception\RuntimeException $e) { + return false; + } + return true; + } + + /** + * Returns the url to redirect to based on current route. + * If $redirect is set and the option to use redirect is set to true, it will return the $redirect url. + * + * @param string $currentRoute + * @param bool $redirect + * @return mixed + */ + private function getRedirect($currentRoute, $redirect = false) + { + $useRedirect = $this->options->getUseRedirectParameterIfPresent(); + $routeExists = ($redirect && $this->routeExists($redirect)); + if (!$useRedirect || !$routeExists) { + $redirect = false; + } + + switch ($currentRoute) { + case 'zfcuser/login': + $route = ($redirect) ?: $this->options->getLoginRedirectRoute(); + return $this->router->assemble(array(), array('name' => $route)); + break; + case 'zfcuser/logout': + $route = ($redirect) ?: $this->options->getLogoutRedirectRoute(); + return $this->router->assemble(array(), array('name' => $route)); + break; + default: + return $this->router->assemble(array(), array('name' => 'zfcuser')); + } + } +} diff --git a/src/ZfcUser/Controller/UserController.php b/src/ZfcUser/Controller/UserController.php index 43d81134..80cd4491 100644 --- a/src/ZfcUser/Controller/UserController.php +++ b/src/ZfcUser/Controller/UserController.php @@ -55,6 +55,22 @@ class UserController extends AbstractActionController */ protected $options; + /** + * @var callable $redirectCallback + */ + protected $redirectCallback; + + /** + * @param callable $redirectCallback + */ + public function __construct($redirectCallback) + { + if (!is_callable($redirectCallback)) { + throw new \InvalidArgumentException('You must supply a callable redirectCallback'); + } + $this->redirectCallback = $redirectCallback; + } + /** * User page */ @@ -116,13 +132,9 @@ public function logoutAction() $this->zfcUserAuthentication()->getAuthAdapter()->logoutAdapters(); $this->zfcUserAuthentication()->getAuthService()->clearIdentity(); - $redirect = $this->params()->fromPost('redirect', $this->params()->fromQuery('redirect', false)); - - if ($this->getOptions()->getUseRedirectParameterIfPresent() && $redirect) { - return $this->redirect()->toRoute($redirect); - } + $redirect = $this->redirectCallback; - return $this->redirect()->toRoute($this->getOptions()->getLogoutRedirectRoute()); + return $redirect(); } /** @@ -155,17 +167,16 @@ public function authenticateAction() ); } - if ($this->getOptions()->getUseRedirectParameterIfPresent() && $redirect) { - return $this->redirect()->toRoute($redirect); - } - $route = $this->getOptions()->getLoginRedirectRoute(); if (is_callable($route)) { $route = $route($this->zfcUserAuthentication()->getIdentity()); + return $this->redirect()->toRoute($route); } - return $this->redirect()->toRoute($route); + $redirect = $this->redirectCallback; + + return $redirect(); } /** diff --git a/src/ZfcUser/Factory/Controller/RedirectCallbackFactory.php b/src/ZfcUser/Factory/Controller/RedirectCallbackFactory.php new file mode 100644 index 00000000..b26989f1 --- /dev/null +++ b/src/ZfcUser/Factory/Controller/RedirectCallbackFactory.php @@ -0,0 +1,29 @@ +get('Router'); + + /* @var Application $application */ + $application = $serviceLocator->get('Application'); + + /* @var ModuleOptions $options */ + $options = $serviceLocator->get('zfcuser_module_options'); + + return new RedirectCallback($application, $router, $options); + } +} diff --git a/src/ZfcUser/Factory/Controller/UserControllerFactory.php b/src/ZfcUser/Factory/Controller/UserControllerFactory.php new file mode 100644 index 00000000..57a763f0 --- /dev/null +++ b/src/ZfcUser/Factory/Controller/UserControllerFactory.php @@ -0,0 +1,29 @@ +getServiceLocator(); + + /* @var RedirectCallback $redirectCallback */ + $redirectCallback = $serviceManager->get('zfcuser_redirect_callback'); + + /* @var UserController $controller */ + $controller = new UserController($redirectCallback); + + return $controller; + } +} diff --git a/tests/ZfcUserTest/Controller/RedirectCallbackTest.php b/tests/ZfcUserTest/Controller/RedirectCallbackTest.php new file mode 100644 index 00000000..236352c1 --- /dev/null +++ b/tests/ZfcUserTest/Controller/RedirectCallbackTest.php @@ -0,0 +1,329 @@ +router = $this->getMockBuilder('Zend\Mvc\Router\RouteInterface') + ->disableOriginalConstructor() + ->getMock(); + + $this->moduleOptions = $this->getMockBuilder('ZfcUser\Options\ModuleOptions') + ->disableOriginalConstructor() + ->getMock(); + + $this->application = $this->getMockBuilder('Zend\Mvc\Application') + ->disableOriginalConstructor() + ->getMock(); + $this->setUpApplication(); + + $this->redirectCallback = new RedirectCallback( + $this->application, + $this->router, + $this->moduleOptions + ); + } + + public function testInvoke() + { + $url = 'someUrl'; + + $this->routeMatch->expects($this->once()) + ->method('getMatchedRouteName') + ->will($this->returnValue('someRoute')); + + $headers = $this->getMock('Zend\Http\Headers'); + $headers->expects($this->once()) + ->method('addHeaderLine') + ->with('Location', $url); + + $this->router->expects($this->any()) + ->method('assemble') + ->with(array(), array('name' => 'zfcuser')) + ->will($this->returnValue($url)); + + $this->response->expects($this->once()) + ->method('getHeaders') + ->will($this->returnValue($headers)); + + $this->response->expects($this->once()) + ->method('setStatusCode') + ->with(302); + + $result = $this->redirectCallback->__invoke(); + + $this->assertSame($this->response, $result); + } + + /** + * @dataProvider providerGetRedirectRouteFromRequest + */ + public function testGetRedirectRouteFromRequest($get, $post, $getRouteExists, $postRouteExists) + { + $expectedResult = false; + + $this->request->expects($this->once()) + ->method('getQuery') + ->will($this->returnValue($get)); + + if ($get) { + $this->router->expects($this->any()) + ->method('assemble') + ->with(array(), array('name' => $get)) + ->will($getRouteExists); + + if ($getRouteExists == $this->returnValue(true)) { + $expectedResult = $get; + } + } + + if (!$get || !$getRouteExists) { + $this->request->expects($this->once()) + ->method('getPost') + ->will($this->returnValue($post)); + + if ($post) { + $this->router->expects($this->any()) + ->method('assemble') + ->with(array(), array('name' => $post)) + ->will($postRouteExists); + + if ($postRouteExists == $this->returnValue(true)) { + $expectedResult = $post; + } + } + } + + $method = new \ReflectionMethod( + 'ZfcUser\Controller\RedirectCallback', + 'getRedirectRouteFromRequest' + ); + $method->setAccessible(true); + $result = $method->invoke($this->redirectCallback); + + $this->assertSame($expectedResult, $result); + } + + public function providerGetRedirectRouteFromRequest() + { + return array( + array('user', false, $this->returnValue('route'), false), + array('user', false, $this->returnValue('route'), $this->returnValue(true)), + array('user', 'user', $this->returnValue('route'), $this->returnValue(true)), + array('user', 'user', $this->throwException(new \Zend\Mvc\Router\Exception\RuntimeException), $this->returnValue(true)), + array('user', 'user', $this->throwException(new \Zend\Mvc\Router\Exception\RuntimeException), $this->throwException(new \Zend\Mvc\Router\Exception\RuntimeException)), + array(false, 'user', false, $this->returnValue(true)), + array(false, 'user', false, $this->throwException(new \Zend\Mvc\Router\Exception\RuntimeException)), + array(false, 'user', false, $this->throwException(new \Zend\Mvc\Router\Exception\RuntimeException)), + ); + } + + public function testRouteExistsRouteExists() + { + $route = 'existingRoute'; + + $this->router->expects($this->once()) + ->method('assemble') + ->with(array(), array('name' => $route)); + + $method = new \ReflectionMethod( + 'ZfcUser\Controller\RedirectCallback', + 'routeExists' + ); + $method->setAccessible(true); + $result = $method->invoke($this->redirectCallback, $route); + + $this->assertTrue($result); + } + + public function testRouteExistsRouteDoesntExists() + { + $route = 'existingRoute'; + + $this->router->expects($this->once()) + ->method('assemble') + ->with(array(), array('name' => $route)) + ->will($this->throwException(new \Zend\Mvc\Router\Exception\RuntimeException)); + + $method = new \ReflectionMethod( + 'ZfcUser\Controller\RedirectCallback', + 'routeExists' + ); + $method->setAccessible(true); + $result = $method->invoke($this->redirectCallback, $route); + + $this->assertFalse($result); + } + + /** + * @dataProvider providerGetRedirectNoRedirectParam + */ + public function testGetRedirectNoRedirectParam($currentRoute, $optionsReturn, $expectedResult, $optionsMethod) + { + $this->moduleOptions->expects($this->once()) + ->method('getUseRedirectParameterIfPresent') + ->will($this->returnValue(true)); + + $this->router->expects($this->at(0)) + ->method('assemble'); + $this->router->expects($this->at(1)) + ->method('assemble') + ->with(array(), array('name' => $optionsReturn)) + ->will($this->returnValue($expectedResult)); + + if ($optionsMethod) { + $this->moduleOptions->expects($this->never()) + ->method($optionsMethod) + ->will($this->returnValue($optionsReturn)); + } + $method = new \ReflectionMethod( + 'ZfcUser\Controller\RedirectCallback', + 'getRedirect' + ); + $method->setAccessible(true); + $result = $method->invoke($this->redirectCallback, $currentRoute, $optionsReturn); + + $this->assertSame($expectedResult, $result); + } + + public function providerGetRedirectNoRedirectParam() + { + return array( + array('zfcuser/login', 'zfcuser', '/user', 'getLoginRedirectRoute'), + array('zfcuser/logout', 'zfcuser/login', '/user/login', 'getLogoutRedirectRoute'), + array('testDefault', 'zfcuser', '/home', false), + ); + } + + public function testGetRedirectWithOptionOnButNoRedirect() + { + $route = 'zfcuser/login'; + $redirect = false; + $expectedResult = '/user/login'; + + $this->moduleOptions->expects($this->once()) + ->method('getUseRedirectParameterIfPresent') + ->will($this->returnValue(true)); + + $this->moduleOptions->expects($this->once()) + ->method('getLoginRedirectRoute') + ->will($this->returnValue($route)); + + $this->router->expects($this->once()) + ->method('assemble') + ->with(array(), array('name' => $route)) + ->will($this->returnValue($expectedResult)); + + $method = new \ReflectionMethod( + 'ZfcUser\Controller\RedirectCallback', + 'getRedirect' + ); + $method->setAccessible(true); + $result = $method->invoke($this->redirectCallback, $route, $redirect); + + $this->assertSame($expectedResult, $result); + } + + public function testGetRedirectWithOptionOnRedirectDoesntExists() + { + $route = 'zfcuser/login'; + $redirect = 'doesntExists'; + $expectedResult = '/user/login'; + + $this->moduleOptions->expects($this->once()) + ->method('getUseRedirectParameterIfPresent') + ->will($this->returnValue(true)); + + $this->router->expects($this->at(0)) + ->method('assemble') + ->with(array(), array('name' => $redirect)) + ->will($this->throwException(new \Zend\Mvc\Router\Exception\RuntimeException)); + + $this->router->expects($this->at(1)) + ->method('assemble') + ->with(array(), array('name' => $route)) + ->will($this->returnValue($expectedResult)); + + $this->moduleOptions->expects($this->once()) + ->method('getLoginRedirectRoute') + ->will($this->returnValue($route)); + + $method = new \ReflectionMethod( + 'ZfcUser\Controller\RedirectCallback', + 'getRedirect' + ); + $method->setAccessible(true); + $result = $method->invoke($this->redirectCallback, $route, $redirect); + + $this->assertSame($expectedResult, $result); + } + + private function setUpApplication() + { + $this->request = $this->getMockBuilder('Zend\Http\PhpEnvironment\Request') + ->disableOriginalConstructor() + ->getMock(); + + $this->response = $this->getMockBuilder('Zend\Http\PhpEnvironment\Response') + ->disableOriginalConstructor() + ->getMock(); + + $this->routeMatch = $this->getMockBuilder('Zend\Mvc\Router\RouteMatch') + ->disableOriginalConstructor() + ->getMock(); + + $this->mvcEvent = $this->getMockBuilder('Zend\Mvc\MvcEvent') + ->disableOriginalConstructor() + ->getMock(); + $this->mvcEvent->expects($this->any()) + ->method('getRouteMatch') + ->will($this->returnValue($this->routeMatch)); + + + $this->application->expects($this->any()) + ->method('getMvcEvent') + ->will($this->returnValue($this->mvcEvent)); + $this->application->expects($this->any()) + ->method('getRequest') + ->will($this->returnValue($this->request)); + $this->application->expects($this->any()) + ->method('getResponse') + ->will($this->returnValue($this->response)); + } +} diff --git a/tests/ZfcUserTest/Controller/UserControllerTest.php b/tests/ZfcUserTest/Controller/UserControllerTest.php index f581c6d6..06fabf8e 100644 --- a/tests/ZfcUserTest/Controller/UserControllerTest.php +++ b/tests/ZfcUserTest/Controller/UserControllerTest.php @@ -2,6 +2,7 @@ namespace ZfcUserTest\Controller; +use ZfcUser\Controller\RedirectCallback; use ZfcUser\Controller\UserController as Controller; use Zend\Http\Response; use Zend\Stdlib\Parameters; @@ -26,9 +27,18 @@ class UserControllerTest extends \PHPUnit_Framework_TestCase protected $options; + /** + * @var \PHPUnit_Framework_MockObject_MockObject|RedirectCallback + */ + protected $redirectCallback; + public function setUp() { - $controller = new Controller; + $this->redirectCallback = $this->getMockBuilder('ZfcUser\Controller\RedirectCallback') + ->disableOriginalConstructor() + ->getMock(); + + $controller = new Controller($this->redirectCallback); $this->controller = $controller; $this->zfcUserAuthenticationPlugin = $this->getMock('ZfcUser\Controller\Plugin\ZfcUserAuthentication'); @@ -362,50 +372,11 @@ public function testLogoutAction($withRedirect, $post, $query) )); - $params = $this->getMock('Zend\Mvc\Controller\Plugin\Params'); - $params->expects($this->any()) - ->method('__invoke') - ->will($this->returnSelf()); - $params->expects($this->once()) - ->method('fromPost') - ->will($this->returnCallback(function ($key, $default) use ($post) { - return $post ?: $default; - })); - $params->expects($this->once()) - ->method('fromQuery') - ->will($this->returnCallback(function ($key, $default) use ($query) { - return $query ?: $default; - })); - $this->pluginManagerPlugins['params'] = $params; - $response = new Response(); - $redirect = $this->getMock('Zend\Mvc\Controller\Plugin\Redirect'); - $redirect->expects($this->any()) - ->method('toRoute') - ->will($this->returnValue($response)); - - if ($withRedirect) { - $expectedLocation = $post ?: $query ?: false; - $this->options->expects($this->once()) - ->method('getUseRedirectParameterIfPresent') - ->will($this->returnValue((bool) $withRedirect)); - $redirect->expects($this->any()) - ->method('toRoute') - ->with($expectedLocation) - ->will($this->returnValue($response)); - } else { - $expectedLocation = "/user/logout"; - $this->options->expects($this->once()) - ->method('getLogoutRedirectRoute') - ->will($this->returnValue($expectedLocation)); - $redirect->expects($this->any()) - ->method('toRoute') - ->with($expectedLocation) - ->will($this->returnValue($response)); - } - - $this->pluginManagerPlugins['redirect']= $redirect; + $this->redirectCallback->expects($this->once()) + ->method('__invoke') + ->will($this->returnValue($response)); $result = $controller->logoutAction(); @@ -513,21 +484,9 @@ public function testAuthenticateAction($wantRedirect, $post, $query, $prepareRes ->will($this->returnValue('user/login')); $this->pluginManagerPlugins['url'] = $url; - } elseif ($wantRedirect && $hasRedirect) { - $redirect->expects($this->once()) - ->method('toRoute') - ->with(($post ?: $query ?: false)) - ->will($this->returnValue($response)); } else { - - $redirect->expects($this->once()) - ->method('toRoute') - ->with('zfcuser') - ->will($this->returnValue($response)); - - $this->options->expects($this->once()) - ->method('getLoginRedirectRoute') - ->will($this->returnValue('zfcuser')); + $this->redirectCallback->expects($this->once()) + ->method('__invoke'); } $this->options->expects($this->any()) @@ -1010,7 +969,7 @@ public function testSetterGetterServices( $serviceName, $callback = null ) { - $controller = new Controller; + $controller = new Controller($this->redirectCallback); $controller->setPluginManager($this->pluginManager);