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