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