Packages

  • 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
  • PHP

Classes

  • AppForm
  • Control
  • Multiplier
  • Presenter
  • PresenterComponent

Interfaces

  • IRenderable
  • ISignalReceiver
  • IStatePersistent

Exceptions

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