Namespaces

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

Classes

  • Control
  • Form
  • Multiplier
  • Presenter
  • PresenterComponent

Interfaces

  • IRenderable
  • ISignalReceiver
  • IStatePersistent

Exceptions

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