PageRenderTime 65ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/libs/Nette/Application/UI/Presenter.php

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