<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@zend.com so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Controller
* @subpackage Dispatcher
* @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @version $Id: Standard.php 24594 2012-01-05 21:27:01Z matthew $
*/
/** Zend_Loader */
require_once 'Zend/Loader.php';
/** Zend_Controller_Dispatcher_Abstract */
require_once 'Zend/Controller/Dispatcher/Abstract.php';
/**
* @category Zend
* @package Zend_Controller
* @subpackage Dispatcher
* @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Controller_Dispatcher_Standard extends Zend_Controller_Dispatcher_Abstract
{
/**
* Current dispatchable directory
* @var string
*/
protected $_curDirectory;
/**
* Current module (formatted)
* @var string
*/
protected $_curModule;
/**
* Controller directory(ies)
* @var array
*/
protected $_controllerDirectory = array();
/**
* Constructor: Set current module to default value
*
* @param array $params
* @return void
*/
public function __construct(array $params = array())
{
parent::__construct($params);
$this->_curModule = $this->getDefaultModule();
}
/**
* Add a single path to the controller directory stack
*
* @param string $path
* @param string $module
* @return Zend_Controller_Dispatcher_Standard
*/
public function addControllerDirectory($path, $module = null)
{
if (null === $module) {
$module = $this->_defaultModule;
}
$module = (string) $module;
$path = rtrim((string) $path, '/\\');
$this->_controllerDirectory[$module] = $path;
return $this;
}
/**
* Set controller directory
*
* @param array|string $directory
* @return Zend_Controller_Dispatcher_Standard
*/
public function setControllerDirectory($directory, $module = null)
{
$this->_controllerDirectory = array();
if (is_string($directory)) {
$this->addControllerDirectory($directory, $module);
} elseif (is_array($directory)) {
foreach ((array) $directory as $module => $path) {
$this->addControllerDirectory($path, $module);
}
} else {
require_once 'Zend/Controller/Exception.php';
throw new Zend_Controller_Exception('Controller directory spec must be either a string or an array');
}
return $this;
}
/**
* Return the currently set directories for Zend_Controller_Action class
* lookup
*
* If a module is specified, returns just that directory.
*
* @param string $module Module name
* @return array|string Returns array of all directories by default, single
* module directory if module argument provided
*/
public function getControllerDirectory($module = null)
{
if (null === $module) {
return $this->_controllerDirectory;
}
$module = (string) $module;
if (array_key_exists($module, $this->_controllerDirectory)) {
return $this->_controllerDirectory[$module];
}
return null;
}
/**
* Remove a controller directory by module name
*
* @param string $module
* @return bool
*/
public function removeControllerDirectory($module)
{
$module = (string) $module;
if (array_key_exists($module, $this->_controllerDirectory)) {
unset($this->_controllerDirectory[$module]);
return true;
}
return false;
}
/**
* Format the module name.
*
* @param string $unformatted
* @return string
*/
public function formatModuleName($unformatted)
{
if (($this->_defaultModule == $unformatted) && !$this->getParam('prefixDefaultModule')) {
return $unformatted;
}
return ucfirst($this->_formatName($unformatted));
}
/**
* Format action class name
*
* @param string $moduleName Name of the current module
* @param string $className Name of the action class
* @return string Formatted class name
*/
public function formatClassName($moduleName, $className)
{
return $this->formatModuleName($moduleName) . '_' . $className;
}
/**
* Convert a class name to a filename
*
* @param string $class
* @return string
*/
public function classToFilename($class)
{
return str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php';
}
/**
* Returns TRUE if the Zend_Controller_Request_Abstract object can be
* dispatched to a controller.
*
* Use this method wisely. By default, the dispatcher will fall back to the
* default controller (either in the module specified or the global default)
* if a given controller does not exist. This method returning false does
* not necessarily indicate the dispatcher will not still dispatch the call.
*
* @param Zend_Controller_Request_Abstract $action
* @return boolean
*/
public function isDispatchable(Zend_Controller_Request_Abstract $request)
{
$className = $this->getControllerClass($request);
if (!$className) {
return false;
}
$finalClass = $className;
if (($this->_defaultModule != $this->_curModule)
|| $this->getParam('prefixDefaultModule'))
{
$finalClass = $this->formatClassName($this->_curModule, $className);
}
if (class_exists($finalClass, false)) {
return true;
}
$fileSpec = $this->classToFilename($className);
$dispatchDir = $this->getDispatchDirectory();
$test = $dispatchDir . DIRECTORY_SEPARATOR . $fileSpec;
return Zend_Loader::isReadable($test);
}
/**
* Dispatch to a controller/action
*
* By default, if a controller is not dispatchable, dispatch() will throw
* an exception. If you wish to use the default controller instead, set the
* param 'useDefaultControllerAlways' via {@link setParam()}.
*
* @param Zend_Controller_Request_Abstract $request
* @param Zend_Controller_Response_Abstract $response
* @return void
* @throws Zend_Controller_Dispatcher_Exception
*/
public function dispatch(Zend_Controller_Request_Abstract $request, Zend_Controller_Response_Abstract $response)
{
$this->setResponse($response);
/**
* Get controller class
*/
if (!$this->isDispatchable($request)) {
$controller = $request->getControllerName();
if (!$this->getParam('useDefaultControllerAlways') && !empty($controller)) {
require_once 'Zend/Controller/Dispatcher/Exception.php';
throw new Zend_Controller_Dispatcher_Exception('Invalid controller specified (' . $request->getControllerName() . ')');
}
$className = $this->getDefaultControllerClass($request);
} else {
$className = $this->getControllerClass($request);
if (!$className) {
$className = $this->getDefaultControllerClass($request);
}
}
/**
* Load the controller class file
*/
$className = $this->loadClass($className);
/**
* Instantiate controller with request, response, and invocation
* arguments; throw exception if it's not an action controller
*/
$controller = new $className($request, $this->getResponse(), $this->getParams());
if (!($controller instanceof Zend_Controller_Action_Interface) &&
!($controller instanceof Zend_Controller_Action)) {
require_once 'Zend/Controller/Dispatcher/Exception.php';
throw new Zend_Controller_Dispatcher_Exception(
'Controller "' . $className . '" is not an instance of Zend_Controller_Action_Interface'
);
}
/**
* Retrieve the action name
*/
$action = $this->getActionMethod($request);
/**
* Dispatch the method call
*/
$request->setDispatched(true);
// by default, buffer output
$disableOb = $this->getParam('disableOutputBuffering');
$obLevel = ob_get_level();
if (empty($disableOb)) {
ob_start();
}
try {
$controller->dispatch($action);
} catch (Exception $e) {
// Clean output buffer on error
$curObLevel = ob_get_level();
if ($curObLevel > $obLevel) {
do {
ob_get_clean();
$curObLevel = ob_get_level();
} while ($curObLevel > $obLevel);
}
throw $e;
}
if (empty($disableOb)) {
$content = ob_get_clean();
$response->appendBody($content);
}
// Destroy the page controller instance and reflection objects
$controller = null;
}
/**
* Load a controller class
*
* Attempts to load the controller class file from
* {@link getControllerDirectory()}. If the controller belongs to a
* module, looks for the module prefix to the controller class.
*
* @param string $className
* @return string Class name loaded
* @throws Zend_Controller_Dispatcher_Exception if class not loaded
*/
public function loadClass($className)
{
$finalClass = $className;
if (($this->_defaultModule != $this->_curModule)
|| $this->getParam('prefixDefaultModule'))
{
$finalClass = $this->formatClassName($this->_curModule, $className);
}
if (class_exists($finalClass, false)) {
return $finalClass;
}
$dispatchDir = $this->getDispatchDirectory();
$loadFile = $dispatchDir . DIRECTORY_SEPARATOR . $this->classToFilename($className);
if (Zend_Loader::isReadable($loadFile)) {
include_once $loadFile;
} else {
require_once 'Zend/Controller/Dispatcher/Exception.php';
throw new Zend_Controller_Dispatcher_Exception('Cannot load controller class "' . $className . '" from file "' . $loadFile . "'");
}
if (!class_exists($finalClass, false)) {
require_once 'Zend/Controller/Dispatcher/Exception.php';
throw new Zend_Controller_Dispatcher_Exception('Invalid controller class ("' . $finalClass . '")');
}
return $finalClass;
}
/**
* Get controller class name
*
* Try request first; if not found, try pulling from request parameter;
* if still not found, fallback to default
*
* @param Zend_Controller_Request_Abstract $request
* @return string|false Returns class name on success
*/
public function getControllerClass(Zend_Controller_Request_Abstract $request)
{
$controllerName = $request->getControllerName();
if (empty($controllerName)) {
if (!$this->getParam('useDefaultControllerAlways')) {
return false;
}
$controllerName = $this->getDefaultControllerName();
$request->setControllerName($controllerName);
}
$className = $this->formatControllerName($controllerName);
$controllerDirs = $this->getControllerDirectory();
$module = $request->getModuleName();
if ($this->isValidModule($module)) {
$this->_curModule = $module;
$this->_curDirectory = $controllerDirs[$module];
} elseif ($this->isValidModule($this->_defaultModule)) {
$request->setModuleName($this->_defaultModule);
$this->_curModule = $this->_defaultModule;
$this->_curDirectory = $controllerDirs[$this->_defaultModule];
} else {
require_once 'Zend/Controller/Exception.php';
throw new Zend_Controller_Exception('No default module defined for this application');
}
return $className;
}
/**
* Determine if a given module is valid
*
* @param string $module
* @return bool
*/
public function isValidModule($module)
{
if (!is_string($module)) {
return false;
}
$module = strtolower($module);
$controllerDir = $this->getControllerDirectory();
foreach (array_keys($controllerDir) as $moduleName) {
if ($module == strtolower($moduleName)) {
return true;
}
}
return false;
}
/**
* Retrieve default controller class
*
* Determines whether the default controller to use lies within the
* requested module, or if the global default should be used.
*
* By default, will only use the module default unless that controller does
* not exist; if this is the case, it falls back to the default controller
* in the default module.
*
* @param Zend_Controller_Request_Abstract $request
* @return string
*/
public function getDefaultControllerClass(Zend_Controller_Request_Abstract $request)
{
$controller = $this->getDefaultControllerName();
$default = $this->formatControllerName($controller);
$request->setControllerName($controller)
->setActionName(null);
$module = $request->getModuleName();
$controllerDirs = $this->getControllerDirectory();
$this->_curModule = $this->_defaultModule;
$this->_curDirectory = $controllerDirs[$this->_defaultModule];
if ($this->isValidModule($module)) {
$found = false;
if (class_exists($default, false)) {
$found = true;
} else {
$moduleDir = $controllerDirs[$module];
$fileSpec = $moduleDir . DIRECTORY_SEPARATOR . $this->classToFilename($default);
if (Zend_Loader::isReadable($fileSpec)) {
$found = true;
$this->_curDirectory = $moduleDir;
}
}
if ($found) {
$request->setModuleName($module);
$this->_curModule = $this->formatModuleName($module);
}
} else {
$request->setModuleName($this->_defaultModule);
}
return $default;
}
/**
* Return the value of the currently selected dispatch directory (as set by
* {@link getController()})
*
* @return string
*/
public function getDispatchDirectory()
{
return $this->_curDirectory;
}
/**
* Determine the action name
*
* First attempt to retrieve from request; then from request params
* using action key; default to default action
*
* Returns formatted action name
*
* @param Zend_Controller_Request_Abstract $request
* @return string
*/
public function getActionMethod(Zend_Controller_Request_Abstract $request)
{
$action = $request->getActionName();
if (empty($action)) {
$action = $this->getDefaultAction();
$request->setActionName($action);
}
return $this->formatActionName($action);
}
}