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