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