      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 1548 2012-07-29 17:07:07Z alphadevx $
     11:  * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
     12:  * @copyright Copyright (c) 2012, 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:      * @since 1.0
    111:      */
    112:     public function __construct() {
    113:         self::$logger = new Logger('FrontController');
    114:
    115:         self::$logger->debug('>>__construct()');
    116:
    117:         global $config;
    118:
    119:         self::$logger->debug('Requested URL is ['.$_SERVER['REQUEST_URI'].']');
    120:
    121:         // direct calls to the front controller
    122:         if (isset($_GET['act'])) {
    123:             self::$logger->debug('Processing direct request to the front controller');
    124:             $this->pageController = $_GET['act'];
    125:         // calls to the front controller via mod_rewrite
    126:         }elseif($config->get('app.use.mod.rewrite') && !isset($_GET['tk'])) {
    127:             self::$logger->debug('Processing a mod_rewrite request');
    128:             $this->handleModRewriteRequests();
    129:         // direct calls to the front controller with an encrypted query string
    130:         }else{
    131:             if (!isset($_GET['tk'])) {
    132:                 self::$logger->warn('No controller action set for the front controller, URL is ['.$_SERVER['REQUEST_URI'].']');
    133:                 throw new ResourceNotFoundException('The file that you have requested cannot be found!');
    134:             }else{
    135:                 self::$logger->debug('Processing a direct request to the front controller with an encrypted token param');
    136:                 $this->setEncrypt(true);
    137:                 try {
    138:                     $this->decodeQuery();
    139:                     $this->populateGetVars();
    140:                     if(isset($_GET['act']))
    141:                         $this->pageController = $_GET['act'];
    142:                     else
    143:                         throw new SecurityException('No act param provided in the secure token!');
    144:                 }catch (SecurityException $e) {
    145:                     self::$logger->error('Error while attempting to decode a secure token in the FrontController: '.$e->getMessage());
    146:                     throw new ResourceNotFoundException('The file that you have requested cannot be found!');
    147:                 }
    148:             }
    149:         }
    150:
    151:         self::$logger->debug('<<__construct');
    152:     }
    153:
    154:     /**
    155:      * Sets the encryption flag
    156:      *
    157:      * @param boolean $encryptedQuery
    158:      * @since 1.0
    159:      */
    160:     public function setEncrypt($encryptedQuery) {
    161:         $this->encryptedQuery = $encryptedQuery;
    162:     }
    163:
    164:     /**
    165:      * Method to populate the global _GET and _REQUEST arrays with the decoded
    166:      * query string
    167:      *
    168:      * @since 1.0
    169:      */
    170:     private function populateGetVars() {
    171:
    172:         $pairs = explode('&', $this->queryString);
    173:
    174:         foreach($pairs as $pair) {
    175:             $keyValue = explode('=', $pair);
    176:             if(count($keyValue) == 2) {
    177:                 $_GET[$keyValue[0]] = $keyValue[1];
    178:                 $_REQUEST[$keyValue[0]] = $keyValue[1];
    179:             }
    180:         }
    181:     }
    182:
    183:     /**
    184:      * Static method for generating an absolute, secure URL for a page controller
    185:      *
    186:      * @param string $params
    187:      * @return string
    188:      * @since 1.0
    189:      */
    190:     public static function generateSecureURL($params) {
    191:         global $config;
    192:
    193:         if($config->get('app.use.mod.rewrite'))
    194:             return $config->get('app.url').'tk/'.FrontController::encodeQuery($params);
    195:         else
    196:             return $config->get('app.url').'?tk='.FrontController::encodeQuery($params);
    197:     }
    198:
    199:     /**
    200:      * Static method for encoding a query string
    201:      *
    202:      * @param string $queryString
    203:      * @return string
    204:      * @since 1.0
    205:      */
    206:     public static function encodeQuery($queryString) {
    207:         global $config;
    208:
    209:         $td = mcrypt_module_open ('tripledes', '', 'ecb', '');
    210:         $iv = mcrypt_create_iv (mcrypt_enc_get_iv_size ($td), MCRYPT_RAND);
    211:         mcrypt_generic_init ($td, $config->get('security.query.string.key'), $iv);
    212:         $encryptedData = mcrypt_generic ($td, $queryString);
    213:         mcrypt_generic_deinit ($td);
    214:         mcrypt_module_close ($td);
    215:
    216:         $return = base64_encode($encryptedData);
    217:         // remove any characters that are likely to cause trouble on a URL
    218:         $return = strtr($return, '+/', '-_');
    219:
    220:         return $return;
    221:     }
    222:
    223:     /**
    224:      * Method to decode the current query string
    225:      *
    226:      * @throws SecurityException
    227:      * @since 1.0
    228:      */
    229:     private function decodeQuery() {
    230:         global $config;
    231:
    232:         if (!isset($_GET['tk'])) {
    233:             throw new SecurityException('No token provided for the front controller!');
    234:         }else{
    235:             $td = mcrypt_module_open('tripledes', '', 'ecb', '');
    236:             $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size ($td), MCRYPT_RAND);
    237:             // replace any troublesome characters from the URL with the original values
    238:             $token = strtr($_GET['tk'], '-_', '+/');
    239:             $token = base64_decode($token);
    240:             $this->queryString = trim(mcrypt_decrypt('tripledes', $config->get('security.query.string.key'), $token, 'ecb', $iv));
    241:         }
    242:     }
    243:
    244:     /**
    245:      * Static method to return the decoded GET paramters from an encrytpted tk value
    246:      *
    247:      * @return string
    248:      * @since 1.0
    249:      */
    250:     public static function decodeQueryParams($tk) {
    251:         global $config;
    252:
    253:         $td = mcrypt_module_open('tripledes', '', 'ecb', '');
    254:         $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size ($td), MCRYPT_RAND);
    255:         // replace any troublesome characters from the URL with the original values
    256:         $token = strtr($tk, '-_', '+/');
    257:         $token = base64_decode($token);
    258:         $params = trim(mcrypt_decrypt('tripledes', $config->get('security.query.string.key'), $token, 'ecb', $iv));
    259:
    260:         return $params;
    261:     }
    262:
    263:     /**
    264:      * Static method to return the decoded GET paramters from an encrytpted tk value as an array of key/value pairs.
    265:      *
    266:      * @return array
    267:      * @since 1.0
    268:      */
    269:     public static function getDecodeQueryParams($tk) {
    270:         global $config;
    271:
    272:         $td = mcrypt_module_open('tripledes', '', 'ecb', '');
    273:         $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size ($td), MCRYPT_RAND);
    274:         // replace any troublesome characters from the URL with the original values
    275:         $token = strtr($tk, '-_', '+/');
    276:         $token = base64_decode($token);
    277:         $params = trim(mcrypt_decrypt('tripledes', $config->get('security.query.string.key'), $token, 'ecb', $iv));
    278:
    279:         $pairs = explode('&', $params);
    280:
    281:         $parameters = array();
    282:
    283:         foreach($pairs as $pair) {
    284:             $split = explode('=', $pair);
    285:             $parameters[$split[0]] = $split[1];
    286:         }
    287:
    288:         return $parameters;
    289:     }
    290:
    291:     /**
    292:      * Method to load the page controller
    293:      *
    294:      * @param boolean $allowRedirects Defaults to true, set to false if you want to prevent the front controller from redirecting the request
    295:      * @throws ResourceNotFoundException
    296:      * @since 1.0
    297:      */
    298:     public function loadController($allowRedirects = true) {
    299:         global $config;
    300:
    301:         if($allowRedirects && $config->get('app.check.installed') && $this->pageController != 'Install' && $this->pageController != 'Login') {
    302:             if(!AlphaDAO::isInstalled()) {
    303:                 self::$logger->info('Invoking the Install controller as the system DB is not installed...');
    304:                 $url = FrontController::generateSecureURL('act=Install');
    305:                 self::$logger->info('Redirecting to ['.$url.']');
    306:                 header('Location: '.$url);
    307:                 exit;
    308:             }
    309:         }
    310:
    311:         // first process any attached filters
    312:         foreach ($this->filters as $filter)
    313:             $filter->process();
    314:
    315:         if($allowRedirects) {
    316:             // if there is an alias configured for the above page controller, redirect there
    317:             if($config->get('app.force.front.controller') && $this->hasAlias($this->pageController)) {
    318:                 // make sure that it is not already an alias-based request to prevent re-direct loop
    319:                 if(empty($this->currentAlias)) {
    320:                     // set the correct HTTP header for the response
    321:                     header('HTTP/1.1 301 Moved Permanently');
    322:
    323:                     // see if there are any other GET params appart from the controller name
    324:                     if (count($_GET) > 1) {
    325:                         $keys = array_keys($_GET);
    326:                         $param = $_GET[$keys[1]];
    327:                         // if its a title then replace spaces with underscores in the URL
    328:                         if($keys[1] == 'title')
    329:                             $param = str_replace(' ','_',$param);
    330:
    331:                         $URL = $config->get('app.url').'/'.$this->getControllerAlias($this->pageController).'/'.
    332:                             $this->getControllerParam($this->pageController).$param;
    333:                     }else{
    334:                         $URL = $config->get('app.url').'/'.$this->getControllerAlias($this->pageController);
    335:                     }
    336:
    337:                     header('Location: '.$URL);
    338:                     exit;
    339:                 }
    340:             }
    341:         }
    342:
    343:         try {
    344:             AlphaController::loadControllerDef($this->pageController);
    345:             $pageController = new $this->pageController();
    346:
    347:             if(!empty($_POST)) {
    348:                 $pageController->doPOST($_REQUEST);
    349:             }else{
    350:                 $pageController->doGET($_GET);
    351:             }
    352:         }catch (LibraryNotInstalledException $e) {
    353:             self::$logger->warn($e->getMessage()."\nStacktrace:\n".$e->getTraceAsString()."\nRequest params:\n".var_export($_REQUEST, true)."\nRequested resource:\n".$_SERVER['REQUEST_URI']);
    354:             throw new LibraryNotInstalledException($e->getMessage());
    355:         }catch (ResourceNotAllowedException $e) {
    356:             self::$logger->warn($e->getMessage()."\nStacktrace:\n".$e->getTraceAsString()."\nRequest params:\n".var_export($_REQUEST, true)."\nRequested resource:\n".$_SERVER['REQUEST_URI']);
    357:             throw new ResourceNotAllowedException($e->getMessage());
    358:         }catch (ResourceNotFoundException $e) {
    359:             self::$logger->warn($e->getMessage()."\nStacktrace:\n".$e->getTraceAsString()."\nRequest params:\n".var_export($_REQUEST, true)."\nRequested resource:\n".$_SERVER['REQUEST_URI']);
    360:             throw new ResourceNotFoundException($e->getMessage());
    361:         }catch (IllegalArguementException $e) {
    362:             self::$logger->warn($e->getMessage()."\nStacktrace:\n".$e->getTraceAsString()."\nRequest params:\n".var_export($_REQUEST, true)."\nRequested resource:\n".$_SERVER['REQUEST_URI']);
    363:
    364:             if($config->get('security.client.temp.blacklist.filter.enabled')) {
    365:                 if(isset($_SERVER['HTTP_USER_AGENT']) && isset($_SERVER['REMOTE_ADDR']) && isset($_SERVER['REQUEST_URI'])) {
    366:                     $request = new BadRequestObject();
    367:                     $request->set('client', $_SERVER['HTTP_USER_AGENT']);
    368:                     $request->set('IP', $_SERVER['REMOTE_ADDR']);
    369:                     $request->set('requestedResource', $_SERVER['REQUEST_URI']);
    370:                     $request->save();
    371:                 }
    372:             }
    373:
    374:             throw new ResourceNotFoundException('The file that you have requested cannot be found!');
    375:         }catch (AlphaException $e) {
    376:             self::$logger->warn($e->getMessage()."\nStacktrace:\n".$e->getTraceAsString()."\nRequest params:\n".var_export($_REQUEST, true)."\nRequested resource:\n".$_SERVER['REQUEST_URI']);
    377:
    378:             if($config->get('security.client.temp.blacklist.filter.enabled')) {
    379:                 if(isset($_SERVER['HTTP_USER_AGENT']) && isset($_SERVER['REMOTE_ADDR']) && isset($_SERVER['REQUEST_URI'])) {
    380:                     $request = new BadRequestObject();
    381:                     $request->set('client', $_SERVER['HTTP_USER_AGENT']);
    382:                     $request->set('IP', $_SERVER['REMOTE_ADDR']);
    383:                     $request->set('requestedResource', $_SERVER['REQUEST_URI']);
    384:                     $request->save();
    385:                 }
    386:             }
    387:
    388:             throw new ResourceNotFoundException('The file that you have requested cannot be found!');
    389:         }
    390:     }
    391:
    392:     /**
    393:      * Used to register a controller alias to enable shorter URLs with mod_rewrite support enabled.  Note that
    394:      * only controllers with a single parameter are supported.
    395:      *
    396:      * @param string $controller The name of the page controller class
    397:      * @param string $alias The URL alias for the page controller
    398:      * @param string $param The name of the GET parameter on the alias URL request
    399:      * @since 1.0
    400:      */
    401:     public function registerAlias($controller, $alias, $param=null) {
    402:         $this->controllerAlias[$alias] = $controller;
    403:         if(isset($param))
    404:             $this->controllerAlias[$alias.'_param'] = $param;
    405:
    406:         // set up the page controller
    407:         $this->handleModRewriteRequests();
    408:     }
    409:
    410:     /**
    411:      * Check to see if an alias exists for the given alias name
    412:      *
    413:      * @param string $alias
    414:      * @return boolean
    415:      * @since 1.0
    416:      */
    417:     public function checkAlias($alias) {
    418:         if(array_key_exists($alias, $this->controllerAlias))
    419:             return true;
    420:         else
    421:             return false;
    422:     }
    423:
    424:     /**
    425:      * Check to see if an alias exists for the given controller name
    426:      *
    427:      * @param string $controller
    428:      * @return boolean
    429:      * @since 1.0
    430:      */
    431:     public function hasAlias($controller) {
    432:         if(in_array($controller, $this->controllerAlias))
    433:             return true;
    434:         else
    435:             return false;
    436:     }
    437:
    438:     /**
    439:      * Gets the full name of the controller for the given alias
    440:      *
    441:      * @param string $alias
    442:      * @return string
    443:      * @since 1.0
    444:      */
    445:     public function getAliasController($alias) {
    446:         if(array_key_exists($alias, $this->controllerAlias))
    447:             return $this->controllerAlias[$alias];
    448:     }
    449:
    450:     /**
    451:      * Gets the name of the alias for the given controller
    452:      *
    453:      * @param string $controller
    454:      * @return string
    455:      * @since 1.0
    456:      */
    457:     public function getControllerAlias($controller) {
    458:         if(in_array($controller, $this->controllerAlias)) {
    459:             $keys = array_keys($this->controllerAlias, $controller);
    460:             // there should only ever be one key per controller
    461:             return $keys[0];
    462:         }
    463:     }
    464:
    465:     /**
    466:      * Gets the parameter name expected in requests to the controller with the given alias
    467:      *
    468:      * @param string $alias
    469:      * @return string
    470:      * @since 1.0
    471:      */
    472:     public function getAliasParam($alias) {
    473:         if(array_key_exists($alias.'_param', $this->controllerAlias))
    474:             return $this->controllerAlias[$alias.'_param'];
    475:         else
    476:             return '';
    477:     }
    478:
    479:     /**
    480:      * Gets the parameter name expected in requests to the controller with the given controller name
    481:      *
    482:      * @param string $controller
    483:      * @return string
    484:      * @since 1.0
    485:      */
    486:     public function getControllerParam($controller) {
    487:         $alias = $this->getControllerAlias($controller);
    488:         if(array_key_exists($alias.'_param', $this->controllerAlias))
    489:             return $this->controllerAlias[$alias.'_param'];
    490:         else
    491:             return '';
    492:     }
    493:
    494:     /**
    495:      * Explodes the provided string into an array based on the array of delimiters provided
    496:      *
    497:      * @param string $string The string to explode.
    498:      * @param array $delimiters An array of delimiters.
    499:      * @todo move to string utils class
    500:      * @return array
    501:      * @since 1.2
    502:      */
    503:     private static function multipleExplode($string, $delimiters = array()){
    504:
    505:         $mainDelim=$delimiters[count($delimiters)-1];
    506:
    507:         array_pop($delimiters);
    508:
    509:         foreach($delimiters as $delimiter) {
    510:             $string = str_replace($delimiter, $mainDelim, $string);
    511:         }
    512:
    513:         $result = explode($mainDelim, $string);
    514:
    515:         return $result;
    516:     }
    517:
    518:     /**
    519:      * Handles all of the rules for mod_rewrite style URL parsing
    520:      *
    521:      * @since 1.0
    522:      */
    523:     private function handleModRewriteRequests() {
    524:         self::$logger->debug('>>handleModRewriteRequests');
    525:         global $config;
    526:
    527:         // strip off the system URL from the request URL
    528:         $request = str_replace($config->get('app.url'), '', 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']);
    529:         self::$logger->debug('$request is ['.$request.']');
    530:         $params = self::multipleExplode($request, array('/','?','&'));
    531:         self::$logger->debug('$params are ['.var_export($params, true).']');
    532:
    533:         try {
    534:             // first param will always be the controller alias
    535:             if(empty($this->currentAlias) && !empty($params[0]))
    536:                 $this->currentAlias = $params[0];
    537:
    538:             // check to see if we can load the page controller without an alias
    539:             AlphaController::loadControllerDef($params[0]);
    540:             self::$logger->debug('Page controller name set on the request URL is ['.$params[0].']');
    541:             $this->pageController = $params[0];
    542:         }catch (IllegalArguementException $iae) {
    543:             // handle request with alias
    544:             self::$logger->debug('The supplied controller alias is ['.$this->currentAlias.']');
    545:
    546:             // check to see if the controller is an alias for something
    547:             if($this->checkAlias($this->currentAlias)) {
    548:                 $this->pageController = $this->getAliasController($this->currentAlias);
    549:                 self::$logger->debug('Page controller name obtained from the URL alias is ['.$this->pageController.']');
    550:
    551:                 if(isset($params[1])) {
    552:                     if(!empty($_POST))
    553:                         $_REQUEST[$this->getAliasParam($this->currentAlias)] = $params[1];
    554:                     else
    555:                         $_GET[$this->getAliasParam($this->currentAlias)] = $params[1];
    556:                 }
    557:             }
    558:         }
    559:
    560:         self::$logger->debug('$params are ['.var_export($params, true).']');
    561:         self::$logger->debug('currentAlias is ['.$this->currentAlias.']');
    562:
    563:         // now populate the _GET vars
    564:         if($this->currentAlias == 'tk') {
    565:             self::$logger->debug('Setting the GET vars for a mod_rewrite request with a tk param');
    566:             $this->setEncrypt(true);
    567:             $this->queryString = FrontController::decodeQueryParams($params[1]);
    568:             $_GET['tk'] = $params[1];
    569:             $this->populateGetVars();
    570:             $this->pageController = $_GET['act'];
    571:         }else{
    572:             $count = count($params);
    573:
    574:             for($i = 1; $i < $count; $i+=2) {
    575:                 if(isset($params[$i+1])) {
    576:                     if(!empty($_POST))
    577:                         $_REQUEST[$params[$i]] = $params[$i+1];
    578:                     else
    579:                         $_GET[$params[$i]] = $params[$i+1];
    580:                 }
    581:             }
    582:         }
    583:
    584:         self::$logger->debug('$_GET is ['.var_export($_GET, true).']');
    585:         self::$logger->debug('<<handleModRewriteRequests');
    586:     }
    587:
    588:     /**
    589:      * Getter for the page controller
    590:      *
    591:      * @return string
    592:      * @since 1.0
    593:      */
    594:     public function getPageController() {
    595:         return $this->pageController;
    596:     }
    597:
    598:     /**
    599:      * Add the supplied filter object to the list of filters ran on each request to the front controller
    600:      *
    601:      * @param AlphaFilterInterface $filterObject
    602:      * @throws IllegalArguementException
    603:      * @since 1.0
    604:      */
    605:     public function registerFilter($filterObject) {
    606:         if($filterObject instanceof AlphaFilterInterface)
    607:             array_push($this->filters, $filterObject);
    608:         else
    609:             throw new IllegalArguementException('Supplied filter object is not a valid AlphaFilterInterface instance!');
    610:     }
    611:
    612:     /**
    613:      * Returns the array of filters currently attached to this FrontController
    614:      *
    615:      * @return array
    616:      * @since 1.0
    617:      */
    618:     public function getFilters() {
    619:         return $this->filters;
    620:     }
    621: }
    622:
    623: ?>