File indexing completed on 2025-01-26 05:24:54
0001 <?php 0002 /** 0003 * Zend Framework 0004 * 0005 * LICENSE 0006 * 0007 * This source file is subject to the new BSD license that is bundled 0008 * with this package in the file LICENSE.txt. 0009 * It is also available through the world-wide-web at this URL: 0010 * http://framework.zend.com/license/new-bsd 0011 * If you did not receive a copy of the license and are unable to 0012 * obtain it through the world-wide-web, please send an email 0013 * to license@zend.com so we can send you a copy immediately. 0014 * 0015 * @category Zend 0016 * @package Zend_Controller 0017 * @subpackage Dispatcher 0018 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0019 * @license http://framework.zend.com/license/new-bsd New BSD License 0020 * @version $Id$ 0021 */ 0022 0023 /** Zend_Loader */ 0024 // require_once 'Zend/Loader.php'; 0025 0026 /** Zend_Controller_Dispatcher_Abstract */ 0027 // require_once 'Zend/Controller/Dispatcher/Abstract.php'; 0028 0029 /** 0030 * @category Zend 0031 * @package Zend_Controller 0032 * @subpackage Dispatcher 0033 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0034 * @license http://framework.zend.com/license/new-bsd New BSD License 0035 */ 0036 class Zend_Controller_Dispatcher_Standard extends Zend_Controller_Dispatcher_Abstract 0037 { 0038 /** 0039 * Current dispatchable directory 0040 * @var string 0041 */ 0042 protected $_curDirectory; 0043 0044 /** 0045 * Current module (formatted) 0046 * @var string 0047 */ 0048 protected $_curModule; 0049 0050 /** 0051 * Controller directory(ies) 0052 * @var array 0053 */ 0054 protected $_controllerDirectory = array(); 0055 0056 /** 0057 * Constructor: Set current module to default value 0058 * 0059 * @param array $params 0060 * @return void 0061 */ 0062 public function __construct(array $params = array()) 0063 { 0064 parent::__construct($params); 0065 $this->_curModule = $this->getDefaultModule(); 0066 } 0067 0068 /** 0069 * Add a single path to the controller directory stack 0070 * 0071 * @param string $path 0072 * @param string $module 0073 * @return Zend_Controller_Dispatcher_Standard 0074 */ 0075 public function addControllerDirectory($path, $module = null) 0076 { 0077 if (null === $module) { 0078 $module = $this->_defaultModule; 0079 } 0080 0081 $module = (string) $module; 0082 $path = rtrim((string) $path, '/\\'); 0083 0084 $this->_controllerDirectory[$module] = $path; 0085 return $this; 0086 } 0087 0088 /** 0089 * Set controller directory 0090 * 0091 * @param array|string $directory 0092 * @return Zend_Controller_Dispatcher_Standard 0093 */ 0094 public function setControllerDirectory($directory, $module = null) 0095 { 0096 $this->_controllerDirectory = array(); 0097 0098 if (is_string($directory)) { 0099 $this->addControllerDirectory($directory, $module); 0100 } elseif (is_array($directory)) { 0101 foreach ((array) $directory as $module => $path) { 0102 $this->addControllerDirectory($path, $module); 0103 } 0104 } else { 0105 // require_once 'Zend/Controller/Exception.php'; 0106 throw new Zend_Controller_Exception('Controller directory spec must be either a string or an array'); 0107 } 0108 0109 return $this; 0110 } 0111 0112 /** 0113 * Return the currently set directories for Zend_Controller_Action class 0114 * lookup 0115 * 0116 * If a module is specified, returns just that directory. 0117 * 0118 * @param string $module Module name 0119 * @return array|string Returns array of all directories by default, single 0120 * module directory if module argument provided 0121 */ 0122 public function getControllerDirectory($module = null) 0123 { 0124 if (null === $module) { 0125 return $this->_controllerDirectory; 0126 } 0127 0128 $module = (string) $module; 0129 if (array_key_exists($module, $this->_controllerDirectory)) { 0130 return $this->_controllerDirectory[$module]; 0131 } 0132 0133 return null; 0134 } 0135 0136 /** 0137 * Remove a controller directory by module name 0138 * 0139 * @param string $module 0140 * @return bool 0141 */ 0142 public function removeControllerDirectory($module) 0143 { 0144 $module = (string) $module; 0145 if (array_key_exists($module, $this->_controllerDirectory)) { 0146 unset($this->_controllerDirectory[$module]); 0147 return true; 0148 } 0149 return false; 0150 } 0151 0152 /** 0153 * Format the module name. 0154 * 0155 * @param string $unformatted 0156 * @return string 0157 */ 0158 public function formatModuleName($unformatted) 0159 { 0160 if (($this->_defaultModule == $unformatted) && !$this->getParam('prefixDefaultModule')) { 0161 return $unformatted; 0162 } 0163 0164 return ucfirst($this->_formatName($unformatted)); 0165 } 0166 0167 /** 0168 * Format action class name 0169 * 0170 * @param string $moduleName Name of the current module 0171 * @param string $className Name of the action class 0172 * @return string Formatted class name 0173 */ 0174 public function formatClassName($moduleName, $className) 0175 { 0176 return $this->formatModuleName($moduleName) . '_' . $className; 0177 } 0178 0179 /** 0180 * Convert a class name to a filename 0181 * 0182 * @param string $class 0183 * @return string 0184 */ 0185 public function classToFilename($class) 0186 { 0187 return str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php'; 0188 } 0189 0190 /** 0191 * Returns TRUE if the Zend_Controller_Request_Abstract object can be 0192 * dispatched to a controller. 0193 * 0194 * Use this method wisely. By default, the dispatcher will fall back to the 0195 * default controller (either in the module specified or the global default) 0196 * if a given controller does not exist. This method returning false does 0197 * not necessarily indicate the dispatcher will not still dispatch the call. 0198 * 0199 * @param Zend_Controller_Request_Abstract $action 0200 * @return boolean 0201 */ 0202 public function isDispatchable(Zend_Controller_Request_Abstract $request) 0203 { 0204 $className = $this->getControllerClass($request); 0205 if (!$className) { 0206 return false; 0207 } 0208 0209 $finalClass = $className; 0210 if (($this->_defaultModule != $this->_curModule) 0211 || $this->getParam('prefixDefaultModule')) 0212 { 0213 $finalClass = $this->formatClassName($this->_curModule, $className); 0214 } 0215 if (class_exists($finalClass, false)) { 0216 return true; 0217 } 0218 0219 $fileSpec = $this->classToFilename($className); 0220 $dispatchDir = $this->getDispatchDirectory(); 0221 $test = $dispatchDir . DIRECTORY_SEPARATOR . $fileSpec; 0222 return Zend_Loader::isReadable($test); 0223 } 0224 0225 /** 0226 * Dispatch to a controller/action 0227 * 0228 * By default, if a controller is not dispatchable, dispatch() will throw 0229 * an exception. If you wish to use the default controller instead, set the 0230 * param 'useDefaultControllerAlways' via {@link setParam()}. 0231 * 0232 * @param Zend_Controller_Request_Abstract $request 0233 * @param Zend_Controller_Response_Abstract $response 0234 * @return void 0235 * @throws Zend_Controller_Dispatcher_Exception 0236 */ 0237 public function dispatch(Zend_Controller_Request_Abstract $request, Zend_Controller_Response_Abstract $response) 0238 { 0239 $this->setResponse($response); 0240 0241 /** 0242 * Get controller class 0243 */ 0244 if (!$this->isDispatchable($request)) { 0245 $controller = $request->getControllerName(); 0246 if (!$this->getParam('useDefaultControllerAlways') && !empty($controller)) { 0247 // require_once 'Zend/Controller/Dispatcher/Exception.php'; 0248 throw new Zend_Controller_Dispatcher_Exception('Invalid controller specified (' . $request->getControllerName() . ')'); 0249 } 0250 0251 $className = $this->getDefaultControllerClass($request); 0252 } else { 0253 $className = $this->getControllerClass($request); 0254 if (!$className) { 0255 $className = $this->getDefaultControllerClass($request); 0256 } 0257 } 0258 0259 /** 0260 * If we're in a module or prefixDefaultModule is on, we must add the module name 0261 * prefix to the contents of $className, as getControllerClass does not do that automatically. 0262 * We must keep a separate variable because modules are not strictly PSR-0: We need the no-module-prefix 0263 * class name to do the class->file mapping, but the full class name to insantiate the controller 0264 */ 0265 $moduleClassName = $className; 0266 if (($this->_defaultModule != $this->_curModule) 0267 || $this->getParam('prefixDefaultModule')) 0268 { 0269 $moduleClassName = $this->formatClassName($this->_curModule, $className); 0270 } 0271 0272 /** 0273 * Load the controller class file 0274 */ 0275 $className = $this->loadClass($className); 0276 0277 /** 0278 * Instantiate controller with request, response, and invocation 0279 * arguments; throw exception if it's not an action controller 0280 */ 0281 $controller = new $moduleClassName($request, $this->getResponse(), $this->getParams()); 0282 if (!($controller instanceof Zend_Controller_Action_Interface) && 0283 !($controller instanceof Zend_Controller_Action)) { 0284 // require_once 'Zend/Controller/Dispatcher/Exception.php'; 0285 throw new Zend_Controller_Dispatcher_Exception( 0286 'Controller "' . $moduleClassName . '" is not an instance of Zend_Controller_Action_Interface' 0287 ); 0288 } 0289 0290 /** 0291 * Retrieve the action name 0292 */ 0293 $action = $this->getActionMethod($request); 0294 0295 /** 0296 * Dispatch the method call 0297 */ 0298 $request->setDispatched(true); 0299 0300 // by default, buffer output 0301 $disableOb = $this->getParam('disableOutputBuffering'); 0302 $obLevel = ob_get_level(); 0303 if (empty($disableOb)) { 0304 ob_start(); 0305 } 0306 0307 try { 0308 $controller->dispatch($action); 0309 } catch (Exception $e) { 0310 // Clean output buffer on error 0311 $curObLevel = ob_get_level(); 0312 if ($curObLevel > $obLevel) { 0313 do { 0314 ob_get_clean(); 0315 $curObLevel = ob_get_level(); 0316 } while ($curObLevel > $obLevel); 0317 } 0318 throw $e; 0319 } 0320 0321 if (empty($disableOb)) { 0322 $content = ob_get_clean(); 0323 $response->appendBody($content); 0324 } 0325 0326 // Destroy the page controller instance and reflection objects 0327 $controller = null; 0328 } 0329 0330 /** 0331 * Load a controller class 0332 * 0333 * Attempts to load the controller class file from 0334 * {@link getControllerDirectory()}. If the controller belongs to a 0335 * module, looks for the module prefix to the controller class. 0336 * 0337 * @param string $className 0338 * @return string Class name loaded 0339 * @throws Zend_Controller_Dispatcher_Exception if class not loaded 0340 */ 0341 public function loadClass($className) 0342 { 0343 $finalClass = $className; 0344 if (($this->_defaultModule != $this->_curModule) 0345 || $this->getParam('prefixDefaultModule')) 0346 { 0347 $finalClass = $this->formatClassName($this->_curModule, $className); 0348 } 0349 if (class_exists($finalClass, false)) { 0350 return $finalClass; 0351 } 0352 0353 $dispatchDir = $this->getDispatchDirectory(); 0354 $loadFile = $dispatchDir . DIRECTORY_SEPARATOR . $this->classToFilename($className); 0355 0356 if (Zend_Loader::isReadable($loadFile)) { 0357 include_once $loadFile; 0358 } else { 0359 // require_once 'Zend/Controller/Dispatcher/Exception.php'; 0360 throw new Zend_Controller_Dispatcher_Exception('Cannot load controller class "' . $className . '" from file "' . $loadFile . "'"); 0361 } 0362 0363 if (!class_exists($finalClass, false)) { 0364 // require_once 'Zend/Controller/Dispatcher/Exception.php'; 0365 throw new Zend_Controller_Dispatcher_Exception('Invalid controller class ("' . $finalClass . '")'); 0366 } 0367 0368 return $finalClass; 0369 } 0370 0371 /** 0372 * Get controller class name 0373 * 0374 * Try request first; if not found, try pulling from request parameter; 0375 * if still not found, fallback to default 0376 * 0377 * @param Zend_Controller_Request_Abstract $request 0378 * @return string|false Returns class name on success 0379 */ 0380 public function getControllerClass(Zend_Controller_Request_Abstract $request) 0381 { 0382 $controllerName = $request->getControllerName(); 0383 if (empty($controllerName)) { 0384 if (!$this->getParam('useDefaultControllerAlways')) { 0385 return false; 0386 } 0387 $controllerName = $this->getDefaultControllerName(); 0388 $request->setControllerName($controllerName); 0389 } 0390 0391 $className = $this->formatControllerName($controllerName); 0392 0393 $controllerDirs = $this->getControllerDirectory(); 0394 $module = $request->getModuleName(); 0395 if ($this->isValidModule($module)) { 0396 $this->_curModule = $module; 0397 $this->_curDirectory = $controllerDirs[$module]; 0398 } elseif ($this->isValidModule($this->_defaultModule)) { 0399 $request->setModuleName($this->_defaultModule); 0400 $this->_curModule = $this->_defaultModule; 0401 $this->_curDirectory = $controllerDirs[$this->_defaultModule]; 0402 } else { 0403 // require_once 'Zend/Controller/Exception.php'; 0404 throw new Zend_Controller_Exception('No default module defined for this application'); 0405 } 0406 0407 return $className; 0408 } 0409 0410 /** 0411 * Determine if a given module is valid 0412 * 0413 * @param string $module 0414 * @return bool 0415 */ 0416 public function isValidModule($module) 0417 { 0418 if (!is_string($module)) { 0419 return false; 0420 } 0421 0422 $module = strtolower($module); 0423 $controllerDir = $this->getControllerDirectory(); 0424 foreach (array_keys($controllerDir) as $moduleName) { 0425 if ($module == strtolower($moduleName)) { 0426 return true; 0427 } 0428 } 0429 0430 return false; 0431 } 0432 0433 /** 0434 * Retrieve default controller class 0435 * 0436 * Determines whether the default controller to use lies within the 0437 * requested module, or if the global default should be used. 0438 * 0439 * By default, will only use the module default unless that controller does 0440 * not exist; if this is the case, it falls back to the default controller 0441 * in the default module. 0442 * 0443 * @param Zend_Controller_Request_Abstract $request 0444 * @return string 0445 */ 0446 public function getDefaultControllerClass(Zend_Controller_Request_Abstract $request) 0447 { 0448 $controller = $this->getDefaultControllerName(); 0449 $default = $this->formatControllerName($controller); 0450 $request->setControllerName($controller) 0451 ->setActionName(null); 0452 0453 $module = $request->getModuleName(); 0454 $controllerDirs = $this->getControllerDirectory(); 0455 $this->_curModule = $this->_defaultModule; 0456 $this->_curDirectory = $controllerDirs[$this->_defaultModule]; 0457 if ($this->isValidModule($module)) { 0458 $found = false; 0459 if (class_exists($default, false)) { 0460 $found = true; 0461 } else { 0462 $moduleDir = $controllerDirs[$module]; 0463 $fileSpec = $moduleDir . DIRECTORY_SEPARATOR . $this->classToFilename($default); 0464 if (Zend_Loader::isReadable($fileSpec)) { 0465 $found = true; 0466 $this->_curDirectory = $moduleDir; 0467 } 0468 } 0469 if ($found) { 0470 $request->setModuleName($module); 0471 $this->_curModule = $this->formatModuleName($module); 0472 } 0473 } else { 0474 $request->setModuleName($this->_defaultModule); 0475 } 0476 0477 return $default; 0478 } 0479 0480 /** 0481 * Return the value of the currently selected dispatch directory (as set by 0482 * {@link getController()}) 0483 * 0484 * @return string 0485 */ 0486 public function getDispatchDirectory() 0487 { 0488 return $this->_curDirectory; 0489 } 0490 0491 /** 0492 * Determine the action name 0493 * 0494 * First attempt to retrieve from request; then from request params 0495 * using action key; default to default action 0496 * 0497 * Returns formatted action name 0498 * 0499 * @param Zend_Controller_Request_Abstract $request 0500 * @return string 0501 */ 0502 public function getActionMethod(Zend_Controller_Request_Abstract $request) 0503 { 0504 $action = $request->getActionName(); 0505 if (empty($action)) { 0506 $action = $this->getDefaultAction(); 0507 $request->setActionName($action); 0508 } 0509 0510 return $this->formatActionName($action); 0511 } 0512 }