Namespaces

  • Latte
    • Loaders
    • Macros
    • Runtime
  • Nette
    • Application
      • Responses
      • Routers
      • UI
    • Bridges
      • ApplicationDI
      • ApplicationLatte
      • ApplicationTracy
      • CacheDI
      • CacheLatte
      • DatabaseDI
      • DatabaseTracy
      • DITracy
      • FormsDI
      • FormsLatte
      • Framework
      • HttpDI
      • HttpTracy
      • MailDI
      • ReflectionDI
      • SecurityDI
      • SecurityTracy
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Conventions
      • Drivers
      • Reflection
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Latte
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
    • Reflection
    • Security
    • Templating
    • Utils
  • NetteModule
  • none
  • Tracy
    • Bridges
      • Nette

Classes

  • Control
  • Form
  • Multiplier
  • Presenter
  • PresenterComponent

Interfaces

  • IRenderable
  • ISignalReceiver
  • IStatePersistent
  • ITemplate
  • ITemplateFactory

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