Overview

Namespaces

  • Alpha
    • Controller
      • Front
    • Exception
    • Model
      • Type
    • Task
    • Util
      • Backup
      • Cache
      • Code
        • Highlight
        • Metric
      • Config
      • Convertor
      • Email
      • Extension
      • Feed
      • File
      • Graph
      • Helper
      • Http
        • Filter
        • Session
      • Image
      • Logging
      • Search
      • Security
    • View
      • Renderer
        • Html
        • Json
      • Widget

Classes

  • FrontController
  • Overview
  • Namespace
  • Class
  • Tree
  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:  * The front controller designed to optionally handle all requests.
 41:  *
 42:  * @since 1.0
 43:  *
 44:  * @author John Collins <dev@alphaframework.org>
 45:  * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
 46:  * @copyright Copyright (c) 2016, John Collins (founder of Alpha Framework).
 47:  * All rights reserved.
 48:  *
 49:  * <pre>
 50:  * Redistribution and use in source and binary forms, with or
 51:  * without modification, are permitted provided that the
 52:  * following conditions are met:
 53:  *
 54:  * * Redistributions of source code must retain the above
 55:  *   copyright notice, this list of conditions and the
 56:  *   following disclaimer.
 57:  * * Redistributions in binary form must reproduce the above
 58:  *   copyright notice, this list of conditions and the
 59:  *   following disclaimer in the documentation and/or other
 60:  *   materials provided with the distribution.
 61:  * * Neither the name of the Alpha Framework nor the names
 62:  *   of its contributors may be used to endorse or promote
 63:  *   products derived from this software without specific
 64:  *   prior written permission.
 65:  *
 66:  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 67:  * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 68:  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 69:  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 70:  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 71:  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 72:  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 73:  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 74:  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 75:  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 76:  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 77:  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 78:  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 79:  * </pre>
 80:  */
 81: class FrontController
 82: {
 83:     /**
 84:      * The GET query string.
 85:      *
 86:      * @var string
 87:      *
 88:      * @since 1.0
 89:      */
 90:     private $queryString;
 91: 
 92:     /**
 93:      * The name of the page controller we want to invoke.
 94:      *
 95:      * @var string
 96:      *
 97:      * @since 1.0
 98:      */
 99:     private $pageController;
100: 
101:     /**
102:      * Boolean to flag if the GET query string is encrypted or not.
103:      *
104:      * @var bool
105:      *
106:      * @since 1.0
107:      */
108:     private $encryptedQuery = false;
109: 
110:     /**
111:      * An array of HTTP filters applied to each request to the front controller.  Each
112:      * member must implement FilterInterface!
113:      *
114:      * @var array
115:      *
116:      * @since 1.0
117:      */
118:     private $filters = array();
119: 
120:     /**
121:      * An associative array of URIs to callable methods to service matching requests.
122:      *
123:      * @var array
124:      *
125:      * @since 2.0
126:      */
127:     private $routes;
128: 
129:     /**
130:      * The route for the current request.
131:      *
132:      * @var string
133:      *
134:      * @since 2.0
135:      */
136:     private $currentRoute;
137: 
138:     /**
139:      * An optional 2D hash array of default request parameter values to use when those params are left off the request.
140:      *
141:      * @var array
142:      *
143:      * @since 2.0
144:      */
145:     private $defaultParamValues;
146: 
147:     /**
148:      * Trace logger.
149:      *
150:      * @var Alpha\Util\Logging\Logger
151:      *
152:      * @since 1.0
153:      */
154:     private static $logger = null;
155: 
156:     /**
157:      * The constructor method.
158:      *
159:      * @throws Alpha\Exception\BadRequestException
160:      *
161:      * @since 1.0
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:      * Sets the encryption flag.
362:      *
363:      * @param bool $encryptedQuery
364:      *
365:      * @since 1.0
366:      */
367:     public function setEncrypt($encryptedQuery)
368:     {
369:         $this->encryptedQuery = $encryptedQuery;
370:     }
371: 
372:     /**
373:      * Static method for generating an absolute, secure URL for a page controller.
374:      *
375:      * @param string $params
376:      *
377:      * @return string
378:      *
379:      * @since 1.0
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:      * Static method for encoding a query string.
394:      *
395:      * @param string $queryString
396:      *
397:      * @return string
398:      *
399:      * @since 1.0
400:      */
401:     public static function encodeQuery($queryString)
402:     {
403:         $config = ConfigProvider::getInstance();
404: 
405:         $return = base64_encode(SecurityUtils::encrypt($queryString));
406:         // remove any characters that are likely to cause trouble on a URL
407:         $return = strtr($return, '+/', '-_');
408: 
409:         return $return;
410:     }
411: 
412:     /**
413:      * Method to decode the current query string.
414:      *
415:      * @throws Alpha\Exception\SecurityException
416:      *
417:      * @since 1.0
418:      * @deprecated
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:             // replace any troublesome characters from the URL with the original values
430:             $token = strtr($params['token'], '-_', '+/');
431:             $token = base64_decode($token);
432:             $this->queryString = SecurityUtils::decrypt($token);
433:         }
434:     }
435: 
436:     /**
437:      * Static method to return the decoded GET paramters from an encrytpted tk value.
438:      *
439:      * @return string
440:      *
441:      * @since 1.0
442:      */
443:     public static function decodeQueryParams($tk)
444:     {
445:         $config = ConfigProvider::getInstance();
446: 
447:         // replace any troublesome characters from the URL with the original values
448:         $token = strtr($tk, '-_', '+/');
449:         $token = base64_decode($token);
450:         $params = SecurityUtils::decrypt($token);
451: 
452:         return $params;
453:     }
454: 
455:     /**
456:      * Static method to return the decoded GET paramters from an encrytpted tk value as an array of key/value pairs.
457:      *
458:      * @return array
459:      *
460:      * @since 1.0
461:      */
462:     public static function getDecodeQueryParams($tk)
463:     {
464:         $config = ConfigProvider::getInstance();
465: 
466:         // replace any troublesome characters from the URL with the original values
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:      * Explodes the provided string into an array based on the array of delimiters provided.
485:      *
486:      * @param string $string     The string to explode.
487:      * @param array  $delimiters An array of delimiters.
488:      *
489:      * @return array
490:      *
491:      * @since 1.2
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:      * Getter for the page controller.
510:      *
511:      * @return string
512:      *
513:      * @since 1.0
514:      */
515:     public function getPageController()
516:     {
517:         return $this->pageController;
518:     }
519: 
520:     /**
521:      * Add the supplied filter object to the list of filters ran on each request to the front controller.
522:      *
523:      * @param Alpha\Util\Http\Filter\FilterInterface $filterObject
524:      *
525:      * @throws Alpha\Exception\IllegalArguementException
526:      *
527:      * @since 1.0
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:      * Returns the array of filters currently attached to this FrontController.
540:      *
541:      * @return array
542:      *
543:      * @since 1.0
544:      */
545:     public function getFilters()
546:     {
547:         return $this->filters;
548:     }
549: 
550:     /**
551:      * Add a new route to map a URI to the callback that will service its requests,
552:      * normally by invoking a controller class.
553:      *
554:      * @param string   $URI      The URL to match, can include params within curly {} braces.
555:      * @param callable $callback The method to service the matched requests (should return a Response!).
556:      *
557:      * @throws Alpha\Exception\IllegalArguementException
558:      *
559:      * @return Alpha\Controller\Front\FrontController
560:      *
561:      * @since 2.0
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:      * Method to allow the setting of default request param values to be used when they are left off the request URI.
577:      *
578:      * @param string $param        The param name (as defined on the route between {} braces)
579:      * @param mixed  $defaultValue The value to use
580:      *
581:      * @return Alpha\Controller\Front\FrontController
582:      *
583:      * @since 2.0
584:      */
585:     public function value($param, $defaultValue)
586:     {
587:         $this->defaultParamValues[$this->currentRoute][$param] = $defaultValue;
588: 
589:         return $this;
590:     }
591: 
592:     /**
593:      * Get the defined callback in the routes array for the URI provided.
594:      *
595:      * @param string $URI The URI to search for.
596:      *
597:      * @return callable
598:      *
599:      * @throws Alpha\Exception\IllegalArguementException
600:      *
601:      * @since 2.0
602:      */
603:     public function getRouteCallback($URI)
604:     {
605:         if (array_key_exists($URI, $this->routes)) { // direct hit due to URL containing no params
606:             $this->currentRoute = $URI;
607: 
608:             return $this->routes[$URI];
609:         } else { // we need to use a regex to match URIs with params
610: 
611:             // route URIs with params provided to callback
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:              // route URIs with params missing (will attempt to layer on defaults later on in Request class)
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:         // if we made it this far then no match was found
637:         throw new IllegalArguementException('No callback defined for URI ['.$URI.']');
638:     }
639: 
640:     /**
641:      * Processes the supplied request by invoking the callable defined matching the request's URI.
642:      *
643:      * @param Alpha\Util\Http\Request $request The request to process
644:      *
645:      * @return Alpha\Util\Http\Response
646:      *
647:      * @throws Alpha\Exception\ResourceNotFoundException
648:      * @throws Alpha\Exception\ResourceNotAllowedException
649:      * @throws Alpha\Exception\AlphaException
650:      *
651:      * @since 2.0
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: 
Alpha Framework 2.0.4 API Documentation API documentation generated by ApiGen 2.8.0