Overview

Packages

  • alpha::controller
  • alpha::controller::front
  • alpha::exceptions
  • alpha::model
  • alpha::model::types
  • alpha::tasks
  • alpha::tests
  • alpha::util
  • alpha::util::cache
  • alpha::util::codehighlight
  • alpha::util::convertors
  • alpha::util::feeds
  • alpha::util::filters
  • alpha::util::graphs
  • alpha::util::helpers
  • alpha::util::metrics
  • alpha::util::search
  • alpha::view
  • alpha::view::renderers
  • alpha::view::widgets

Classes

  • FrontController
  • Overview
  • Package
  • Class
  • Tree
  • Deprecated
  1: <?php
  2: 
  3: /**
  4:  *
  5:  * The front controller designed to optionally handle all requests
  6:  *
  7:  * @package alpha::controller::front
  8:  * @since 1.0
  9:  * @author John Collins <dev@alphaframework.org>
 10:  * @version $Id: FrontController.inc 1693 2013-12-09 23:33:24Z alphadevx $
 11:  * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
 12:  * @copyright Copyright (c) 2013, John Collins (founder of Alpha Framework).
 13:  * All rights reserved.
 14:  *
 15:  * <pre>
 16:  * Redistribution and use in source and binary forms, with or
 17:  * without modification, are permitted provided that the
 18:  * following conditions are met:
 19:  *
 20:  * * Redistributions of source code must retain the above
 21:  *   copyright notice, this list of conditions and the
 22:  *   following disclaimer.
 23:  * * Redistributions in binary form must reproduce the above
 24:  *   copyright notice, this list of conditions and the
 25:  *   following disclaimer in the documentation and/or other
 26:  *   materials provided with the distribution.
 27:  * * Neither the name of the Alpha Framework nor the names
 28:  *   of its contributors may be used to endorse or promote
 29:  *   products derived from this software without specific
 30:  *   prior written permission.
 31:  *
 32:  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 33:  * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 34:  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 35:  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 36:  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 37:  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 38:  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 39:  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 40:  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 41:  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 42:  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 43:  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 44:  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 45:  * </pre>
 46:  *
 47:  */
 48: class FrontController {
 49:     /**
 50:      * The GET query string
 51:      *
 52:      * @var string
 53:      * @since 1.0
 54:      */
 55:     private $queryString;
 56: 
 57:     /**
 58:      * The name of the page controller we want to invoke
 59:      *
 60:      * @var string
 61:      * @since 1.0
 62:      */
 63:     private $pageController;
 64: 
 65:     /**
 66:      * Boolean to flag if the GET query string is encrypted or not
 67:      *
 68:      * @var boolean
 69:      * @since 1.0
 70:      */
 71:     private $encryptedQuery = false;
 72: 
 73:     /**
 74:      * An array of controller alias
 75:      *
 76:      * @var array
 77:      * @since 1.0
 78:      */
 79:     private $controllerAlias = array();
 80: 
 81:     /**
 82:      * An array of HTTP filters applied to each request to the front controller.  Each
 83:      * member must implement AlphaFilterInterface!
 84:      *
 85:      * @var array
 86:      * @since 1.0
 87:      */
 88:     private $filters = array();
 89: 
 90:     /**
 91:      * The name of the current alias
 92:      *
 93:      * @var string
 94:      * @since 1.0
 95:      */
 96:     private $currentAlias;
 97: 
 98:     /**
 99:      * Trace logger
100:      *
101:      * @var Logger
102:      * @since 1.0
103:      */
104:     private static $logger = null;
105: 
106:     /**
107:      * The constructor method
108:      *
109:      * @throws ResourceNotFoundException
110:      * @throws BadRequestException
111:      * @since 1.0
112:      */
113:     public function __construct() {
114:         self::$logger = new Logger('FrontController');
115: 
116:         self::$logger->debug('>>__construct()');
117: 
118:         global $config;
119: 
120:         mb_internal_encoding('UTF-8');
121:         mb_http_output('UTF-8');
122:         mb_http_input('UTF-8');
123:         ini_set('default_charset', 'utf-8');
124:         if (!mb_check_encoding())
125:             throw new BadRequestException('Request character encoding does not match expected UTF-8');
126: 
127:         self::$logger->debug('Requested URL is ['.$_SERVER['REQUEST_URI'].']');
128: 
129:         // direct calls to the front controller
130:         if (isset($_GET['act'])) {
131:             self::$logger->debug('Processing direct request to the front controller');
132:             $this->pageController = $_GET['act'];
133:         // calls to the front controller via mod_rewrite
134:         }elseif($config->get('app.use.mod.rewrite') && !isset($_GET['tk'])) {
135:             self::$logger->debug('Processing a mod_rewrite request');
136:             $this->handleModRewriteRequests();
137:         // direct calls to the front controller with an encrypted query string
138:         }else{
139:             if (!isset($_GET['tk'])) {
140:                 self::$logger->warn('No controller action set for the front controller, URL is ['.$_SERVER['REQUEST_URI'].']');
141:                 throw new ResourceNotFoundException('The file that you have requested cannot be found!');
142:             }else{
143:                 self::$logger->debug('Processing a direct request to the front controller with an encrypted token param');
144:                 $this->setEncrypt(true);
145:                 try {
146:                     $this->decodeQuery();
147:                     $this->populateGetVars();
148:                     if(isset($_GET['act']))
149:                         $this->pageController = $_GET['act'];
150:                     else
151:                         throw new SecurityException('No act param provided in the secure token!');
152:                 }catch (SecurityException $e) {
153:                     self::$logger->error('Error while attempting to decode a secure token in the FrontController: '.$e->getMessage());
154:                     throw new ResourceNotFoundException('The file that you have requested cannot be found!');
155:                 }
156:             }
157:         }
158: 
159:         self::$logger->debug('<<__construct');
160:     }
161: 
162:     /**
163:      * Sets the encryption flag
164:      *
165:      * @param boolean $encryptedQuery
166:      * @since 1.0
167:      */
168:     public function setEncrypt($encryptedQuery) {
169:         $this->encryptedQuery = $encryptedQuery;
170:     }
171: 
172:     /**
173:      * Method to populate the global _GET and _REQUEST arrays with the decoded
174:      * query string
175:      *
176:      * @since 1.0
177:      */
178:     private function populateGetVars() {
179: 
180:         $pairs = explode('&', $this->queryString);
181: 
182:         foreach($pairs as $pair) {
183:             $keyValue = explode('=', $pair);
184:             if(count($keyValue) == 2) {
185:                 $_GET[$keyValue[0]] = $keyValue[1];
186:                 $_REQUEST[$keyValue[0]] = $keyValue[1];
187:             }
188:         }
189:     }
190: 
191:     /**
192:      * Static method for generating an absolute, secure URL for a page controller
193:      *
194:      * @param string $params
195:      * @return string
196:      * @since 1.0
197:      */
198:     public static function generateSecureURL($params) {
199:         global $config;
200: 
201:         if($config->get('app.use.mod.rewrite'))
202:             return $config->get('app.url').'tk/'.FrontController::encodeQuery($params);
203:         else
204:             return $config->get('app.url').'?tk='.FrontController::encodeQuery($params);
205:     }
206: 
207:     /**
208:      * Static method for encoding a query string
209:      *
210:      * @param string $queryString
211:      * @return string
212:      * @since 1.0
213:      */
214:     public static function encodeQuery($queryString) {
215:         global $config;
216: 
217:         $return = base64_encode(AlphaSecurityUtils::encrypt($queryString));
218:         // remove any characters that are likely to cause trouble on a URL
219:         $return = strtr($return, '+/', '-_');
220: 
221:         return $return;
222:     }
223: 
224:     /**
225:      * Method to decode the current query string
226:      *
227:      * @throws SecurityException
228:      * @since 1.0
229:      */
230:     private function decodeQuery() {
231:         global $config;
232: 
233:         if (!isset($_GET['tk'])) {
234:             throw new SecurityException('No token provided for the front controller!');
235:         }else{
236:             // replace any troublesome characters from the URL with the original values
237:             $token = strtr($_GET['tk'], '-_', '+/');
238:             $token = base64_decode($token);
239:             $this->queryString = trim(AlphaSecurityUtils::decrypt($token));
240:         }
241:     }
242: 
243:     /**
244:      * Static method to return the decoded GET paramters from an encrytpted tk value
245:      *
246:      * @return string
247:      * @since 1.0
248:      */
249:     public static function decodeQueryParams($tk) {
250:         global $config;
251: 
252:         // replace any troublesome characters from the URL with the original values
253:         $token = strtr($tk, '-_', '+/');
254:         $token = base64_decode($token);
255:         $params = trim(AlphaSecurityUtils::decrypt($token));
256: 
257:         return $params;
258:     }
259: 
260:     /**
261:      * Static method to return the decoded GET paramters from an encrytpted tk value as an array of key/value pairs.
262:      *
263:      * @return array
264:      * @since 1.0
265:      */
266:     public static function getDecodeQueryParams($tk) {
267:         global $config;
268: 
269:         // replace any troublesome characters from the URL with the original values
270:         $token = strtr($tk, '-_', '+/');
271:         $token = base64_decode($token);
272:         $params = trim(AlphaSecurityUtils::decrypt($token));
273: 
274:         $pairs = explode('&', $params);
275: 
276:         $parameters = array();
277: 
278:         foreach($pairs as $pair) {
279:             $split = explode('=', $pair);
280:             $parameters[$split[0]] = $split[1];
281:         }
282: 
283:         return $parameters;
284:     }
285: 
286:     /**
287:      * Method to load the page controller
288:      *
289:      * @param boolean $allowRedirects Defaults to true, set to false if you want to prevent the front controller from redirecting the request
290:      * @throws ResourceNotFoundException
291:      * @since 1.0
292:      */
293:     public function loadController($allowRedirects = true) {
294:         global $config;
295: 
296:         if($allowRedirects && $config->get('app.check.installed') && $this->pageController != 'Install' && $this->pageController != 'Login') {
297:             if(!AlphaDAO::isInstalled()) {
298:                 self::$logger->info('Invoking the Install controller as the system DB is not installed...');
299:                 $url = FrontController::generateSecureURL('act=Install');
300:                 self::$logger->info('Redirecting to ['.$url.']');
301:                 header('Location: '.$url);
302:                 exit;
303:             }
304:         }
305: 
306:         // first process any attached filters
307:         foreach ($this->filters as $filter)
308:             $filter->process();
309: 
310:         if($allowRedirects) {
311:             // if there is an alias configured for the above page controller, redirect there
312:             if($config->get('app.force.front.controller') && $this->hasAlias($this->pageController)) {
313:                 // make sure that it is not already an alias-based request to prevent re-direct loop
314:                 if(empty($this->currentAlias)) {
315:                     // set the correct HTTP header for the response
316:                     header('HTTP/1.1 301 Moved Permanently');
317: 
318:                     // see if there are any other GET params appart from the controller name
319:                     if (count($_GET) > 1) {
320:                         $keys = array_keys($_GET);
321:                         $param = $_GET[$keys[1]];
322:                         // if its a title then replace spaces with underscores in the URL
323:                         if($keys[1] == 'title')
324:                             $param = str_replace(' ','_',$param);
325: 
326:                         $URL = $config->get('app.url').'/'.$this->getControllerAlias($this->pageController).'/'.
327:                             $this->getControllerParam($this->pageController).$param;
328:                     }else{
329:                         $URL = $config->get('app.url').'/'.$this->getControllerAlias($this->pageController);
330:                     }
331: 
332:                     header('Location: '.$URL);
333:                     exit;
334:                 }
335:             }
336:         }
337: 
338:         try {
339:             AlphaController::loadControllerDef($this->pageController);
340:             $pageController = new $this->pageController();
341: 
342:             if(!empty($_POST)) {
343:                 $pageController->doPOST($_REQUEST);
344:             }else{
345:                 $pageController->doGET($_GET);
346:             }
347:         }catch (LibraryNotInstalledException $e) {
348:             self::$logger->warn($e->getMessage()."\nStacktrace:\n".$e->getTraceAsString()."\nRequest params:\n".var_export($_REQUEST, true)."\nRequested resource:\n".$_SERVER['REQUEST_URI']);
349:             throw new LibraryNotInstalledException($e->getMessage());
350:         }catch (ResourceNotAllowedException $e) {
351:             self::$logger->warn($e->getMessage()."\nStacktrace:\n".$e->getTraceAsString()."\nRequest params:\n".var_export($_REQUEST, true)."\nRequested resource:\n".$_SERVER['REQUEST_URI']);
352:             throw new ResourceNotAllowedException($e->getMessage());
353:         }catch (ResourceNotFoundException $e) {
354:             self::$logger->warn($e->getMessage()."\nStacktrace:\n".$e->getTraceAsString()."\nRequest params:\n".var_export($_REQUEST, true)."\nRequested resource:\n".$_SERVER['REQUEST_URI']);
355:             throw new ResourceNotFoundException($e->getMessage());
356:         }catch (IllegalArguementException $e) {
357:             self::$logger->warn($e->getMessage()."\nStacktrace:\n".$e->getTraceAsString()."\nRequest params:\n".var_export($_REQUEST, true)."\nRequested resource:\n".$_SERVER['REQUEST_URI']);
358: 
359:             if($config->get('security.client.temp.blacklist.filter.enabled')) {
360:                 if(isset($_SERVER['HTTP_USER_AGENT']) && isset($_SERVER['REMOTE_ADDR']) && isset($_SERVER['REQUEST_URI'])) {
361:                     $request = new BadRequestObject();
362:                     $request->set('client', $_SERVER['HTTP_USER_AGENT']);
363:                     $request->set('IP', $_SERVER['REMOTE_ADDR']);
364:                     $request->set('requestedResource', $_SERVER['REQUEST_URI']);
365:                     $request->save();
366:                 }
367:             }
368: 
369:             throw new ResourceNotFoundException('The file that you have requested cannot be found!');
370:         }catch (AlphaException $e) {
371:             self::$logger->warn($e->getMessage()."\nStacktrace:\n".$e->getTraceAsString()."\nRequest params:\n".var_export($_REQUEST, true)."\nRequested resource:\n".$_SERVER['REQUEST_URI']);
372: 
373:             if($config->get('security.client.temp.blacklist.filter.enabled')) {
374:                 if(isset($_SERVER['HTTP_USER_AGENT']) && isset($_SERVER['REMOTE_ADDR']) && isset($_SERVER['REQUEST_URI'])) {
375:                     $request = new BadRequestObject();
376:                     $request->set('client', $_SERVER['HTTP_USER_AGENT']);
377:                     $request->set('IP', $_SERVER['REMOTE_ADDR']);
378:                     $request->set('requestedResource', $_SERVER['REQUEST_URI']);
379:                     $request->save();
380:                 }
381:             }
382: 
383:             throw new ResourceNotFoundException('The file that you have requested cannot be found!');
384:         }
385:     }
386: 
387:     /**
388:      * Used to register a controller alias to enable shorter URLs with mod_rewrite support enabled.  Note that
389:      * only controllers with a single parameter are supported.
390:      *
391:      * @param string $controller The name of the page controller class
392:      * @param string $alias The URL alias for the page controller
393:      * @param string $param The name of the GET parameter on the alias URL request
394:      * @since 1.0
395:      */
396:     public function registerAlias($controller, $alias, $param=null) {
397:         $this->controllerAlias[$alias] = $controller;
398:         if(isset($param))
399:             $this->controllerAlias[$alias.'_param'] = $param;
400: 
401:         // set up the page controller
402:         $this->handleModRewriteRequests();
403:     }
404: 
405:     /**
406:      * Check to see if an alias exists for the given alias name
407:      *
408:      * @param string $alias
409:      * @return boolean
410:      * @since 1.0
411:      */
412:     public function checkAlias($alias) {
413:         if(array_key_exists($alias, $this->controllerAlias))
414:             return true;
415:         else
416:             return false;
417:     }
418: 
419:     /**
420:      * Check to see if an alias exists for the given controller name
421:      *
422:      * @param string $controller
423:      * @return boolean
424:      * @since 1.0
425:      */
426:     public function hasAlias($controller) {
427:         if(in_array($controller, $this->controllerAlias))
428:             return true;
429:         else
430:             return false;
431:     }
432: 
433:     /**
434:      * Gets the full name of the controller for the given alias
435:      *
436:      * @param string $alias
437:      * @return string
438:      * @since 1.0
439:      */
440:     public function getAliasController($alias) {
441:         if(array_key_exists($alias, $this->controllerAlias))
442:             return $this->controllerAlias[$alias];
443:     }
444: 
445:     /**
446:      * Gets the name of the alias for the given controller
447:      *
448:      * @param string $controller
449:      * @return string
450:      * @since 1.0
451:      */
452:     public function getControllerAlias($controller) {
453:         if(in_array($controller, $this->controllerAlias)) {
454:             $keys = array_keys($this->controllerAlias, $controller);
455:             // there should only ever be one key per controller
456:             return $keys[0];
457:         }
458:     }
459: 
460:     /**
461:      * Gets the parameter name expected in requests to the controller with the given alias
462:      *
463:      * @param string $alias
464:      * @return string
465:      * @since 1.0
466:      */
467:     public function getAliasParam($alias) {
468:         if(array_key_exists($alias.'_param', $this->controllerAlias))
469:             return $this->controllerAlias[$alias.'_param'];
470:         else
471:             return '';
472:     }
473: 
474:     /**
475:      * Gets the parameter name expected in requests to the controller with the given controller name
476:      *
477:      * @param string $controller
478:      * @return string
479:      * @since 1.0
480:      */
481:     public function getControllerParam($controller) {
482:         $alias = $this->getControllerAlias($controller);
483:         if(array_key_exists($alias.'_param', $this->controllerAlias))
484:             return $this->controllerAlias[$alias.'_param'];
485:         else
486:             return '';
487:     }
488: 
489:     /**
490:      * Explodes the provided string into an array based on the array of delimiters provided
491:      *
492:      * @param string $string The string to explode.
493:      * @param array $delimiters An array of delimiters.
494:      * @todo move to string utils class
495:      * @return array
496:      * @since 1.2
497:      */
498:     private static function multipleExplode($string, $delimiters = array()){
499: 
500:         $mainDelim=$delimiters[count($delimiters)-1];
501: 
502:         array_pop($delimiters);
503: 
504:         foreach($delimiters as $delimiter) {
505:             $string = str_replace($delimiter, $mainDelim, $string);
506:         }
507: 
508:         $result = explode($mainDelim, $string);
509: 
510:         return $result;
511:     }
512: 
513:     /**
514:      * Handles all of the rules for mod_rewrite style URL parsing
515:      *
516:      * @since 1.0
517:      */
518:     private function handleModRewriteRequests() {
519:         self::$logger->debug('>>handleModRewriteRequests');
520:         global $config;
521: 
522:         // strip off the system URL from the request URL
523:         $request = str_replace($config->get('app.url'), '', 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']);
524:         self::$logger->debug('$request is ['.$request.']');
525:         $params = self::multipleExplode($request, array('/','?','&'));
526:         self::$logger->debug('$params are ['.var_export($params, true).']');
527: 
528:         try {
529:             // first param will always be the controller alias
530:             if(empty($this->currentAlias) && !empty($params[0]))
531:                 $this->currentAlias = $params[0];
532: 
533:             // check to see if we can load the page controller without an alias
534:             AlphaController::loadControllerDef($params[0]);
535:             self::$logger->debug('Page controller name set on the request URL is ['.$params[0].']');
536:             $this->pageController = $params[0];
537:         }catch (IllegalArguementException $iae) {
538:             // handle request with alias
539:             self::$logger->debug('The supplied controller alias is ['.$this->currentAlias.']');
540: 
541:             // check to see if the controller is an alias for something
542:             if($this->checkAlias($this->currentAlias)) {
543:                 $this->pageController = $this->getAliasController($this->currentAlias);
544:                 self::$logger->debug('Page controller name obtained from the URL alias is ['.$this->pageController.']');
545: 
546:                 if(isset($params[1])) {
547:                     if(!empty($_POST))
548:                         $_REQUEST[$this->getAliasParam($this->currentAlias)] = $params[1];
549:                     else
550:                         $_GET[$this->getAliasParam($this->currentAlias)] = $params[1];
551:                 }
552:             }
553:         }
554: 
555:         self::$logger->debug('$params are ['.var_export($params, true).']');
556:         self::$logger->debug('currentAlias is ['.$this->currentAlias.']');
557: 
558:         // now populate the _GET vars
559:         if($this->currentAlias == 'tk') {
560:             self::$logger->debug('Setting the GET vars for a mod_rewrite request with a tk param');
561:             $this->setEncrypt(true);
562:             $this->queryString = FrontController::decodeQueryParams($params[1]);
563:             $_GET['tk'] = $params[1];
564:             $this->populateGetVars();
565:             $this->pageController = $_GET['act'];
566:         }else{
567:             $count = count($params);
568: 
569:             for($i = 1; $i < $count; $i+=2) {
570:                 if(isset($params[$i+1])) {
571:                     if(!empty($_POST))
572:                         $_REQUEST[$params[$i]] = $params[$i+1];
573:                     else
574:                         $_GET[$params[$i]] = $params[$i+1];
575:                 }
576:             }
577:         }
578: 
579:         self::$logger->debug('$_GET is ['.var_export($_GET, true).']');
580:         self::$logger->debug('<<handleModRewriteRequests');
581:     }
582: 
583:     /**
584:      * Getter for the page controller
585:      *
586:      * @return string
587:      * @since 1.0
588:      */
589:     public function getPageController() {
590:         return $this->pageController;
591:     }
592: 
593:     /**
594:      * Add the supplied filter object to the list of filters ran on each request to the front controller
595:      *
596:      * @param AlphaFilterInterface $filterObject
597:      * @throws IllegalArguementException
598:      * @since 1.0
599:      */
600:     public function registerFilter($filterObject) {
601:         if($filterObject instanceof AlphaFilterInterface)
602:             array_push($this->filters, $filterObject);
603:         else
604:             throw new IllegalArguementException('Supplied filter object is not a valid AlphaFilterInterface instance!');
605:     }
606: 
607:     /**
608:      * Returns the array of filters currently attached to this FrontController
609:      *
610:      * @return array
611:      * @since 1.0
612:      */
613:     public function getFilters() {
614:         return $this->filters;
615:     }
616: }
617: 
618: ?>
Alpha Framework 1.2.4 API Documentation API documentation generated by ApiGen 2.8.0