You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

882 lines
23 KiB

2 years ago
<?php
// +---------------------------------------------------------------------------+
// | This file is part of the core package. |
// | Copyright (c) laiketui.com |
// | |
// | For the full copyright and license information, please view the LICENSE |
// | file that was distributed with this source code. You can also view the |
// | LICENSE file online at http://www.laiketui.com |
// +---------------------------------------------------------------------------+
/**
* Controller directs application flow.
*
* @package laiketui
* @subpackage controller
*
* @author ketter (ketter@laiketui.com)
* @since 3.0.0
*/
abstract class Controller extends MojaviObject
{
// +-----------------------------------------------------------------------+
// | PRIVATE VARIABLES |
// +-----------------------------------------------------------------------+
private
$actionStack = null,
$context = null,
$databaseManager = null,
$maxForwards = 20,
$renderMode = View::RENDER_CLIENT,
$request = null,
$securityFilter = null,
$storage = null,
$user = null;
private static
$instance = null;
// +-----------------------------------------------------------------------+
// | METHODS |
// +-----------------------------------------------------------------------+
/**
* Indicates whether or not a module has a specific action.
*
* @param string A module name.
* @param string An action name.
*
* @return bool true, if the action exists, otherwise false.
*
* @author ketter (ketter@laiketui.com)
* @since 3.0.0
*/
public function actionExists ($moduleName, $actionName)
{
$file = MO_MODULE_DIR . '/' . $moduleName . '/actions/' . $actionName .
'Action.class.php';
return is_readable($file);
}
// -------------------------------------------------------------------------
/**
* Forward the request to another action.
*
* @param string A module name.
* @param string An action name.
*
* @return void
*
* @throws <b>ConfigurationException</b> If an invalid configuration setting
* has been found.
* @throws <b>ForwardException</b> If an error occurs while forwarding the
* request.
* @throws <b>InitializationException</b> If the action could not be
* initialized.
* @throws <b>SecurityException</b> If the action requires security but
* the user implementation is not of type
* SecurityUser.
*
* @author ketter (ketter@laiketui.com)
* @since 3.0.0
*/
public function forward ($moduleName, $actionName)
{
// replace periods with slashes for action sub-directories
$actionName = str_replace('.', '/', $actionName);
// replace unwanted characters
$moduleName = preg_replace('/[^a-z0-9\-_]+/i', '', $moduleName);
$actionName = preg_replace('/[^a-z0-9\-_\/]+/i', '', $actionName);
if ($this->actionStack->getSize() >= $this->maxForwards)
{
// let's kill this party before it turns into cpu cycle hell
$error = 'Too many forwards have been detected for this request';
throw new ForwardException($error);
}
if (!MO_AVAILABLE)
{
// application is unavailable
$moduleName = MO_UNAVAILABLE_MODULE;
$actionName = MO_UNAVAILABLE_ACTION;
if (!$this->actionExists($moduleName, $actionName))
{
// cannot find unavailable module/action
$error = 'Invalid configuration settings: ' .
'MO_UNAVAILABLE_MODULE "%s", ' .
'MO_UNAVAILABLE_ACTION "%s"';
$error = sprintf($error, $moduleName, $actionName);
throw new ConfigurationException($error);
}
} else if (!$this->actionExists($moduleName, $actionName))
{
// the requested action doesn't exist
// track the requested module so we have access to the data
// in the error 404 page
$this->request->setAttribute('requested_action', $actionName);
$this->request->setAttribute('requested_module', $moduleName);
// switch to error 404 action
$moduleName = MO_ERROR_404_MODULE;
$actionName = MO_ERROR_404_ACTION;
if (!$this->actionExists($moduleName, $actionName))
{
// cannot find unavailable module/action
$error = 'Invalid configuration settings: ' .
'MO_ERROR_404_MODULE "%s", ' .
'MO_ERROR_404_ACTION "%s"';
$error = sprintf($error, $moduleName, $actionName);
throw new ConfigurationException($error);
}
}
// create an instance of the action
$actionInstance = $this->getAction($moduleName, $actionName);
// add a new action stack entry
$this->actionStack->addEntry($moduleName, $actionName,
$actionInstance);
// include the module configuration
ConfigCache::import('modules/' . $moduleName . '/config/module.ini');
if (constant('MOD_' . strtoupper($moduleName) . '_ENABLED'))
{
// module is enabled
// check for a module config.php
$moduleConfig = MO_MODULE_DIR . '/' . $moduleName . '/config.php';
if (is_readable($moduleConfig))
{
require_once($moduleConfig);
}
// initialize the action
if ($actionInstance->initialize($this->context))
{
// create a new filter chain
$filterChain = new FilterChain();
if (MO_AVAILABLE)
{
// the application is available so we'll register
// global and module filters, otherwise skip them
// does this action require security?
if (MO_USE_SECURITY && $actionInstance->isSecure())
{
if (!($this->user instanceof SecurityUser))
{
// we've got security on but the user implementation
// isn't a sub-class of SecurityUser
$error = 'Security is enabled, but your User ' .
'implementation isn\'t a sub-class of ' .
'SecurityUser';
throw new SecurityException($error);
}
// register security filter
$filterChain->register($this->securityFilter);
}
// load filters
$this->loadGlobalFilters($filterChain);
$this->loadModuleFilters($filterChain);
}
// register the execution filter
$execFilter = new ExecutionFilter();
$execFilter->initialize($this->context);
$filterChain->register($execFilter);
if ($moduleName == MO_ERROR_404_MODULE &&
$actionName == MO_ERROR_404_ACTION)
{
header('HTTP/1.0 404 Not Found');
header('Status: 404 Not Found');
}
// process the filter chain
$filterChain->execute();
} else
{
// action failed to initialize
$error = 'Action initialization failed for module "%s", ' .
'action "%s"';
$error = sprintf($error, $moduleName, $actionName);
throw new InitializationException($error);
}
} else
{
// module is disabled
$moduleName = MO_MODULE_DISABLED_MODULE;
$actionName = MO_MODULE_DISABLED_ACTION;
if (!$this->actionExists($moduleName, $actionName))
{
// cannot find mod disabled module/action
$error = 'Invalid configuration settings: ' .
'MO_MODULE_DISABLED_MODULE "%s", ' .
'MO_MODULE_DISABLED_ACTION "%s"';
$error = sprintf($error, $moduleName, $actionName);
throw new ConfigurationException($error);
}
$this->forward($moduleName, $actionName);
}
}
// -------------------------------------------------------------------------
/**
* Retrieve an Action implementation instance.
*
* @param string A module name.
* @param string An action name.
*
* @return Action An Action implementation instance, if the action exists,
* otherwise null.
*
* @author ketter (ketter@laiketui.com)
* @since 3.0.0
*/
public function getAction ($moduleName, $actionName)
{
$file = MO_MODULE_DIR . '/' . $moduleName . '/actions/' . $actionName .
'Action.class.php';
require_once($file);
$position = strrpos($actionName, '/');
if ($position > -1)
{
$actionName = substr($actionName, $position + 1);
}
$class = $actionName . 'Action';
// fix for same name classes
$moduleClass = $moduleName . '_' . $class;
if (class_exists($moduleClass, false))
{
$class = $moduleClass;
}
return new $class();
}
// -------------------------------------------------------------------------
/**
* Retrieve the action stack.
*
* @return ActionStack An ActionStack instance, if the action stack is
* enabled, otherwise null.
*
* @author ketter (ketter@laiketui.com)
* @since 3.0.0
*/
public function getActionStack ()
{
return $this->actionStack;
}
// -------------------------------------------------------------------------
/**
* Retrieve the current application context.
*
* @return Context A Context instance.
*
* @author ketter (ketter@laiketui.com)
* @since 3.0.0
*/
public function getContext ()
{
return $this->context;
}
// -------------------------------------------------------------------------
/**
* Retrieve a global Model implementation instance.
*
* @param string A model name.
*
* @return Model A Model implementation instance, if the model exists,
* otherwise null.
*
* @author ketter (ketter@laiketui.com)
* @since 3.0.0
*/
public function getGlobalModel ($modelName)
{
$file = MO_LIB_DIR . '/models/' . $modelName . 'Model.class.php';
require_once($file);
$class = $modelName . 'Model';
// create model instance and initialize it
$model = new $class();
$model->initialize($this->context);
return $model;
}
// -------------------------------------------------------------------------
/**
* Retrieve the singleton instance of this class.
*
* @return Controller A Controller implementation instance.
*
* @throws <b>ControllerException</b> If a controller implementation
* instance has not been created.
*
* @author ketter (ketter@laiketui.com)
* @since 3.0.0
*/
public static function getInstance ()
{
if (isset(self::$instance))
{
return self::$instance;
}
// an instance of the controller has not been created
$error = 'A Controller implementation instance has not been created';
throw new ControllerException($error);
}
// -------------------------------------------------------------------------
/**
* Retrieve a Model implementation instance.
*
* @param string A module name.
* @param string A model name.
*
* @return Model A Model implementation instance, if the model exists,
* otherwise null.
*
* @author ketter (ketter@laiketui.com)
* @since 3.0.0
*/
public function getModel ($moduleName, $modelName)
{
$file = MO_MODULE_DIR . '/' . $moduleName . '/models/' . $modelName .
'Model.class.php';
require_once($file);
$class = $modelName . 'Model';
// fix for same name classes
$moduleClass = $moduleName . '_' . $class;
if (class_exists($moduleClass, false))
{
$class = $moduleClass;
}
// create model instance and initialize it
$model = new $class();
$model->initialize($this->context);
return $model;
}
// -------------------------------------------------------------------------
/**
* Retrieve the presentation rendering mode.
*
* @return int One of the following:
* - View::RENDER_CLIENT
* - View::RENDER_VAR
*
* @author ketter (ketter@laiketui.com)
* @since 3.0.0
*/
public function getRenderMode ()
{
return $this->renderMode;
}
// -------------------------------------------------------------------------
/**
* Retrieve a View implementation instance.
*
* @param string A module name.
* @param string A view name.
*
* @return View A View implementation instance, if the model exists,
* otherwise null.
*
* @author ketter (ketter@laiketui.com)
* @since 3.0.0
*/
public function getView ($moduleName, $viewName)
{
$file = MO_MODULE_DIR . '/' . $moduleName . '/views/' . $viewName .
'View.class.php';
require_once($file);
$position = strrpos($viewName, '/');
if ($position > -1)
{
$viewName = substr($viewName, $position + 1);
}
$class = $viewName . 'View';
// fix for same name classes
$moduleClass = $moduleName . '_' . $class;
if (class_exists($moduleClass, false))
{
$class = $moduleClass;
}
return new $class();
}
// -------------------------------------------------------------------------
/**
* Initialize this controller.
*
* @return void
*
* @author ketter (ketter@laiketui.com)
* @since 3.0.0
*/
protected function initialize ()
{
if (MO_USE_DATABASE)
{
// setup our database connections
$this->databaseManager = new DatabaseManager();
$this->databaseManager->initialize();
}
// create a new action stack
$this->actionStack = new ActionStack();
// create factory implementation instances
$config = ConfigCache::checkConfig('config/factories.ini');
// include the factories configuration
require_once($config);
// register our shutdown function
register_shutdown_function(array($this, 'shutdown'));
// $this->context is created in the factories configuration up above
// TODO: logging setup
// set max forwards
if (defined('MO_MAX_FORWARDS'))
{
$this->maxForwards = MO_MAX_FORWARDS;
}
}
// -------------------------------------------------------------------------
/**
* Load global filters.
*
* @param FilterChain A FilterChain instance.
*
* @return void
*
* @author ketter (ketter@laiketui.com)
* @since 3.0.0
*/
private function loadGlobalFilters ($filterChain)
{
static $list = array();
// grab our global filter ini and preset the module name
$config = MO_CONFIG_DIR . '/filters.ini';
$moduleName = 'global';
if (!isset($list[$moduleName]) && is_readable($config))
{
// load global filters
require_once(ConfigCache::checkConfig('config/filters.ini'));
}
// register filters
foreach ($list[$moduleName] as $filter)
{
$filterChain->register($filter);
}
}
// -------------------------------------------------------------------------
/**
* Load module filters.
*
* @param FilterChain A FilterChain instance.
*
* @return void
*
* @author ketter (ketter@laiketui.com)
* @since 3.0.0
*/
private function loadModuleFilters ($filterChain)
{
// filter list cache file
static $list = array();
// get the module name
$moduleName = $this->context->getModuleName();
if (!isset($list[$moduleName]))
{
// we haven't loaded a filter list for this module yet
$config = MO_MODULE_DIR . '/' . $moduleName . '/config/filters.ini';
if (is_readable($config))
{
require_once(ConfigCache::checkConfig($config));
} else
{
// add an emptry array for this module since no filters
// exist
$list[$moduleName] = array();
}
}
// register filters
foreach ($list[$moduleName] as $filter)
{
$filterChain->register($filter);
}
}
// -------------------------------------------------------------------------
/**
* Indicates whether or not a module has a specific model.
*
* @param string A module name.
* @param string A model name.
*
* @return bool true, if the model exists, otherwise false.
*
* @author ketter (ketter@laiketui.com)
* @since 3.0.0
*/
public function modelExists ($moduleName, $modelName)
{
$file = MO_MODULE_DIR . '/' . $moduleName . '/models/' . $modelName .
'Model.class.php';
return is_readable($file);
}
// -------------------------------------------------------------------------
/**
* Indicates whether or not a module exists.
*
* @param string A module name.
*
* @return bool true, if the module exists, otherwise false.
*
* @author ketter (ketter@laiketui.com)
* @since 3.0.0
*/
public function moduleExists ($moduleName)
{
$file = MO_MODULE_DIR . '/' . $moduleName . '/config/module.ini';
return is_readable($file);
}
// -------------------------------------------------------------------------
/**
* Retrieve a new Controller implementation instance.
*
* @param string A Controller implementation name.
*
* @return Controller A Controller implementation instance.
*
* @throws <b>FactoryException</b> If a new controller implementation
* instance cannot be created.
*
* @author ketter (ketter@laiketui.com)
* @since 3.0.0
*/
public static function newInstance ($class)
{
try
{
if (!isset(self::$instance))
{
// the class exists
$object = new $class();
if (!($object instanceof Controller))
{
// the class name is of the wrong type
$error = 'Class "%s" is not of the type Controller';
$error = sprintf($error, $class);
throw new FactoryException($error);
}
// set our singleton instance
self::$instance = $object;
return $object;
} else
{
$type = get_class(self::$instance);
// an instance has already been created
$error = 'A Controller implementation instance has already ' .
'been created';
throw new FactoryException($error);
}
} catch (MojaviException $e)
{
$e->printStackTrace();
} catch (Exception $e)
{
// most likely an exception from a third-party library
$e = new MojaviException($e->getMessage());
$e->printStackTrace();
}
}
// -------------------------------------------------------------------------
/**
* Set the presentation rendering mode.
*
* @param int A rendering mode.
*
* @return void
*
* @throws <b>RenderException</b> - If an invalid render mode has been set.
*
* @author ketter (ketter@laiketui.com)
* @since 3.0.0
*/
public function setRenderMode ($mode)
{
if ($mode == View::RENDER_CLIENT || $mode == View::RENDER_VAR ||
$mode == View::RENDER_NONE)
{
$this->renderMode = $mode;
return;
}
// invalid rendering mode type
$error = 'Invalid rendering mode: %s';
$error = sprintf($error, $mode);
throw new RenderException($error);
}
// -------------------------------------------------------------------------
/**
* Execute the shutdown procedure.
*
* @return void
*
* @author ketter (ketter@laiketui.com)
* @since 3.0.0
*/
public function shutdown ()
{
$this->user->shutdown();
session_write_close();
$this->storage->shutdown();
$this->request->shutdown();
if (MO_USE_DATABASE)
{
$this->databaseManager->shutdown();
}
}
// -------------------------------------------------------------------------
/**
* Indicates whether or not a module has a specific view.
*
* @param string A module name.
* @param string A view name.
*
* @return bool true, if the view exists, otherwise false.
*
* @author ketter (ketter@laiketui.com)
* @since 3.0.0
*/
public function viewExists ($moduleName, $viewName)
{
$file = MO_MODULE_DIR . '/' . $moduleName . '/views/' . $viewName .
'View.class.php';
return is_readable($file);
}
}
?>