ConfigurationException If an invalid configuration setting
     *                                       has been found.
     * @throws ForwardException If an error occurs while forwarding the
     *                                 request.
     * @throws InitializationException If the action could not be
     *                                        initialized.
     * @throws SecurityException 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 ControllerException 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 FactoryException 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 RenderException - 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);
    }
}
?>