cakephp2-php8/cake/dispatcher.php
mark_story 7032729651 Fixing how webroot is calculated for installs not using url rewriting. Also fixes using the top level index.php file.
Test cases added for using both index.php files.
Cleaned up existing tests for environment detection.
Fixes #1261, Fixes #1432
2011-01-23 17:08:09 -05:00

663 lines
18 KiB
PHP

<?php
/**
* Dispatcher takes the URL information, parses it for paramters and
* tells the involved controllers what to do.
*
* This is the heart of Cake's operation.
*
* PHP versions 4 and 5
*
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @package cake
* @subpackage cake.cake
* @since CakePHP(tm) v 0.2.9
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
/**
* List of helpers to include
*/
App::import('Core', 'Router');
App::import('Controller', 'Controller', false);
/**
* 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;
/**
* 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()) {
if ($this->base === false) {
$this->base = $this->baseUrl();
}
if (is_array($url)) {
$url = $this->__extractParams($url, $additionalParams);
} else {
if ($url) {
$_GET['url'] = $url;
}
$url = $this->getUrl();
$this->params = array_merge($this->parseParams($url), $additionalParams);
}
$this->here = $this->base . '/' . $url;
if ($this->asset($url) || $this->cached($url)) {
return;
}
$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 = $this->params['action'][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'], '_') > 0) {
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 = isset($this->params['plugin']) ? $this->params['plugin'] : null;
$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 (isset($this->params['return']) && $this->params['return'] == 1) {
$controller->autoRender = false;
}
if (!empty($this->params['bare'])) {
$controller->autoLayout = false;
}
return $this->_invoke($controller, $this->params);
}
/**
* Initializes the components and models a controller will be using.
* Triggers the controller action, and invokes the rendering if Controller::$autoRender is true and echo's the output.
* Otherwise the return value of the controller action are returned.
*
* @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->startupProcess();
$methods = array_flip($controller->methods);
if (!isset($methods[strtolower($params['action'])])) {
if ($controller->scaffold !== false) {
App::import('Controller', 'Scaffold', false);
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 = call_user_func_array(array(&$controller, $params['action']), $params['pass']);
if ($controller->autoRender) {
$controller->output = $controller->render();
} elseif (empty($controller->output)) {
$controller->output = $output;
}
$controller->shutdownProcess();
if (isset($params['return'])) {
return $controller->output;
}
echo($controller->output);
}
/**
* Sets the params when $url is passed as an array to Object::requestAction();
* Merges the $url and $additionalParams and creates a string url.
*
* @param array $url Array or request parameters
* @param array $additionalParams Array of additional parameters.
* @return string $url The generated url string.
* @access private
*/
function __extractParams($url, $additionalParams = array()) {
$defaults = array('pass' => array(), 'named' => array(), 'form' => array());
$params = array_merge($defaults, $url, $additionalParams);
$this->params = $params;
$params += array('base' => false, 'url' => array());
return ltrim(Router::reverse($params), '/');
}
/**
* 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 (!empty($_SERVER)) {
$_SERVER['REQUEST_METHOD'] = $params['form']['_method'];
} else {
$_ENV['REQUEST_METHOD'] = $params['form']['_method'];
}
unset($params['form']['_method']);
}
}
$namedExpressions = Router::getNamedExpressions();
extract($namedExpressions);
include CONFIGS . 'routes.php';
$params = array_merge(array('controller' => '', 'action' => ''), Router::parse($fromUrl), $params);
if (empty($params['action'])) {
$params['action'] = 'index';
}
if (isset($params['form']['data'])) {
$params['data'] = $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) {
if (is_array($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;
}
}
} else {
$params['data'][$model][$key] = $fields;
}
}
}
}
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) {
$replace = array('<', '>', '*', '\'', '"');
$base = str_replace($replace, '', 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 = '/' . basename($baseUrl);
$base = dirname($baseUrl);
if ($base === DS || $base === '.') {
$base = '';
}
$this->webroot = $base . '/';
$docRoot = env('DOCUMENT_ROOT');
$script = realpath(env('SCRIPT_FILENAME'));
$docRootContainsWebroot = strpos($docRoot, $dir . '/' . $webroot);
if (!empty($base) || !$docRootContainsWebroot) {
if (strpos($this->webroot, $dir) === false) {
$this->webroot .= $dir . '/' ;
}
if (strpos($this->webroot, $webroot) === false) {
$this->webroot .= $webroot . '/';
}
}
return $base . $file;
}
/**
* 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() {
$controller = false;
$ctrlClass = $this->__loadController($this->params);
if (!$ctrlClass) {
return $controller;
}
$ctrlClass .= 'Controller';
if (class_exists($ctrlClass)) {
$controller =& new $ctrlClass();
}
return $controller;
}
/**
* Load controller and return controller classname
*
* @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'])) {
$pluginName = $controller = Inflector::camelize($params['plugin']);
$pluginPath = $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 = explode('?', $uri, 2);
if (isset($uri[1])) {
parse_str($uri[1], $_GET);
}
$uri = $uri[0];
} else {
$uri = env('QUERY_STRING');
}
if (is_string($uri) && 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) === 0) {
$elements = explode($base, $uri, 2);
} 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 view cache
*
* @param string $url Requested URL
* @access public
*/
function cached($url) {
if (Configure::read('Cache.check') === true) {
$path = $this->here;
if ($this->here == '/') {
$path = 'home';
}
$path = strtolower(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('View', 'View', false);
}
$controller = null;
$view =& new View($controller);
$return = $view->renderCache($filename, getMicrotime());
if (!$return) {
ClassRegistry::removeObject('view');
}
return $return;
}
}
return false;
}
/**
* Checks if a requested asset exists and sends it to the browser
*
* @param $url string $url Requested URL
* @return boolean True on success if the asset file was found and sent
* @access public
*/
function asset($url) {
if (strpos($url, '..') !== false || strpos($url, '.') === false) {
return false;
}
$filters = Configure::read('Asset.filter');
$isCss = (
strpos($url, 'ccss/') === 0 ||
preg_match('#^(theme/([^/]+)/ccss/)|(([^/]+)(?<!css)/ccss)/#i', $url)
);
$isJs = (
strpos($url, 'cjs/') === 0 ||
preg_match('#^/((theme/[^/]+)/cjs/)|(([^/]+)(?<!js)/cjs)/#i', $url)
);
if (($isCss && empty($filters['css'])) || ($isJs && empty($filters['js']))) {
header('HTTP/1.1 404 Not Found');
return $this->_stop();
} elseif ($isCss) {
include WWW_ROOT . DS . $filters['css'];
$this->_stop();
} elseif ($isJs) {
include WWW_ROOT . DS . $filters['js'];
$this->_stop();
}
$controller = null;
$ext = array_pop(explode('.', $url));
$parts = explode('/', $url);
$assetFile = null;
if ($parts[0] === 'theme') {
$themeName = $parts[1];
unset($parts[0], $parts[1]);
$fileFragment = implode(DS, $parts);
$path = App::themePath($themeName) . 'webroot' . DS;
if (file_exists($path . $fileFragment)) {
$assetFile = $path . $fileFragment;
}
} else {
$plugin = $parts[0];
unset($parts[0]);
$fileFragment = implode(DS, $parts);
$pluginWebroot = App::pluginPath($plugin) . 'webroot' . DS;
if (file_exists($pluginWebroot . $fileFragment)) {
$assetFile = $pluginWebroot . $fileFragment;
}
}
if ($assetFile !== null) {
$this->_deliverAsset($assetFile, $ext);
return true;
}
return false;
}
/**
* Sends an asset file to the client
*
* @param string $assetFile Path to the asset file in the file system
* @param string $ext The extension of the file to determine its mime type
* @return void
* @access protected
*/
function _deliverAsset($assetFile, $ext) {
$ob = @ini_get("zlib.output_compression") !== '1' && extension_loaded("zlib") && (strpos(env('HTTP_ACCEPT_ENCODING'), 'gzip') !== false);
$compressionEnabled = $ob && Configure::read('Asset.compress');
if ($compressionEnabled) {
ob_start();
ob_start('ob_gzhandler');
}
App::import('View', 'Media', false);
$controller = null;
$Media = new MediaView($controller);
if (isset($Media->mimeType[$ext])) {
$contentType = $Media->mimeType[$ext];
} else {
$contentType = 'application/octet-stream';
$agent = env('HTTP_USER_AGENT');
if (preg_match('%Opera(/| )([0-9].[0-9]{1,2})%', $agent) || preg_match('/MSIE ([0-9].[0-9]{1,2})/', $agent)) {
$contentType = 'application/octetstream';
}
}
header("Date: " . date("D, j M Y G:i:s ", filemtime($assetFile)) . 'GMT');
header('Content-type: ' . $contentType);
header("Expires: " . gmdate("D, j M Y H:i:s", time() + DAY) . " GMT");
header("Cache-Control: cache");
header("Pragma: cache");
if ($ext === 'css' || $ext === 'js') {
include($assetFile);
} else {
if ($compressionEnabled) {
ob_clean();
}
readfile($assetFile);
}
if ($compressionEnabled) {
ob_end_flush();
}
}
}