Namespaces

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

Classes

  • Control
  • Form
  • Multiplier
  • Presenter
  • PresenterComponent

Interfaces

  • IRenderable
  • ISignalReceiver
  • IStatePersistent

Exceptions

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