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 $onShutdown;
48:
49:
50: public $autoCanonicalize = true;
51:
52:
53: public $absoluteUrls = false;
54:
55:
56: private $request;
57:
58:
59: private $response;
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($throw = 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, $httpCode = null)
668: {
669: if ($this->isAjax()) {
670: $this->payload->redirect = (string) $url;
671: $this->sendPayload();
672:
673: } elseif (!$httpCode) {
674: $httpCode = $this->httpRequest->isMethod('post')
675: ? Http\IResponse::S303_POST_GET
676: : Http\IResponse::S302_FOUND;
677: }
678: $this->sendResponse(new Responses\RedirectResponse($url, $httpCode));
679: }
680:
681:
682: 683: 684: 685: 686: 687: 688:
689: public function error($message = null, $httpCode = Http\IResponse::S404_NOT_FOUND)
690: {
691: throw new Application\BadRequestException($message, $httpCode);
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:
736: public function canonicalize($destination = null, array $args = [])
737: {
738: if (!$this->isAjax() && ($this->request->isMethod('get') || $this->request->isMethod('head'))) {
739: try {
740: $url = $this->createRequest(
741: $this,
742: $destination ?: $this->action,
743: $args + $this->getGlobalState() + $this->request->getParameters(),
744: 'redirectX'
745: );
746: } catch (InvalidLinkException $e) {
747: }
748: if (isset($url) && !$this->httpRequest->getUrl()->isEqual($url)) {
749: $this->sendResponse(new Responses\RedirectResponse($url, Http\IResponse::S301_MOVED_PERMANENTLY));
750: }
751: }
752: }
753:
754:
755: 756: 757: 758: 759: 760: 761: 762:
763: public function lastModified($lastModified, $etag = null, $expire = null)
764: {
765: if ($expire !== null) {
766: $this->httpResponse->setExpiration($expire);
767: }
768: $helper = new Http\Context($this->httpRequest, $this->httpResponse);
769: if (!$helper->isModified($lastModified, $etag)) {
770: $this->terminate();
771: }
772: }
773:
774:
775: 776: 777: 778: 779: 780: 781: 782: 783: 784:
785: protected function createRequest($component, $destination, array $args, $mode)
786: {
787:
788:
789: $this->lastCreatedRequest = $this->lastCreatedRequestFlag = null;
790:
791:
792:
793: $a = strpos($destination, '#');
794: if ($a === false) {
795: $fragment = '';
796: } else {
797: $fragment = substr($destination, $a);
798: $destination = substr($destination, 0, $a);
799: }
800:
801:
802: $a = strpos($destination, '?');
803: if ($a !== false) {
804: parse_str(substr($destination, $a + 1), $args);
805: $destination = substr($destination, 0, $a);
806: }
807:
808:
809: $a = strpos($destination, '//');
810: if ($a === false) {
811: $scheme = false;
812: } else {
813: $scheme = substr($destination, 0, $a);
814: $destination = substr($destination, $a + 2);
815: }
816:
817:
818: if (!$component instanceof self || substr($destination, -1) === '!') {
819: list($cname, $signal) = Helpers::splitName(rtrim($destination, '!'));
820: if ($cname !== '') {
821: $component = $component->getComponent(strtr($cname, ':', '-'));
822: }
823: if ($signal === '') {
824: throw new InvalidLinkException('Signal must be non-empty string.');
825: }
826: $destination = 'this';
827: }
828:
829: if ($destination == null) {
830: throw new InvalidLinkException('Destination must be non-empty string.');
831: }
832:
833:
834: $current = false;
835: list($presenter, $action) = Helpers::splitName($destination);
836: if ($presenter === '') {
837: $action = $destination === 'this' ? $this->action : $action;
838: $presenter = $this->getName();
839: $presenterClass = get_class($this);
840:
841: } else {
842: if ($presenter[0] === ':') {
843: $presenter = substr($presenter, 1);
844: if (!$presenter) {
845: throw new InvalidLinkException("Missing presenter name in '$destination'.");
846: }
847: } else {
848: list($module, , $sep) = Helpers::splitName($this->getName());
849: $presenter = $module . $sep . $presenter;
850: }
851: if (!$this->presenterFactory) {
852: throw new Nette\InvalidStateException('Unable to create link to other presenter, service PresenterFactory has not been set.');
853: }
854: try {
855: $presenterClass = $this->presenterFactory->getPresenterClass($presenter);
856: } catch (Application\InvalidPresenterException $e) {
857: throw new InvalidLinkException($e->getMessage(), 0, $e);
858: }
859: }
860:
861:
862: if (isset($signal)) {
863: $reflection = new ComponentReflection(get_class($component));
864: if ($signal === 'this') {
865: $signal = '';
866: if (array_key_exists(0, $args)) {
867: throw new InvalidLinkException("Unable to pass parameters to 'this!' signal.");
868: }
869:
870: } elseif (strpos($signal, self::NAME_SEPARATOR) === false) {
871:
872: $method = $component->formatSignalMethod($signal);
873: if (!$reflection->hasCallableMethod($method)) {
874: throw new InvalidLinkException("Unknown signal '$signal', missing handler {$reflection->getName()}::$method()");
875: }
876:
877: self::argsToParams(get_class($component), $method, $args, [], $missing);
878: }
879:
880:
881: if ($args && array_intersect_key($args, $reflection->getPersistentParams())) {
882: $component->saveState($args);
883: }
884:
885: if ($args && $component !== $this) {
886: $prefix = $component->getUniqueId() . self::NAME_SEPARATOR;
887: foreach ($args as $key => $val) {
888: unset($args[$key]);
889: $args[$prefix . $key] = $val;
890: }
891: }
892: }
893:
894:
895: if (is_subclass_of($presenterClass, __CLASS__)) {
896: if ($action === '') {
897: $action = self::DEFAULT_ACTION;
898: }
899:
900: $current = ($action === '*' || strcasecmp($action, $this->action) === 0) && $presenterClass === get_class($this);
901:
902: $reflection = new ComponentReflection($presenterClass);
903:
904:
905: $method = $presenterClass::formatActionMethod($action);
906: if (!$reflection->hasCallableMethod($method)) {
907: $method = $presenterClass::formatRenderMethod($action);
908: if (!$reflection->hasCallableMethod($method)) {
909: $method = null;
910: }
911: }
912:
913:
914: if ($method === null) {
915: if (array_key_exists(0, $args)) {
916: throw new InvalidLinkException("Unable to pass parameters to action '$presenter:$action', missing corresponding method.");
917: }
918: } else {
919: self::argsToParams($presenterClass, $method, $args, $destination === 'this' ? $this->params : [], $missing);
920: }
921:
922:
923: if ($args && array_intersect_key($args, $reflection->getPersistentParams())) {
924: $this->saveState($args, $reflection);
925: }
926:
927: if ($mode === 'redirect') {
928: $this->saveGlobalState();
929: }
930:
931: $globalState = $this->getGlobalState($destination === 'this' ? null : $presenterClass);
932: if ($current && $args) {
933: $tmp = $globalState + $this->params;
934: foreach ($args as $key => $val) {
935: if (http_build_query([$val]) !== (isset($tmp[$key]) ? http_build_query([$tmp[$key]]) : '')) {
936: $current = false;
937: break;
938: }
939: }
940: }
941: $args += $globalState;
942: }
943:
944: if ($mode !== 'test' && !empty($missing)) {
945: foreach ($missing as $rp) {
946: if (!array_key_exists($rp->getName(), $args)) {
947: throw new InvalidLinkException("Missing parameter \${$rp->getName()} required by {$rp->getDeclaringClass()->getName()}::{$rp->getDeclaringFunction()->getName()}()");
948: }
949: }
950: }
951:
952:
953: if ($action) {
954: $args[self::ACTION_KEY] = $action;
955: }
956: if (!empty($signal)) {
957: $args[self::SIGNAL_KEY] = $component->getParameterId($signal);
958: $current = $current && $args[self::SIGNAL_KEY] === $this->getParameter(self::SIGNAL_KEY);
959: }
960: if (($mode === 'redirect' || $mode === 'forward') && $this->hasFlashSession()) {
961: $args[self::FLASH_KEY] = $this->getFlashKey();
962: }
963:
964: $this->lastCreatedRequest = new Application\Request($presenter, Application\Request::FORWARD, $args);
965: $this->lastCreatedRequestFlag = ['current' => $current];
966:
967: return $mode === 'forward' || $mode === 'test'
968: ? null
969: : $this->requestToUrl($this->lastCreatedRequest, $mode === 'link' && $scheme === false && !$this->absoluteUrls) . $fragment;
970: }
971:
972:
973: 974: 975: 976: 977:
978: protected function requestToUrl(Application\Request $request, $relative = null)
979: {
980: if ($this->refUrlCache === null) {
981: $this->refUrlCache = new Http\Url($this->httpRequest->getUrl());
982: $this->refUrlCache->setPath($this->httpRequest->getUrl()->getScriptPath());
983: }
984: if (!$this->router) {
985: throw new Nette\InvalidStateException('Unable to generate URL, service Router has not been set.');
986: }
987:
988: $url = $this->router->constructUrl($request, $this->refUrlCache);
989: if ($url === null) {
990: $params = $request->getParameters();
991: unset($params[self::ACTION_KEY]);
992: $params = urldecode(http_build_query($params, '', ', '));
993: throw new InvalidLinkException("No route for {$request->getPresenterName()}:{$request->getParameter('action')}($params)");
994: }
995:
996: if ($relative === null ? !$this->absoluteUrls : $relative) {
997: $hostUrl = $this->refUrlCache->getHostUrl() . '/';
998: if (strncmp($url, $hostUrl, strlen($hostUrl)) === 0) {
999: $url = substr($url, strlen($hostUrl) - 1);
1000: }
1001: }
1002:
1003: return $url;
1004: }
1005:
1006:
1007: 1008: 1009: 1010: 1011: 1012: 1013: 1014: 1015: 1016: 1017:
1018: public static function argsToParams($class, $method, &$args, $supplemental = [], &$missing = [])
1019: {
1020: $i = 0;
1021: $rm = new \ReflectionMethod($class, $method);
1022: foreach ($rm->getParameters() as $param) {
1023: list($type, $isClass) = ComponentReflection::getParameterType($param);
1024: $name = $param->getName();
1025:
1026: if (array_key_exists($i, $args)) {
1027: $args[$name] = $args[$i];
1028: unset($args[$i]);
1029: $i++;
1030:
1031: } elseif (array_key_exists($name, $args)) {
1032:
1033:
1034: } elseif (array_key_exists($name, $supplemental)) {
1035: $args[$name] = $supplemental[$name];
1036: }
1037:
1038: if (!isset($args[$name])) {
1039: if (!$param->isDefaultValueAvailable() && !$param->allowsNull() && $type !== 'NULL' && $type !== 'array') {
1040: $missing[] = $param;
1041: unset($args[$name]);
1042: }
1043: continue;
1044: }
1045:
1046: if (!ComponentReflection::convertType($args[$name], $type, $isClass)) {
1047: throw new InvalidLinkException(sprintf(
1048: 'Argument $%s passed to %s() must be %s, %s given.',
1049: $name,
1050: $rm->getDeclaringClass()->getName() . '::' . $rm->getName(),
1051: $type === 'NULL' ? 'scalar' : $type,
1052: is_object($args[$name]) ? get_class($args[$name]) : gettype($args[$name])
1053: ));
1054: }
1055:
1056: $def = $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null;
1057: if ($args[$name] === $def || ($def === null && $args[$name] === '')) {
1058: $args[$name] = null;
1059: }
1060: }
1061:
1062: if (array_key_exists($i, $args)) {
1063: throw new InvalidLinkException("Passed more parameters than method $class::{$rm->getName()}() expects.");
1064: }
1065: }
1066:
1067:
1068: 1069: 1070: 1071: 1072:
1073: protected function handleInvalidLink(InvalidLinkException $e)
1074: {
1075: if ($this->invalidLinkMode & self::INVALID_LINK_EXCEPTION) {
1076: throw $e;
1077: } elseif ($this->invalidLinkMode & self::INVALID_LINK_WARNING) {
1078: trigger_error('Invalid link: ' . $e->getMessage(), E_USER_WARNING);
1079: }
1080: return $this->invalidLinkMode & self::INVALID_LINK_TEXTUAL
1081: ? '#error: ' . $e->getMessage()
1082: : '#';
1083: }
1084:
1085:
1086:
1087:
1088:
1089: 1090: 1091: 1092: 1093:
1094: public function storeRequest($expiration = '+ 10 minutes')
1095: {
1096: $session = $this->getSession('Nette.Application/requests');
1097: do {
1098: $key = Nette\Utils\Random::generate(5);
1099: } while (isset($session[$key]));
1100:
1101: $session[$key] = [$this->user ? $this->user->getId() : null, $this->request];
1102: $session->setExpiration($expiration, $key);
1103: return $key;
1104: }
1105:
1106:
1107: 1108: 1109: 1110: 1111:
1112: public function restoreRequest($key)
1113: {
1114: $session = $this->getSession('Nette.Application/requests');
1115: if (!isset($session[$key]) || ($session[$key][0] !== null && $session[$key][0] !== $this->getUser()->getId())) {
1116: return;
1117: }
1118: $request = clone $session[$key][1];
1119: unset($session[$key]);
1120: $request->setFlag(Application\Request::RESTORED, true);
1121: $params = $request->getParameters();
1122: $params[self::FLASH_KEY] = $this->getFlashKey();
1123: $request->setParameters($params);
1124: $this->sendResponse(new Responses\ForwardResponse($request));
1125: }
1126:
1127:
1128:
1129:
1130:
1131: 1132: 1133: 1134: 1135:
1136: public static function getPersistentComponents()
1137: {
1138: return (array) ComponentReflection::parseAnnotation(new \ReflectionClass(get_called_class()), 'persistent');
1139: }
1140:
1141:
1142: 1143: 1144: 1145:
1146: protected function getGlobalState($forClass = null)
1147: {
1148: $sinces = &$this->globalStateSinces;
1149:
1150: if ($this->globalState === null) {
1151: $state = [];
1152: foreach ($this->globalParams as $id => $params) {
1153: $prefix = $id . self::NAME_SEPARATOR;
1154: foreach ($params as $key => $val) {
1155: $state[$prefix . $key] = $val;
1156: }
1157: }
1158: $this->saveState($state, $forClass ? new ComponentReflection($forClass) : null);
1159:
1160: if ($sinces === null) {
1161: $sinces = [];
1162: foreach ($this->getReflection()->getPersistentParams() as $name => $meta) {
1163: $sinces[$name] = $meta['since'];
1164: }
1165: }
1166:
1167: $components = $this->getReflection()->getPersistentComponents();
1168: $iterator = $this->getComponents(true);
1169:
1170: foreach ($iterator as $name => $component) {
1171: if ($iterator->getDepth() === 0) {
1172:
1173: $since = isset($components[$name]['since']) ? $components[$name]['since'] : false;
1174: }
1175: if (!$component instanceof IStatePersistent) {
1176: continue;
1177: }
1178: $prefix = $component->getUniqueId() . self::NAME_SEPARATOR;
1179: $params = [];
1180: $component->saveState($params);
1181: foreach ($params as $key => $val) {
1182: $state[$prefix . $key] = $val;
1183: $sinces[$prefix . $key] = $since;
1184: }
1185: }
1186:
1187: } else {
1188: $state = $this->globalState;
1189: }
1190:
1191: if ($forClass !== null) {
1192: $since = null;
1193: foreach ($state as $key => $foo) {
1194: if (!isset($sinces[$key])) {
1195: $x = strpos($key, self::NAME_SEPARATOR);
1196: $x = $x === false ? $key : substr($key, 0, $x);
1197: $sinces[$key] = isset($sinces[$x]) ? $sinces[$x] : false;
1198: }
1199: if ($since !== $sinces[$key]) {
1200: $since = $sinces[$key];
1201: $ok = $since && is_a($forClass, $since, true);
1202: }
1203: if (!$ok) {
1204: unset($state[$key]);
1205: }
1206: }
1207: }
1208:
1209: return $state;
1210: }
1211:
1212:
1213: 1214: 1215:
1216: public function saveState(array &$params, ComponentReflection $reflection = null)
1217: {
1218: $reflection = $reflection ?: $this->getReflection();
1219: $reflection->saveState($this, $params);
1220: }
1221:
1222:
1223: 1224: 1225: 1226:
1227: protected function saveGlobalState()
1228: {
1229: $this->globalParams = [];
1230: $this->globalState = $this->getGlobalState();
1231: }
1232:
1233:
1234: 1235: 1236: 1237: 1238:
1239: private function initGlobalParameters()
1240: {
1241:
1242: $this->globalParams = [];
1243: $selfParams = [];
1244:
1245: $params = $this->request->getParameters();
1246: if (($tmp = $this->request->getPost('_' . self::SIGNAL_KEY)) !== null) {
1247: $params[self::SIGNAL_KEY] = $tmp;
1248: } elseif ($this->isAjax()) {
1249: $params += $this->request->getPost();
1250: if (($tmp = $this->request->getPost(self::SIGNAL_KEY)) !== null) {
1251: $params[self::SIGNAL_KEY] = $tmp;
1252: }
1253: }
1254:
1255: foreach ($params as $key => $value) {
1256: if (!preg_match('#^((?:[a-z0-9_]+-)*)((?!\d+\z)[a-z0-9_]+)\z#i', $key, $matches)) {
1257: continue;
1258: } elseif (!$matches[1]) {
1259: $selfParams[$key] = $value;
1260: } else {
1261: $this->globalParams[substr($matches[1], 0, -1)][$matches[2]] = $value;
1262: }
1263: }
1264:
1265:
1266: $this->changeAction(isset($selfParams[self::ACTION_KEY]) ? $selfParams[self::ACTION_KEY] : self::DEFAULT_ACTION);
1267:
1268:
1269: $this->signalReceiver = $this->getUniqueId();
1270: if (isset($selfParams[self::SIGNAL_KEY])) {
1271: $param = $selfParams[self::SIGNAL_KEY];
1272: if (!is_string($param)) {
1273: $this->error('Signal name is not string.');
1274: }
1275: $pos = strrpos($param, '-');
1276: if ($pos) {
1277: $this->signalReceiver = substr($param, 0, $pos);
1278: $this->signal = substr($param, $pos + 1);
1279: } else {
1280: $this->signalReceiver = $this->getUniqueId();
1281: $this->signal = $param;
1282: }
1283: if ($this->signal == null) {
1284: $this->signal = null;
1285: }
1286: }
1287:
1288: $this->loadState($selfParams);
1289: }
1290:
1291:
1292: 1293: 1294: 1295: 1296: 1297:
1298: public function popGlobalParameters($id)
1299: {
1300: if (isset($this->globalParams[$id])) {
1301: $res = $this->globalParams[$id];
1302: unset($this->globalParams[$id]);
1303: return $res;
1304:
1305: } else {
1306: return [];
1307: }
1308: }
1309:
1310:
1311:
1312:
1313:
1314: 1315: 1316:
1317: private function getFlashKey()
1318: {
1319: $flashKey = $this->getParameter(self::FLASH_KEY);
1320: return is_string($flashKey) && $flashKey !== ''
1321: ? $flashKey
1322: : null;
1323: }
1324:
1325:
1326: 1327: 1328: 1329:
1330: public function hasFlashSession()
1331: {
1332: $flashKey = $this->getFlashKey();
1333: return $flashKey !== null
1334: && $this->getSession()->hasSection('Nette.Application.Flash/' . $flashKey);
1335: }
1336:
1337:
1338: 1339: 1340: 1341:
1342: public function getFlashSession()
1343: {
1344: $flashKey = $this->getFlashKey();
1345: if ($flashKey === null) {
1346: $this->params[self::FLASH_KEY] = $flashKey = Nette\Utils\Random::generate(4);
1347: }
1348: return $this->getSession('Nette.Application.Flash/' . $flashKey);
1349: }
1350:
1351:
1352:
1353:
1354:
1355: public function injectPrimary(Nette\DI\Container $context = null, Application\IPresenterFactory $presenterFactory = null, Application\IRouter $router = null,
1356: Http\IRequest $httpRequest, Http\IResponse $httpResponse, Http\Session $session = null, Nette\Security\User $user = null, ITemplateFactory $templateFactory = null)
1357: {
1358: if ($this->presenterFactory !== null) {
1359: throw new Nette\InvalidStateException('Method ' . __METHOD__ . ' is intended for initialization and should not be called more than once.');
1360: }
1361:
1362: $this->context = $context;
1363: $this->presenterFactory = $presenterFactory;
1364: $this->router = $router;
1365: $this->httpRequest = $httpRequest;
1366: $this->httpResponse = $httpResponse;
1367: $this->session = $session;
1368: $this->user = $user;
1369: $this->templateFactory = $templateFactory;
1370: }
1371:
1372:
1373: 1374: 1375: 1376: 1377:
1378: public function getContext()
1379: {
1380: if (!$this->context) {
1381: throw new Nette\InvalidStateException('Context has not been set.');
1382: }
1383: return $this->context;
1384: }
1385:
1386:
1387: 1388: 1389:
1390: public function getHttpRequest()
1391: {
1392: return $this->httpRequest;
1393: }
1394:
1395:
1396: 1397: 1398:
1399: public function getHttpResponse()
1400: {
1401: return $this->httpResponse;
1402: }
1403:
1404:
1405: 1406: 1407: 1408:
1409: public function getSession($namespace = null)
1410: {
1411: if (!$this->session) {
1412: throw new Nette\InvalidStateException('Service Session has not been set.');
1413: }
1414: return $namespace === null ? $this->session : $this->session->getSection($namespace);
1415: }
1416:
1417:
1418: 1419: 1420:
1421: public function getUser()
1422: {
1423: if (!$this->user) {
1424: throw new Nette\InvalidStateException('Service User has not been set.');
1425: }
1426: return $this->user;
1427: }
1428:
1429:
1430: 1431: 1432:
1433: public function getTemplateFactory()
1434: {
1435: if (!$this->templateFactory) {
1436: throw new Nette\InvalidStateException('Service TemplateFactory has not been set.');
1437: }
1438: return $this->templateFactory;
1439: }
1440: }
1441: