Packages

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

Classes

  • AppForm
  • Control
  • Multiplier
  • Presenter
  • PresenterComponent

Interfaces

  • IRenderable
  • ISignalReceiver
  • IStatePersistent

Exceptions

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