* @version 0.2 * @copyright GNU Lesser General Public License (LGPL) version 3 */ /** * Front Controller * * The is the root class of the application, the mother of initialization process * It's a singleton, defining mostly static methods * @since 0.1 */ class FrontController extends HelpableSingleton { private static $instance; protected $autoPath; public $controller; public $action; public $request; public $settings; public $user; public $db; /** * private constructor (singleton) */ private function __construct() { $this->autoPath = $authoPath = array(); include APP_CONFIG_PATH.'path.php'; $this->autoPath = $autoPath; unset($autoPath); $this->controller = $GLOBALS['config']['app']['default_controller']; $this->action = $GLOBALS['config']['app']['default_action']; $this->request = array(); $this->_helpers = array(); $this->db = array(); if (!isset($_SESSION['appVariables'])) { $_SESSION['appVariables'] = array(); } } /** * the getInstance() method returns a single instance of the object */ public static function getInstance() { if(!isset(self::$instance)){ spl_autoload_register(array('FrontController','autoLoad')); $object= __CLASS__; self::$instance=new $object; self::$instance->initApp(); } return self::$instance; } // ---- APP INITIALIZATION --------------------------------------------------- /** * app initialization (from config) */ protected function initApp() { if (APP_SETUP_DATABASE) { // load up database settings (host, username, etc...) include APP_CONFIG_PATH.'db.php'; // connect to database $this->initDatabase(); } if (APP_SETUP_MESSAGING) { // initialize messaging (trans-pages messages) $this->initMessaging(); } if (APP_SETUP_DATABASE) { // load general settings if (APP_SETUP_GLOBAL_SETTINGS) { $this->loadSettings(); } // load user (if needed) $this->loadUser(APP_SETUP_USER_MODEL); // load user preferences if (APP_SETUP_USER_SETTINGS) { $this->loadUserSettings(); } } if (APP_SETUP_TRANSLATOR) { // initialize translations system $this->initTranslator(); } if (APP_SETUP_NAVI) { // initialize navigation (referers, redirects and URL manipulation) $this->initNavi(); } } /** * initialize database connection */ public function initDatabase() { $i = count($this->db); $this->db[$i] = new DbConnector(); $this->db[$i]->connect(); } /** * load application settings * @todo load settings * @todo set default controller and action */ public function loadSettings() { $this->settings = new SettingModel(); // -TODO- load 'em up } /** * load and authenticate user */ public function loadUser($class) { if (empty($class)) { return false; } $class = StringHelper::flatToCamel($class,true).'Model'; $this->user = new $class; $this->user->enableAuthentication(); $this->user->connectDb(); $this->user->checkLogin(); } /** * load user settings * @todo load user settings and override global settings */ public function loadUserSettings() { // might overload default controller and action // -TODO- if ($this->user->isLoggedIn()) { $this->setSessionDefault('usertask', $this->user->getUid()); } } /** * initialize messaging system */ public function initMessaging() { $this->addHelper('messaging'); } /** * initialize referrers system */ public function initNavi() { $this->addHelper('navi'); } /** * initialize translator */ public function initTranslator() { $this->addHelper('translator'); } /** * launch application controller */ public function run() { // parse URL : look for controller, action, parameters... $this->parseUrl(); // load up requested Application Controller $con = self::loadController($this->controller); if (!$con) { // controller not found, throw exception if (isset($GLOBALS['config']['error']['not_found'])) { $this->_sendErrorCodeToRobots('404'); include $GLOBALS['config']['error']['not_found']; exit; } else { try { throw new AppException('Controller '.$this->controller.' not defined in '.implode(', ', $this->autoPath['controller'])); } catch(Exception $e) { error_log('error loading controller '.$this->controller); echo $e; exit; } } } $obj = new $con; $act = $this->action; // if submitted, call requested action (mainReaction by default) if (!empty($_POST)) { if (method_exists($obj, $act.'Reaction')) { if (!call_user_func(array($obj,$act.'Reaction'))) { // stop here return true; } } else { self::log_error('method '.$act.'Reaction not defined in controller '.$this->controller); } } // if still needed, call requested action (mainAction by default) if (method_exists($obj, $act.'Action')) { call_user_func(array($obj, $act.'Action')); } else { if (isset($GLOBALS['config']['error']['not_found'])) { $this->_sendErrorCodeToRobots('404'); include $GLOBALS['config']['error']['not_found']; exit; } self::log_error('method '.$act.'Action not defined in controller '.$this->controller); return false; } return true; } // ---- HTTP QUERY PARSER ---------------------------------------------------- /** * clean $_POST, $_GET and $_REQUEST if magic quotes are on */ protected static function cleanRequest() { if (!get_magic_quotes_gpc()) { return true; } $arrReq = array('_POST','_GET','_REQUEST'); foreach ($arrReq as $key) { if (isset($$key)) { array_walk($$key, 'stripslashes'); } } return true; } /** * gets HTTP variable * @return null if not found, value if set */ public function getReqVar($key) { if (isset($this->request[$key])) { return $this->request[$key]; } else { return null; } } /** * checks if variable is submitted by HTTP request * @param $mix an array or string containing key(s) to be checked * @return first found in request */ public function chkReqVar($mix) { $arr = StringHelper::mixedToArray($mix); foreach($arr as $key) { if (isset($this->request[$key])) { return $key; } } return false; } /** * parse request for controller, action and other parameters */ public function parseUrl() { // clean ugly magic quotes self::cleanRequest(); // parse URL path $req = trim($_SERVER['REQUEST_URI'],'/'); if ($req) { if ($pos = strrpos($_SERVER['REQUEST_URI'],'.html')) { $req = substr($req, 0, $pos-1); } else if ($pos = strrpos($_SERVER['REQUEST_URI'],'?')) { $req = substr($req, 0, $pos-1); } $arrReq = explode('/',$req); if (count($arrReq)) { $this->controller = array_shift($arrReq); } if (count($arrReq)) { $this->action = array_shift($arrReq); } else { $this->action = 'main'; } while(count($arrReq)) { $key = array_shift($arrReq); if (count($arrReq)) { $val = array_shift($arrReq); $this->request[$key] = urldecode($val); } else { $this->request[$key] = ''; } } } // parse query string if (count($_REQUEST)) { foreach($_REQUEST as $key => $val) { switch($key) { case 'controller' : $this->controller = $val; break; case 'action' : $this->action = $val; break; default : $this->request[$key] = $val; break; } } } // get real names $this->controller = StringHelper::flatToCamel($this->controller, true,'-'); $this->action = StringHelper::flatToCamel($this->action, false,'-'); } /** * generate URL */ public static function getUrl($controller='', $action = '', $params = '') { if (!$controller) { return APP_WWW_URI; } if (!$action) { return APP_WWW_URI.$controller; } $url = APP_WWW_URI.StringHelper::camelToFlat($controller,'-') .'/'.StringHelper::camelToFlat($action,'-'); if (is_array($params)) { foreach ($params as $key => $val) { $url .= '/'.urlencode($key).'/'.urlencode($val); } } return $url; } /** * returns current URL * @param mixed params false if no parameter, true if all current parameters, array to submit other parameters */ public function thisUrl($params=false) { if (is_array($params)) { return self::getUrl($this->controller, $this->action, $params); } else { return self::getUrl($this->controller, $this->action, $params?$this->request:false); } } /** * return navigation menu */ public function menu($id='pages') { $str = ''; return $str; } /** * send error code (http header) to robots */ protected function _sendErrorCodeToRobots($code) { if (preg_match('/'.APP_ROBOT_AGENT.'/', $_SERVER['HTTP_USER_AGENT'])) { header("Status : 404 Not Found"); header("HTTP/1.1 404 Not Found"); } } // ---- APPLICATION VARIABLES ------------------------------------------------ /** * get a persistent variable * try first from $_REQUEST, then look into session variables * if found in $_REQUEST, save it in session for later use */ public function sessionVariable($key) { if (isset($this->request[$key])) { $this->setSessionVariable($key, $this->request[$key]); } return $this->getSessionVariable($key); } /** * set a session variable */ public function setSessionVariable($key, $value) { $_SESSION['appVariables'][$key] = $value; } /** * set a session variable default's value */ public function setSessionDefault($key, $default) { if (!isset($_SESSION['appVariables'][$key])) { $_SESSION['appVariables'][$key] = $default; } } /** * get a session variable */ public function getSessionVariable($key) { if (!isset($_SESSION['appVariables'][$key])) { return NULL; } else { return $_SESSION['appVariables'][$key]; } } /** * reset a session variable */ public function cleanSessionVariable($keys) { $keys = StringHelper::mixedToArray($keys); foreach($keys as $key) { if (isset($_SESSION['appVariables'][$key])) { unset($_SESSION['appVariables'][$key]); } } } // ---- AUTOLOAD setup ------------------------------------------------------- /** * generic method loading classes */ public function _load($file, $type) { self::log_front("loading $file [$type]"); foreach($this->autoPath[$type] as $path) { if (file_exists($path.$file)) { self::log_front("-> Yes in $path"); include_once($path.$file); return true; } else { self::log_front("!> No $file in $path"); } } return false; } /** * static method loading any type of class definition * check all accessible folder that may contain the class definition * and include it if necessary */ protected static function load($class, $type) { $file = StringHelper::camelToFlat($class); if ($idx = strrpos($file, '_'.$type)) { $file = substr($file, 0, $idx); } $file .= '.php'; if (!class_exists($class)) { $obj = self::getInstance(); if (!$obj->_load($file, $type)) { return false; } } return $class; } /** * load core class */ public static function loadClass($class) { return self::load($class, 'class'); } /** * load helper class */ public static function loadHelper($class) { return self::load($class, 'helper'); } /** * load a model class */ public static function loadModel($class) { return self::load($class, 'model'); } /** * load a controller class */ public static function loadController($class) { return self::load($class, 'controller'); } /** * load a view class */ public static function loadView($class) { return self::load($class, 'view'); } /** * autoload class * @parameter would require the type of class to be said in there */ private static function autoLoad($name) { $str = StringHelper::camelToFlat($name); $type = 'class'; $sep = strrpos($str,'_'); if ($sep) { $class = substr($str, 0, $sep); $type = substr($str, $sep+1); switch ($type){ case 'helper': case 'class': case 'controller': case 'model': case 'view': // valid type break; default: // invalid type, must be a core class $type = 'class'; break; } } return self::load($name, $type); } // ---- LOGGING and DEBUGGING ------------------------------------------------ public static function log_front($str) { self::log_any($GLOBALS['config']['log_front'], 'front', $str); } public static function log_debug($str) { self::log_any($GLOBALS['config']['log_debug'], 'debug', $str); } public static function log_message($str) { self::log_any($GLOBALS['config']['log_message'], 'message', $str); } public static function log_warn($str) { self::log_any($GLOBALS['config']['log_warn'], 'warning', $str); } public static function log_error($str) { self::log_any($GLOBALS['config']['log_error'], 'error', $str); } public static function log_core_debug($str) { self::log_any($GLOBALS['config']['log_core'], 'error', $str); } public static function log_any($mode, $head, $str) { switch ($mode) { case 1: $arr = explode("\n", trim($str)); foreach ($arr as $s) { error_log($GLOBALS['config']['log_signature']." $head : $s"); } break; case 2: echo VarStr::html($GLOBALS['config']['log_signature']." $head : ".nl2br($str)); break; } } } class FC extends FrontController { } class AppException extends Exception { // Redefine the exception so message isn't optional public function __construct($message, $code = 0, Exception $previous = null) { // make sure everything is assigned properly parent::__construct($message, $code, $previous); } // custom string representation of object public function __toString() { echo "
".parent::__toString()."
"; return __CLASS__; } }