PageRenderTime 56ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/www/libs/nette-dev/Application/Presenter.php

https://github.com/bazo/Mokuji
PHP | 1264 lines | 968 code | 142 blank | 154 comment | 93 complexity | 2ab92bbedc37dd99db3eaecc071f41fb MD5 | raw file
Possible License(s): BSD-3-Clause, MIT
  1. <?php
  2. /**
  3. * Nette Framework
  4. *
  5. * @copyright Copyright (c) 2004, 2010 David Grudl
  6. * @license http://nettephp.com/license Nette license
  7. * @link http://nettephp.com
  8. * @category Nette
  9. * @package Nette\Application
  10. */
  11. /**
  12. * Presenter object represents a webpage instance. It executes all the logic for the request.
  13. *
  14. * @copyright Copyright (c) 2004, 2010 David Grudl
  15. * @package Nette\Application
  16. *
  17. * @property-read PresenterRequest $request
  18. * @property-read array $signal
  19. * @property-read string $action
  20. * @property string $view
  21. * @property string $layout
  22. * @property-read mixed $payload
  23. * @property-read Application $application
  24. */
  25. abstract class Presenter extends Control implements IPresenter
  26. {
  27. /**#@+ bad link handling {@link Presenter::$invalidLinkMode} */
  28. const INVALID_LINK_SILENT = 1;
  29. const INVALID_LINK_WARNING = 2;
  30. const INVALID_LINK_EXCEPTION = 3;
  31. /**#@-*/
  32. /**#@+ @ignore internal special parameter key */
  33. const SIGNAL_KEY = 'do';
  34. const ACTION_KEY = 'action';
  35. const FLASH_KEY = '_fid';
  36. /**#@-*/
  37. /** @var string */
  38. public static $defaultAction = 'default';
  39. /** @var int */
  40. public static $invalidLinkMode;
  41. /** @var array of function(Presenter $sender, IPresenterResponse $response = NULL); Occurs when the presenter is shutting down */
  42. public $onShutdown;
  43. /** @var PresenterRequest */
  44. private $request;
  45. /** @var IPresenterResponse */
  46. private $response;
  47. /** @var bool automatically call canonicalize() */
  48. public $autoCanonicalize = TRUE;
  49. /** @var bool use absolute Urls or paths? */
  50. public $absoluteUrls = FALSE;
  51. /** @var array */
  52. private $globalParams;
  53. /** @var array */
  54. private $globalState;
  55. /** @var array */
  56. private $globalStateSinces;
  57. /** @var string */
  58. private $action;
  59. /** @var string */
  60. private $view;
  61. /** @var string */
  62. private $layout;
  63. /** @var stdClass */
  64. private $payload;
  65. /** @var string */
  66. private $signalReceiver;
  67. /** @var string */
  68. private $signal;
  69. /** @var bool */
  70. private $ajaxMode;
  71. /** @var bool */
  72. private $startupCheck;
  73. /** @var PresenterRequest */
  74. private $lastCreatedRequest;
  75. /** @var array */
  76. private $lastCreatedRequestFlag;
  77. /**
  78. * @return PresenterRequest
  79. */
  80. final public function getRequest()
  81. {
  82. return $this->request;
  83. }
  84. /**
  85. * Returns self.
  86. * @return Presenter
  87. */
  88. final public function getPresenter($need = TRUE)
  89. {
  90. return $this;
  91. }
  92. /**
  93. * Returns a name that uniquely identifies component.
  94. * @return string
  95. */
  96. final public function getUniqueId()
  97. {
  98. return '';
  99. }
  100. /********************* interface IPresenter ****************d*g**/
  101. /**
  102. * @param PresenterRequest
  103. * @return IPresenterResponse
  104. */
  105. public function run(PresenterRequest $request)
  106. {
  107. try {
  108. // STARTUP
  109. $this->request = $request;
  110. $this->payload = (object) NULL;
  111. $this->setParent($this->getParent(), $request->getPresenterName());
  112. $this->initGlobalParams();
  113. $this->startup();
  114. if (!$this->startupCheck) {
  115. $class = $this->reflection->getMethod('startup')->getDeclaringClass()->getName();
  116. trigger_error("Method $class::startup() or its descendant doesn't call parent::startup().", E_USER_WARNING);
  117. }
  118. // calls $this->action<Action>()
  119. $this->tryCall($this->formatActionMethod($this->getAction()), $this->params);
  120. if ($this->autoCanonicalize) {
  121. $this->canonicalize();
  122. }
  123. if ($this->getHttpRequest()->isMethod('head')) {
  124. $this->terminate();
  125. }
  126. // SIGNAL HANDLING
  127. // calls $this->handle<Signal>()
  128. $this->processSignal();
  129. // RENDERING VIEW
  130. $this->beforeRender();
  131. // calls $this->render<View>()
  132. $this->tryCall($this->formatRenderMethod($this->getView()), $this->params);
  133. $this->afterRender();
  134. // save component tree persistent state
  135. $this->saveGlobalState();
  136. if ($this->isAjax()) {
  137. $this->payload->state = $this->getGlobalState();
  138. }
  139. // finish template rendering
  140. $this->sendTemplate();
  141. } catch (AbortException $e) {
  142. // continue with shutting down
  143. } /* finally */ {
  144. if ($this->isAjax()) try {
  145. $hasPayload = (array) $this->payload; unset($hasPayload['state']);
  146. if ($this->response instanceof RenderResponse && ($this->isControlInvalid() || $hasPayload)) { // snippets - TODO
  147. SnippetHelper::$outputAllowed = FALSE;
  148. $this->response->send();
  149. $this->sendPayload();
  150. } elseif (!$this->response && $hasPayload) { // back compatibility for use terminate() instead of sendPayload()
  151. $this->sendPayload();
  152. }
  153. } catch (AbortException $e) { }
  154. if ($this->hasFlashSession()) {
  155. $this->getFlashSession()->setExpiration($this->response instanceof RedirectingResponse ? '+ 30 seconds': '+ 3 seconds');
  156. }
  157. // SHUTDOWN
  158. $this->onShutdown($this, $this->response);
  159. $this->shutdown($this->response);
  160. return $this->response;
  161. }
  162. }
  163. /**
  164. * @return void
  165. */
  166. protected function startup()
  167. {
  168. $this->startupCheck = TRUE;
  169. }
  170. /**
  171. * Common render method.
  172. * @return void
  173. */
  174. protected function beforeRender()
  175. {
  176. }
  177. /**
  178. * Common render method.
  179. * @return void
  180. */
  181. protected function afterRender()
  182. {
  183. }
  184. /**
  185. * @param IPresenterResponse optional catched exception
  186. * @return void
  187. */
  188. protected function shutdown($response)
  189. {
  190. }
  191. /********************* signal handling ****************d*g**/
  192. /**
  193. * @return void
  194. * @throws BadSignalException
  195. */
  196. public function processSignal()
  197. {
  198. if ($this->signal === NULL) return;
  199. $component = $this->signalReceiver === '' ? $this : $this->getComponent($this->signalReceiver, FALSE);
  200. if ($component === NULL) {
  201. throw new BadSignalException("The signal receiver component '$this->signalReceiver' is not found.");
  202. } elseif (!$component instanceof ISignalReceiver) {
  203. throw new BadSignalException("The signal receiver component '$this->signalReceiver' is not ISignalReceiver implementor.");
  204. }
  205. $component->signalReceived($this->signal);
  206. $this->signal = NULL;
  207. }
  208. /**
  209. * Returns pair signal receiver and name.
  210. * @return array|NULL
  211. */
  212. final public function getSignal()
  213. {
  214. return $this->signal === NULL ? NULL : array($this->signalReceiver, $this->signal);
  215. }
  216. /**
  217. * Checks if the signal receiver is the given one.
  218. * @param mixed component or its id
  219. * @param string signal name (optional)
  220. * @return bool
  221. */
  222. final public function isSignalReceiver($component, $signal = NULL)
  223. {
  224. if ($component instanceof Component) {
  225. $component = $component === $this ? '' : $component->lookupPath(__CLASS__, TRUE);
  226. }
  227. if ($this->signal === NULL) {
  228. return FALSE;
  229. } elseif ($signal === TRUE) {
  230. return $component === ''
  231. || strncmp($this->signalReceiver . '-', $component . '-', strlen($component) + 1) === 0;
  232. } elseif ($signal === NULL) {
  233. return $this->signalReceiver === $component;
  234. } else {
  235. return $this->signalReceiver === $component && strcasecmp($signal, $this->signal) === 0;
  236. }
  237. }
  238. /********************* rendering ****************d*g**/
  239. /**
  240. * Returns current action name.
  241. * @return string
  242. */
  243. final public function getAction($fullyQualified = FALSE)
  244. {
  245. return $fullyQualified ? ':' . $this->getName() . ':' . $this->action : $this->action;
  246. }
  247. /**
  248. * Changes current action. Only alphanumeric characters are allowed.
  249. * @param string
  250. * @return void
  251. */
  252. public function changeAction($action)
  253. {
  254. if (preg_match("#^[a-zA-Z0-9][a-zA-Z0-9_\x7f-\xff]*$#", $action)) {
  255. $this->action = $action;
  256. $this->view = $action;
  257. } else {
  258. throw new BadRequestException("Action name '$action' is not alphanumeric string.");
  259. }
  260. }
  261. /**
  262. * Returns current view.
  263. * @return string
  264. */
  265. final public function getView()
  266. {
  267. return $this->view;
  268. }
  269. /**
  270. * Changes current view. Any name is allowed.
  271. * @param string
  272. * @return Presenter provides a fluent interface
  273. */
  274. public function setView($view)
  275. {
  276. $this->view = (string) $view;
  277. return $this;
  278. }
  279. /**
  280. * Returns current layout name.
  281. * @return string|FALSE
  282. */
  283. final public function getLayout()
  284. {
  285. return $this->layout;
  286. }
  287. /**
  288. * Changes or disables layout.
  289. * @param string|FALSE
  290. * @return Presenter provides a fluent interface
  291. */
  292. public function setLayout($layout)
  293. {
  294. $this->layout = $layout === FALSE ? FALSE : (string) $layout;
  295. return $this;
  296. }
  297. /**
  298. * @return void
  299. * @throws BadRequestException if no template found
  300. * @throws AbortException
  301. */
  302. public function sendTemplate()
  303. {
  304. $template = $this->getTemplate();
  305. if (!$template) return;
  306. if ($template instanceof IFileTemplate && !$template->getFile()) {
  307. // content template
  308. $files = $this->formatTemplateFiles($this->getName(), $this->view);
  309. foreach ($files as $file) {
  310. if (is_file($file)) {
  311. $template->setFile($file);
  312. break;
  313. }
  314. }
  315. if (!$template->getFile()) {
  316. $file = str_replace(Environment::getVariable('appDir'), "\xE2\x80\xA6", reset($files));
  317. throw new BadRequestException("Page not found. Missing template '$file'.");
  318. }
  319. // layout template
  320. if ($this->layout !== FALSE) {
  321. $files = $this->formatLayoutTemplateFiles($this->getName(), $this->layout ? $this->layout : 'layout');
  322. foreach ($files as $file) {
  323. if (is_file($file)) {
  324. $template->layout = $file;
  325. $template->_extends = $file;
  326. break;
  327. }
  328. }
  329. if (empty($template->layout) && $this->layout !== NULL) {
  330. $file = str_replace(Environment::getVariable('appDir'), "\xE2\x80\xA6", reset($files));
  331. throw new FileNotFoundException("Layout not found. Missing template '$file'.");
  332. }
  333. }
  334. }
  335. $this->terminate(new RenderResponse($template));
  336. }
  337. /**
  338. * Formats layout template file names.
  339. * @param string
  340. * @param string
  341. * @return array
  342. */
  343. public function formatLayoutTemplateFiles($presenter, $layout)
  344. {
  345. $appDir = Environment::getVariable('appDir');
  346. $path = '/' . str_replace(':', 'Module/', $presenter);
  347. $pathP = substr_replace($path, '/templates', strrpos($path, '/'), 0);
  348. $list = array(
  349. "$appDir$pathP/@$layout.phtml",
  350. "$appDir$pathP.@$layout.phtml",
  351. );
  352. while (($path = substr($path, 0, strrpos($path, '/'))) !== FALSE) {
  353. $list[] = "$appDir$path/templates/@$layout.phtml";
  354. }
  355. return $list;
  356. }
  357. /**
  358. * Formats view template file names.
  359. * @param string
  360. * @param string
  361. * @return array
  362. */
  363. public function formatTemplateFiles($presenter, $view)
  364. {
  365. $appDir = Environment::getVariable('appDir');
  366. $path = '/' . str_replace(':', 'Module/', $presenter);
  367. $pathP = substr_replace($path, '/templates', strrpos($path, '/'), 0);
  368. $path = substr_replace($path, '/templates', strrpos($path, '/'));
  369. return array(
  370. "$appDir$pathP/$view.phtml",
  371. "$appDir$pathP.$view.phtml",
  372. "$appDir$path/@global.$view.phtml",
  373. );
  374. }
  375. /**
  376. * Formats action method name.
  377. * @param string
  378. * @return string
  379. */
  380. protected static function formatActionMethod($action)
  381. {
  382. return 'action' . $action;
  383. }
  384. /**
  385. * Formats render view method name.
  386. * @param string
  387. * @return string
  388. */
  389. protected static function formatRenderMethod($view)
  390. {
  391. return 'render' . $view;
  392. }
  393. /********************* partial AJAX rendering ****************d*g**/
  394. /**
  395. * @return stdClass
  396. */
  397. final public function getPayload()
  398. {
  399. return $this->payload;
  400. }
  401. /**
  402. * Is AJAX request?
  403. * @return bool
  404. */
  405. public function isAjax()
  406. {
  407. if ($this->ajaxMode === NULL) {
  408. $this->ajaxMode = $this->getHttpRequest()->isAjax();
  409. }
  410. return $this->ajaxMode;
  411. }
  412. /**
  413. * Sends AJAX payload to the output.
  414. * @return void
  415. * @throws AbortException
  416. */
  417. public function sendPayload()
  418. {
  419. $this->terminate(new JsonResponse($this->payload));
  420. }
  421. /********************* navigation & flow ****************d*g**/
  422. /**
  423. * Forward to another presenter or action.
  424. * @param string|PresenterRequest
  425. * @param array|mixed
  426. * @return void
  427. * @throws AbortException
  428. */
  429. public function forward($destination, $args = array())
  430. {
  431. if ($destination instanceof PresenterRequest) {
  432. $this->terminate(new ForwardingResponse($destination));
  433. } elseif (!is_array($args)) {
  434. $args = func_get_args();
  435. array_shift($args);
  436. }
  437. $this->createRequest($this, $destination, $args, 'forward');
  438. $this->terminate(new ForwardingResponse($this->lastCreatedRequest));
  439. }
  440. /**
  441. * Redirect to another URL and ends presenter execution.
  442. * @param string
  443. * @param int HTTP error code
  444. * @return void
  445. * @throws AbortException
  446. */
  447. public function redirectUri($uri, $code = NULL)
  448. {
  449. if ($this->isAjax()) {
  450. $this->payload->redirect = (string) $uri;
  451. $this->sendPayload();
  452. } elseif (!$code) {
  453. $code = $this->getHttpRequest()->isMethod('post') ? IHttpResponse::S303_POST_GET : IHttpResponse::S302_FOUND;
  454. }
  455. $this->terminate(new RedirectingResponse($uri, $code));
  456. }
  457. /**
  458. * Link to myself.
  459. * @return string
  460. */
  461. public function backlink()
  462. {
  463. return $this->getAction(TRUE);
  464. }
  465. /**
  466. * Returns the last created PresenterRequest.
  467. * @return PresenterRequest
  468. */
  469. public function getLastCreatedRequest()
  470. {
  471. return $this->lastCreatedRequest;
  472. }
  473. /**
  474. * Returns the last created PresenterRequest flag.
  475. * @param string
  476. * @return bool
  477. */
  478. public function getLastCreatedRequestFlag($flag)
  479. {
  480. return !empty($this->lastCreatedRequestFlag[$flag]);
  481. }
  482. /**
  483. * Correctly terminates presenter.
  484. * @param IPresenterResponse
  485. * @return void
  486. * @throws AbortException
  487. */
  488. public function terminate(IPresenterResponse $response = NULL)
  489. {
  490. $this->response = $response;
  491. throw new AbortException();
  492. }
  493. /**
  494. * Conditional redirect to canonicalized URI.
  495. * @return void
  496. * @throws AbortException
  497. */
  498. public function canonicalize()
  499. {
  500. if (!$this->isAjax() && ($this->request->isMethod('get') || $this->request->isMethod('head'))) {
  501. $uri = $this->createRequest($this, $this->action, $this->getGlobalState() + $this->request->params, 'redirectX');
  502. if ($uri !== NULL && !$this->getHttpRequest()->getUri()->isEqual($uri)) {
  503. $this->terminate(new RedirectingResponse($uri, IHttpResponse::S301_MOVED_PERMANENTLY));
  504. }
  505. }
  506. }
  507. /**
  508. * Attempts to cache the sent entity by its last modification date
  509. * @param string|int|DateTime last modified time
  510. * @param string strong entity tag validator
  511. * @param mixed optional expiration time
  512. * @return void
  513. * @throws AbortException
  514. * @deprecated
  515. */
  516. public function lastModified($lastModified, $etag = NULL, $expire = NULL)
  517. {
  518. if (!Environment::isProduction()) {
  519. return;
  520. }
  521. if ($expire !== NULL) {
  522. $this->getHttpResponse()->setExpiration($expire);
  523. }
  524. if (!$this->getHttpContext()->isModified($lastModified, $etag)) {
  525. $this->terminate();
  526. }
  527. }
  528. /**
  529. * PresenterRequest/URL factory.
  530. * @param PresenterComponent base
  531. * @param string destination in format "[[module:]presenter:]action" or "signal!"
  532. * @param array array of arguments
  533. * @param string forward|redirect|link
  534. * @return string URL
  535. * @throws InvalidLinkException
  536. * @ignore internal
  537. */
  538. final protected function createRequest($component, $destination, array $args, $mode)
  539. {
  540. // note: createRequest supposes that saveState(), run() & tryCall() behaviour is final
  541. // cached services for better performance
  542. static $presenterLoader, $router, $httpRequest;
  543. if ($presenterLoader === NULL) {
  544. $presenterLoader = $this->getApplication()->getPresenterLoader();
  545. $router = $this->getApplication()->getRouter();
  546. $httpRequest = $this->getHttpRequest();
  547. }
  548. $this->lastCreatedRequest = $this->lastCreatedRequestFlag = NULL;
  549. // PARSE DESTINATION
  550. // 1) fragment
  551. $a = strpos($destination, '#');
  552. if ($a === FALSE) {
  553. $fragment = '';
  554. } else {
  555. $fragment = substr($destination, $a);
  556. $destination = substr($destination, 0, $a);
  557. }
  558. // 2) ?query syntax
  559. $a = strpos($destination, '?');
  560. if ($a !== FALSE) {
  561. parse_str(substr($destination, $a + 1), $args); // requires disabled magic quotes
  562. $destination = substr($destination, 0, $a);
  563. }
  564. // 3) URL scheme
  565. $a = strpos($destination, '//');
  566. if ($a === FALSE) {
  567. $scheme = FALSE;
  568. } else {
  569. $scheme = substr($destination, 0, $a);
  570. $destination = substr($destination, $a + 2);
  571. }
  572. // 4) signal or empty
  573. if (!($component instanceof Presenter) || substr($destination, -1) === '!') {
  574. $signal = rtrim($destination, '!');
  575. $a = strrpos($signal, ':');
  576. if ($a !== FALSE) {
  577. $component = $component->getComponent(strtr(substr($signal, 0, $a), ':', '-'));
  578. $signal = (string) substr($signal, $a + 1);
  579. }
  580. if ($signal == NULL) { // intentionally ==
  581. throw new InvalidLinkException("Signal must be non-empty string.");
  582. }
  583. $destination = 'this';
  584. }
  585. if ($destination == NULL) { // intentionally ==
  586. throw new InvalidLinkException("Destination must be non-empty string.");
  587. }
  588. // 5) presenter: action
  589. $current = FALSE;
  590. $a = strrpos($destination, ':');
  591. if ($a === FALSE) {
  592. $action = $destination === 'this' ? $this->action : $destination;
  593. $presenter = $this->getName();
  594. $presenterClass = get_class($this);
  595. } else {
  596. $action = (string) substr($destination, $a + 1);
  597. if ($destination[0] === ':') { // absolute
  598. if ($a < 2) {
  599. throw new InvalidLinkException("Missing presenter name in '$destination'.");
  600. }
  601. $presenter = substr($destination, 1, $a - 1);
  602. } else { // relative
  603. $presenter = $this->getName();
  604. $b = strrpos($presenter, ':');
  605. if ($b === FALSE) { // no module
  606. $presenter = substr($destination, 0, $a);
  607. } else { // with module
  608. $presenter = substr($presenter, 0, $b + 1) . substr($destination, 0, $a);
  609. }
  610. }
  611. $presenterClass = $presenterLoader->getPresenterClass($presenter);
  612. }
  613. // PROCESS SIGNAL ARGUMENTS
  614. if (isset($signal)) { // $component must be IStatePersistent
  615. $reflection = new PresenterComponentReflection(get_class($component));
  616. if ($signal === 'this') { // means "no signal"
  617. $signal = '';
  618. if (array_key_exists(0, $args)) {
  619. throw new InvalidLinkException("Extra parameter for signal '{$reflection->name}:$signal!'.");
  620. }
  621. } elseif (strpos($signal, self::NAME_SEPARATOR) === FALSE) { // TODO: AppForm exception
  622. // counterpart of signalReceived() & tryCall()
  623. $method = $component->formatSignalMethod($signal);
  624. if (!$reflection->hasCallableMethod($method)) {
  625. throw new InvalidLinkException("Unknown signal '{$reflection->name}:$signal!'.");
  626. }
  627. if ($args) { // convert indexed parameters to named
  628. self::argsToParams(get_class($component), $method, $args);
  629. }
  630. }
  631. // counterpart of IStatePersistent
  632. if ($args && array_intersect_key($args, $reflection->getPersistentParams())) {
  633. $component->saveState($args);
  634. }
  635. if ($args && $component !== $this) {
  636. $prefix = $component->getUniqueId() . self::NAME_SEPARATOR;
  637. foreach ($args as $key => $val) {
  638. unset($args[$key]);
  639. $args[$prefix . $key] = $val;
  640. }
  641. }
  642. }
  643. // PROCESS ARGUMENTS
  644. if (is_subclass_of($presenterClass, __CLASS__)) {
  645. if ($action === '') {
  646. /*$action = $presenterClass::$defaultAction;*/ // in PHP 5.3
  647. $action = self::$defaultAction;
  648. }
  649. $current = ($action === '*' || $action === $this->action) && $presenterClass === get_class($this); // TODO
  650. $reflection = new PresenterComponentReflection($presenterClass);
  651. if ($args || $destination === 'this') {
  652. // counterpart of run() & tryCall()
  653. // in PHP 5.3
  654. $method = call_user_func(array($presenterClass, 'formatActionMethod'), $action);
  655. if (!$reflection->hasCallableMethod($method)) {
  656. // in PHP 5.3
  657. $method = call_user_func(array($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 = 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) ClassReflection::from(func_get_arg(0))->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 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 HttpRequest
  947. */
  948. protected function getHttpRequest()
  949. {
  950. return Environment::getHttpRequest();
  951. }
  952. /**
  953. * @return HttpResponse
  954. */
  955. protected function getHttpResponse()
  956. {
  957. return Environment::getHttpResponse();
  958. }
  959. /**
  960. * @return 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 Session
  975. */
  976. protected function getSession($namespace = NULL)
  977. {
  978. return Environment::getSession($namespace);
  979. }
  980. /**
  981. * @return User
  982. */
  983. protected function getUser()
  984. {
  985. return Environment::getUser();
  986. }
  987. }