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