1: <?php
2:
3: namespace Alpha\Controller\Front;
4:
5: use Alpha\Util\Logging\Logger;
6: use Alpha\Util\Config\ConfigProvider;
7: use Alpha\Util\Security\SecurityUtils;
8: use Alpha\Util\Http\Filter\FilterInterface;
9: use Alpha\Util\Http\Response;
10: use Alpha\Util\Http\Request;
11: use Alpha\Exception\BadRequestException;
12: use Alpha\Exception\ResourceNotFoundException;
13: use Alpha\Exception\SecurityException;
14: use Alpha\Exception\IllegalArguementException;
15: use Alpha\Exception\AlphaException;
16: use Alpha\Controller\Controller;
17: use Alpha\Controller\ArticleController;
18: use Alpha\Controller\AttachmentController;
19: use Alpha\Controller\CacheController;
20: use Alpha\Controller\DEnumController;
21: use Alpha\Controller\ExcelController;
22: use Alpha\Controller\FeedController;
23: use Alpha\Controller\GenSecureQueryStringController;
24: use Alpha\Controller\ImageController;
25: use Alpha\Controller\ListActiveRecordsController;
26: use Alpha\Controller\LogController;
27: use Alpha\Controller\LoginController;
28: use Alpha\Controller\LogoutController;
29: use Alpha\Controller\MetricController;
30: use Alpha\Controller\RecordSelectorController;
31: use Alpha\Controller\SearchController;
32: use Alpha\Controller\SequenceController;
33: use Alpha\Controller\TagController;
34: use Alpha\Controller\IndexController;
35: use Alpha\Controller\InstallController;
36: use Alpha\Controller\ActiveRecordController;
37: use Alpha\Controller\PhpinfoController;
38:
39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80:
81: class FrontController
82: {
83: 84: 85: 86: 87: 88: 89:
90: private $queryString;
91:
92: 93: 94: 95: 96: 97: 98:
99: private $pageController;
100:
101: 102: 103: 104: 105: 106: 107:
108: private $encryptedQuery = false;
109:
110: 111: 112: 113: 114: 115: 116: 117:
118: private $filters = array();
119:
120: 121: 122: 123: 124: 125: 126:
127: private $routes;
128:
129: 130: 131: 132: 133: 134: 135:
136: private $currentRoute;
137:
138: 139: 140: 141: 142: 143: 144:
145: private $defaultParamValues;
146:
147: 148: 149: 150: 151: 152: 153:
154: private static $logger = null;
155:
156: 157: 158: 159: 160: 161: 162:
163: public function __construct()
164: {
165: self::$logger = new Logger('FrontController');
166:
167: self::$logger->debug('>>__construct()');
168:
169: $config = ConfigProvider::getInstance();
170:
171: mb_internal_encoding('UTF-8');
172: mb_http_output('UTF-8');
173: mb_http_input('UTF-8');
174: ini_set('default_charset', 'utf-8');
175: if (!mb_check_encoding()) {
176: throw new BadRequestException('Request character encoding does not match expected UTF-8');
177: }
178:
179: $this->addRoute('/', function ($request) {
180: $controller = new IndexController();
181:
182: return $controller->process($request);
183: });
184:
185: $this->addRoute('/a/{title}/{view}', function ($request) {
186: $controller = new ArticleController();
187:
188: return $controller->process($request);
189: })->value('title', null)->value('view', 'detailed');
190:
191: $this->addRoute('/articles/{start}/{limit}', function ($request) {
192: $controller = new ArticleController();
193:
194: return $controller->process($request);
195: })->value('start', 0)->value('limit', $config->get('app.list.page.amount'));
196:
197: $this->addRoute('/attach/{articleOID}/{filename}', function ($request) {
198: $controller = new AttachmentController();
199:
200: return $controller->process($request);
201: });
202:
203: $this->addRoute('/cache', function ($request) {
204: $controller = new CacheController();
205:
206: return $controller->process($request);
207: });
208:
209: $this->addRoute('/denum/{denumOID}', function ($request) {
210: $controller = new DEnumController();
211:
212: return $controller->process($request);
213: })->value('denumOID', null);
214:
215: $this->addRoute('/excel/{ActiveRecordType}/{ActiveRecordOID}', function ($request) {
216: $controller = new ExcelController();
217:
218: return $controller->process($request);
219: })->value('ActiveRecordOID', null);
220:
221: $this->addRoute('/feed/{ActiveRecordType}/{type}', function ($request) {
222: $controller = new FeedController();
223:
224: return $controller->process($request);
225: })->value('type', 'Atom');
226:
227: $this->addRoute('/gensecure', function ($request) {
228: $controller = new GenSecureQueryStringController();
229:
230: return $controller->process($request);
231: });
232:
233: $this->addRoute('/image/{source}/{width}/{height}/{type}/{quality}/{scale}/{secure}/{var1}/{var2}', function ($request) {
234: $controller = new ImageController();
235:
236: return $controller->process($request);
237: })->value('var1', null)->value('var2', null);
238:
239: $this->addRoute('/listactiverecords', function ($request) {
240: $controller = new ListActiveRecordsController();
241:
242: return $controller->process($request);
243: });
244:
245: $this->addRoute('/log/{logPath}', function ($request) {
246: $controller = new LogController();
247:
248: return $controller->process($request);
249: });
250:
251: $this->addRoute('/login', function ($request) {
252: $controller = new LoginController();
253:
254: return $controller->process($request);
255: });
256:
257: $this->addRoute('/logout', function ($request) {
258: $controller = new LogoutController();
259:
260: return $controller->process($request);
261: });
262:
263: $this->addRoute('/metric', function ($request) {
264: $controller = new MetricController();
265:
266: return $controller->process($request);
267: });
268:
269: $this->addRoute('/recordselector/12m/{ActiveRecordOID}/{field}/{relatedClass}/{relatedClassField}/{relatedClassDisplayField}/{relationType}', function ($request) {
270: $controller = new RecordSelectorController();
271:
272: return $controller->process($request);
273: })->value('relationType', 'ONE-TO-MANY');
274:
275: $this->addRoute('/recordselector/m2m/{ActiveRecordOID}/{field}/{relatedClassLeft}/{relatedClassLeftDisplayField}/{relatedClassRight}/{relatedClassRightDisplayField}/{accessingClassName}/{lookupOIDs}/{relationType}', function ($request) {
276: $controller = new RecordSelectorController();
277:
278: return $controller->process($request);
279: })->value('relationType', 'MANY-TO-MANY');
280:
281: $this->addRoute('/search/{query}/{start}/{limit}', function ($request) {
282: $controller = new SearchController();
283:
284: return $controller->process($request);
285: })->value('start', 0)->value('limit', $config->get('app.list.page.amount'));
286:
287: $this->addRoute('/sequence/{start}/{limit}', function ($request) {
288: $controller = new SequenceController();
289:
290: return $controller->process($request);
291: })->value('start', 0)->value('limit', $config->get('app.list.page.amount'));
292:
293: $this->addRoute('/tag/{ActiveRecordType}/{ActiveRecordOID}', function ($request) {
294: $controller = new TagController();
295:
296: return $controller->process($request);
297: });
298:
299: $this->addRoute('/install', function ($request) {
300: $controller = new InstallController();
301:
302: return $controller->process($request);
303: });
304:
305: $this->addRoute('/record/{ActiveRecordType}/{ActiveRecordOID}/{view}', function ($request) {
306: $controller = new ActiveRecordController();
307:
308: return $controller->process($request);
309: })->value('ActiveRecordOID', null)->value('view', 'detailed');
310:
311: $this->addRoute('/records/{ActiveRecordType}/{start}/{limit}', function ($request) {
312: $controller = new ActiveRecordController();
313:
314: return $controller->process($request);
315: })->value('start', 0)->value('limit', $config->get('app.list.page.amount'));
316:
317: $this->addRoute('/tk/{token}', function ($request) {
318: $params = self::getDecodeQueryParams($request->getParam('token'));
319:
320: if (isset($params['act'])) {
321: $className = $params['act'];
322:
323: if (class_exists($className)) {
324: $controller = new $className();
325:
326: if (isset($params['ActiveRecordType']) && $params['act'] == 'Alpha\Controller\ActiveRecordController') {
327: $customController = $controller->getCustomControllerName($params['ActiveRecordType']);
328: if ($customController != null) {
329: $controller = new $customController();
330: }
331: }
332:
333: $request->setParams(array_merge($params, $request->getParams()));
334:
335: return $controller->process($request);
336: }
337: }
338:
339: self::$logger->warn('Bad params ['.print_r($params, true).'] provided on a /tk/ request');
340:
341: return new Response(404, 'Resource not found');
342: });
343:
344: $this->addRoute('/alpha/service', function ($request) {
345: $controller = new LoginController();
346: $controller->setUnitOfWork(array('Alpha\Controller\LoginController', 'Alpha\Controller\ListActiveRecordsController'));
347:
348: return $controller->process($request);
349: });
350:
351: $this->addRoute('/phpinfo', function ($request) {
352: $controller = new PhpinfoController();
353:
354: return $controller->process($request);
355: });
356:
357: self::$logger->debug('<<__construct');
358: }
359:
360: 361: 362: 363: 364: 365: 366:
367: public function setEncrypt($encryptedQuery)
368: {
369: $this->encryptedQuery = $encryptedQuery;
370: }
371:
372: 373: 374: 375: 376: 377: 378: 379: 380:
381: public static function generateSecureURL($params)
382: {
383: $config = ConfigProvider::getInstance();
384:
385: if ($config->get('app.use.mod.rewrite')) {
386: return $config->get('app.url').'/tk/'.self::encodeQuery($params);
387: } else {
388: return $config->get('app.url').'?tk='.self::encodeQuery($params);
389: }
390: }
391:
392: 393: 394: 395: 396: 397: 398: 399: 400:
401: public static function encodeQuery($queryString)
402: {
403: $config = ConfigProvider::getInstance();
404:
405: $return = base64_encode(SecurityUtils::encrypt($queryString));
406:
407: $return = strtr($return, '+/', '-_');
408:
409: return $return;
410: }
411:
412: 413: 414: 415: 416: 417: 418: 419:
420: private function decodeQuery()
421: {
422: $config = ConfigProvider::getInstance();
423:
424: $params = $this->request->getParams();
425:
426: if (!isset($params['token'])) {
427: throw new SecurityException('No token provided for the front controller!');
428: } else {
429:
430: $token = strtr($params['token'], '-_', '+/');
431: $token = base64_decode($token);
432: $this->queryString = SecurityUtils::decrypt($token);
433: }
434: }
435:
436: 437: 438: 439: 440: 441: 442:
443: public static function decodeQueryParams($tk)
444: {
445: $config = ConfigProvider::getInstance();
446:
447:
448: $token = strtr($tk, '-_', '+/');
449: $token = base64_decode($token);
450: $params = SecurityUtils::decrypt($token);
451:
452: return $params;
453: }
454:
455: 456: 457: 458: 459: 460: 461:
462: public static function getDecodeQueryParams($tk)
463: {
464: $config = ConfigProvider::getInstance();
465:
466:
467: $token = strtr($tk, '-_', '+/');
468: $token = base64_decode($token);
469: $params = SecurityUtils::decrypt($token);
470:
471: $pairs = explode('&', $params);
472:
473: $parameters = array();
474:
475: foreach ($pairs as $pair) {
476: $split = explode('=', $pair);
477: $parameters[$split[0]] = $split[1];
478: }
479:
480: return $parameters;
481: }
482:
483: 484: 485: 486: 487: 488: 489: 490: 491: 492:
493: private static function multipleExplode($string, $delimiters = array())
494: {
495: $mainDelim = $delimiters[count($delimiters) - 1];
496:
497: array_pop($delimiters);
498:
499: foreach ($delimiters as $delimiter) {
500: $string = str_replace($delimiter, $mainDelim, $string);
501: }
502:
503: $result = explode($mainDelim, $string);
504:
505: return $result;
506: }
507:
508: 509: 510: 511: 512: 513: 514:
515: public function getPageController()
516: {
517: return $this->pageController;
518: }
519:
520: 521: 522: 523: 524: 525: 526: 527: 528:
529: public function registerFilter($filterObject)
530: {
531: if ($filterObject instanceof FilterInterface) {
532: array_push($this->filters, $filterObject);
533: } else {
534: throw new IllegalArguementException('Supplied filter object is not a valid FilterInterface instance!');
535: }
536: }
537:
538: 539: 540: 541: 542: 543: 544:
545: public function getFilters()
546: {
547: return $this->filters;
548: }
549:
550: 551: 552: 553: 554: 555: 556: 557: 558: 559: 560: 561: 562:
563: public function addRoute($URI, $callback)
564: {
565: if (is_callable($callback)) {
566: $this->routes[$URI] = $callback;
567: $this->currentRoute = $URI;
568:
569: return $this;
570: } else {
571: throw new IllegalArguementException('Callback provided for route ['.$URI.'] is not callable');
572: }
573: }
574:
575: 576: 577: 578: 579: 580: 581: 582: 583: 584:
585: public function value($param, $defaultValue)
586: {
587: $this->defaultParamValues[$this->currentRoute][$param] = $defaultValue;
588:
589: return $this;
590: }
591:
592: 593: 594: 595: 596: 597: 598: 599: 600: 601: 602:
603: public function getRouteCallback($URI)
604: {
605: if (array_key_exists($URI, $this->routes)) {
606: $this->currentRoute = $URI;
607:
608: return $this->routes[$URI];
609: } else {
610:
611:
612: foreach ($this->routes as $route => $callback) {
613: $pattern = '#^'.$route.'$#s';
614: $pattern = preg_replace('#\{\S+\}#', '\S+', $pattern);
615:
616: if (preg_match($pattern, $URI)) {
617: $this->currentRoute = $route;
618:
619: return $callback;
620: }
621: }
622:
623:
624: foreach ($this->routes as $route => $callback) {
625: $pattern = '#^'.$route.'$#s';
626: $pattern = preg_replace('#\/\{\S+\}#', '\/?', $pattern);
627:
628: if (preg_match($pattern, $URI)) {
629: $this->currentRoute = $route;
630:
631: return $callback;
632: }
633: }
634: }
635:
636:
637: throw new IllegalArguementException('No callback defined for URI ['.$URI.']');
638: }
639:
640: 641: 642: 643: 644: 645: 646: 647: 648: 649: 650: 651: 652:
653: public function process($request)
654: {
655: foreach ($this->filters as $filter) {
656: $filter->process($request);
657: }
658:
659: try {
660: $callback = $this->getRouteCallback($request->getURI());
661: } catch (IllegalArguementException $e) {
662: self::$logger->info($e->getMessage());
663: throw new ResourceNotFoundException('Resource not found');
664: }
665:
666: if ($request->getURI() != $this->currentRoute) {
667: if (isset($this->defaultParamValues[$this->currentRoute])) {
668: $request->parseParamsFromRoute($this->currentRoute, $this->defaultParamValues[$this->currentRoute]);
669: } else {
670: $request->parseParamsFromRoute($this->currentRoute);
671: }
672: }
673:
674: try {
675: $response = call_user_func($callback, $request);
676: } catch (ResourceNotFoundException $rnfe) {
677: self::$logger->info('ResourceNotFoundException throw, source message ['.$rnfe->getMessage().']');
678:
679: return new Response(404, $rnfe->getMessage());
680: }
681:
682: if ($response instanceof Response) {
683: return $response;
684: } else {
685: self::$logger->error('The callable defined for route ['.$request->getURI().'] does not return a Response object');
686: throw new AlphaException('Unable to process request');
687: }
688: }
689: }
690: