Packages

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

Classes

  • AppForm
  • Control
  • Presenter
  • PresenterComponent

Interfaces

  • IPartiallyRenderable
  • IRenderable
  • ISignalReceiver
  • IStatePersistent

Exceptions

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