<?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-2014 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License * @version $Id$ */ /** 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-2014 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); } } /** * If we're in a module or prefixDefaultModule is on, we must add the module name * prefix to the contents of $className, as getControllerClass does not do that automatically. * We must keep a separate variable because modules are not strictly PSR-0: We need the no-module-prefix * class name to do the class->file mapping, but the full class name to insantiate the controller */ $moduleClassName = $className; if (($this->_defaultModule != $this->_curModule) || $this->getParam('prefixDefaultModule')) { $moduleClassName = $this->formatClassName($this->_curModule, $className); } /** * 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 $moduleClassName($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 "' . $moduleClassName . '" 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); } }