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

  • NAppForm
  • NControl
  • NMultiplier
  • NPresenter
  • NPresenterComponent

Interfaces

  • IRenderable
  • ISignalReceiver
  • IStatePersistent

Exceptions

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