PageRenderTime 54ms CodeModel.GetById 10ms app.highlight 23ms RepoModel.GetById 11ms app.codeStats 0ms

/Nette/Application/Presenter.php

https://github.com/DocX/nette
PHP | 1372 lines | 1046 code | 158 blank | 168 comment | 104 complexity | 22e2e6e80432075194cb3a5a4d72a88e MD5 | raw file
   1<?php
   2
   3/**
   4 * Nette Framework
   5 *
   6 * Copyright (c) 2004, 2009 David Grudl (http://davidgrudl.com)
   7 *
   8 * This source file is subject to the "Nette license" that is bundled
   9 * with this package in the file license.txt.
  10 *
  11 * For more information please see http://nettephp.com
  12 *
  13 * @copyright  Copyright (c) 2004, 2009 David Grudl
  14 * @license    http://nettephp.com/license  Nette license
  15 * @link       http://nettephp.com
  16 * @category   Nette
  17 * @package    Nette\Application
  18 */
  19
  20/*namespace Nette\Application;*/
  21
  22/*use Nette\Environment;*/
  23
  24
  25
  26require_once dirname(__FILE__) . '/../Application/Control.php';
  27
  28require_once dirname(__FILE__) . '/../Application/IPresenter.php';
  29
  30
  31
  32/**
  33 * Presenter object represents a webpage instance. It executes all the logic for the request.
  34 *
  35 * @author     David Grudl
  36 * @copyright  Copyright (c) 2004, 2009 David Grudl
  37 * @package    Nette\Application
  38 *
  39 * @property-read PresenterRequest $request
  40 * @property-read int $phase
  41 * @property-read array $signal
  42 * @property-read string $action
  43 * @property   string $view
  44 * @property   string $layout
  45 * @property-read mixed $payload
  46 * @property-read Application $application
  47 */
  48abstract class Presenter extends Control implements IPresenter
  49{
  50	/**#@+ life cycle phases {@link Presenter::getPhase()} */
  51	const PHASE_STARTUP = 1;
  52	const PHASE_SIGNAL = 3;
  53	const PHASE_RENDER = 4;
  54	const PHASE_SHUTDOWN = 5;
  55	/**#@-*/
  56
  57	/**#@+ bad link handling {@link Presenter::$invalidLinkMode} */
  58	const INVALID_LINK_SILENT = 1;
  59	const INVALID_LINK_WARNING = 2;
  60	const INVALID_LINK_EXCEPTION = 3;
  61	/**#@-*/
  62
  63	/**#@+ special parameter key */
  64	const SIGNAL_KEY = 'do';
  65	const ACTION_KEY = 'action';
  66	const FLASH_KEY = '_fid';
  67	/**#@-*/
  68
  69	/** @var string */
  70	public static $defaultAction = 'default';
  71
  72	/** @var int */
  73	public static $invalidLinkMode;
  74
  75	/** @var array of function(Presenter $sender, IPresenterResponse $response = NULL); Occurs when the presenter is shutting down */
  76	public $onShutdown;
  77
  78	/** @var bool (experimental) */
  79	public $oldLayoutMode = TRUE;
  80
  81	/** @var bool (experimental) */
  82	public $oldModuleMode = TRUE;
  83
  84	/** @var PresenterRequest */
  85	private $request;
  86
  87	/** @var IPresenterResponse */
  88	private $response;
  89
  90	/** @var int */
  91	private $phase;
  92
  93	/** @var bool  automatically call canonicalize() */
  94	public $autoCanonicalize = TRUE;
  95
  96	/** @var bool  use absolute Urls or paths? */
  97	public $absoluteUrls = FALSE;
  98
  99	/** @var array */
 100	private $globalParams;
 101
 102	/** @var array */
 103	private $globalState;
 104
 105	/** @var array */
 106	private $globalStateSinces;
 107
 108	/** @var string */
 109	private $action;
 110
 111	/** @var string */
 112	private $view;
 113
 114	/** @var string */
 115	private $layout;
 116
 117	/** @var stdClass */
 118	private $payload;
 119
 120	/** @var string */
 121	private $signalReceiver;
 122
 123	/** @var string */
 124	private $signal;
 125
 126	/** @var bool */
 127	private $ajaxMode;
 128
 129	/** @var bool */
 130	private $startupCheck;
 131
 132	/** @var PresenterRequest */
 133	private $lastCreatedRequest;
 134
 135	/** @var array */
 136	private $lastCreatedRequestFlag;
 137
 138
 139
 140	/**
 141	 * @return PresenterRequest
 142	 */
 143	final public function getRequest()
 144	{
 145		return $this->request;
 146	}
 147
 148
 149
 150	/**
 151	 * Returns self.
 152	 * @return Presenter
 153	 */
 154	final public function getPresenter($need = TRUE)
 155	{
 156		return $this;
 157	}
 158
 159
 160
 161	/**
 162	 * Returns a name that uniquely identifies component.
 163	 * @return string
 164	 */
 165	final public function getUniqueId()
 166	{
 167		return '';
 168	}
 169
 170
 171
 172	/********************* interface IPresenter ****************d*g**/
 173
 174
 175
 176	/**
 177	 * @param  PresenterRequest
 178	 * @return IPresenterResponse
 179	 */
 180	public function run(PresenterRequest $request)
 181	{
 182		try {
 183			// PHASE 1: STARTUP
 184			$this->phase = self::PHASE_STARTUP;
 185			$this->request = $request;
 186			$this->payload = (object) NULL;
 187			$this->setParent($this->getParent(), $request->getPresenterName());
 188
 189			$this->initGlobalParams();
 190			$this->startup();
 191			if (!$this->startupCheck) {
 192				$class = $this->reflection->getMethod('startup')->getDeclaringClass()->getName();
 193				trigger_error("Method $class::startup() or its descendant doesn't call parent::startup().", E_USER_WARNING);
 194			}
 195			// calls $this->action<Action>()
 196			$this->tryCall($this->formatActionMethod($this->getAction()), $this->params);
 197
 198			if ($this->autoCanonicalize) {
 199				$this->canonicalize();
 200			}
 201			if ($this->getHttpRequest()->isMethod('head')) {
 202				$this->terminate();
 203			}
 204
 205			// back compatibility
 206			if (method_exists($this, 'beforePrepare')) {
 207				$this->beforePrepare();
 208				trigger_error('beforePrepare() is deprecated; use createComponent{Name}() instead.', E_USER_WARNING);
 209			}
 210			if ($this->tryCall('prepare' . $this->getView(), $this->params)) {
 211				trigger_error('prepare' . ucfirst($this->getView()) . '() is deprecated; use createComponent{Name}() instead.', E_USER_WARNING);
 212			}
 213
 214			// PHASE 2: SIGNAL HANDLING
 215			$this->phase = self::PHASE_SIGNAL;
 216			// calls $this->handle<Signal>()
 217			$this->processSignal();
 218
 219			// PHASE 3: RENDERING VIEW
 220			$this->phase = self::PHASE_RENDER;
 221
 222			$this->beforeRender();
 223			// calls $this->render<View>()
 224			$this->tryCall($this->formatRenderMethod($this->getView()), $this->params);
 225			$this->afterRender();
 226
 227			// save component tree persistent state
 228			$this->saveGlobalState();
 229			if ($this->isAjax()) {
 230				$this->payload->state = $this->getGlobalState();
 231			}
 232
 233			// finish template rendering
 234			$this->sendTemplate();
 235
 236		} catch (AbortException $e) {
 237			// continue with shutting down
 238		} /* finally */ {
 239
 240			// PHASE 4: SHUTDOWN
 241			$this->phase = self::PHASE_SHUTDOWN;
 242
 243			// back compatibility for use terminate() instead of sendPayload()
 244			if ($this->isAjax() && !($this->response instanceof ForwardingResponse || $this->response instanceof JsonResponse) && (array) $this->payload) {
 245				try { $this->sendPayload(); }
 246				catch (AbortException $e) { }
 247			}
 248
 249			if ($this->hasFlashSession()) {
 250				$this->getFlashSession()->setExpiration($this->response instanceof RedirectingResponse ? '+ 30 seconds': '+ 3 seconds');
 251			}
 252
 253			$this->onShutdown($this, $this->response);
 254			$this->shutdown($this->response);
 255
 256			return $this->response;
 257		}
 258	}
 259
 260
 261
 262	/**
 263	 * Returns current presenter life cycle phase.
 264	 * @return int
 265	 */
 266	final public function getPhase()
 267	{
 268		return $this->phase;
 269	}
 270
 271
 272
 273	/**
 274	 * @return void
 275	 */
 276	protected function startup()
 277	{
 278		$this->startupCheck = TRUE;
 279	}
 280
 281
 282
 283	/**
 284	 * Common render method.
 285	 * @return void
 286	 */
 287	protected function beforeRender()
 288	{
 289	}
 290
 291
 292
 293	/**
 294	 * Common render method.
 295	 * @return void
 296	 */
 297	protected function afterRender()
 298	{
 299	}
 300
 301
 302
 303	/**
 304	 * @param  IPresenterResponse  optional catched exception
 305	 * @return void
 306	 */
 307	protected function shutdown($response)
 308	{
 309	}
 310
 311
 312
 313	/********************* signal handling ****************d*g**/
 314
 315
 316
 317	/**
 318	 * @return void
 319	 * @throws BadSignalException
 320	 */
 321	public function processSignal()
 322	{
 323		if ($this->signal === NULL) return;
 324
 325		$component = $this->signalReceiver === '' ? $this : $this->getComponent($this->signalReceiver, FALSE);
 326		if ($component === NULL) {
 327			throw new BadSignalException("The signal receiver component '$this->signalReceiver' is not found.");
 328
 329		} elseif (!$component instanceof ISignalReceiver) {
 330			throw new BadSignalException("The signal receiver component '$this->signalReceiver' is not ISignalReceiver implementor.");
 331		}
 332
 333		// auto invalidate
 334		if ($this->oldLayoutMode && $component instanceof IRenderable) {
 335			$component->invalidateControl();
 336		}
 337
 338		$component->signalReceived($this->signal);
 339		$this->signal = NULL;
 340	}
 341
 342
 343
 344	/**
 345	 * Returns pair signal receiver and name.
 346	 * @return array|NULL
 347	 */
 348	final public function getSignal()
 349	{
 350		return $this->signal === NULL ? NULL : array($this->signalReceiver, $this->signal);
 351	}
 352
 353
 354
 355	/**
 356	 * Checks if the signal receiver is the given one.
 357	 * @param  mixed  component or its id
 358	 * @param  string signal name (optional)
 359	 * @return bool
 360	 */
 361	final public function isSignalReceiver($component, $signal = NULL)
 362	{
 363		if ($component instanceof /*Nette\*/Component) {
 364			$component = $component === $this ? '' : $component->lookupPath(__CLASS__, TRUE);
 365		}
 366
 367		if ($this->signal === NULL) {
 368			return FALSE;
 369
 370		} elseif ($signal === TRUE) {
 371			return $component === ''
 372				|| strncmp($this->signalReceiver . '-', $component . '-', strlen($component) + 1) === 0;
 373
 374		} elseif ($signal === NULL) {
 375			return $this->signalReceiver === $component;
 376
 377		} else {
 378			return $this->signalReceiver === $component && strcasecmp($signal, $this->signal) === 0;
 379		}
 380	}
 381
 382
 383
 384	/********************* rendering ****************d*g**/
 385
 386
 387
 388	/**
 389	 * Returns current action name.
 390	 * @return string
 391	 */
 392	final public function getAction($fullyQualified = FALSE)
 393	{
 394		return $fullyQualified ? ':' . $this->getName() . ':' . $this->action : $this->action;
 395	}
 396
 397
 398
 399	/**
 400	 * Changes current action. Only alphanumeric characters are allowed.
 401	 * @param  string
 402	 * @return void
 403	 */
 404	public function changeAction($action)
 405	{
 406		if (preg_match("#^[a-zA-Z0-9][a-zA-Z0-9_\x7f-\xff]*$#", $action)) {
 407			$this->action = $action;
 408			$this->view = $action;
 409
 410		} else {
 411			throw new BadRequestException("Action name '$action' is not alphanumeric string.");
 412		}
 413	}
 414
 415
 416
 417	/**
 418	 * Returns current view.
 419	 * @return string
 420	 */
 421	final public function getView()
 422	{
 423		return $this->view;
 424	}
 425
 426
 427
 428	/**
 429	 * Changes current view. Any name is allowed.
 430	 * @param  string
 431	 * @return Presenter  provides a fluent interface
 432	 */
 433	public function setView($view)
 434	{
 435		$this->view = (string) $view;
 436		return $this;
 437	}
 438
 439
 440
 441	/**
 442	 * Returns current layout name.
 443	 * @return string|FALSE
 444	 */
 445	final public function getLayout()
 446	{
 447		return $this->layout;
 448	}
 449
 450
 451
 452	/**
 453	 * Changes or disables layout.
 454	 * @param  string|FALSE
 455	 * @return Presenter  provides a fluent interface
 456	 */
 457	public function setLayout($layout)
 458	{
 459		$this->layout = (string) $layout;
 460		return $this;
 461	}
 462
 463
 464
 465	/**
 466	 * @return void
 467	 * @throws BadRequestException if no template found
 468	 * @throws AbortException
 469	 */
 470	public function sendTemplate()
 471	{
 472		$template = $this->getTemplate();
 473		if (!$template) return;
 474
 475		if ($template instanceof /*Nette\Templates\*/IFileTemplate && !$template->getFile()) {
 476
 477			// content template
 478			$files = $this->formatTemplateFiles($this->getName(), $this->view);
 479			foreach ($files as $file) {
 480				if (is_file($file)) {
 481					$template->setFile($file);
 482					break;
 483				}
 484			}
 485
 486			if (!$template->getFile()) {
 487				$file = str_replace(Environment::getVariable('appDir'), "\xE2\x80\xA6", reset($files));
 488				throw new BadRequestException("Page not found. Missing template '$file'.");
 489			}
 490
 491			// layout template
 492			if ($this->layout !== FALSE) {
 493				$files = $this->formatLayoutTemplateFiles($this->getName(), $this->layout ? $this->layout : 'layout');
 494				foreach ($files as $file) {
 495					if (is_file($file)) {
 496						$template->layout = $file;
 497						if ($this->oldLayoutMode) {
 498							$template->content = clone $template;
 499							$template->setFile($file);
 500						} else {
 501							$template->_extends = $file;
 502						}
 503						break;
 504					}
 505				}
 506
 507				if (empty($template->layout) && $this->layout !== NULL) {
 508					$file = str_replace(Environment::getVariable('appDir'), "\xE2\x80\xA6", reset($files));
 509					throw new BadRequestException("Layout not found. Missing template '$file'.");
 510				}
 511			}
 512		}
 513
 514		if ($this->isAjax()) { // TODO!
 515			/*Nette\Templates\*/SnippetHelper::$outputAllowed = FALSE;
 516			$template->render();
 517			$this->sendPayload();
 518		}
 519
 520		$this->terminate(new RenderResponse($template));
 521	}
 522
 523
 524
 525	/**
 526	 * Formats layout template file names.
 527	 * @param  string
 528	 * @param  string
 529	 * @return array
 530	 */
 531	public function formatLayoutTemplateFiles($presenter, $layout)
 532	{
 533		if ($this->oldModuleMode) {
 534			$root = Environment::getVariable('templatesDir', Environment::getVariable('appDir') . '/templates'); // back compatibility
 535			$presenter = str_replace(':', 'Module/', $presenter);
 536			$module = substr($presenter, 0, (int) strrpos($presenter, '/'));
 537			$base = '';
 538			if ($root === Environment::getVariable('appDir') . '/presenters') {
 539				$base = 'templates/';
 540				if ($module === '') {
 541					$presenter = 'templates/' . $presenter;
 542				} else {
 543					$presenter = substr_replace($presenter, '/templates', strrpos($presenter, '/'), 0);
 544				}
 545			}
 546			return array(
 547				"$root/$presenter/@$layout.phtml",
 548				"$root/$presenter.@$layout.phtml",
 549				"$root/$module/$base@$layout.phtml",
 550				"$root/$base@$layout.phtml",
 551			);
 552		}
 553
 554		$appDir = Environment::getVariable('appDir');
 555		$path = '/' . str_replace(':', 'Module/', $presenter);
 556		$pathP = substr_replace($path, '/templates', strrpos($path, '/'), 0);
 557		$list = array(
 558			"$appDir$pathP/@$layout.phtml",
 559			"$appDir$pathP.@$layout.phtml",
 560		);
 561		while (($path = substr($path, 0, strrpos($path, '/'))) !== FALSE) {
 562			$list[] = "$appDir$path/templates/@$layout.phtml";
 563		}
 564		return $list;
 565	}
 566
 567
 568
 569	/**
 570	 * Formats view template file names.
 571	 * @param  string
 572	 * @param  string
 573	 * @return array
 574	 */
 575	public function formatTemplateFiles($presenter, $view)
 576	{
 577		if ($this->oldModuleMode) {
 578			$root = Environment::getVariable('templatesDir', Environment::getVariable('appDir') . '/templates'); // back compatibility
 579			$presenter = str_replace(':', 'Module/', $presenter);
 580			$dir = '';
 581			if ($root === Environment::getVariable('appDir') . '/presenters') { // special supported case
 582				$pos = strrpos($presenter, '/');
 583				$presenter = $pos === FALSE ? 'templates/' . $presenter : substr_replace($presenter, '/templates', $pos, 0);
 584				$dir = 'templates/';
 585			}
 586			return array(
 587				"$root/$presenter/$view.phtml",
 588				"$root/$presenter.$view.phtml",
 589				"$root/$dir@global.$view.phtml",
 590			);
 591		}
 592
 593		$appDir = Environment::getVariable('appDir');
 594		$path = '/' . str_replace(':', 'Module/', $presenter);
 595		$pathP = substr_replace($path, '/templates', strrpos($path, '/'), 0);
 596		$path = substr_replace($path, '/templates', strrpos($path, '/'));
 597		return array(
 598			"$appDir$pathP/$view.phtml",
 599			"$appDir$pathP.$view.phtml",
 600			"$appDir$path/@global.$view.phtml",
 601		);
 602	}
 603
 604
 605
 606	/**
 607	 * Formats action method name.
 608	 * @param  string
 609	 * @return string
 610	 */
 611	protected static function formatActionMethod($action)
 612	{
 613		return 'action' . $action;
 614	}
 615
 616
 617
 618	/**
 619	 * Formats render view method name.
 620	 * @param  string
 621	 * @return string
 622	 */
 623	protected static function formatRenderMethod($view)
 624	{
 625		return 'render' . $view;
 626	}
 627
 628
 629
 630	/**
 631	 * @deprecated
 632	 */
 633	protected function renderTemplate()
 634	{
 635		throw new /*\*/DeprecatedException(__METHOD__ . '() is deprecated; use $presenter->sendTemplate() instead.');
 636	}
 637
 638
 639
 640	/********************* partial AJAX rendering ****************d*g**/
 641
 642
 643
 644	/**
 645	 * @return stdClass
 646	 */
 647	final public function getPayload()
 648	{
 649		return $this->payload;
 650	}
 651
 652
 653
 654	/**
 655	 * Is AJAX request?
 656	 * @return bool
 657	 */
 658	public function isAjax()
 659	{
 660		if ($this->ajaxMode === NULL) {
 661			$this->ajaxMode = $this->getHttpRequest()->isAjax();
 662		}
 663		return $this->ajaxMode;
 664	}
 665
 666
 667
 668	/**
 669	 * Sends AJAX payload to the output.
 670	 * @return void
 671	 * @throws AbortException
 672	 */
 673	protected function sendPayload()
 674	{
 675		$this->terminate(new JsonResponse($this->payload));
 676	}
 677
 678
 679
 680	/**
 681	 * @deprecated
 682	 */
 683	public function getAjaxDriver()
 684	{
 685		throw new /*\*/DeprecatedException(__METHOD__ . '() is deprecated; use $presenter->payload instead.');
 686	}
 687
 688
 689
 690	/********************* navigation & flow ****************d*g**/
 691
 692
 693
 694	/**
 695	 * Forward to another presenter or action.
 696	 * @param  string|PresenterRequest
 697	 * @param  array|mixed
 698	 * @return void
 699	 * @throws AbortException
 700	 */
 701	public function forward($destination, $args = array())
 702	{
 703		if ($destination instanceof PresenterRequest) {
 704			$this->terminate(new ForwardingResponse($destination));
 705
 706		} elseif (!is_array($args)) {
 707			$args = func_get_args();
 708			array_shift($args);
 709		}
 710
 711		$this->createRequest($this, $destination, $args, 'forward');
 712		$this->terminate(new ForwardingResponse($this->lastCreatedRequest));
 713	}
 714
 715
 716
 717	/**
 718	 * Redirect to another URL and ends presenter execution.
 719	 * @param  string
 720	 * @param  int HTTP error code
 721	 * @return void
 722	 * @throws AbortException
 723	 */
 724	public function redirectUri($uri, $code = NULL)
 725	{
 726		if ($this->isAjax()) {
 727			$this->payload->redirect = (string) $uri;
 728			$this->sendPayload();
 729
 730		} elseif (!$code) {
 731			$code = $this->getHttpRequest()->isMethod('post') ? /*Nette\Web\*/IHttpResponse::S303_POST_GET : /*Nette\Web\*/IHttpResponse::S302_FOUND;
 732		}
 733		$this->terminate(new RedirectingResponse($uri, $code));
 734	}
 735
 736
 737
 738	/**
 739	 * Link to myself.
 740	 * @return string
 741	 */
 742	public function backlink()
 743	{
 744		return $this->getAction(TRUE);
 745	}
 746
 747
 748
 749	/**
 750	 * Returns the last created PresenterRequest.
 751	 * @return PresenterRequest
 752	 */
 753	public function getLastCreatedRequest()
 754	{
 755		return $this->lastCreatedRequest;
 756	}
 757
 758
 759
 760	/**
 761	 * Returns the last created PresenterRequest flag.
 762	 * @param  string
 763	 * @return bool
 764	 */
 765	public function getLastCreatedRequestFlag($flag)
 766	{
 767		return !empty($this->lastCreatedRequestFlag[$flag]);
 768	}
 769
 770
 771
 772	/**
 773	 * Correctly terminates presenter.
 774	 * @param  IPresenterResponse
 775	 * @return void
 776	 * @throws AbortException
 777	 */
 778	public function terminate(IPresenterResponse $response = NULL)
 779	{
 780		$this->response = $response;
 781		throw new AbortException();
 782	}
 783
 784
 785
 786	/**
 787	 * Conditional redirect to canonicalized URI.
 788	 * @return void
 789	 * @throws AbortException
 790	 */
 791	public function canonicalize()
 792	{
 793		if (!$this->isAjax() && ($this->request->isMethod('get') || $this->request->isMethod('head'))) {
 794			$uri = $this->createRequest($this, $this->action, $this->getGlobalState() + $this->request->params, 'redirectX');
 795			if ($uri !== NULL && !$this->getHttpRequest()->getUri()->isEqual($uri)) {
 796				$this->terminate(new RedirectingResponse($uri, /*Nette\Web\*/IHttpResponse::S301_MOVED_PERMANENTLY));
 797			}
 798		}
 799	}
 800
 801
 802
 803	/**
 804	 * Attempts to cache the sent entity by its last modification date
 805	 * @param  int    last modified time as unix timestamp
 806	 * @param  string strong entity tag validator
 807	 * @param  mixed  optional expiration time
 808	 * @return int    date of the client's cache version, if available
 809	 * @throws AbortException
 810	 */
 811	public function lastModified($lastModified, $etag = NULL, $expire = NULL)
 812	{
 813		if (!Environment::isProduction()) {
 814			return NULL;
 815		}
 816
 817		$httpResponse = $this->getHttpResponse();
 818		$match = FALSE;
 819
 820		if ($lastModified > 0) {
 821			$httpResponse->setHeader('Last-Modified', /*Nette\Web\*/HttpResponse::date($lastModified));
 822		}
 823
 824		if ($etag != NULL) { // intentionally ==
 825			$etag = '"' . addslashes($etag) . '"';
 826			$httpResponse->setHeader('ETag', $etag);
 827		}
 828
 829		if ($expire !== NULL) {
 830			$httpResponse->expire($expire);
 831		}
 832
 833		$ifNoneMatch = $this->getHttpRequest()->getHeader('if-none-match');
 834		$ifModifiedSince = $this->getHttpRequest()->getHeader('if-modified-since');
 835		if ($ifModifiedSince !== NULL) {
 836			$ifModifiedSince = strtotime($ifModifiedSince);
 837		}
 838
 839		if ($ifNoneMatch !== NULL) {
 840			if ($ifNoneMatch === '*') {
 841				$match = TRUE; // match, check if-modified-since
 842
 843			} elseif ($etag == NULL || strpos(' ' . strtr($ifNoneMatch, ",\t", '  '), ' ' . $etag) === FALSE) {
 844				return $ifModifiedSince; // no match, ignore if-modified-since
 845
 846			} else {
 847				$match = TRUE; // match, check if-modified-since
 848			}
 849		}
 850
 851		if ($ifModifiedSince !== NULL) {
 852			if ($lastModified > 0 && $lastModified <= $ifModifiedSince) {
 853				$match = TRUE;
 854
 855			} else {
 856				return $ifModifiedSince;
 857			}
 858		}
 859
 860		if ($match) {
 861			$httpResponse->setCode(/*Nette\Web\*/IHttpResponse::S304_NOT_MODIFIED);
 862			$httpResponse->setHeader('Content-Length', '0');
 863			$this->terminate();
 864
 865		} else {
 866			return $ifModifiedSince;
 867		}
 868
 869		return NULL;
 870	}
 871
 872
 873
 874	/**
 875	 * PresenterRequest/URL factory.
 876	 * @param  PresenterComponent  base
 877	 * @param  string   destination in format "[[module:]presenter:]action" or "signal!"
 878	 * @param  array    array of arguments
 879	 * @param  string   forward|redirect|link
 880	 * @return string   URL
 881	 * @throws InvalidLinkException
 882	 * @internal
 883	 */
 884	final protected function createRequest($component, $destination, array $args, $mode)
 885	{
 886		// note: createRequest supposes that saveState(), run() & tryCall() behaviour is final
 887
 888		// cached services for better performance
 889		static $presenterLoader, $router, $httpRequest;
 890		if ($presenterLoader === NULL) {
 891			$presenterLoader = $this->getApplication()->getPresenterLoader();
 892			$router = $this->getApplication()->getRouter();
 893			$httpRequest = $this->getHttpRequest();
 894		}
 895
 896		$this->lastCreatedRequest = $this->lastCreatedRequestFlag = NULL;
 897
 898		// PARSE DESTINATION
 899		// 1) fragment
 900		$a = strpos($destination, '#');
 901		if ($a === FALSE) {
 902			$fragment = '';
 903		} else {
 904			$fragment = substr($destination, $a);
 905			$destination = substr($destination, 0, $a);
 906		}
 907
 908		// 2) ?query syntax
 909		$a = strpos($destination, '?');
 910		if ($a !== FALSE) {
 911			parse_str(substr($destination, $a + 1), $args); // requires disabled magic quotes
 912			$destination = substr($destination, 0, $a);
 913		}
 914
 915		// 3) URL scheme
 916		$a = strpos($destination, '//');
 917		if ($a === FALSE) {
 918			$scheme = FALSE;
 919		} else {
 920			$scheme = substr($destination, 0, $a);
 921			$destination = substr($destination, $a + 2);
 922		}
 923
 924		// 4) signal or empty
 925		if (!($component instanceof Presenter) || substr($destination, -1) === '!') {
 926			$signal = rtrim($destination, '!');
 927			$a = strrpos($signal, ':');
 928			if ($a !== FALSE) {
 929				$component = $component->getComponent(strtr(substr($signal, 0, $a), ':', '-'));
 930				$signal = (string) substr($signal, $a + 1);
 931			}
 932			if ($signal == NULL) {  // intentionally ==
 933				throw new InvalidLinkException("Signal must be non-empty string.");
 934			}
 935			$destination = 'this';
 936		}
 937
 938		if ($destination == NULL) {  // intentionally ==
 939			throw new InvalidLinkException("Destination must be non-empty string.");
 940		}
 941
 942		// 5) presenter: action
 943		$current = FALSE;
 944		$a = strrpos($destination, ':');
 945		if ($a === FALSE) {
 946			$action = $destination === 'this' ? $this->action : $destination;
 947			$presenter = $this->getName();
 948			$presenterClass = $this->getClass();
 949
 950		} else {
 951			$action = (string) substr($destination, $a + 1);
 952			if ($destination[0] === ':') { // absolute
 953				if ($a < 2) {
 954					throw new InvalidLinkException("Missing presenter name in '$destination'.");
 955				}
 956				$presenter = substr($destination, 1, $a - 1);
 957
 958			} else { // relative
 959				$presenter = $this->getName();
 960				$b = strrpos($presenter, ':');
 961				if ($b === FALSE) { // no module
 962					$presenter = substr($destination, 0, $a);
 963				} else { // with module
 964					$presenter = substr($presenter, 0, $b + 1) . substr($destination, 0, $a);
 965				}
 966			}
 967			$presenterClass = $presenterLoader->getPresenterClass($presenter);
 968		}
 969
 970		// PROCESS SIGNAL ARGUMENTS
 971		if (isset($signal)) { // $component must be IStatePersistent
 972			$class = get_class($component);
 973			if ($signal === 'this') { // means "no signal"
 974				$signal = '';
 975				if (array_key_exists(0, $args)) {
 976					throw new InvalidLinkException("Extra parameter for signal '$class:$signal!'.");
 977				}
 978
 979			} elseif (strpos($signal, self::NAME_SEPARATOR) === FALSE) { // TODO: AppForm exception
 980				// counterpart of signalReceived() & tryCall()
 981				$method = $component->formatSignalMethod($signal);
 982				if (!PresenterHelpers::isMethodCallable($class, $method)) {
 983					throw new InvalidLinkException("Unknown signal '$class:$signal!'.");
 984				}
 985				if ($args) { // convert indexed parameters to named
 986					PresenterHelpers::argsToParams($class, $method, $args);
 987				}
 988			}
 989
 990			// counterpart of IStatePersistent
 991			if ($args && array_intersect_key($args, PresenterHelpers::getPersistentParams($class))) {
 992				$component->saveState($args);
 993			}
 994
 995			if ($args && $component !== $this) {
 996				$prefix = $component->getUniqueId() . self::NAME_SEPARATOR;
 997				foreach ($args as $key => $val) {
 998					unset($args[$key]);
 999					$args[$prefix . $key] = $val;
1000				}
1001			}
1002		}
1003
1004		// PROCESS ARGUMENTS
1005		if (is_subclass_of($presenterClass, __CLASS__)) {
1006			if ($action === '') {
1007				/*$action = $presenterClass::$defaultAction;*/ // in PHP 5.3
1008				/**/$action = self::$defaultAction;/**/
1009			}
1010
1011			$current = ($action === '*' || $action === $this->action) && $presenterClass === $this->getClass(); // TODO
1012
1013			if ($args || $destination === 'this') {
1014				// counterpart of run() & tryCall()
1015				/*$method = $presenterClass::formatActionMethod($action);*/ // in PHP 5.3
1016				/**/$method = call_user_func(array($presenterClass, 'formatActionMethod'), $action);/**/
1017				if (!PresenterHelpers::isMethodCallable($presenterClass, $method)) {
1018					/*$method = $presenterClass::formatRenderMethod($action);*/ // in PHP 5.3
1019					/**/$method = call_user_func(array($presenterClass, 'formatRenderMethod'), $action);/**/
1020					if (!PresenterHelpers::isMethodCallable($presenterClass, $method)) {
1021						$method = NULL;
1022					}
1023				}
1024
1025				// convert indexed parameters to named
1026				if ($method === NULL) {
1027					if (array_key_exists(0, $args)) {
1028						throw new InvalidLinkException("Extra parameter for '$presenter:$action'.");
1029					}
1030
1031				} elseif ($destination === 'this') {
1032					PresenterHelpers::argsToParams($presenterClass, $method, $args, $this->params);
1033
1034				} else {
1035					PresenterHelpers::argsToParams($presenterClass, $method, $args);
1036				}
1037			}
1038
1039			// counterpart of IStatePersistent
1040			if ($args && array_intersect_key($args, PresenterHelpers::getPersistentParams($presenterClass))) {
1041				$this->saveState($args, $presenterClass);
1042			}
1043
1044			$globalState = $this->getGlobalState($destination === 'this' ? NULL : $presenterClass);
1045			if ($current && $args) {
1046				$tmp = $globalState + $this->params;
1047				foreach ($args as $key => $val) {
1048					if ((string) $val !== (isset($tmp[$key]) ? (string) $tmp[$key] : '')) {
1049						$current = FALSE;
1050						break;
1051					}
1052				}
1053			}
1054			$args += $globalState;
1055		}
1056
1057		// ADD ACTION & SIGNAL & FLASH
1058		$args[self::ACTION_KEY] = $action;
1059		if (!empty($signal)) {
1060			$args[self::SIGNAL_KEY] = $component->getParamId($signal);
1061			$current = $current && $args[self::SIGNAL_KEY] === $this->getParam(self::SIGNAL_KEY);
1062		}
1063		if (($mode === 'redirect' || $mode === 'forward') && $this->hasFlashSession()) {
1064			$args[self::FLASH_KEY] = $this->getParam(self::FLASH_KEY);
1065		}
1066
1067		$this->lastCreatedRequest = new PresenterRequest(
1068			$presenter,
1069			PresenterRequest::FORWARD,
1070			$args,
1071			array(),
1072			array()
1073		);
1074		$this->lastCreatedRequestFlag = array('current' => $current);
1075
1076		if ($mode === 'forward') return;
1077
1078		// CONSTRUCT URL
1079		$uri = $router->constructUrl($this->lastCreatedRequest, $httpRequest);
1080		if ($uri === NULL) {
1081			unset($args[self::ACTION_KEY]);
1082			$params = urldecode(http_build_query($args, NULL, ', '));
1083			throw new InvalidLinkException("No route for $presenter:$action($params)");
1084		}
1085
1086		// make URL relative if possible
1087		if ($mode === 'link' && $scheme === FALSE && !$this->absoluteUrls) {
1088			$hostUri = $httpRequest->getUri()->getHostUri();
1089			if (strncmp($uri, $hostUri, strlen($hostUri)) === 0) {
1090				$uri = substr($uri, strlen($hostUri));
1091			}
1092		}
1093
1094		return $uri . $fragment;
1095	}
1096
1097
1098
1099	/**
1100	 * Invalid link handler. Descendant can override this method to change default behaviour.
1101	 * @param  InvalidLinkException
1102	 * @return string
1103	 * @throws InvalidLinkException
1104	 */
1105	protected function handleInvalidLink($e)
1106	{
1107		if (self::$invalidLinkMode === NULL) {
1108			self::$invalidLinkMode = Environment::isProduction()
1109				? self::INVALID_LINK_SILENT : self::INVALID_LINK_WARNING;
1110		}
1111
1112		if (self::$invalidLinkMode === self::INVALID_LINK_SILENT) {
1113			return '#';
1114
1115		} elseif (self::$invalidLinkMode === self::INVALID_LINK_WARNING) {
1116			return 'error: ' . htmlSpecialChars($e->getMessage());
1117
1118		} else { // self::INVALID_LINK_EXCEPTION
1119			throw $e;
1120		}
1121	}
1122
1123
1124
1125	/********************* interface IStatePersistent ****************d*g**/
1126
1127
1128
1129	/**
1130	 * Returns array of persistent components.
1131	 * This default implementation detects components by class-level annotation @persistent(cmp1, cmp2).
1132	 * @return array
1133	 */
1134	public static function getPersistentComponents()
1135	{
1136		return (array) /*Nette\*/Annotations::get(new /*\*/ReflectionClass(/**/func_get_arg(0)/**//*get_called_class()*/), 'persistent');
1137	}
1138
1139
1140
1141	/**
1142	 * Saves state information for all subcomponents to $this->globalState.
1143	 * @return array
1144	 */
1145	private function getGlobalState($forClass = NULL)
1146	{
1147		$sinces = & $this->globalStateSinces;
1148
1149		if ($this->globalState === NULL) {
1150			if ($this->phase >= self::PHASE_SHUTDOWN) {
1151				throw new /*\*/InvalidStateException("Presenter is shutting down, cannot save state.");
1152			}
1153
1154			$state = array();
1155			foreach ($this->globalParams as $id => $params) {
1156				$prefix = $id . self::NAME_SEPARATOR;
1157				foreach ($params as $key => $val) {
1158					$state[$prefix . $key] = $val;
1159				}
1160			}
1161			$this->saveState($state, $forClass);
1162
1163			if ($sinces === NULL) {
1164				$sinces = array();
1165				foreach (PresenterHelpers::getPersistentParams(get_class($this)) as $nm => $meta) {
1166					$sinces[$nm] = $meta['since'];
1167				}
1168			}
1169
1170			$components = PresenterHelpers::getPersistentComponents(get_class($this));
1171			$iterator = $this->getComponents(TRUE, 'Nette\Application\IStatePersistent');
1172			foreach ($iterator as $name => $component)
1173			{
1174				if ($iterator->getDepth() === 0) {
1175					// counts with RecursiveIteratorIterator::SELF_FIRST
1176					$since = isset($components[$name]['since']) ? $components[$name]['since'] : FALSE; // FALSE = nonpersistent
1177				}
1178				$prefix = $component->getUniqueId() . self::NAME_SEPARATOR;
1179				$params = array();
1180				$component->saveState($params);
1181				foreach ($params as $key => $val) {
1182					$state[$prefix . $key] = $val;
1183					$sinces[$prefix . $key] = $since;
1184				}
1185			}
1186
1187		} else {
1188			$state = $this->globalState;
1189		}
1190
1191		if ($forClass !== NULL) {
1192			$since = NULL;
1193			foreach ($state as $key => $foo) {
1194				if (!isset($sinces[$key])) {
1195					$x = strpos($key, self::NAME_SEPARATOR);
1196					$x = $x === FALSE ? $key : substr($key, 0, $x);
1197					$sinces[$key] = isset($sinces[$x]) ? $sinces[$x] : FALSE;
1198				}
1199				if ($since !== $sinces[$key]) {
1200					$since = $sinces[$key];
1201					$ok = $since && (is_subclass_of($forClass, $since) || $forClass === $since);
1202				}
1203				if (!$ok) {
1204					unset($state[$key]);
1205				}
1206			}
1207		}
1208
1209		return $state;
1210	}
1211
1212
1213
1214	/**
1215	 * Permanently saves state information for all subcomponents to $this->globalState.
1216	 * @return void
1217	 */
1218	protected function saveGlobalState()
1219	{
1220		// load lazy components
1221		foreach ($this->globalParams as $id => $foo) {
1222			$this->getComponent($id, FALSE);
1223		}
1224
1225		$this->globalParams = array();
1226		$this->globalState = $this->getGlobalState();
1227	}
1228
1229
1230
1231	/**
1232	 * Initializes $this->globalParams, $this->signal & $this->signalReceiver, $this->action, $this->view. Called by run().
1233	 * @return void
1234	 * @throws BadRequestException if action name is not valid
1235	 */
1236	private function initGlobalParams()
1237	{
1238		// init $this->globalParams
1239		$this->globalParams = array();
1240		$selfParams = array();
1241
1242		$params = $this->request->getParams();
1243		if ($this->isAjax()) {
1244			$params = $this->request->getPost() + $params;
1245		}
1246
1247		foreach ($params as $key => $value) {
1248			$a = strlen($key) > 2 ? strrpos($key, self::NAME_SEPARATOR, -2) : FALSE;
1249			if ($a === FALSE) {
1250				$selfParams[$key] = $value;
1251			} else {
1252				$this->globalParams[substr($key, 0, $a)][substr($key, $a + 1)] = $value;
1253			}
1254		}
1255
1256		// init & validate $this->action & $this->view
1257		$this->changeAction(isset($selfParams[self::ACTION_KEY]) ? $selfParams[self::ACTION_KEY] : self::$defaultAction);
1258
1259		// init $this->signalReceiver and key 'signal' in appropriate params array
1260		$this->signalReceiver = $this->getUniqueId();
1261		if (!empty($selfParams[self::SIGNAL_KEY])) {
1262			$param = $selfParams[self::SIGNAL_KEY];
1263			$pos = strrpos($param, '-');
1264			if ($pos) {
1265				$this->signalReceiver = substr($param, 0, $pos);
1266				$this->signal = substr($param, $pos + 1);
1267			} else {
1268				$this->signalReceiver = $this->getUniqueId();
1269				$this->signal = $param;
1270			}
1271			if ($this->signal == NULL) { // intentionally ==
1272				$this->signal = NULL;
1273			}
1274		}
1275
1276		$this->loadState($selfParams);
1277	}
1278
1279
1280
1281	/**
1282	 * Pops parameters for specified component.
1283	 * @param  string  component id
1284	 * @return array
1285	 */
1286	final public function popGlobalParams($id)
1287	{
1288		if (isset($this->globalParams[$id])) {
1289			$res = $this->globalParams[$id];
1290			unset($this->globalParams[$id]);
1291			return $res;
1292
1293		} else {
1294			return array();
1295		}
1296	}
1297
1298
1299
1300	/********************* flash session ****************d*g**/
1301
1302
1303
1304	/**
1305	 * Checks if a flash session namespace exists.
1306	 * @return bool
1307	 */
1308	public function hasFlashSession()
1309	{
1310		return !empty($this->params[self::FLASH_KEY])
1311			&& $this->getSession()->hasNamespace('Nette.Application.Flash/' . $this->params[self::FLASH_KEY]);
1312	}
1313
1314
1315
1316	/**
1317	 * Returns session namespace provided to pass temporary data between redirects.
1318	 * @return Nette\Web\SessionNamespace
1319	 */
1320	public function getFlashSession()
1321	{
1322		if (empty($this->params[self::FLASH_KEY])) {
1323			$this->params[self::FLASH_KEY] = substr(md5(lcg_value()), 0, 4);
1324		}
1325		return $this->getSession()->getNamespace('Nette.Application.Flash/' . $this->params[self::FLASH_KEY]);
1326	}
1327
1328
1329
1330	/********************* backend ****************d*g**/
1331
1332
1333
1334	/**
1335	 * @return Nette\Web\IHttpRequest
1336	 */
1337	protected function getHttpRequest()
1338	{
1339		return Environment::getHttpRequest();
1340	}
1341
1342
1343
1344	/**
1345	 * @return Nette\Web\IHttpResponse
1346	 */
1347	protected function getHttpResponse()
1348	{
1349		return Environment::getHttpResponse();
1350	}
1351
1352
1353
1354	/**
1355	 * @return Application
1356	 */
1357	public function getApplication()
1358	{
1359		return Environment::getApplication();
1360	}
1361
1362
1363
1364	/**
1365	 * @return Nette\Web\Session
1366	 */
1367	private function getSession()
1368	{
1369		return Environment::getSession();
1370	}
1371
1372}