Cake/Routing/Dispatcher.php
| 1 | <?php |
|---|---|
| 2 | /** |
| 3 | * Dispatcher takes the URL information, parses it for parameters and |
| 4 | * tells the involved controllers what to do. |
| 5 | * |
| 6 | * This is the heart of Cake's operation. |
| 7 | * |
| 8 | * PHP 5 |
| 9 | * |
| 10 | * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) |
| 11 | * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org) |
| 12 | * |
| 13 | * Licensed under The MIT License |
| 14 | * Redistributions of files must retain the above copyright notice. |
| 15 | * |
| 16 | * @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org) |
| 17 | * @link http://cakephp.org CakePHP(tm) Project |
| 18 | * @package Cake.Routing |
| 19 | * @since CakePHP(tm) v 0.2.9 |
| 20 | * @license MIT License (http://www.opensource.org/licenses/mit-license.php) |
| 21 | */ |
| 22 | |
| 23 | App::uses('Router', 'Routing'); |
| 24 | App::uses('CakeRequest', 'Network'); |
| 25 | App::uses('CakeResponse', 'Network'); |
| 26 | App::uses('Controller', 'Controller'); |
| 27 | App::uses('Scaffold', 'Controller'); |
| 28 | App::uses('View', 'View'); |
| 29 | App::uses('Debugger', 'Utility'); |
| 30 | |
| 31 | /** |
| 32 | * Dispatcher converts Requests into controller actions. It uses the dispatched Request |
| 33 | * to locate and load the correct controller. If found, the requested action is called on |
| 34 | * the controller. |
| 35 | * |
| 36 | * @package Cake.Routing |
| 37 | */ |
| 38 | class Dispatcher { |
| 39 | |
| 40 | /** |
| 41 | * Constructor. |
| 42 | * |
| 43 | * @param string $base The base directory for the application. Writes `App.base` to Configure. |
| 44 | */ |
| 45 | public function __construct($base = false) { |
| 46 | if ($base !== false) { |
| 47 | Configure::write('App.base', $base); |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | /** |
| 52 | * Dispatches and invokes given Request, handing over control to the involved controller. If the controller is set |
| 53 | * to autoRender, via Controller::$autoRender, then Dispatcher will render the view. |
| 54 | * |
| 55 | * Actions in CakePHP can be any public method on a controller, that is not declared in Controller. If you |
| 56 | * want controller methods to be public and in-accessible by URL, then prefix them with a `_`. |
| 57 | * For example `public function _loadPosts() { }` would not be accessible via URL. Private and protected methods |
| 58 | * are also not accessible via URL. |
| 59 | * |
| 60 | * If no controller of given name can be found, invoke() will throw an exception. |
| 61 | * If the controller is found, and the action is not found an exception will be thrown. |
| 62 | * |
| 63 | * @param CakeRequest $request Request object to dispatch. |
| 64 | * @param CakeResponse $response Response object to put the results of the dispatch into. |
| 65 | * @param array $additionalParams Settings array ("bare", "return") which is melded with the GET and POST params |
| 66 | * @return boolean Success |
| 67 | * @throws MissingControllerException When the controller is missing. |
| 68 | */ |
| 69 | public function dispatch(CakeRequest $request, CakeResponse $response, $additionalParams = array()) { |
| 70 | if ($this->asset($request->url, $response) || $this->cached($request->here())) { |
| 71 | return; |
| 72 | } |
| 73 | |
| 74 | Router::setRequestInfo($request); |
| 75 | $request = $this->parseParams($request, $additionalParams); |
| 76 | $controller = $this->_getController($request, $response); |
| 77 | |
| 78 | if (!($controller instanceof Controller)) { |
| 79 | throw new MissingControllerException(array( |
| 80 | 'class' => Inflector::camelize($request->params['controller']) . 'Controller', |
| 81 | 'plugin' => empty($request->params['plugin']) ? null : Inflector::camelize($request->params['plugin']) |
| 82 | )); |
| 83 | } |
| 84 | |
| 85 | return $this->_invoke($controller, $request, $response); |
| 86 | } |
| 87 | |
| 88 | /** |
| 89 | * Initializes the components and models a controller will be using. |
| 90 | * Triggers the controller action, and invokes the rendering if Controller::$autoRender is true and echo's the output. |
| 91 | * Otherwise the return value of the controller action are returned. |
| 92 | * |
| 93 | * @param Controller $controller Controller to invoke |
| 94 | * @param CakeRequest $request The request object to invoke the controller for. |
| 95 | * @param CakeResponse $response The response object to receive the output |
| 96 | * @return void |
| 97 | */ |
| 98 | protected function _invoke(Controller $controller, CakeRequest $request, CakeResponse $response) { |
| 99 | $controller->constructClasses(); |
| 100 | $controller->startupProcess(); |
| 101 | |
| 102 | $render = true; |
| 103 | $result = $controller->invokeAction($request); |
| 104 | if ($result instanceof CakeResponse) { |
| 105 | $render = false; |
| 106 | $response = $result; |
| 107 | } |
| 108 | |
| 109 | if ($render && $controller->autoRender) { |
| 110 | $response = $controller->render(); |
| 111 | } elseif ($response->body() === null) { |
| 112 | $response->body($result); |
| 113 | } |
| 114 | $controller->shutdownProcess(); |
| 115 | |
| 116 | if (isset($request->params['return'])) { |
| 117 | return $response->body(); |
| 118 | } |
| 119 | $response->send(); |
| 120 | } |
| 121 | |
| 122 | /** |
| 123 | * Applies Routing and additionalParameters to the request to be dispatched. |
| 124 | * If Routes have not been loaded they will be loaded, and app/Config/routes.php will be run. |
| 125 | * |
| 126 | * @param CakeRequest $request CakeRequest object to mine for parameter information. |
| 127 | * @param array $additionalParams An array of additional parameters to set to the request. |
| 128 | * Useful when Object::requestAction() is involved |
| 129 | * @return CakeRequest The request object with routing params set. |
| 130 | */ |
| 131 | public function parseParams(CakeRequest $request, $additionalParams = array()) { |
| 132 | if (count(Router::$routes) == 0) { |
| 133 | $namedExpressions = Router::getNamedExpressions(); |
| 134 | extract($namedExpressions); |
| 135 | $this->_loadRoutes(); |
| 136 | } |
| 137 | |
| 138 | $params = Router::parse($request->url); |
| 139 | $request->addParams($params); |
| 140 | |
| 141 | if (!empty($additionalParams)) { |
| 142 | $request->addParams($additionalParams); |
| 143 | } |
| 144 | return $request; |
| 145 | } |
| 146 | |
| 147 | /** |
| 148 | * Get controller to use, either plugin controller or application controller |
| 149 | * |
| 150 | * @param CakeRequest $request Request object |
| 151 | * @param CakeResponse $response Response for the controller. |
| 152 | * @return mixed name of controller if not loaded, or object if loaded |
| 153 | */ |
| 154 | protected function _getController($request, $response) { |
| 155 | $ctrlClass = $this->_loadController($request); |
| 156 | if (!$ctrlClass) { |
| 157 | return false; |
| 158 | } |
| 159 | $reflection = new ReflectionClass($ctrlClass); |
| 160 | if ($reflection->isAbstract() || $reflection->isInterface()) { |
| 161 | return false; |
| 162 | } |
| 163 | return $reflection->newInstance($request, $response); |
| 164 | } |
| 165 | |
| 166 | /** |
| 167 | * Load controller and return controller classname |
| 168 | * |
| 169 | * @param CakeRequest $request |
| 170 | * @return string|bool Name of controller class name |
| 171 | */ |
| 172 | protected function _loadController($request) { |
| 173 | $pluginName = $pluginPath = $controller = null; |
| 174 | if (!empty($request->params['plugin'])) { |
| 175 | $pluginName = $controller = Inflector::camelize($request->params['plugin']); |
| 176 | $pluginPath = $pluginName . '.'; |
| 177 | } |
| 178 | if (!empty($request->params['controller'])) { |
| 179 | $controller = Inflector::camelize($request->params['controller']); |
| 180 | } |
| 181 | if ($pluginPath . $controller) { |
| 182 | $class = $controller . 'Controller'; |
| 183 | App::uses('AppController', 'Controller'); |
| 184 | App::uses($pluginName . 'AppController', $pluginPath . 'Controller'); |
| 185 | App::uses($class, $pluginPath . 'Controller'); |
| 186 | if (class_exists($class)) { |
| 187 | return $class; |
| 188 | } |
| 189 | } |
| 190 | return false; |
| 191 | } |
| 192 | |
| 193 | /** |
| 194 | * Loads route configuration |
| 195 | * |
| 196 | * @return void |
| 197 | */ |
| 198 | protected function _loadRoutes() { |
| 199 | include APP . 'Config' . DS . 'routes.php'; |
| 200 | } |
| 201 | |
| 202 | /** |
| 203 | * Outputs cached dispatch view cache |
| 204 | * |
| 205 | * @param string $path Requested URL path with any query string parameters |
| 206 | * @return string|boolean False if is not cached or output |
| 207 | */ |
| 208 | public function cached($path) { |
| 209 | if (Configure::read('Cache.check') === true) { |
| 210 | if ($path == '/') { |
| 211 | $path = 'home'; |
| 212 | } |
| 213 | $path = strtolower(Inflector::slug($path)); |
| 214 | |
| 215 | $filename = CACHE . 'views' . DS . $path . '.php'; |
| 216 | |
| 217 | if (!file_exists($filename)) { |
| 218 | $filename = CACHE . 'views' . DS . $path . '_index.php'; |
| 219 | } |
| 220 | if (file_exists($filename)) { |
| 221 | $controller = null; |
| 222 | $view = new View($controller); |
| 223 | return $view->renderCache($filename, microtime(true)); |
| 224 | } |
| 225 | } |
| 226 | return false; |
| 227 | } |
| 228 | |
| 229 | /** |
| 230 | * Checks if a requested asset exists and sends it to the browser |
| 231 | * |
| 232 | * @param string $url Requested URL |
| 233 | * @param CakeResponse $response The response object to put the file contents in. |
| 234 | * @return boolean True on success if the asset file was found and sent |
| 235 | */ |
| 236 | public function asset($url, CakeResponse $response) { |
| 237 | if (strpos($url, '..') !== false || strpos($url, '.') === false) { |
| 238 | return false; |
| 239 | } |
| 240 | $filters = Configure::read('Asset.filter'); |
| 241 | $isCss = ( |
| 242 | strpos($url, 'ccss/') === 0 || |
| 243 | preg_match('#^(theme/([^/]+)/ccss/)|(([^/]+)(?<!css)/ccss)/#i', $url) |
| 244 | ); |
| 245 | $isJs = ( |
| 246 | strpos($url, 'cjs/') === 0 || |
| 247 | preg_match('#^/((theme/[^/]+)/cjs/)|(([^/]+)(?<!js)/cjs)/#i', $url) |
| 248 | ); |
| 249 | if (($isCss && empty($filters['css'])) || ($isJs && empty($filters['js']))) { |
| 250 | $response->statusCode(404); |
| 251 | $response->send(); |
| 252 | return true; |
| 253 | } elseif ($isCss) { |
| 254 | include WWW_ROOT . DS . $filters['css']; |
| 255 | return true; |
| 256 | } elseif ($isJs) { |
| 257 | include WWW_ROOT . DS . $filters['js']; |
| 258 | return true; |
| 259 | } |
| 260 | $pathSegments = explode('.', $url); |
| 261 | $ext = array_pop($pathSegments); |
| 262 | $parts = explode('/', $url); |
| 263 | $assetFile = null; |
| 264 | |
| 265 | if ($parts[0] === 'theme') { |
| 266 | $themeName = $parts[1]; |
| 267 | unset($parts[0], $parts[1]); |
| 268 | $fileFragment = urldecode(implode(DS, $parts)); |
| 269 | $path = App::themePath($themeName) . 'webroot' . DS; |
| 270 | if (file_exists($path . $fileFragment)) { |
| 271 | $assetFile = $path . $fileFragment; |
| 272 | } |
| 273 | } else { |
| 274 | $plugin = Inflector::camelize($parts[0]); |
| 275 | if (CakePlugin::loaded($plugin)) { |
| 276 | unset($parts[0]); |
| 277 | $fileFragment = urldecode(implode(DS, $parts)); |
| 278 | $pluginWebroot = CakePlugin::path($plugin) . 'webroot' . DS; |
| 279 | if (file_exists($pluginWebroot . $fileFragment)) { |
| 280 | $assetFile = $pluginWebroot . $fileFragment; |
| 281 | } |
| 282 | } |
| 283 | } |
| 284 | |
| 285 | if ($assetFile !== null) { |
| 286 | $this->_deliverAsset($response, $assetFile, $ext); |
| 287 | return true; |
| 288 | } |
| 289 | return false; |
| 290 | } |
| 291 | |
| 292 | /** |
| 293 | * Sends an asset file to the client |
| 294 | * |
| 295 | * @param CakeResponse $response The response object to use. |
| 296 | * @param string $assetFile Path to the asset file in the file system |
| 297 | * @param string $ext The extension of the file to determine its mime type |
| 298 | * @return void |
| 299 | */ |
| 300 | protected function _deliverAsset(CakeResponse $response, $assetFile, $ext) { |
| 301 | ob_start(); |
| 302 | $compressionEnabled = Configure::read('Asset.compress') && $response->compress(); |
| 303 | if ($response->type($ext) == $ext) { |
| 304 | $contentType = 'application/octet-stream'; |
| 305 | $agent = env('HTTP_USER_AGENT'); |
| 306 | if (preg_match('%Opera(/| )([0-9].[0-9]{1,2})%', $agent) || preg_match('/MSIE ([0-9].[0-9]{1,2})/', $agent)) { |
| 307 | $contentType = 'application/octetstream'; |
| 308 | } |
| 309 | $response->type($contentType); |
| 310 | } |
| 311 | if (!$compressionEnabled) { |
| 312 | $response->header('Content-Length', filesize($assetFile)); |
| 313 | } |
| 314 | $response->cache(filemtime($assetFile)); |
| 315 | $response->send(); |
| 316 | ob_clean(); |
| 317 | if ($ext === 'css' || $ext === 'js') { |
| 318 | include $assetFile; |
| 319 | } else { |
| 320 | readfile($assetFile); |
| 321 | } |
| 322 | |
| 323 | if ($compressionEnabled) { |
| 324 | ob_end_flush(); |
| 325 | } |
| 326 | } |
| 327 | |
| 328 | } |
| 329 | |
| 330 |
