Packages

  • Nette
    • Application
      • Application\Diagnostics
      • Application\Responses
      • Application\Routers
      • Application\UI
    • Caching
      • Caching\Storages
    • ComponentModel
    • Config
      • Config\Extensions
    • Database
      • Database\Diagnostics
      • Database\Drivers
      • Database\Reflection
      • Database\Table
    • DI
      • DI\Diagnostics
    • Diagnostics
    • Forms
      • Forms\Controls
      • Forms\Rendering
    • Http
    • Iterators
    • Latte
      • Latte\Macros
    • Loaders
    • Localization
    • Mail
    • Reflection
    • Security
      • Security\Diagnostics
    • Templating
    • Utils
      • Utils\PhpGenerator
  • NetteModule
  • None
  • PHP

Classes

  • NAppForm
  • NControl
  • NMultiplier
  • NPresenter
  • NPresenterComponent

Interfaces

  • IRenderable
  • ISignalReceiver
  • IStatePersistent

Exceptions

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