* Copyright 2005-2008, Cake Software Foundation, Inc. * 1785 E. Sahara Avenue, Suite 490-204 * Las Vegas, Nevada 89104 * * Licensed under The MIT License * Redistributions of files must retain the above copyright notice. * * @filesource * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project * @package cake * @subpackage cake.cake * @since CakePHP(tm) v 0.2.9 * @version $Revision$ * @modifiedby $LastChangedBy$ * @lastmodified $Date$ * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ /** * List of helpers to include */ App::import('Core', array('Router', 'Controller')); /** * Dispatcher translates URLs to controller-action-paramter triads. * * Dispatches the request, creating appropriate models and controllers. * * @package cake * @subpackage cake.cake */ class Dispatcher extends Object { /** * Base URL * * @var string * @access public */ var $base = false; /** * webroot path * * @var string * @access public */ var $webroot = '/'; /** * Current URL * * @var string * @access public */ var $here = false; /** * Admin route (if on it) * * @var string * @access public */ var $admin = false; /** * Plugin being served (if any) * * @var string * @access public */ var $plugin = null; /** * the params for this request * * @var string * @access public */ var $params = null; /** * Constructor. */ function __construct($url = null, $base = false) { if ($base !== false) { Configure::write('App.base', $base); } if ($url !== null) { return $this->dispatch($url); } } /** * Dispatches and invokes given URL, handing over control to the involved controllers, and then renders the results (if autoRender is set). * * If no controller of given name can be found, invoke() shows error messages in * the form of Missing Controllers information. It does the same with Actions (methods of Controllers are called * Actions). * * @param string $url URL information to work on * @param array $additionalParams Settings array ("bare", "return") which is melded with the GET and POST params * @return boolean Success * @access public */ function dispatch($url = null, $additionalParams = array()) { $parse = true; if (is_array($url)) { $url = $this->__extractParams($url, $additionalParams); $parse = false; } if ($this->base === false) { $this->base = $this->baseUrl(); } if ($url !== null) { $_GET['url'] = $url; } if ($parse) { $url = $this->getUrl(); } $this->here = $this->base . '/' . $url; if ($this->cached($url)) { $this->_stop(); } if ($parse) { $this->params = array_merge($this->parseParams($url), $additionalParams); } $controller =& $this->__getController(); if (!is_object($controller)) { Router::setRequestInfo(array($this->params, array('base' => $this->base, 'webroot' => $this->webroot))); return $this->cakeError('missingController', array( array( 'className' => Inflector::camelize($this->params['controller']) . 'Controller', 'webroot' => $this->webroot, 'url' => $url, 'base' => $this->base ) )); } $privateAction = (bool)(strpos($this->params['action'], '_', 0) === 0); $prefixes = Router::prefixes(); if (!empty($prefixes)) { if (isset($this->params['prefix'])) { $this->params['action'] = $this->params['prefix'] . '_' . $this->params['action']; } elseif (strpos($this->params['action'], '_') !== false && !$privateAction) { list($prefix, $action) = explode('_', $this->params['action']); $privateAction = in_array($prefix, $prefixes); } } Router::setRequestInfo(array( $this->params, array('base' => $this->base, 'here' => $this->here, 'webroot' => $this->webroot) )); if ($privateAction) { return $this->cakeError('privateAction', array( array( 'className' => Inflector::camelize($this->params['controller'] . "Controller"), 'action' => $this->params['action'], 'webroot' => $this->webroot, 'url' => $url, 'base' => $this->base ) )); } $controller->base = $this->base; $controller->here = $this->here; $controller->webroot = $this->webroot; $controller->plugin = $this->plugin; $controller->params =& $this->params; $controller->action =& $this->params['action']; $controller->passedArgs = array_merge($this->params['pass'], $this->params['named']); if (!empty($this->params['data'])) { $controller->data =& $this->params['data']; } else { $controller->data = null; } if (array_key_exists('return', $this->params) && $this->params['return'] == 1) { $controller->autoRender = false; } if (!empty($this->params['bare'])) { $controller->autoLayout = false; } if (array_key_exists('layout', $this->params)) { if (empty($this->params['layout'])) { $controller->autoLayout = false; } else { $controller->layout = $this->params['layout']; } } if (isset($this->params['viewPath'])) { $controller->viewPath = $this->params['viewPath']; } return $this->_invoke($controller, $this->params); } /** * Invokes given controller's render action if autoRender option is set. Otherwise the * contents of the operation are returned as a string. * * @param object $controller Controller to invoke * @param array $params Parameters with at least the 'action' to invoke * @param boolean $missingAction Set to true if missing action should be rendered, false otherwise * @return string Output as sent by controller * @access protected */ function _invoke(&$controller, $params) { $controller->constructClasses(); $controller->Component->initialize($controller); $controller->beforeFilter(); $controller->Component->startup($controller); $childMethods = get_class_methods($controller); $parentMethods = get_class_methods('Controller'); foreach ($childMethods as $key => $value) { $childMethods[$key] = strtolower($value); } foreach ($parentMethods as $key => $value) { $parentMethods[$key] = strtolower($value); } $classMethods = array_diff($childMethods, $parentMethods); $classMethods = array_flip($classMethods); if (!isset($classMethods[strtolower($params['action'])])) { if ($controller->scaffold !== false) { App::import('Core', 'Scaffold'); return new Scaffold($controller, $params); } return $this->cakeError('missingAction', array( array( 'className' => Inflector::camelize($params['controller']."Controller"), 'action' => $params['action'], 'webroot' => $this->webroot, 'url' => $this->here, 'base' => $this->base))); } $output = $controller->dispatchMethod($params['action'], $params['pass']); if ($controller->autoRender) { $controller->output = $controller->render(); } elseif (empty($controller->output)) { $controller->output = $output; } $controller->Component->shutdown($controller); $controller->afterFilter(); if (isset($params['return'])) { return $controller->output; } echo($controller->output); } /** * Sets the params when $url is passed as an array to Object::requestAction(); * * @param array $url * @param array $additionalParams * @return null * @access private * @todo commented Router::url(). this improved performance, * will work on this more later. */ function __extractParams($url, $additionalParams = array()) { $defaults = array('pass' => array(), 'named' => array(), 'form' => array()); $this->params = array_merge($defaults, $url, $additionalParams); //$url = Router::url($url); //return $url; } /** * Returns array of GET and POST parameters. GET parameters are taken from given URL. * * @param string $fromUrl URL to mine for parameter information. * @return array Parameters found in POST and GET. * @access public */ function parseParams($fromUrl) { $params = array(); if (isset($_POST)) { $params['form'] = $_POST; if (ini_get('magic_quotes_gpc') == 1) { $params['form'] = stripslashes_deep($params['form']); } if (env('HTTP_X_HTTP_METHOD_OVERRIDE')) { $params['form']['_method'] = env('HTTP_X_HTTP_METHOD_OVERRIDE'); } if (isset($params['form']['_method'])) { if (isset($_SERVER) && !empty($_SERVER)) { $_SERVER['REQUEST_METHOD'] = $params['form']['_method']; } else { $_ENV['REQUEST_METHOD'] = $params['form']['_method']; } unset($params['form']['_method']); } } extract(Router::getNamedExpressions()); include CONFIGS . 'routes.php'; $params = array_merge(Router::parse($fromUrl), $params); if (empty($params['action'])) { $params['action'] = 'index'; } if (isset($params['form']['data'])) { $params['data'] = Router::stripEscape($params['form']['data']); unset($params['form']['data']); } if (isset($_GET)) { if (ini_get('magic_quotes_gpc') == 1) { $url = stripslashes_deep($_GET); } else { $url = $_GET; } if (isset($params['url'])) { $params['url'] = array_merge($params['url'], $url); } else { $params['url'] = $url; } } foreach ($_FILES as $name => $data) { if ($name != 'data') { $params['form'][$name] = $data; } } if (isset($_FILES['data'])) { foreach ($_FILES['data'] as $key => $data) { foreach ($data as $model => $fields) { foreach ($fields as $field => $value) { if (is_array($value)) { foreach ($value as $k => $v) { $params['data'][$model][$field][$k][$key] = $v; } } else { $params['data'][$model][$field][$key] = $value; } } } } } return $params; } /** * Returns a base URL and sets the proper webroot * * @return string Base URL * @access public */ function baseUrl() { $dir = $webroot = null; $config = Configure::read('App'); extract($config); if (!$base) { $base = $this->base; } if ($base !== false) { $this->webroot = $base . '/'; return $this->base = $base; } if (!$baseUrl) { $base = dirname(env('PHP_SELF')); if ($webroot === 'webroot' && $webroot === basename($base)) { $base = dirname($base); } if ($dir === 'app' && $dir === basename($base)) { $base = dirname($base); } if ($base === DS || $base === '.') { $base = ''; } $this->webroot = $base .'/'; return $base; } $file = null; if ($baseUrl) { $file = '/' . basename($baseUrl); $base = dirname($baseUrl); if ($base === DS || $base === '.') { $base = ''; } $this->webroot = $base .'/'; if (strpos($this->webroot, $dir) === false) { $this->webroot .= $dir . '/' ; } if (strpos($this->webroot, $webroot) === false) { $this->webroot .= $webroot . '/'; } return $base . $file; } return false; } /** * Restructure params in case we're serving a plugin. * * @param array $params Array on where to re-set 'controller', 'action', and 'pass' indexes * @param boolean $reverse * @return array Restructured array * @access protected */ function _restructureParams($params, $reverse = false) { if($reverse === true) { extract(Router::getArgs($params['action'])); $params = array_merge($params, array('controller'=> $params['plugin'], 'action'=> $params['controller'], 'pass' => array_merge($pass, $params['pass']), 'named' => array_merge($named, $params['named']))); $this->plugin = $params['plugin']; } else { $params['plugin'] = $params['controller']; $params['controller'] = $params['action']; if (isset($params['pass'][0])) { $params['action'] = $params['pass'][0]; array_shift($params['pass']); } else { $params['action'] = null; } } return $params; } /** * Get controller to use, either plugin controller or application controller * * @param array $params Array of parameters * @return mixed name of controller if not loaded, or object if loaded * @access private */ function &__getController($params = null) { if (!is_array($params)) { $params = $this->params; } $controller = false; if (!$ctrlClass = $this->__loadController($params)) { if (!isset($params['plugin'])) { $params = $this->_restructureParams($params); } else { $params = $this->_restructureParams($params, true); } if (!$ctrlClass = $this->__loadController($params)) { return $controller; } } $name = $ctrlClass; $ctrlClass = $ctrlClass . 'Controller'; if (class_exists($ctrlClass)) { if (strtolower(get_parent_class($ctrlClass)) === strtolower($name . 'AppController') && empty($params['plugin'])) { $params = $this->_restructureParams($params); $params = $this->_restructureParams($params, true); } $this->params = $params; $controller =& new $ctrlClass(); } return $controller; } /** * Load controller and return controller class * * @param array $params Array of parameters * @return string|bool Name of controller class name * @access private */ function __loadController($params) { $pluginName = $pluginPath = $controller = null; if (!empty($params['plugin'])) { $this->plugin = $params['plugin']; $pluginName = Inflector::camelize($params['plugin']); $pluginPath = $pluginName . '.'; $this->params['controller'] = $this->plugin; $controller = $pluginName; } if (!empty($params['controller'])) { $controller = Inflector::camelize($params['controller']); } if ($pluginPath . $controller) { if (App::import('Controller', $pluginPath . $controller)) { return $controller; } } return false; } /** * Returns the REQUEST_URI from the server environment, or, failing that, * constructs a new one, using the PHP_SELF constant and other variables. * * @return string URI * @access public */ function uri() { foreach (array('HTTP_X_REWRITE_URL', 'REQUEST_URI', 'argv') as $var) { if ($uri = env($var)) { if ($var == 'argv') { $uri = $uri[0]; } break; } } $base = preg_replace('/^\//', '', '' . Configure::read('App.baseUrl')); if ($base) { $uri = preg_replace('/^(?:\/)?(?:' . preg_quote($base, '/') . ')?(?:url=)?/', '', $uri); } if (PHP_SAPI == 'isapi') { $uri = preg_replace('/^(?:\/)?(?:\/)?(?:\?)?(?:url=)?/', '', $uri); } if (!empty($uri)) { if (key($_GET) && strpos(key($_GET), '?') !== false) { unset($_GET[key($_GET)]); } $uri = preg_split('/\?/', $uri, 2); if (isset($uri[1])) { parse_str($uri[1], $_GET); } $uri = $uri[0]; } elseif (empty($uri) && is_string(env('QUERY_STRING'))) { $uri = env('QUERY_STRING'); } if (strpos($uri, 'index.php') !== false) { list(, $uri) = explode('index.php', $uri, 2); } if (empty($uri) || $uri == '/' || $uri == '//') { return ''; } return str_replace('//', '/', '/' . $uri); } /** * Returns and sets the $_GET[url] derived from the REQUEST_URI * * @param string $uri Request URI * @param string $base Base path * @return string URL * @access public */ function getUrl($uri = null, $base = null) { if (empty($_GET['url'])) { if ($uri == null) { $uri = $this->uri(); } if ($base == null) { $base = $this->base; } $url = null; $tmpUri = preg_replace('/^(?:\?)?(?:\/)?/', '', $uri); $baseDir = preg_replace('/^\//', '', dirname($base)) . '/'; if ($tmpUri === '/' || $tmpUri == $baseDir || $tmpUri == $base) { $url = $_GET['url'] = '/'; } else { if ($base && strpos($uri, $base) !== false) { $elements = explode($base, $uri); } elseif (preg_match('/^[\/\?\/|\/\?|\?\/]/', $uri)) { $elements = array(1 => preg_replace('/^[\/\?\/|\/\?|\?\/]/', '', $uri)); } else { $elements = array(); } if (!empty($elements[1])) { $_GET['url'] = $elements[1]; $url = $elements[1]; } else { $url = $_GET['url'] = '/'; } if (strpos($url, '/') === 0 && $url != '/') { $url = $_GET['url'] = substr($url, 1); } } } else { $url = $_GET['url']; } if ($url{0} == '/') { $url = substr($url, 1); } return $url; } /** * Outputs cached dispatch for js, css, img, view cache * * @param string $url Requested URL * @access public */ function cached($url) { if (strpos($url, 'css/') !== false || strpos($url, 'js/') !== false || strpos($url, 'img/') !== false) { if (strpos($url, 'ccss/') === 0) { include WWW_ROOT . DS . Configure::read('Asset.filter.css'); $this->_stop(); } elseif (strpos($url, 'cjs/') === 0) { include WWW_ROOT . DS . Configure::read('Asset.filter.js'); $this->_stop(); } $isAsset = false; $assets = array('js' => 'text/javascript', 'css' => 'text/css', 'gif' => 'image/gif', 'jpg' => 'image/jpeg', 'png' => 'image/png'); $ext = array_pop(explode('.', $url)); foreach ($assets as $type => $contentType) { if ($type === $ext) { if ($type === 'css' || $type === 'js') { $pos = strpos($url, $type . '/'); } else { $pos = strpos($url, 'img/'); } $isAsset = true; break; } } if ($isAsset === true) { $ob = @ini_get("zlib.output_compression") !== true && extension_loaded("zlib") && (strpos(env('HTTP_ACCEPT_ENCODING'), 'gzip') !== false); if ($ob && Configure::read('Asset.compress')) { ob_start(); ob_start('ob_gzhandler'); } $assetFile = null; $paths = array(); if ($pos > 0) { $plugin = substr($url, 0, $pos - 1); $url = str_replace($plugin . '/', '', $url); $pluginPaths = Configure::read('pluginPaths'); $count = count($pluginPaths); for ($i = 0; $i < $count; $i++) { $paths[] = $pluginPaths[$i] . $plugin . DS . 'vendors' . DS; } } $paths = array_merge($paths, Configure::read('vendorPaths')); foreach ($paths as $path) { if (is_file($path . $url) && file_exists($path . $url)) { $assetFile = $path . $url; break; } } if ($assetFile !== null) { $fileModified = filemtime($assetFile); header("Date: " . date("D, j M Y G:i:s ", $fileModified) . 'GMT'); header('Content-type: ' . $assets[$type]); header("Expires: " . gmdate("D, j M Y H:i:s", time() + DAY) . " GMT"); header("Cache-Control: cache"); header("Pragma: cache"); if ($type === 'css' || $type === 'js') { include($assetFile); } else { readfile($assetFile); } if(Configure::read('Asset.compress')) { ob_end_flush(); } return true; } } } if (Configure::read('Cache.check') === true) { $path = $this->here; if ($this->here == '/') { $path = 'home'; } $path = Inflector::slug($path); $filename = CACHE . 'views' . DS . $path . '.php'; if (!file_exists($filename)) { $filename = CACHE . 'views' . DS . $path . '_index.php'; } if (file_exists($filename)) { if (!class_exists('View')) { App::import('Core', 'View'); } $controller = null; $view =& new View($controller, false); return $view->renderCache($filename, getMicrotime()); } } return false; } } ?>