Namespaces

  • Nette
    • Application
    • Caching
    • Collections
    • Config
    • Forms
    • IO
    • Loaders
    • Mail
    • Reflection
    • Security
    • Templates
    • Web
  • None
  • PHP

Classes

  • AppForm
  • Application
  • CliRouter
  • Control
  • DownloadResponse
  • ForwardingResponse
  • JsonResponse
  • Link
  • MultiRouter
  • Presenter
  • PresenterComponent
  • PresenterLoader
  • PresenterRequest
  • RedirectingResponse
  • RenderResponse
  • Route
  • SimpleRouter

Interfaces

  • IPartiallyRenderable
  • IPresenter
  • IPresenterLoader
  • IPresenterResponse
  • IRenderable
  • IRouter
  • ISignalReceiver
  • IStatePersistent

Exceptions

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