diff --git a/src/Application/Application.php b/src/Application/Application.php index 72eb621c4..0ab5a2cf7 100644 --- a/src/Application/Application.php +++ b/src/Application/Application.php @@ -67,13 +67,17 @@ class Application extends Nette\Object /** @var IRouter */ private $router; + /** @var IRequestStorage */ + private $requestStorage; - public function __construct(IPresenterFactory $presenterFactory, IRouter $router, Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse) + + public function __construct(IPresenterFactory $presenterFactory, IRouter $router, Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse, IRequestStorage $requestStorage) { $this->httpRequest = $httpRequest; $this->httpResponse = $httpResponse; $this->presenterFactory = $presenterFactory; $this->router = $router; + $this->requestStorage = $requestStorage; } @@ -111,7 +115,7 @@ public function run() */ public function createInitialRequest() { - $request = $this->router->match($this->httpRequest); + $request = $this->requestStorage->restore() ?: $this->router->match($this->httpRequest); if (!$request instanceof Request) { throw new BadRequestException('No route for HTTP request.'); diff --git a/src/Application/IRequestStorage.php b/src/Application/IRequestStorage.php new file mode 100644 index 000000000..2facedfec --- /dev/null +++ b/src/Application/IRequestStorage.php @@ -0,0 +1,40 @@ +session = $session; + $this->user = $user; + } + + + /** + * Stores request and returns key. + * @return string key + */ + public function store(Request $request, Http\Url $url, $expiration = '10 minutes') + { + $session = $this->session->getSection(__CLASS__); + do { + $key = Nette\Utils\Random::generate(5); + } while (isset($session[$key])); + + $session[$key] = array(clone $request, clone $url, $this->user->getId()); + $session->setExpiration($expiration, $key); + return $key; + } + + + /** + * Restores original URL. + * @param string key + * @return string|NULL + */ + public function getUrl($key) + { + list($request, $url, $user) = $this->session->getSection(__CLASS__)->$key; + if (!$request || !$url || ($user !== NULL && $user !== $this->user->getId())) { + return; + } + + $request->setFlag($request::RESTORED, TRUE); + $this->session->getFlashSection(__CLASS__)->request = $request; + + $url->setQueryParameter(Http\Session::FLASH_KEY, $this->session->getFlashId()); + return (string) $url; + } + + + /** + * Returns stored request. + * @return Request|NULL + */ + public function restore() + { + return $this->session->getFlashId() + ? $this->session->getFlashSection(__CLASS__)->request + : NULL; + } + +} diff --git a/src/Application/UI/Presenter.php b/src/Application/UI/Presenter.php index 0600f6e13..cc5134fcd 100644 --- a/src/Application/UI/Presenter.php +++ b/src/Application/UI/Presenter.php @@ -126,6 +126,9 @@ abstract class Presenter extends Control implements Application\IPresenter /** @var ITemplateFactory */ private $templateFactory; + /** @var Application\IRequestStorage */ + private $requestStorage; + public function __construct() { @@ -1073,14 +1076,10 @@ protected function handleInvalidLink(InvalidLinkException $e) */ public function storeRequest($expiration = '+ 10 minutes') { - $session = $this->getSession('Nette.Application/requests'); - do { - $key = Nette\Utils\Random::generate(5); - } while (isset($session[$key])); - - $session[$key] = array($this->getUser()->getId(), $this->request); - $session->setExpiration($expiration, $key); - return $key; + if (!$this->requestStorage) { + throw new Nette\InvalidStateException('Service IRequestStorage has not been set.'); + } + return $this->requestStorage->store($this->request, $this->httpRequest->getUrl(), $expiration); } @@ -1091,17 +1090,11 @@ public function storeRequest($expiration = '+ 10 minutes') */ public function restoreRequest($key) { - $session = $this->getSession('Nette.Application/requests'); - if (!isset($session[$key]) || ($session[$key][0] !== NULL && $session[$key][0] !== $this->getUser()->getId())) { - return; + if (!$this->requestStorage) { + throw new Nette\InvalidStateException('Service IRequestStorage has not been set.'); + } elseif ($url = $this->requestStorage->getUrl($key)) { + $this->redirectUrl($url); } - $request = clone $session[$key][1]; - unset($session[$key]); - $request->setFlag(Application\Request::RESTORED, TRUE); - $params = $request->getParameters(); - $params[Http\Session::FLASH_KEY] = $this->getSession()->getFlashId(); - $request->setParameters($params); - $this->sendResponse(new Responses\ForwardResponse($request)); } @@ -1298,7 +1291,8 @@ public function getFlashSession() public function injectPrimary(Nette\DI\Container $context = NULL, Nette\Application\IPresenterFactory $presenterFactory = NULL, Nette\Application\IRouter $router = NULL, - Http\IRequest $httpRequest, Http\IResponse $httpResponse, Http\Session $session = NULL, Nette\Security\User $user = NULL, ITemplateFactory $templateFactory = NULL) + Http\IRequest $httpRequest, Http\IResponse $httpResponse, Http\Session $session = NULL, Nette\Security\User $user = NULL, ITemplateFactory $templateFactory = NULL, + Application\IRequestStorage $requestStorage = NULL) { if ($this->presenterFactory !== NULL) { throw new Nette\InvalidStateException("Method " . __METHOD__ . " is intended for initialization and should not be called more than once."); @@ -1312,6 +1306,7 @@ public function injectPrimary(Nette\DI\Container $context = NULL, Nette\Applicat $this->session = $session; $this->user = $user; $this->templateFactory = $templateFactory; + $this->requestStorage = $requestStorage; } diff --git a/tests/Application/Presenter.storeRequest().phpt b/tests/Application/Presenter.storeRequest().phpt index ae1b98a81..763e08727 100644 --- a/tests/Application/Presenter.storeRequest().phpt +++ b/tests/Application/Presenter.storeRequest().phpt @@ -81,21 +81,18 @@ class MockUser extends Security\User } } -class MockHttpRequest extends Http\Request -{ - public function __construct() {} -} - $presenter = new TestPresenter(); $presenter->injectPrimary( NULL, NULL, NULL, - new MockHttpRequest, + new Http\Request($url = new Http\UrlScript), new Http\Response, $session = new MockSession, - $user = new MockUser + $user = new MockUser, + NULL, + new Application\RequestStorage($session, $user) ); $section = $session->testSection = new MockSessionSection($session); @@ -110,4 +107,6 @@ Assert::same($expiration, $section->testExpiration); Assert::same($key, $section->testExpirationVariables); Assert::same($key, $section->testedKeyExistence); Assert::same($key, $section->storedKey); -Assert::same(array($user->getId(), $applicationRequest), $section->storedValue); +Assert::equal($applicationRequest, $section->storedValue[0]); +Assert::equal($url, $section->storedValue[1]); +Assert::same($user->getId(), $section->storedValue[2]); diff --git a/tests/Application/RequestStorage.phpt b/tests/Application/RequestStorage.phpt new file mode 100644 index 000000000..e7295458b --- /dev/null +++ b/tests/Application/RequestStorage.phpt @@ -0,0 +1,50 @@ +mockSection = new MockSessionSection; +$session->mockFlashSection = new MockSessionSection; + +$user = new MockUser; +$user->mockIdentity = new Identity(42); + +$requestStorage = new Application\RequestStorage($session, $user); + +$applicationRequest = new Application\Request('Presenter', 'action', array('param' => 'value')); + +$key = $requestStorage->store($applicationRequest, $httpRequest->getUrl()); + + +// restore key +Assert::null($requestStorage->getUrl('bad_key')); + +$redirect = $requestStorage->getUrl($key); +Assert::same($url . '&_fid=x', $redirect); + + +// redirect to original URL +$httpRequest = new Http\Request(new Http\UrlScript($redirect)); + +$application = new Application\Application(new MockPresenterFactory, new MockRouter, $httpRequest, new MockResponse, $requestStorage); + +$applicationRequest->setFlag(Application\Request::RESTORED); +Assert::equal($applicationRequest, $application->createInitialRequest()); diff --git a/tests/Application/mocks.php b/tests/Application/mocks.php new file mode 100644 index 000000000..3fa41d477 --- /dev/null +++ b/tests/Application/mocks.php @@ -0,0 +1,112 @@ +mockSection; + } + + public function getFlashId() + { + return $this->mockFlashId; + } + + public function getFlashSection($section) + { + $this->mockFlashId = 'x'; + return $this->mockFlashSection; + } + +} + + +class MockSessionSection extends Nette\Object implements \ArrayAccess +{ + public $data; + + public function __isset($name) + { + return isset($this->data[$name]); + } + + public function __set($name, $value) + { + $this->data[$name] = $value; + } + + public function &__get($name) + { + return $this->data[$name]; + } + + public function setExpiration($expiraton, $variables = NULL) + {} + + public function offsetExists($name) + { + return $this->__isset($name); + } + + public function offsetSet($name, $value) + { + $this->__set($name, $value); + } + + public function offsetGet($name) + { + return $this->__get($name); + } + + public function offsetUnset($name) + { + $this->__unset($name); + } +} + + +class MockUser extends Nette\Security\User +{ + public $mockIdentity; + + public function __construct() + {} + + public function getIdentity() + { + return $this->mockIdentity; + } +} + + +class MockPresenterFactory extends Nette\Object implements Nette\Application\IPresenterFactory +{ + function getPresenterClass(& $name) + { + return str_replace(':', 'Module\\', $name) . 'Presenter'; + } + + function createPresenter($name) + {} +} + + +class MockRouter extends Nette\Object implements Nette\Application\IRouter +{ + function match(Nette\Http\IRequest $httpRequest) + {} + + function constructUrl(Nette\Application\Request $appRequest, Nette\Http\Url $refUrl) + {} +} + + +class MockResponse extends \Nette\Http\Response +{ + public function __construct() {} +}