Namespaces

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

Classes

  • Control
  • Form
  • Presenter
  • PresenterComponent

Interfaces

  • IPartiallyRenderable
  • IRenderable
  • ISignalReceiver
  • IStatePersistent

Exceptions

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