Packages

  • Nette
    • Application
    • Caching
    • Collections
    • Config
    • Forms
    • IO
    • Loaders
    • Mail
    • Reflection
    • Security
    • Templates
    • Web
  • None
  • PHP

Classes

  • NAppForm
  • NApplication
  • NCliRouter
  • NControl
  • NDownloadResponse
  • NForwardingResponse
  • NJsonResponse
  • NLink
  • NMultiRouter
  • NPresenter
  • NPresenterComponent
  • NPresenterLoader
  • NPresenterRequest
  • NRedirectingResponse
  • NRenderResponse
  • NRoute
  • NSimpleRouter

Interfaces

  • IPartiallyRenderable
  • IPresenter
  • IPresenterLoader
  • IPresenterResponse
  • IRenderable
  • IRouter
  • ISignalReceiver
  • IStatePersistent

Exceptions

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