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