Overview

Namespaces

  • Budovy
  • Kdyby
    • BootstrapFormRenderer
      • DI
      • Latte
  • Nette
    • Application
      • Diagnostics
      • Responses
      • Routers
      • UI
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Diagnostics
      • Drivers
      • Reflection
      • Table
    • DI
      • Config
        • Adapters
      • Diagnostics
      • Extensions
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
      • Diagnostics
    • Iterators
    • Latte
      • Macros
    • Loaders
    • Localization
    • Mail
    • PhpGenerator
    • Reflection
    • Security
      • Diagnostics
    • Templating
    • Utils
  • NetteModule
  • Nextras
    • Datagrid
  • None
  • PHP
  • Tester
    • CodeCoverage
    • Runner
      • Output
  • Vodacek
    • Forms
      • Controls
  • WebLoader
    • Filter
    • Nette

Classes

  • Control
  • Form
  • Multiplier
  • Presenter
  • PresenterComponent

Interfaces

  • IRenderable
  • ISignalReceiver
  • IStatePersistent

Exceptions

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