Namespaces

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

Classes

  • Control
  • Form
  • Multiplier
  • Presenter
  • PresenterComponent

Interfaces

  • IRenderable
  • ISignalReceiver
  • IStatePersistent

Exceptions

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