diff --git a/src/Application/Application.php b/src/Application/Application.php index 42dafccce..3b54eb404 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] = [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 4e5968fed..4242c49bf 100644 --- a/src/Application/UI/Presenter.php +++ b/src/Application/UI/Presenter.php @@ -42,7 +42,7 @@ abstract class Presenter extends Control implements Application\IPresenter /** @internal special parameter key */ const SIGNAL_KEY = 'do', ACTION_KEY = 'action', - FLASH_KEY = '_fid', + FLASH_KEY = Http\Session::FLASH_KEY, DEFAULT_ACTION = 'default'; /** @var int */ @@ -126,6 +126,9 @@ abstract class Presenter extends Control implements Application\IPresenter /** @var ITemplateFactory */ private $templateFactory; + /** @var Application\IRequestStorage */ + private $requestStorage; + public function __construct() { @@ -237,10 +240,6 @@ public function run(Application\Request $request) } } catch (Application\AbortException $e) { } - if ($this->hasFlashSession()) { - $this->getFlashSession()->setExpiration($this->response instanceof Responses\RedirectResponse ? '+ 30 seconds' : '+ 3 seconds'); - } - // SHUTDOWN $this->onShutdown($this, $this->response); $this->shutdown($this->response); @@ -955,8 +954,8 @@ protected function createRequest($component, $destination, array $args, $mode) $args[self::SIGNAL_KEY] = $component->getParameterId($signal); $current = $current && $args[self::SIGNAL_KEY] === $this->getParameter(self::SIGNAL_KEY); } - if (($mode === 'redirect' || $mode === 'forward') && $this->hasFlashSession()) { - $args[self::FLASH_KEY] = $this->getParameter(self::FLASH_KEY); + if ($mode === 'redirect' || $mode === 'forward') { + $args[Http\Session::FLASH_KEY] = $this->getSession()->getFlashId(); } $this->lastCreatedRequest = new Application\Request( @@ -1081,14 +1080,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] = [$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); } @@ -1099,17 +1094,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[self::FLASH_KEY] = $this->getParameter(self::FLASH_KEY); - $request->setParameters($params); - $this->sendResponse(new Responses\ForwardResponse($request)); } @@ -1289,8 +1278,7 @@ public function popGlobalParameters($id) */ public function hasFlashSession() { - return !empty($this->params[self::FLASH_KEY]) - && $this->getSession()->hasSection('Nette.Application.Flash/' . $this->params[self::FLASH_KEY]); + return (bool) $this->getSession()->getFlashId(); } @@ -1300,10 +1288,7 @@ public function hasFlashSession() */ public function getFlashSession() { - if (empty($this->params[self::FLASH_KEY])) { - $this->params[self::FLASH_KEY] = Nette\Utils\Random::generate(4); - } - return $this->getSession('Nette.Application.Flash/' . $this->params[self::FLASH_KEY]); + return $this->getSession()->getFlashSection('Nette.Application.Flash'); } @@ -1311,7 +1296,8 @@ public function getFlashSession() public function injectPrimary(Nette\DI\Container $context = NULL, Application\IPresenterFactory $presenterFactory = NULL, 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."); @@ -1325,6 +1311,7 @@ public function injectPrimary(Nette\DI\Container $context = NULL, Application\IP $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 95a1ea528..eb95cb19d 100644 --- a/tests/Application/Presenter.storeRequest().phpt +++ b/tests/Application/Presenter.storeRequest().phpt @@ -87,10 +87,12 @@ $presenter->injectPrimary( NULL, NULL, new Application\Routers\SimpleRouter, - new Http\Request(new Http\UrlScript), + 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); @@ -105,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([$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..36c59bb85 --- /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', ['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() {} +}