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\Application\Helpers;
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 $onShutdown;
48:
49:
50: private $request;
51:
52:
53: private $response;
54:
55:
56: public $autoCanonicalize = TRUE;
57:
58:
59: public $absoluteUrls = FALSE;
60:
61:
62: private $globalParams;
63:
64:
65: private $globalState;
66:
67:
68: private $globalStateSinces;
69:
70:
71: private $action;
72:
73:
74: private $view;
75:
76:
77: private $layout;
78:
79:
80: private $payload;
81:
82:
83: private $signalReceiver;
84:
85:
86: private $signal;
87:
88:
89: private $ajaxMode;
90:
91:
92: private $startupCheck;
93:
94:
95: private $lastCreatedRequest;
96:
97:
98: private $lastCreatedRequestFlag;
99:
100:
101: private $context;
102:
103:
104: private $httpRequest;
105:
106:
107: private $httpResponse;
108:
109:
110: private $session;
111:
112:
113: private $presenterFactory;
114:
115:
116: private $router;
117:
118:
119: private $user;
120:
121:
122: private $templateFactory;
123:
124:
125: private $refUrlCache;
126:
127:
128: public function __construct()
129: {
130: $this->payload = new \stdClass;
131: }
132:
133:
134: 135: 136:
137: public function getRequest()
138: {
139: return $this->request;
140: }
141:
142:
143: 144: 145: 146:
147: public function getPresenter($need = TRUE)
148: {
149: return $this;
150: }
151:
152:
153: 154: 155: 156:
157: public function getUniqueId()
158: {
159: return '';
160: }
161:
162:
163:
164:
165:
166: 167: 168:
169: public function run(Application\Request $request)
170: {
171: try {
172:
173: $this->request = $request;
174: $this->payload = $this->payload ?: new \stdClass;
175: $this->setParent($this->getParent(), $request->getPresenterName());
176:
177: if (!$this->httpResponse->isSent()) {
178: $this->httpResponse->addHeader('Vary', 'X-Requested-With');
179: }
180:
181: $this->initGlobalParameters();
182: $this->checkRequirements($this->getReflection());
183: $this->startup();
184: if (!$this->startupCheck) {
185: $class = $this->getReflection()->getMethod('startup')->getDeclaringClass()->getName();
186: throw new Nette\InvalidStateException("Method $class::startup() or its descendant doesn't call parent::startup().");
187: }
188:
189: $this->tryCall($this->formatActionMethod($this->action), $this->params);
190:
191:
192: foreach ($this->globalParams as $id => $foo) {
193: $this->getComponent($id, FALSE);
194: }
195:
196: if ($this->autoCanonicalize) {
197: $this->canonicalize();
198: }
199: if ($this->httpRequest->isMethod('head')) {
200: $this->terminate();
201: }
202:
203:
204:
205: $this->processSignal();
206:
207:
208: $this->beforeRender();
209:
210: $this->tryCall($this->formatRenderMethod($this->view), $this->params);
211: $this->afterRender();
212:
213:
214: $this->saveGlobalState();
215: if ($this->isAjax()) {
216: $this->payload->state = $this->getGlobalState();
217: }
218:
219:
220: if ($this->getTemplate()) {
221: $this->sendTemplate();
222: }
223:
224: } catch (Application\AbortException $e) {
225:
226: if ($this->isAjax()) {
227: try {
228: $hasPayload = (array) $this->payload;
229: unset($hasPayload['state']);
230: if ($this->response instanceof Responses\TextResponse && $this->isControlInvalid()) {
231: $this->snippetMode = TRUE;
232: $this->response->send($this->httpRequest, $this->httpResponse);
233: $this->sendPayload();
234: } elseif (!$this->response && $hasPayload) {
235: trigger_error('Use $presenter->sendPayload() instead of terminate() to send payload.');
236: $this->sendPayload();
237: }
238: } catch (Application\AbortException $e) {
239: }
240: }
241:
242: if ($this->hasFlashSession()) {
243: $this->getFlashSession()->setExpiration($this->response instanceof Responses\RedirectResponse ? '+ 30 seconds' : '+ 3 seconds');
244: }
245:
246:
247: $this->onShutdown($this, $this->response);
248: $this->shutdown($this->response);
249:
250: return $this->response;
251: }
252: }
253:
254:
255: 256: 257:
258: protected function startup()
259: {
260: $this->startupCheck = TRUE;
261: }
262:
263:
264: 265: 266: 267:
268: protected function beforeRender()
269: {
270: }
271:
272:
273: 274: 275: 276:
277: protected function afterRender()
278: {
279: }
280:
281:
282: 283: 284: 285:
286: protected function shutdown($response)
287: {
288: }
289:
290:
291: 292: 293: 294:
295: public function checkRequirements($element)
296: {
297: $user = (array) ComponentReflection::parseAnnotation($element, 'User');
298: if (in_array('loggedIn', $user, TRUE) && !$this->getUser()->isLoggedIn()) {
299: throw new Application\ForbiddenRequestException;
300: }
301: }
302:
303:
304:
305:
306:
307: 308: 309: 310:
311: public function processSignal()
312: {
313: if ($this->signal === NULL) {
314: return;
315: }
316:
317: $component = $this->signalReceiver === '' ? $this : $this->getComponent($this->signalReceiver, FALSE);
318: if ($component === NULL) {
319: throw new BadSignalException("The signal receiver component '$this->signalReceiver' is not found.");
320:
321: } elseif (!$component instanceof ISignalReceiver) {
322: throw new BadSignalException("The signal receiver component '$this->signalReceiver' is not ISignalReceiver implementor.");
323: }
324:
325: $component->signalReceived($this->signal);
326: $this->signal = NULL;
327: }
328:
329:
330: 331: 332: 333:
334: public function getSignal()
335: {
336: return $this->signal === NULL ? NULL : [$this->signalReceiver, $this->signal];
337: }
338:
339:
340: 341: 342: 343: 344: 345:
346: public function isSignalReceiver($component, $signal = NULL)
347: {
348: if ($component instanceof Nette\ComponentModel\Component) {
349: $component = $component === $this ? '' : $component->lookupPath(__CLASS__, TRUE);
350: }
351:
352: if ($this->signal === NULL) {
353: return FALSE;
354:
355: } elseif ($signal === TRUE) {
356: return $component === ''
357: || strncmp($this->signalReceiver . '-', $component . '-', strlen($component) + 1) === 0;
358:
359: } elseif ($signal === NULL) {
360: return $this->signalReceiver === $component;
361:
362: } else {
363: return $this->signalReceiver === $component && strcasecmp($signal, $this->signal) === 0;
364: }
365: }
366:
367:
368:
369:
370:
371: 372: 373: 374:
375: public function getAction($fullyQualified = FALSE)
376: {
377: return $fullyQualified ? ':' . $this->getName() . ':' . $this->action : $this->action;
378: }
379:
380:
381: 382: 383: 384: 385:
386: public function changeAction($action)
387: {
388: if (is_string($action) && Nette\Utils\Strings::match($action, '#^[a-zA-Z0-9][a-zA-Z0-9_\x7f-\xff]*\z#')) {
389: $this->action = $action;
390: $this->view = $action;
391:
392: } else {
393: $this->error('Action name is not alphanumeric string.');
394: }
395: }
396:
397:
398: 399: 400: 401:
402: public function getView()
403: {
404: return $this->view;
405: }
406:
407:
408: 409: 410: 411: 412:
413: public function setView($view)
414: {
415: $this->view = (string) $view;
416: return $this;
417: }
418:
419:
420: 421: 422: 423:
424: public function getLayout()
425: {
426: return $this->layout;
427: }
428:
429:
430: 431: 432: 433: 434:
435: public function setLayout($layout)
436: {
437: $this->layout = $layout === FALSE ? FALSE : (string) $layout;
438: return $this;
439: }
440:
441:
442: 443: 444: 445: 446:
447: public function sendTemplate()
448: {
449: $template = $this->getTemplate();
450: if (!$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})\z#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 Responses\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})\z#U', "\xE2\x80\xA6\$1", reset($files));
489: $file = strtr($file, '/', DIRECTORY_SEPARATOR);
490: throw new Nette\FileNotFoundException("Layout not found. Missing template '$file'.");
491: }
492: }
493:
494:
495: 496: 497: 498:
499: public function formatLayoutTemplateFiles()
500: {
501: if (preg_match('#/|\\\\#', $this->layout)) {
502: return [$this->layout];
503: }
504: list($module, $presenter) = Helpers::splitName($this->getName());
505: $layout = $this->layout ? $this->layout : 'layout';
506: $dir = dirname($this->getReflection()->getFileName());
507: $dir = is_dir("$dir/templates") ? $dir : dirname($dir);
508: $list = [
509: "$dir/templates/$presenter/@$layout.latte",
510: "$dir/templates/$presenter.@$layout.latte",
511: ];
512: do {
513: $list[] = "$dir/templates/@$layout.latte";
514: $dir = dirname($dir);
515: } while ($dir && $module && (list($module) = Helpers::splitName($module)));
516: return $list;
517: }
518:
519:
520: 521: 522: 523:
524: public function formatTemplateFiles()
525: {
526: list(, $presenter) = Helpers::splitName($this->getName());
527: $dir = dirname($this->getReflection()->getFileName());
528: $dir = is_dir("$dir/templates") ? $dir : dirname($dir);
529: return [
530: "$dir/templates/$presenter/$this->view.latte",
531: "$dir/templates/$presenter.$this->view.latte",
532: ];
533: }
534:
535:
536: 537: 538: 539: 540:
541: public static function formatActionMethod($action)
542: {
543: return 'action' . $action;
544: }
545:
546:
547: 548: 549: 550: 551:
552: public static function formatRenderMethod($view)
553: {
554: return 'render' . $view;
555: }
556:
557:
558: 559: 560:
561: protected function createTemplate()
562: {
563: return $this->getTemplateFactory()->createTemplate($this);
564: }
565:
566:
567:
568:
569:
570: 571: 572:
573: public function getPayload()
574: {
575: return $this->payload;
576: }
577:
578:
579: 580: 581: 582:
583: public function isAjax()
584: {
585: if ($this->ajaxMode === NULL) {
586: $this->ajaxMode = $this->httpRequest->isAjax();
587: }
588: return $this->ajaxMode;
589: }
590:
591:
592: 593: 594: 595: 596:
597: public function sendPayload()
598: {
599: $this->sendResponse(new Responses\JsonResponse($this->payload));
600: }
601:
602:
603: 604: 605: 606: 607: 608:
609: public function sendJson($data)
610: {
611: $this->sendResponse(new Responses\JsonResponse($data));
612: }
613:
614:
615:
616:
617:
618: 619: 620: 621: 622:
623: public function sendResponse(Application\IResponse $response)
624: {
625: $this->response = $response;
626: $this->terminate();
627: }
628:
629:
630: 631: 632: 633: 634:
635: public function terminate()
636: {
637: throw new Application\AbortException();
638: }
639:
640:
641: 642: 643: 644: 645: 646: 647:
648: public function forward($destination, $args = [])
649: {
650: if ($destination instanceof Application\Request) {
651: $this->sendResponse(new Responses\ForwardResponse($destination));
652: }
653:
654: $args = func_num_args() < 3 && is_array($args) ? $args : array_slice(func_get_args(), 1);
655: $this->createRequest($this, $destination, $args, 'forward');
656: $this->sendResponse(new Responses\ForwardResponse($this->lastCreatedRequest));
657: }
658:
659:
660: 661: 662: 663: 664: 665: 666:
667: public function redirectUrl($url, $code = NULL)
668: {
669: if ($this->isAjax()) {
670: $this->payload->redirect = (string) $url;
671: $this->sendPayload();
672:
673: } elseif (!$code) {
674: $code = $this->httpRequest->isMethod('post')
675: ? Http\IResponse::S303_POST_GET
676: : Http\IResponse::S302_FOUND;
677: }
678: $this->sendResponse(new Responses\RedirectResponse($url, $code));
679: }
680:
681:
682: 683: 684: 685: 686: 687: 688:
689: public function error($message = NULL, $code = Http\IResponse::S404_NOT_FOUND)
690: {
691: throw new Application\BadRequestException($message, $code);
692: }
693:
694:
695: 696: 697: 698: 699:
700: public function backlink()
701: {
702: trigger_error(__METHOD__ . '() is deprecated.', E_USER_DEPRECATED);
703: return $this->getAction(TRUE);
704: }
705:
706:
707: 708: 709: 710: 711:
712: public function getLastCreatedRequest()
713: {
714: return $this->lastCreatedRequest;
715: }
716:
717:
718: 719: 720: 721: 722: 723:
724: public function getLastCreatedRequestFlag($flag)
725: {
726: return !empty($this->lastCreatedRequestFlag[$flag]);
727: }
728:
729:
730: 731: 732: 733: 734:
735: public function canonicalize()
736: {
737: if (!$this->isAjax() && ($this->request->isMethod('get') || $this->request->isMethod('head'))) {
738: try {
739: $url = $this->createRequest($this, $this->action, $this->getGlobalState() + $this->request->getParameters(), 'redirectX');
740: } catch (InvalidLinkException $e) {
741: }
742: if (isset($url) && !$this->httpRequest->getUrl()->isEqual($url)) {
743: $this->sendResponse(new Responses\RedirectResponse($url, Http\IResponse::S301_MOVED_PERMANENTLY));
744: }
745: }
746: }
747:
748:
749: 750: 751: 752: 753: 754: 755: 756:
757: public function lastModified($lastModified, $etag = NULL, $expire = NULL)
758: {
759: if ($expire !== NULL) {
760: $this->httpResponse->setExpiration($expire);
761: }
762: $helper = new Http\Context($this->httpRequest, $this->httpResponse);
763: if (!$helper->isModified($lastModified, $etag)) {
764: $this->terminate();
765: }
766: }
767:
768:
769: 770: 771: 772: 773: 774: 775: 776: 777: 778:
779: protected function createRequest($component, $destination, array $args, $mode)
780: {
781:
782:
783: $this->lastCreatedRequest = $this->lastCreatedRequestFlag = NULL;
784:
785:
786:
787: $a = strpos($destination, '#');
788: if ($a === FALSE) {
789: $fragment = '';
790: } else {
791: $fragment = substr($destination, $a);
792: $destination = substr($destination, 0, $a);
793: }
794:
795:
796: $a = strpos($destination, '?');
797: if ($a !== FALSE) {
798: parse_str(substr($destination, $a + 1), $args);
799: $destination = substr($destination, 0, $a);
800: }
801:
802:
803: $a = strpos($destination, '//');
804: if ($a === FALSE) {
805: $scheme = FALSE;
806: } else {
807: $scheme = substr($destination, 0, $a);
808: $destination = substr($destination, $a + 2);
809: }
810:
811:
812: if (!$component instanceof self || substr($destination, -1) === '!') {
813: list($cname, $signal) = Helpers::splitName(rtrim($destination, '!'));
814: if ($cname !== '') {
815: $component = $component->getComponent(strtr($cname, ':', '-'));
816: }
817: if ($signal === '') {
818: throw new InvalidLinkException('Signal must be non-empty string.');
819: }
820: $destination = 'this';
821: }
822:
823: if ($destination == NULL) {
824: throw new InvalidLinkException('Destination must be non-empty string.');
825: }
826:
827:
828: $current = FALSE;
829: list($presenter, $action) = Helpers::splitName($destination);
830: if ($presenter === '') {
831: $action = $destination === 'this' ? $this->action : $action;
832: $presenter = $this->getName();
833: $presenterClass = get_class($this);
834:
835: } else {
836: if ($presenter[0] === ':') {
837: $presenter = substr($presenter, 1);
838: if (!$presenter) {
839: throw new InvalidLinkException("Missing presenter name in '$destination'.");
840: }
841: } else {
842: list($module, , $sep) = Helpers::splitName($this->getName());
843: $presenter = $module . $sep . $presenter;
844: }
845: if (!$this->presenterFactory) {
846: throw new Nette\InvalidStateException('Unable to create link to other presenter, service PresenterFactory has not been set.');
847: }
848: try {
849: $presenterClass = $this->presenterFactory->getPresenterClass($presenter);
850: } catch (Application\InvalidPresenterException $e) {
851: throw new InvalidLinkException($e->getMessage(), NULL, $e);
852: }
853: }
854:
855:
856: if (isset($signal)) {
857: $reflection = new ComponentReflection(get_class($component));
858: if ($signal === 'this') {
859: $signal = '';
860: if (array_key_exists(0, $args)) {
861: throw new InvalidLinkException("Unable to pass parameters to 'this!' signal.");
862: }
863:
864: } elseif (strpos($signal, self::NAME_SEPARATOR) === FALSE) {
865:
866: $method = $component->formatSignalMethod($signal);
867: if (!$reflection->hasCallableMethod($method)) {
868: throw new InvalidLinkException("Unknown signal '$signal', missing handler {$reflection->getName()}::$method()");
869: }
870:
871: self::argsToParams(get_class($component), $method, $args, [], $missing);
872: }
873:
874:
875: if ($args && array_intersect_key($args, $reflection->getPersistentParams())) {
876: $component->saveState($args);
877: }
878:
879: if ($args && $component !== $this) {
880: $prefix = $component->getUniqueId() . self::NAME_SEPARATOR;
881: foreach ($args as $key => $val) {
882: unset($args[$key]);
883: $args[$prefix . $key] = $val;
884: }
885: }
886: }
887:
888:
889: if (is_subclass_of($presenterClass, __CLASS__)) {
890: if ($action === '') {
891: $action = self::DEFAULT_ACTION;
892: }
893:
894: $current = ($action === '*' || strcasecmp($action, $this->action) === 0) && $presenterClass === get_class($this);
895:
896: $reflection = new ComponentReflection($presenterClass);
897:
898:
899: $method = $presenterClass::formatActionMethod($action);
900: if (!$reflection->hasCallableMethod($method)) {
901: $method = $presenterClass::formatRenderMethod($action);
902: if (!$reflection->hasCallableMethod($method)) {
903: $method = NULL;
904: }
905: }
906:
907:
908: if ($method === NULL) {
909: if (array_key_exists(0, $args)) {
910: throw new InvalidLinkException("Unable to pass parameters to action '$presenter:$action', missing corresponding method.");
911: }
912: } else {
913: self::argsToParams($presenterClass, $method, $args, $destination === 'this' ? $this->params : [], $missing);
914: }
915:
916:
917: if ($args && array_intersect_key($args, $reflection->getPersistentParams())) {
918: $this->saveState($args, $reflection);
919: }
920:
921: if ($mode === 'redirect') {
922: $this->saveGlobalState();
923: }
924:
925: $globalState = $this->getGlobalState($destination === 'this' ? NULL : $presenterClass);
926: if ($current && $args) {
927: $tmp = $globalState + $this->params;
928: foreach ($args as $key => $val) {
929: if (http_build_query([$val]) !== (isset($tmp[$key]) ? http_build_query([$tmp[$key]]) : '')) {
930: $current = FALSE;
931: break;
932: }
933: }
934: }
935: $args += $globalState;
936: }
937:
938: if ($mode !== 'test' && !empty($missing)) {
939: foreach ($missing as $rp) {
940: if (!array_key_exists($rp->getName(), $args)) {
941: throw new InvalidLinkException("Missing parameter \${$rp->getName()} required by {$rp->getDeclaringClass()->getName()}::{$rp->getDeclaringFunction()->getName()}()");
942: }
943: }
944: }
945:
946:
947: if ($action) {
948: $args[self::ACTION_KEY] = $action;
949: }
950: if (!empty($signal)) {
951: $args[self::SIGNAL_KEY] = $component->getParameterId($signal);
952: $current = $current && $args[self::SIGNAL_KEY] === $this->getParameter(self::SIGNAL_KEY);
953: }
954: if (($mode === 'redirect' || $mode === 'forward') && $this->hasFlashSession()) {
955: $args[self::FLASH_KEY] = $this->getFlashKey();
956: }
957:
958: $this->lastCreatedRequest = new Application\Request(
959: $presenter,
960: Application\Request::FORWARD,
961: $args,
962: [],
963: []
964: );
965: $this->lastCreatedRequestFlag = ['current' => $current];
966:
967: if ($mode === 'forward' || $mode === 'test') {
968: return;
969: }
970:
971:
972: if ($this->refUrlCache === NULL) {
973: $this->refUrlCache = new Http\Url($this->httpRequest->getUrl());
974: $this->refUrlCache->setPath($this->httpRequest->getUrl()->getScriptPath());
975: }
976: if (!$this->router) {
977: throw new Nette\InvalidStateException('Unable to generate URL, service Router has not been set.');
978: }
979: $url = $this->router->constructUrl($this->lastCreatedRequest, $this->refUrlCache);
980: if ($url === NULL) {
981: unset($args[self::ACTION_KEY]);
982: $params = urldecode(http_build_query($args, NULL, ', '));
983: throw new InvalidLinkException("No route for $presenter:$action($params)");
984: }
985:
986:
987: if ($mode === 'link' && $scheme === FALSE && !$this->absoluteUrls) {
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 . $fragment;
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, IStatePersistent::class);
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: $prefix = $component->getUniqueId() . self::NAME_SEPARATOR;
1167: $params = [];
1168: $component->saveState($params);
1169: foreach ($params as $key => $val) {
1170: $state[$prefix . $key] = $val;
1171: $sinces[$prefix . $key] = $since;
1172: }
1173: }
1174:
1175: } else {
1176: $state = $this->globalState;
1177: }
1178:
1179: if ($forClass !== NULL) {
1180: $since = NULL;
1181: foreach ($state as $key => $foo) {
1182: if (!isset($sinces[$key])) {
1183: $x = strpos($key, self::NAME_SEPARATOR);
1184: $x = $x === FALSE ? $key : substr($key, 0, $x);
1185: $sinces[$key] = isset($sinces[$x]) ? $sinces[$x] : FALSE;
1186: }
1187: if ($since !== $sinces[$key]) {
1188: $since = $sinces[$key];
1189: $ok = $since && is_a($forClass, $since, TRUE);
1190: }
1191: if (!$ok) {
1192: unset($state[$key]);
1193: }
1194: }
1195: }
1196:
1197: return $state;
1198: }
1199:
1200:
1201: 1202: 1203: 1204:
1205: protected function saveGlobalState()
1206: {
1207: $this->globalParams = [];
1208: $this->globalState = $this->getGlobalState();
1209: }
1210:
1211:
1212: 1213: 1214: 1215: 1216:
1217: private function initGlobalParameters()
1218: {
1219:
1220: $this->globalParams = [];
1221: $selfParams = [];
1222:
1223: $params = $this->request->getParameters();
1224: if (($tmp = $this->request->getPost('_' . self::SIGNAL_KEY)) !== NULL) {
1225: $params[self::SIGNAL_KEY] = $tmp;
1226: } elseif ($this->isAjax()) {
1227: $params += $this->request->getPost();
1228: if (($tmp = $this->request->getPost(self::SIGNAL_KEY)) !== NULL) {
1229: $params[self::SIGNAL_KEY] = $tmp;
1230: }
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 [];
1285: }
1286: }
1287:
1288:
1289:
1290:
1291:
1292: 1293: 1294:
1295: private function getFlashKey()
1296: {
1297: $flashKey = $this->getParameter(self::FLASH_KEY);
1298: return is_string($flashKey) && $flashKey !== ''
1299: ? $flashKey
1300: : NULL;
1301: }
1302:
1303:
1304: 1305: 1306: 1307:
1308: public function hasFlashSession()
1309: {
1310: $flashKey = $this->getFlashKey();
1311: return $flashKey !== NULL
1312: && $this->getSession()->hasSection('Nette.Application.Flash/' . $flashKey);
1313: }
1314:
1315:
1316: 1317: 1318: 1319:
1320: public function getFlashSession()
1321: {
1322: $flashKey = $this->getFlashKey();
1323: if ($flashKey === NULL) {
1324: $this->params[self::FLASH_KEY] = $flashKey = Nette\Utils\Random::generate(4);
1325: }
1326: return $this->getSession('Nette.Application.Flash/' . $flashKey);
1327: }
1328:
1329:
1330:
1331:
1332:
1333: public function injectPrimary(Nette\DI\Container $context = NULL, Application\IPresenterFactory $presenterFactory = NULL, Application\IRouter $router = NULL,
1334: Http\IRequest $httpRequest, Http\IResponse $httpResponse, Http\Session $session = NULL, Nette\Security\User $user = NULL, ITemplateFactory $templateFactory = NULL)
1335: {
1336: if ($this->presenterFactory !== NULL) {
1337: throw new Nette\InvalidStateException('Method ' . __METHOD__ . ' is intended for initialization and should not be called more than once.');
1338: }
1339:
1340: $this->context = $context;
1341: $this->presenterFactory = $presenterFactory;
1342: $this->router = $router;
1343: $this->httpRequest = $httpRequest;
1344: $this->httpResponse = $httpResponse;
1345: $this->session = $session;
1346: $this->user = $user;
1347: $this->templateFactory = $templateFactory;
1348: }
1349:
1350:
1351: 1352: 1353: 1354: 1355:
1356: public function getContext()
1357: {
1358: if (!$this->context) {
1359: throw new Nette\InvalidStateException('Context has not been set.');
1360: }
1361: return $this->context;
1362: }
1363:
1364:
1365: 1366: 1367:
1368: public function getHttpRequest()
1369: {
1370: return $this->httpRequest;
1371: }
1372:
1373:
1374: 1375: 1376:
1377: public function getHttpResponse()
1378: {
1379: return $this->httpResponse;
1380: }
1381:
1382:
1383: 1384: 1385: 1386:
1387: public function getSession($namespace = NULL)
1388: {
1389: if (!$this->session) {
1390: throw new Nette\InvalidStateException('Service Session has not been set.');
1391: }
1392: return $namespace === NULL ? $this->session : $this->session->getSection($namespace);
1393: }
1394:
1395:
1396: 1397: 1398:
1399: public function getUser()
1400: {
1401: if (!$this->user) {
1402: throw new Nette\InvalidStateException('Service User has not been set.');
1403: }
1404: return $this->user;
1405: }
1406:
1407:
1408: 1409: 1410:
1411: public function getTemplateFactory()
1412: {
1413: if (!$this->templateFactory) {
1414: throw new Nette\InvalidStateException('Service TemplateFactory has not been set.');
1415: }
1416: return $this->templateFactory;
1417: }
1418:
1419: }
1420: