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.
881 lines
23 KiB
881 lines
23 KiB
<?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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
?>
|
|
|