PageRenderTime 46ms CodeModel.GetById 11ms RepoModel.GetById 1ms app.codeStats 0ms

/libs/Nette/Application/Presenter.php

https://github.com/Vrtak-CZ/ORM-benchmark
PHP | 1266 lines | 971 code | 144 blank | 151 comment | 93 complexity | 057602d29f0b048987b830f3e238fc8b MD5 | raw file
  1. <?php
  2. /**
  3. * Nette Framework
  4. *
  5. * @copyright Copyright (c) 2004, 2010 David Grudl
  6. * @license http://nette.org/license Nette license
  7. * @link http://nette.org
  8. * @category Nette
  9. * @package Nette\Application
  10. */
  11. namespace Nette\Application;
  12. use Nette,
  13. Nette\Environment;
  14. /**
  15. * Presenter object represents a webpage instance. It executes all the logic for the request.
  16. *
  17. * @copyright Copyright (c) 2004, 2010 David Grudl
  18. * @package Nette\Application
  19. *
  20. * @property-read PresenterRequest $request
  21. * @property-read array $signal
  22. * @property-read string $action
  23. * @property string $view
  24. * @property string $layout
  25. * @property-read mixed $payload
  26. * @property-read Application $application
  27. */
  28. abstract class Presenter extends Control implements IPresenter
  29. {
  30. /**#@+ bad link handling {@link Presenter::$invalidLinkMode} */
  31. const INVALID_LINK_SILENT = 1;
  32. const INVALID_LINK_WARNING = 2;
  33. const INVALID_LINK_EXCEPTION = 3;
  34. /**#@-*/
  35. /**#@+ @ignore internal special parameter key */
  36. const SIGNAL_KEY = 'do';
  37. const ACTION_KEY = 'action';
  38. const FLASH_KEY = '_fid';
  39. /**#@-*/
  40. /** @var string */
  41. public static $defaultAction = 'default';
  42. /** @var int */
  43. public static $invalidLinkMode;
  44. /** @var array of function(Presenter $sender, IPresenterResponse $response = NULL); Occurs when the presenter is shutting down */
  45. public $onShutdown;
  46. /** @var PresenterRequest */
  47. private $request;
  48. /** @var IPresenterResponse */
  49. private $response;
  50. /** @var bool automatically call canonicalize() */
  51. public $autoCanonicalize = TRUE;
  52. /** @var bool use absolute Urls or paths? */
  53. public $absoluteUrls = FALSE;
  54. /** @var array */
  55. private $globalParams;
  56. /** @var array */
  57. private $globalState;
  58. /** @var array */
  59. private $globalStateSinces;
  60. /** @var string */
  61. private $action;
  62. /** @var string */
  63. private $view;
  64. /** @var string */
  65. private $layout;
  66. /** @var stdClass */
  67. private $payload;
  68. /** @var string */
  69. private $signalReceiver;
  70. /** @var string */
  71. private $signal;
  72. /** @var bool */
  73. private $ajaxMode;
  74. /** @var bool */
  75. private $startupCheck;
  76. /** @var PresenterRequest */
  77. private $lastCreatedRequest;
  78. /** @var array */
  79. private $lastCreatedRequestFlag;
  80. /**
  81. * @return PresenterRequest
  82. */
  83. final public function getRequest()
  84. {
  85. return $this->request;
  86. }
  87. /**
  88. * Returns self.
  89. * @return Presenter
  90. */
  91. final public function getPresenter($need = TRUE)
  92. {
  93. return $this;
  94. }
  95. /**
  96. * Returns a name that uniquely identifies component.
  97. * @return string
  98. */
  99. final public function getUniqueId()
  100. {
  101. return '';
  102. }
  103. /********************* interface IPresenter ****************d*g**/
  104. /**
  105. * @param PresenterRequest
  106. * @return IPresenterResponse
  107. */
  108. public function run(PresenterRequest $request)
  109. {
  110. try {
  111. // STARTUP
  112. $this->request = $request;
  113. $this->payload = (object) NULL;
  114. $this->setParent($this->getParent(), $request->getPresenterName());
  115. $this->initGlobalParams();
  116. $this->startup();
  117. if (!$this->startupCheck) {
  118. $class = $this->reflection->getMethod('startup')->getDeclaringClass()->getName();
  119. throw new \InvalidStateException("Method $class::startup() or its descendant doesn't call parent::startup().");
  120. }
  121. // calls $this->action<Action>()
  122. $this->tryCall($this->formatActionMethod($this->getAction()), $this->params);
  123. if ($this->autoCanonicalize) {
  124. $this->canonicalize();
  125. }
  126. if ($this->getHttpRequest()->isMethod('head')) {
  127. $this->terminate();
  128. }
  129. // SIGNAL HANDLING
  130. // calls $this->handle<Signal>()
  131. $this->processSignal();
  132. // RENDERING VIEW
  133. $this->beforeRender();
  134. // calls $this->render<View>()
  135. $this->tryCall($this->formatRenderMethod($this->getView()), $this->params);
  136. $this->afterRender();
  137. // save component tree persistent state
  138. $this->saveGlobalState();
  139. if ($this->isAjax()) {
  140. $this->payload->state = $this->getGlobalState();
  141. }
  142. // finish template rendering
  143. $this->sendTemplate();
  144. } catch (AbortException $e) {
  145. // continue with shutting down
  146. } /* finally */ {
  147. if ($this->isAjax()) try {
  148. $hasPayload = (array) $this->payload; unset($hasPayload['state']);
  149. if ($this->response instanceof RenderResponse && ($this->isControlInvalid() || $hasPayload)) { // snippets - TODO
  150. Nette\Templates\SnippetHelper::$outputAllowed = FALSE;
  151. $this->response->send();
  152. $this->sendPayload();
  153. } elseif (!$this->response && $hasPayload) { // back compatibility for use terminate() instead of sendPayload()
  154. $this->sendPayload();
  155. }
  156. } catch (AbortException $e) { }
  157. if ($this->hasFlashSession()) {
  158. $this->getFlashSession()->setExpiration($this->response instanceof RedirectingResponse ? '+ 30 seconds': '+ 3 seconds');
  159. }
  160. // SHUTDOWN
  161. $this->onShutdown($this, $this->response);
  162. $this->shutdown($this->response);
  163. return $this->response;
  164. }
  165. }
  166. /**
  167. * @return void
  168. */
  169. protected function startup()
  170. {
  171. $this->startupCheck = TRUE;
  172. }
  173. /**
  174. * Common render method.
  175. * @return void
  176. */
  177. protected function beforeRender()
  178. {
  179. }
  180. /**
  181. * Common render method.
  182. * @return void
  183. */
  184. protected function afterRender()
  185. {
  186. }
  187. /**
  188. * @param IPresenterResponse optional catched exception
  189. * @return void
  190. */
  191. protected function shutdown($response)
  192. {
  193. }
  194. /********************* signal handling ****************d*g**/
  195. /**
  196. * @return void
  197. * @throws BadSignalException
  198. */
  199. public function processSignal()
  200. {
  201. if ($this->signal === NULL) return;
  202. $component = $this->signalReceiver === '' ? $this : $this->getComponent($this->signalReceiver, FALSE);
  203. if ($component === NULL) {
  204. throw new BadSignalException("The signal receiver component '$this->signalReceiver' is not found.");
  205. } elseif (!$component instanceof ISignalReceiver) {
  206. throw new BadSignalException("The signal receiver component '$this->signalReceiver' is not ISignalReceiver implementor.");
  207. }
  208. $component->signalReceived($this->signal);
  209. $this->signal = NULL;
  210. }
  211. /**
  212. * Returns pair signal receiver and name.
  213. * @return array|NULL
  214. */
  215. final public function getSignal()
  216. {
  217. return $this->signal === NULL ? NULL : array($this->signalReceiver, $this->signal);
  218. }
  219. /**
  220. * Checks if the signal receiver is the given one.
  221. * @param mixed component or its id
  222. * @param string signal name (optional)
  223. * @return bool
  224. */
  225. final public function isSignalReceiver($component, $signal = NULL)
  226. {
  227. if ($component instanceof Nette\Component) {
  228. $component = $component === $this ? '' : $component->lookupPath(__CLASS__, TRUE);
  229. }
  230. if ($this->signal === NULL) {
  231. return FALSE;
  232. } elseif ($signal === TRUE) {
  233. return $component === ''
  234. || strncmp($this->signalReceiver . '-', $component . '-', strlen($component) + 1) === 0;
  235. } elseif ($signal === NULL) {
  236. return $this->signalReceiver === $component;
  237. } else {
  238. return $this->signalReceiver === $component && strcasecmp($signal, $this->signal) === 0;
  239. }
  240. }
  241. /********************* rendering ****************d*g**/
  242. /**
  243. * Returns current action name.
  244. * @return string
  245. */
  246. final public function getAction($fullyQualified = FALSE)
  247. {
  248. return $fullyQualified ? ':' . $this->getName() . ':' . $this->action : $this->action;
  249. }
  250. /**
  251. * Changes current action. Only alphanumeric characters are allowed.
  252. * @param string
  253. * @return void
  254. */
  255. public function changeAction($action)
  256. {
  257. if (Nette\String::match($action, "#^[a-zA-Z0-9][a-zA-Z0-9_\x7f-\xff]*$#")) {
  258. $this->action = $action;
  259. $this->view = $action;
  260. } else {
  261. throw new BadRequestException("Action name '$action' is not alphanumeric string.");
  262. }
  263. }
  264. /**
  265. * Returns current view.
  266. * @return string
  267. */
  268. final public function getView()
  269. {
  270. return $this->view;
  271. }
  272. /**
  273. * Changes current view. Any name is allowed.
  274. * @param string
  275. * @return Presenter provides a fluent interface
  276. */
  277. public function setView($view)
  278. {
  279. $this->view = (string) $view;
  280. return $this;
  281. }
  282. /**
  283. * Returns current layout name.
  284. * @return string|FALSE
  285. */
  286. final public function getLayout()
  287. {
  288. return $this->layout;
  289. }
  290. /**
  291. * Changes or disables layout.
  292. * @param string|FALSE
  293. * @return Presenter provides a fluent interface
  294. */
  295. public function setLayout($layout)
  296. {
  297. $this->layout = $layout === FALSE ? FALSE : (string) $layout;
  298. return $this;
  299. }
  300. /**
  301. * @return void
  302. * @throws BadRequestException if no template found
  303. * @throws AbortException
  304. */
  305. public function sendTemplate()
  306. {
  307. $template = $this->getTemplate();
  308. if (!$template) return;
  309. if ($template instanceof Nette\Templates\IFileTemplate && !$template->getFile()) {
  310. // content template
  311. $files = $this->formatTemplateFiles($this->getName(), $this->view);
  312. foreach ($files as $file) {
  313. if (is_file($file)) {
  314. $template->setFile($file);
  315. break;
  316. }
  317. }
  318. if (!$template->getFile()) {
  319. $file = str_replace(Environment::getVariable('appDir'), "\xE2\x80\xA6", reset($files));
  320. throw new BadRequestException("Page not found. Missing template '$file'.");
  321. }
  322. // layout template
  323. if ($this->layout !== FALSE) {
  324. $files = $this->formatLayoutTemplateFiles($this->getName(), $this->layout ? $this->layout : 'layout');
  325. foreach ($files as $file) {
  326. if (is_file($file)) {
  327. $template->layout = $file;
  328. $template->_extends = $file;
  329. break;
  330. }
  331. }
  332. if (empty($template->layout) && $this->layout !== NULL) {
  333. $file = str_replace(Environment::getVariable('appDir'), "\xE2\x80\xA6", reset($files));
  334. throw new \FileNotFoundException("Layout not found. Missing template '$file'.");
  335. }
  336. }
  337. }
  338. $this->terminate(new RenderResponse($template));
  339. }
  340. /**
  341. * Formats layout template file names.
  342. * @param string
  343. * @param string
  344. * @return array
  345. */
  346. public function formatLayoutTemplateFiles($presenter, $layout)
  347. {
  348. $appDir = Environment::getVariable('appDir');
  349. $path = '/' . str_replace(':', 'Module/', $presenter);
  350. $pathP = substr_replace($path, '/templates', strrpos($path, '/'), 0);
  351. $list = array(
  352. "$appDir$pathP/@$layout.phtml",
  353. "$appDir$pathP.@$layout.phtml",
  354. );
  355. while (($path = substr($path, 0, strrpos($path, '/'))) !== FALSE) {
  356. $list[] = "$appDir$path/templates/@$layout.phtml";
  357. }
  358. return $list;
  359. }
  360. /**
  361. * Formats view template file names.
  362. * @param string
  363. * @param string
  364. * @return array
  365. */
  366. public function formatTemplateFiles($presenter, $view)
  367. {
  368. $appDir = Environment::getVariable('appDir');
  369. $path = '/' . str_replace(':', 'Module/', $presenter);
  370. $pathP = substr_replace($path, '/templates', strrpos($path, '/'), 0);
  371. $path = substr_replace($path, '/templates', strrpos($path, '/'));
  372. return array(
  373. "$appDir$pathP/$view.phtml",
  374. "$appDir$pathP.$view.phtml",
  375. "$appDir$path/@global.$view.phtml",
  376. );
  377. }
  378. /**
  379. * Formats action method name.
  380. * @param string
  381. * @return string
  382. */
  383. protected static function formatActionMethod($action)
  384. {
  385. return 'action' . $action;
  386. }
  387. /**
  388. * Formats render view method name.
  389. * @param string
  390. * @return string
  391. */
  392. protected static function formatRenderMethod($view)
  393. {
  394. return 'render' . $view;
  395. }
  396. /********************* partial AJAX rendering ****************d*g**/
  397. /**
  398. * @return stdClass
  399. */
  400. final public function getPayload()
  401. {
  402. return $this->payload;
  403. }
  404. /**
  405. * Is AJAX request?
  406. * @return bool
  407. */
  408. public function isAjax()
  409. {
  410. if ($this->ajaxMode === NULL) {
  411. $this->ajaxMode = $this->getHttpRequest()->isAjax();
  412. }
  413. return $this->ajaxMode;
  414. }
  415. /**
  416. * Sends AJAX payload to the output.
  417. * @return void
  418. * @throws AbortException
  419. */
  420. public function sendPayload()
  421. {
  422. $this->terminate(new JsonResponse($this->payload));
  423. }
  424. /********************* navigation & flow ****************d*g**/
  425. /**
  426. * Forward to another presenter or action.
  427. * @param string|PresenterRequest
  428. * @param array|mixed
  429. * @return void
  430. * @throws AbortException
  431. */
  432. public function forward($destination, $args = array())
  433. {
  434. if ($destination instanceof PresenterRequest) {
  435. $this->terminate(new ForwardingResponse($destination));
  436. } elseif (!is_array($args)) {
  437. $args = func_get_args();
  438. array_shift($args);
  439. }
  440. $this->createRequest($this, $destination, $args, 'forward');
  441. $this->terminate(new ForwardingResponse($this->lastCreatedRequest));
  442. }
  443. /**
  444. * Redirect to another URL and ends presenter execution.
  445. * @param string
  446. * @param int HTTP error code
  447. * @return void
  448. * @throws AbortException
  449. */
  450. public function redirectUri($uri, $code = NULL)
  451. {
  452. if ($this->isAjax()) {
  453. $this->payload->redirect = (string) $uri;
  454. $this->sendPayload();
  455. } elseif (!$code) {
  456. $code = $this->getHttpRequest()->isMethod('post') ? Nette\Web\IHttpResponse::S303_POST_GET : Nette\Web\IHttpResponse::S302_FOUND;
  457. }
  458. $this->terminate(new RedirectingResponse($uri, $code));
  459. }
  460. /**
  461. * Link to myself.
  462. * @return string
  463. */
  464. public function backlink()
  465. {
  466. return $this->getAction(TRUE);
  467. }
  468. /**
  469. * Returns the last created PresenterRequest.
  470. * @return PresenterRequest
  471. */
  472. public function getLastCreatedRequest()
  473. {
  474. return $this->lastCreatedRequest;
  475. }
  476. /**
  477. * Returns the last created PresenterRequest flag.
  478. * @param string
  479. * @return bool
  480. */
  481. public function getLastCreatedRequestFlag($flag)
  482. {
  483. return !empty($this->lastCreatedRequestFlag[$flag]);
  484. }
  485. /**
  486. * Correctly terminates presenter.
  487. * @param IPresenterResponse
  488. * @return void
  489. * @throws AbortException
  490. */
  491. public function terminate(IPresenterResponse $response = NULL)
  492. {
  493. $this->response = $response;
  494. throw new AbortException();
  495. }
  496. /**
  497. * Conditional redirect to canonicalized URI.
  498. * @return void
  499. * @throws AbortException
  500. */
  501. public function canonicalize()
  502. {
  503. if (!$this->isAjax() && ($this->request->isMethod('get') || $this->request->isMethod('head'))) {
  504. $uri = $this->createRequest($this, $this->action, $this->getGlobalState() + $this->request->params, 'redirectX');
  505. if ($uri !== NULL && !$this->getHttpRequest()->getUri()->isEqual($uri)) {
  506. $this->terminate(new RedirectingResponse($uri, Nette\Web\IHttpResponse::S301_MOVED_PERMANENTLY));
  507. }
  508. }
  509. }
  510. /**
  511. * Attempts to cache the sent entity by its last modification date
  512. * @param string|int|DateTime last modified time
  513. * @param string strong entity tag validator
  514. * @param mixed optional expiration time
  515. * @return void
  516. * @throws AbortException
  517. * @deprecated
  518. */
  519. public function lastModified($lastModified, $etag = NULL, $expire = NULL)
  520. {
  521. if (!Environment::isProduction()) {
  522. return;
  523. }
  524. if ($expire !== NULL) {
  525. $this->getHttpResponse()->setExpiration($expire);
  526. }
  527. if (!$this->getHttpContext()->isModified($lastModified, $etag)) {
  528. $this->terminate();
  529. }
  530. }
  531. /**
  532. * PresenterRequest/URL factory.
  533. * @param PresenterComponent base
  534. * @param string destination in format "[[module:]presenter:]action" or "signal!"
  535. * @param array array of arguments
  536. * @param string forward|redirect|link
  537. * @return string URL
  538. * @throws InvalidLinkException
  539. * @ignore internal
  540. */
  541. final protected function createRequest($component, $destination, array $args, $mode)
  542. {
  543. // note: createRequest supposes that saveState(), run() & tryCall() behaviour is final
  544. // cached services for better performance
  545. static $presenterLoader, $router, $httpRequest;
  546. if ($presenterLoader === NULL) {
  547. $presenterLoader = $this->getApplication()->getPresenterLoader();
  548. $router = $this->getApplication()->getRouter();
  549. $httpRequest = $this->getHttpRequest();
  550. }
  551. $this->lastCreatedRequest = $this->lastCreatedRequestFlag = NULL;
  552. // PARSE DESTINATION
  553. // 1) fragment
  554. $a = strpos($destination, '#');
  555. if ($a === FALSE) {
  556. $fragment = '';
  557. } else {
  558. $fragment = substr($destination, $a);
  559. $destination = substr($destination, 0, $a);
  560. }
  561. // 2) ?query syntax
  562. $a = strpos($destination, '?');
  563. if ($a !== FALSE) {
  564. parse_str(substr($destination, $a + 1), $args); // requires disabled magic quotes
  565. $destination = substr($destination, 0, $a);
  566. }
  567. // 3) URL scheme
  568. $a = strpos($destination, '//');
  569. if ($a === FALSE) {
  570. $scheme = FALSE;
  571. } else {
  572. $scheme = substr($destination, 0, $a);
  573. $destination = substr($destination, $a + 2);
  574. }
  575. // 4) signal or empty
  576. if (!($component instanceof Presenter) || substr($destination, -1) === '!') {
  577. $signal = rtrim($destination, '!');
  578. $a = strrpos($signal, ':');
  579. if ($a !== FALSE) {
  580. $component = $component->getComponent(strtr(substr($signal, 0, $a), ':', '-'));
  581. $signal = (string) substr($signal, $a + 1);
  582. }
  583. if ($signal == NULL) { // intentionally ==
  584. throw new InvalidLinkException("Signal must be non-empty string.");
  585. }
  586. $destination = 'this';
  587. }
  588. if ($destination == NULL) { // intentionally ==
  589. throw new InvalidLinkException("Destination must be non-empty string.");
  590. }
  591. // 5) presenter: action
  592. $current = FALSE;
  593. $a = strrpos($destination, ':');
  594. if ($a === FALSE) {
  595. $action = $destination === 'this' ? $this->action : $destination;
  596. $presenter = $this->getName();
  597. $presenterClass = get_class($this);
  598. } else {
  599. $action = (string) substr($destination, $a + 1);
  600. if ($destination[0] === ':') { // absolute
  601. if ($a < 2) {
  602. throw new InvalidLinkException("Missing presenter name in '$destination'.");
  603. }
  604. $presenter = substr($destination, 1, $a - 1);
  605. } else { // relative
  606. $presenter = $this->getName();
  607. $b = strrpos($presenter, ':');
  608. if ($b === FALSE) { // no module
  609. $presenter = substr($destination, 0, $a);
  610. } else { // with module
  611. $presenter = substr($presenter, 0, $b + 1) . substr($destination, 0, $a);
  612. }
  613. }
  614. $presenterClass = $presenterLoader->getPresenterClass($presenter);
  615. }
  616. // PROCESS SIGNAL ARGUMENTS
  617. if (isset($signal)) { // $component must be IStatePersistent
  618. $reflection = new PresenterComponentReflection(get_class($component));
  619. if ($signal === 'this') { // means "no signal"
  620. $signal = '';
  621. if (array_key_exists(0, $args)) {
  622. throw new InvalidLinkException("Extra parameter for signal '{$reflection->name}:$signal!'.");
  623. }
  624. } elseif (strpos($signal, self::NAME_SEPARATOR) === FALSE) { // TODO: AppForm exception
  625. // counterpart of signalReceived() & tryCall()
  626. $method = $component->formatSignalMethod($signal);
  627. if (!$reflection->hasCallableMethod($method)) {
  628. throw new InvalidLinkException("Unknown signal '{$reflection->name}:$signal!'.");
  629. }
  630. if ($args) { // convert indexed parameters to named
  631. self::argsToParams(get_class($component), $method, $args);
  632. }
  633. }
  634. // counterpart of IStatePersistent
  635. if ($args && array_intersect_key($args, $reflection->getPersistentParams())) {
  636. $component->saveState($args);
  637. }
  638. if ($args && $component !== $this) {
  639. $prefix = $component->getUniqueId() . self::NAME_SEPARATOR;
  640. foreach ($args as $key => $val) {
  641. unset($args[$key]);
  642. $args[$prefix . $key] = $val;
  643. }
  644. }
  645. }
  646. // PROCESS ARGUMENTS
  647. if (is_subclass_of($presenterClass, __CLASS__)) {
  648. if ($action === '') {
  649. $action = $presenterClass::$defaultAction;
  650. }
  651. $current = ($action === '*' || $action === $this->action) && $presenterClass === get_class($this); // TODO
  652. $reflection = new PresenterComponentReflection($presenterClass);
  653. if ($args || $destination === 'this') {
  654. // counterpart of run() & tryCall()
  655. $method = $presenterClass::formatActionMethod($action);
  656. if (!$reflection->hasCallableMethod($method)) {
  657. $method = $presenterClass::formatRenderMethod($action);
  658. if (!$reflection->hasCallableMethod($method)) {
  659. $method = NULL;
  660. }
  661. }
  662. // convert indexed parameters to named
  663. if ($method === NULL) {
  664. if (array_key_exists(0, $args)) {
  665. throw new InvalidLinkException("Extra parameter for '$presenter:$action'.");
  666. }
  667. } elseif ($destination === 'this') {
  668. self::argsToParams($presenterClass, $method, $args, $this->params);
  669. } else {
  670. self::argsToParams($presenterClass, $method, $args);
  671. }
  672. }
  673. // counterpart of IStatePersistent
  674. if ($args && array_intersect_key($args, $reflection->getPersistentParams())) {
  675. $this->saveState($args, $reflection);
  676. }
  677. $globalState = $this->getGlobalState($destination === 'this' ? NULL : $presenterClass);
  678. if ($current && $args) {
  679. $tmp = $globalState + $this->params;
  680. foreach ($args as $key => $val) {
  681. if ((string) $val !== (isset($tmp[$key]) ? (string) $tmp[$key] : '')) {
  682. $current = FALSE;
  683. break;
  684. }
  685. }
  686. }
  687. $args += $globalState;
  688. }
  689. // ADD ACTION & SIGNAL & FLASH
  690. $args[self::ACTION_KEY] = $action;
  691. if (!empty($signal)) {
  692. $args[self::SIGNAL_KEY] = $component->getParamId($signal);
  693. $current = $current && $args[self::SIGNAL_KEY] === $this->getParam(self::SIGNAL_KEY);
  694. }
  695. if (($mode === 'redirect' || $mode === 'forward') && $this->hasFlashSession()) {
  696. $args[self::FLASH_KEY] = $this->getParam(self::FLASH_KEY);
  697. }
  698. $this->lastCreatedRequest = new PresenterRequest(
  699. $presenter,
  700. PresenterRequest::FORWARD,
  701. $args,
  702. array(),
  703. array()
  704. );
  705. $this->lastCreatedRequestFlag = array('current' => $current);
  706. if ($mode === 'forward') return;
  707. // CONSTRUCT URL
  708. $uri = $router->constructUrl($this->lastCreatedRequest, $httpRequest);
  709. if ($uri === NULL) {
  710. unset($args[self::ACTION_KEY]);
  711. $params = urldecode(http_build_query($args, NULL, ', '));
  712. throw new InvalidLinkException("No route for $presenter:$action($params)");
  713. }
  714. // make URL relative if possible
  715. if ($mode === 'link' && $scheme === FALSE && !$this->absoluteUrls) {
  716. $hostUri = $httpRequest->getUri()->getHostUri();
  717. if (strncmp($uri, $hostUri, strlen($hostUri)) === 0) {
  718. $uri = substr($uri, strlen($hostUri));
  719. }
  720. }
  721. return $uri . $fragment;
  722. }
  723. /**
  724. * Converts list of arguments to named parameters.
  725. * @param string class name
  726. * @param string method name
  727. * @param array arguments
  728. * @param array supplemental arguments
  729. * @return void
  730. * @throws InvalidLinkException
  731. */
  732. private static function argsToParams($class, $method, & $args, $supplemental = array())
  733. {
  734. static $cache;
  735. $params = & $cache[strtolower($class . ':' . $method)];
  736. if ($params === NULL) {
  737. $params = Nette\Reflection\MethodReflection::from($class, $method)->getDefaultParameters();
  738. }
  739. $i = 0;
  740. foreach ($params as $name => $def) {
  741. if (array_key_exists($i, $args)) {
  742. $args[$name] = $args[$i];
  743. unset($args[$i]);
  744. $i++;
  745. } elseif (array_key_exists($name, $args)) {
  746. // continue with process
  747. } elseif (array_key_exists($name, $supplemental)) {
  748. $args[$name] = $supplemental[$name];
  749. } else {
  750. continue;
  751. }
  752. if ($def === NULL) {
  753. if ((string) $args[$name] === '') $args[$name] = NULL; // value transmit is unnecessary
  754. } else {
  755. settype($args[$name], gettype($def));
  756. if ($args[$name] === $def) $args[$name] = NULL;
  757. }
  758. }
  759. if (array_key_exists($i, $args)) {
  760. throw new InvalidLinkException("Extra parameter for signal '$class:$method'.");
  761. }
  762. }
  763. /**
  764. * Invalid link handler. Descendant can override this method to change default behaviour.
  765. * @param InvalidLinkException
  766. * @return string
  767. * @throws InvalidLinkException
  768. */
  769. protected function handleInvalidLink($e)
  770. {
  771. if (self::$invalidLinkMode === NULL) {
  772. self::$invalidLinkMode = Environment::isProduction()
  773. ? self::INVALID_LINK_SILENT : self::INVALID_LINK_WARNING;
  774. }
  775. if (self::$invalidLinkMode === self::INVALID_LINK_SILENT) {
  776. return '#';
  777. } elseif (self::$invalidLinkMode === self::INVALID_LINK_WARNING) {
  778. return 'error: ' . $e->getMessage();
  779. } else { // self::INVALID_LINK_EXCEPTION
  780. throw $e;
  781. }
  782. }
  783. /********************* interface IStatePersistent ****************d*g**/
  784. /**
  785. * Returns array of persistent components.
  786. * This default implementation detects components by class-level annotation @persistent(cmp1, cmp2).
  787. * @return array
  788. */
  789. public static function getPersistentComponents()
  790. {
  791. return (array) Nette\Reflection\ClassReflection::from(get_called_class())->getAnnotation('persistent');
  792. }
  793. /**
  794. * Saves state information for all subcomponents to $this->globalState.
  795. * @return array
  796. */
  797. private function getGlobalState($forClass = NULL)
  798. {
  799. $sinces = & $this->globalStateSinces;
  800. if ($this->globalState === NULL) {
  801. $state = array();
  802. foreach ($this->globalParams as $id => $params) {
  803. $prefix = $id . self::NAME_SEPARATOR;
  804. foreach ($params as $key => $val) {
  805. $state[$prefix . $key] = $val;
  806. }
  807. }
  808. $this->saveState($state, $forClass ? new PresenterComponentReflection($forClass) : NULL);
  809. if ($sinces === NULL) {
  810. $sinces = array();
  811. foreach ($this->getReflection()->getPersistentParams() as $nm => $meta) {
  812. $sinces[$nm] = $meta['since'];
  813. }
  814. }
  815. $components = $this->getReflection()->getPersistentComponents();
  816. $iterator = $this->getComponents(TRUE, 'Nette\Application\IStatePersistent');
  817. foreach ($iterator as $name => $component)
  818. {
  819. if ($iterator->getDepth() === 0) {
  820. // counts with RecursiveIteratorIterator::SELF_FIRST
  821. $since = isset($components[$name]['since']) ? $components[$name]['since'] : FALSE; // FALSE = nonpersistent
  822. }
  823. $prefix = $component->getUniqueId() . self::NAME_SEPARATOR;
  824. $params = array();
  825. $component->saveState($params);
  826. foreach ($params as $key => $val) {
  827. $state[$prefix . $key] = $val;
  828. $sinces[$prefix . $key] = $since;
  829. }
  830. }
  831. } else {
  832. $state = $this->globalState;
  833. }
  834. if ($forClass !== NULL) {
  835. $since = NULL;
  836. foreach ($state as $key => $foo) {
  837. if (!isset($sinces[$key])) {
  838. $x = strpos($key, self::NAME_SEPARATOR);
  839. $x = $x === FALSE ? $key : substr($key, 0, $x);
  840. $sinces[$key] = isset($sinces[$x]) ? $sinces[$x] : FALSE;
  841. }
  842. if ($since !== $sinces[$key]) {
  843. $since = $sinces[$key];
  844. $ok = $since && (is_subclass_of($forClass, $since) || $forClass === $since);
  845. }
  846. if (!$ok) {
  847. unset($state[$key]);
  848. }
  849. }
  850. }
  851. return $state;
  852. }
  853. /**
  854. * Permanently saves state information for all subcomponents to $this->globalState.
  855. * @return void
  856. */
  857. protected function saveGlobalState()
  858. {
  859. // load lazy components
  860. foreach ($this->globalParams as $id => $foo) {
  861. $this->getComponent($id, FALSE);
  862. }
  863. $this->globalParams = array();
  864. $this->globalState = $this->getGlobalState();
  865. }
  866. /**
  867. * Initializes $this->globalParams, $this->signal & $this->signalReceiver, $this->action, $this->view. Called by run().
  868. * @return void
  869. * @throws BadRequestException if action name is not valid
  870. */
  871. private function initGlobalParams()
  872. {
  873. // init $this->globalParams
  874. $this->globalParams = array();
  875. $selfParams = array();
  876. $params = $this->request->getParams();
  877. if ($this->isAjax()) {
  878. $params = $this->request->getPost() + $params;
  879. }
  880. foreach ($params as $key => $value) {
  881. $a = strlen($key) > 2 ? strrpos($key, self::NAME_SEPARATOR, -2) : FALSE;
  882. if ($a === FALSE) {
  883. $selfParams[$key] = $value;
  884. } else {
  885. $this->globalParams[substr($key, 0, $a)][substr($key, $a + 1)] = $value;
  886. }
  887. }
  888. // init & validate $this->action & $this->view
  889. $this->changeAction(isset($selfParams[self::ACTION_KEY]) ? $selfParams[self::ACTION_KEY] : self::$defaultAction);
  890. // init $this->signalReceiver and key 'signal' in appropriate params array
  891. $this->signalReceiver = $this->getUniqueId();
  892. if (!empty($selfParams[self::SIGNAL_KEY])) {
  893. $param = $selfParams[self::SIGNAL_KEY];
  894. $pos = strrpos($param, '-');
  895. if ($pos) {
  896. $this->signalReceiver = substr($param, 0, $pos);
  897. $this->signal = substr($param, $pos + 1);
  898. } else {
  899. $this->signalReceiver = $this->getUniqueId();
  900. $this->signal = $param;
  901. }
  902. if ($this->signal == NULL) { // intentionally ==
  903. $this->signal = NULL;
  904. }
  905. }
  906. $this->loadState($selfParams);
  907. }
  908. /**
  909. * Pops parameters for specified component.
  910. * @param string component id
  911. * @return array
  912. */
  913. final public function popGlobalParams($id)
  914. {
  915. if (isset($this->globalParams[$id])) {
  916. $res = $this->globalParams[$id];
  917. unset($this->globalParams[$id]);
  918. return $res;
  919. } else {
  920. return array();
  921. }
  922. }
  923. /********************* flash session ****************d*g**/
  924. /**
  925. * Checks if a flash session namespace exists.
  926. * @return bool
  927. */
  928. public function hasFlashSession()
  929. {
  930. return !empty($this->params[self::FLASH_KEY])
  931. && $this->getSession()->hasNamespace('Nette.Application.Flash/' . $this->params[self::FLASH_KEY]);
  932. }
  933. /**
  934. * Returns session namespace provided to pass temporary data between redirects.
  935. * @return Nette\Web\SessionNamespace
  936. */
  937. public function getFlashSession()
  938. {
  939. if (empty($this->params[self::FLASH_KEY])) {
  940. $this->params[self::FLASH_KEY] = substr(md5(lcg_value()), 0, 4);
  941. }
  942. return $this->getSession('Nette.Application.Flash/' . $this->params[self::FLASH_KEY]);
  943. }
  944. /********************* backend ****************d*g**/
  945. /**
  946. * @return Nette\Web\HttpRequest
  947. */
  948. protected function getHttpRequest()
  949. {
  950. return Environment::getHttpRequest();
  951. }
  952. /**
  953. * @return Nette\Web\HttpResponse
  954. */
  955. protected function getHttpResponse()
  956. {
  957. return Environment::getHttpResponse();
  958. }
  959. /**
  960. * @return Nette\Web\HttpContext
  961. */
  962. protected function getHttpContext()
  963. {
  964. return Environment::getHttpContext();
  965. }
  966. /**
  967. * @return Application
  968. */
  969. public function getApplication()
  970. {
  971. return Environment::getApplication();
  972. }
  973. /**
  974. * @return Nette\Web\Session
  975. */
  976. protected function getSession($namespace = NULL)
  977. {
  978. return Environment::getSession($namespace);
  979. }
  980. /**
  981. * @return Nette\Web\User
  982. */
  983. protected function getUser()
  984. {
  985. return Environment::getUser();
  986. }
  987. }