Dispatcher.php 11 KB

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