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