Implementing prefix-based routing, and transitioning configuration away from global constants

git-svn-id: https://svn.cakephp.org/repo/branches/1.2.x.x@5531 3807eeeb-6ff5-0310-8944-8be069107fe0
This commit is contained in:
nate 2007-08-16 05:44:06 +00:00
parent 82b263a8aa
commit 3e7044d4b8
14 changed files with 219 additions and 113 deletions

View file

@ -54,7 +54,7 @@
* In production mode, flash messages redirect after a time interval.
* In development mode, you need to click the flash message to continue.
*/
define('DEBUG', 2);
Configure::write('debug', 2);
/**
* Turn off or enable cache checking application-wide.
*
@ -126,11 +126,14 @@
* 'admin' -> admin_index() and /admin/controller/index
* 'superuser' -> superuser_index() and /superuser/controller/index
*/
// define('CAKE_ADMIN', 'admin');
// Configure::write('Routing.admin', 'admin');
/**
* Enable or disable CakePHP webservices routing. Set to 'off' or 'on'.
*
* @deprecated
* @see Router::parseExtensions()
*/
define('WEBSERVICES', 'off');
Configure::write('Routing.webservices', 'off');
/**
* Compress CSS output by removing comments, whitespace, repeating tags, etc.
* This requires a/var/cache directory to be writable by the web server for caching.

View file

@ -63,7 +63,7 @@ require_once CORE_PATH . 'cake' . DS . 'bootstrap.php';
require_once CAKE . 'basics.php';
require_once CAKE . 'config' . DS . 'paths.php';
require_once CAKE . 'tests' . DS . 'lib' . DS . 'test_manager.php';
if (DEBUG < 1) {
if (Configure::read('debug') < 1) {
die('Invalid url.');
}

View file

@ -742,7 +742,7 @@
/**
* Prints out debug information about given variable.
*
* Only runs if DEBUG level is non-zero.
* Only runs if debug level is non-zero.
*
* @param boolean $var Variable to show debug information for.
* @param boolean $show_html If set to true, the method prints the debug data in a screen-friendly way.
@ -1550,6 +1550,45 @@
return false;
}
}
/**
* Implements http_build_query for PHP4.
*
* @param string $data Data to set in query string
* @param string $prefix If numeric indices, prepend this to index for elements in base array.
* @param string $argSep String used to separate arguments
* @param string $baseKey Base key
* @return string URL encoded query string
* @see http://php.net/http_build_query
*/
if (!function_exists('http_build_query')) {
function http_build_query($data, $prefix = null, $argSep = null, $baseKey = null) {
if (empty($argSep)) {
$argSep = ini_get('arg_separator.output');
}
if (is_object($data)) {
$data = get_object_vars($data);
}
$out = array();
foreach ((array)$data as $key => $v) {
if (is_numeric($key) && !empty($prefix)) {
$key = $prefix . $key;
}
$key = urlencode($key);
if (!empty($baseKey)) {
$key = $baseKey . '[' . $key . ']';
}
if (is_array($v) || is_object($v)) {
$out[] = http_build_query($v, $prefix, $argSep, $key);
} else {
$out[] = $key . '=' . urlencode($v);
}
}
return implode($argSep, $out);
}
}
/**
* Wraps ternary operations. If $condition is a non-empty value, $val1 is returned, otherwise $val2.
* Don't use for isset() conditions, or wrap your variable with @ operator:

View file

@ -35,10 +35,10 @@ if (!defined('PHP5')) {
if (!isset($bootstrap)) {
require CORE_PATH . 'cake' . DS . 'basics.php';
$TIME_START = getMicrotime();
require APP_PATH . 'config' . DS . 'core.php';
require CORE_PATH . 'cake' . DS . 'config' . DS . 'paths.php';
require LIBS . 'object.php';
require LIBS . 'configure.php';
require APP_PATH . 'config' . DS . 'core.php';
}
require LIBS . 'cache.php';
require LIBS . 'session.php';
@ -62,7 +62,10 @@ if (!defined('PHP5')) {
Configure::store(null, 'class.paths');
Configure::load('class.paths');
Configure::write('debug', DEBUG);
if (defined('DEBUG')) {
Configure::write('debug', DEBUG);
}
/**
* Check for IIS Server
*/

View file

@ -54,7 +54,7 @@
* In production mode, flash messages redirect after a time interval.
* In development mode, you need to click the flash message to continue.
*/
define('DEBUG', 2);
Configure::write('debug', 2);
/**
* Turn off or enable cache checking application-wide.
*
@ -130,7 +130,7 @@
/**
* Enable or disable CakePHP webservices routing. Set to 'off' or 'on'.
*/
define('WEBSERVICES', 'off');
Configure::write('Routing.webservices', 'off');
/**
* Compress CSS output by removing comments, whitespace, repeating tags, etc.
* This requires a/var/cache directory to be writable by the web server for caching.

View file

@ -63,7 +63,7 @@ require_once CORE_PATH . 'cake' . DS . 'bootstrap.php';
require_once CAKE . 'basics.php';
require_once CAKE . 'config' . DS . 'paths.php';
require_once CAKE . 'tests' . DS . 'lib' . DS . 'test_manager.php';
if (DEBUG < 1) {
if (Configure::read('debug') < 1) {
die('Invalid url.');
}

View file

@ -123,12 +123,10 @@ class Dispatcher extends Object {
if ($url !== null) {
$_GET['url'] = $url;
}
$url = $this->getUrl();
$this->here = $this->base . '/' . $url;
$this->cached($url);
$this->params = array_merge($this->parseParams($url), $additionalParams);
$controller = $this->__getController();
@ -158,12 +156,13 @@ class Dispatcher extends Object {
$this->params['action'] = 'index';
}
if (defined('CAKE_ADMIN')) {
$this->admin = CAKE_ADMIN ;
if (isset($this->params[$this->admin])) {
$this->params['action'] = $this->admin.'_'.$this->params['action'];
} elseif (strpos($this->params['action'], $this->admin) === 0) {
$privateAction = true;
$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) {
list($prefix, $action) = explode('_', $this->params['action']);
$privateAction = in_array($prefix, $prefixes);
}
}

View file

@ -238,7 +238,11 @@ class Configure extends Object {
$_this =& Configure::getInstance();
if ($var === 'debug') {
if (!isset($_this->debug)) {
$_this->debug = DEBUG;
if (defined('DEBUG')) {
$_this->debug = DEBUG;
} else {
$_this->debug = 0;
}
}
return $_this->debug;
}
@ -522,7 +526,15 @@ class Configure extends Object {
if (defined('BASE_URL')) {
$baseUrl = BASE_URL;
}
$_this->write('App', array('base'=> false, 'baseUrl'=> $baseUrl, 'dir'=> APP_DIR, 'webroot'=> WEBROOT_DIR));
$_this->write('App', array('base' => false, 'baseUrl' => $baseUrl, 'dir' => APP_DIR, 'webroot' => WEBROOT_DIR));
if (defined('CAKE_ADMIN')) {
$_this->write('Routing.admin', CAKE_ADMIN);
}
if (defined('WEBSERVICES')) {
$_this->write('Routing.webservices', WEBSERVICES);
}
$modelPaths = null;
$viewPaths = null;
$controllerPaths = null;

View file

@ -56,6 +56,13 @@ class Router extends Object {
* @access private
*/
var $__admin = null;
/**
* List of action prefixes used in connected routes
*
* @var array
* @access private
*/
var $__prefixes = array();
/**
* Directive for Router to parse out file extensions for mapping to Content-types.
*
@ -209,6 +216,10 @@ class Router extends Object {
if (!empty($default) && empty($default['action'])) {
$default['action'] = 'index';
}
if (isset($default['prefix'])) {
$_this->__prefixes[] = $default['prefix'];
$_this->__prefixes = array_unique($_this->__prefixes);
}
if ($route = $_this->writeRoute($route, $default, $params)) {
$_this->routes[] = $route;
}
@ -284,6 +295,17 @@ class Router extends Object {
return array($route, '#^' . join('', $parsed) . '[\/]*$#', $names, $default, $params);
}
}
/**
* Returns the list of prefixes used in connected routes
*
* @return array A list of prefixes used in connected routes
* @access public
* @static
*/
function prefixes() {
$_this =& Router::getInstance();
return $_this->__prefixes;
}
/**
* Parses given URL and returns an array of controllers, action and parameters
* taken from that URL.
@ -419,15 +441,21 @@ class Router extends Object {
function __connectDefaultRoutes() {
$_this =& Router::getInstance();
if (defined('CAKE_ADMIN') && $_this->__admin != null) {
$_this->routes[] = $_this->__admin;
$_this->__admin = null;
if ($admin = Configure::read('Routing.admin')) {
$params = array('prefix' => $admin, $admin => true);
$_this->connect("/{$admin}/:controller", $params);
$_this->connect("/{$admin}/:controller/:action/*", $params);
}
$_this->connect('/:controller', array('action' => 'index'));
/**
* Deprecated
*
*/
$_this->connect('/bare/:controller/:action/*', array('bare' => '1'));
$_this->connect('/ajax/:controller/:action/*', array('bare' => '1'));
if (defined('WEBSERVICES') && WEBSERVICES == 'on') {
if (Configure::read('Routing.webservices') == 'on') {
trigger_error('Deprecated: webservices routes are deprecated and will not be supported in future versions. Use Router::parseExtensions() instead.', E_USER_WARNING);
$_this->connect('/rest/:controller/:action/*', array('webservices' => 'Rest'));
$_this->connect('/rss/:controller/:action/*', array('webservices' => 'Rss'));
@ -441,9 +469,14 @@ class Router extends Object {
$plugins = array_map(array(&$Inflector, 'underscore'), Configure::listObjects('plugin'));
if(!empty($plugins)) {
$match = array('plugin' => implode('|', $plugins));
$_this->connect('/:plugin/:controller/:action/*', array('action' => 'index'), array('plugin' => implode('|', $plugins)));
array_unshift($_this->routes, $_this->routes[count($_this->routes) - 1]);
unset($_this->routes[count($_this->routes) - 1]);
$_this->promote();
if ($admin) {
$_this->connect("/{$admin}/:plugin/:controller/:action/*", am($params, array('action' => 'index')), $match);
$_this->promote();
}
}
}
/**
@ -519,6 +552,27 @@ class Router extends Object {
$_this->{$key} = $val;
}
}
/**
* Promote a route (by default, the last one added) to the beginning of the list
*
* @param $which A zero-based array index representing the route to move. For example,
* if 3 routes have been added, the last route would be 2.
* @return boolean Retuns false if no route exists at the position specified by $which.
* @access public
* @static
*/
function promote($which = null) {
$_this =& Router::getInstance();
if ($which == null) {
$which = count($_this->routes) - 1;
}
if (!isset($_this->routes[$which])) {
return false;
}
array_unshift($_this->routes, $_this->routes[$which]);
unset($_this->routes[$which]);
return true;
}
/**
* Finds URL for specified action.
*
@ -544,8 +598,6 @@ class Router extends Object {
if (!empty($_this->__params)) {
if (isset($this) && !isset($this->params['requested'])) {
$params = $_this->__params[0];
} elseif (isset($this) && isset($this->params['requested'])) {
$params = end($_this->__params);
} else {
$params = end($_this->__params);
}
@ -693,6 +745,8 @@ class Router extends Object {
*/
function mapRouteElements($route, $url) {
$_this =& Router::getInstance();
unset($route[3]['prefix']);
$defaults = $route[3];
$params = Set::diff($url, $defaults);
$routeParams = $route[2];
@ -708,9 +762,6 @@ class Router extends Object {
if (!strpos($route[0], '*') && !empty($pass)) {
return false;
}
if (defined('CAKE_ADMIN') && isset($params[CAKE_ADMIN])) {
return false;
}
if (!empty($params)) {
$required = array_diff(array_keys($defaults), array_keys($url));
}
@ -725,19 +776,11 @@ class Router extends Object {
return false;
}
if (isset($_this->testing)) {
pr($defaults);
pr($url);
pr($params);
pr($required);
}
foreach ($defaults as $key => $val) {
if ($url[$key] != $val && !in_array($key, $routeParams)) {
return false;
}
}
$filled = array_intersect_key($url, array_combine($routeParams, array_keys($routeParams)));
if (array_keys($filled) != $routeParams) {
@ -750,10 +793,6 @@ class Router extends Object {
return false;
}
if (!empty($route[3]['controller']) && $url['controller'] != $route[3]['controller']) {
return false;
}
if (!empty($route[4])) {
foreach ($route[4] as $key => $reg) {
if (isset($url[$key]) && !preg_match('/' . $reg . '/', $url[$key])) {
@ -761,7 +800,6 @@ class Router extends Object {
}
}
}
return array(Router::__mapRoute($route, am($url, compact('pass'))), $url);
}
/**
@ -980,43 +1018,4 @@ class Router extends Object {
}
}
if (!function_exists('http_build_query')) {
/**
* Implements http_build_query for PHP4.
*
* @param string $data Data to set in query string
* @param string $prefix If numeric indices, prepend this to index for elements in base array.
* @param string $argSep String used to separate arguments
* @param string $baseKey Base key
* @return string URL encoded query string
* @see http://php.net/http_build_query
*/
function http_build_query($data, $prefix = null, $argSep = null, $baseKey = null) {
if (empty($argSep)) {
$argSep = ini_get('arg_separator.output');
}
if (is_object($data)) {
$data = get_object_vars($data);
}
$out = array();
foreach ((array)$data as $key => $v) {
if (is_numeric($key) && !empty($prefix)) {
$key = $prefix . $key;
}
$key = urlencode($key);
if (!empty($baseKey)) {
$key = $baseKey . '[' . $key . ']';
}
if (is_array($v) || is_object($v)) {
$out[] = http_build_query($v, $prefix, $argSep, $key);
} else {
$out[] = $key . '=' . urlencode($v);
}
}
return implode($argSep, $out);
}
}
?>

View file

@ -518,22 +518,31 @@ class Set extends Object {
$val2 = $val1;
$val1 = $this->get();
}
if (is_object($val2) && (is_a($val2, 'set') || is_a($val2, 'Set'))) {
$val2 = $val2->get();
}
$out = array();
if (empty($val1)) {
return (array)$val2;
} elseif (empty($val2)) {
return (array)$val1;
}
foreach ($val1 as $key => $val) {
if (isset($val2[$key]) && $val2[$key] != $val) {
$out[$key] = $val;
} elseif (!array_key_exists($key, $val2)) {
$out[$key] = $val;
}
unset($val2[$key]);
}
foreach ($val2 as $key => $val) {
if (!isset($out[$key])) {
$out[$key] = $val;
}
}
return $out;
}

View file

@ -645,7 +645,7 @@ class XML extends XMLNode {
return $data;
}
/**
* If DEBUG is on, this method echoes an error message.
* If debug mode is on, this method echoes an error message.
*
* @param string $msg Error message
* @param int $code Error code
@ -653,7 +653,7 @@ class XML extends XMLNode {
* @access public
*/
function error($msg, $code = 0, $line = 0) {
if (DEBUG) {
if (Configure::read('debug')) {
echo $msg . " " . $code . " " . $line;
}
}

View file

@ -389,7 +389,7 @@ class DispatcherTest extends UnitTestCase {
Configure::write('App.baseUrl','/index.php');
$url = 'some_controller/home/param:value/param2:value2';
restore_error_handler();
$controller = $dispatcher->dispatch($url, array('return'=> 1));
$controller = $dispatcher->dispatch($url, array('return' => 1));
set_error_handler('simpleTestErrorHandler');
$expected = 'missingController';
@ -402,7 +402,7 @@ class DispatcherTest extends UnitTestCase {
$url = 'some_pages/redirect/param:value/param2:value2';
restore_error_handler();
@$controller = $dispatcher->dispatch($url, array('return'=> 1));
@$controller = $dispatcher->dispatch($url, array('return' => 1));
set_error_handler('simpleTestErrorHandler');
$expected = 'privateAction';
@ -433,43 +433,30 @@ class DispatcherTest extends UnitTestCase {
$expected = 'Pages';
$this->assertEqual($expected, $controller->name);
$expected = array('param'=>'value', 'param2'=>'value2');
$expected = array('param'=>'value', 'param2' => 'value2');
$this->assertIdentical($expected, $controller->namedArgs);
}
function testAdminDispatch() {
$_POST = array();
if (!defined('CAKE_ADMIN')) {
define('CAKE_ADMIN', 'admin');
}
$dispatcher =& new TestDispatcher();
Configure::write('Routing.admin', 'admin');
Configure::write('App.baseUrl','/cake/repo/branches/1.2.x.x/index.php');
$url = 'admin/test_dispatch_pages/index/param:value/param2:value2';
Router::reload();
$Router =& Router::getInstance();
if (defined('CAKE_ADMIN')) {
$admin = CAKE_ADMIN;
if (!empty($admin)) {
$Router->__admin = array(
'/:' . $admin . '/:controller/:action/*',
'/^(?:\/(?:(' . $admin . ')(?:\\/([a-zA-Z0-9_\\-\\.\\;\\:]+)(?:\\/([a-zA-Z0-9_\\-\\.\\;\\:]+)(?:[\\/\\?](.*))?)?)?))[\/]*$/',
array($admin, 'controller', 'action'), array()
);
}
}
restore_error_handler();
@$controller = $dispatcher->dispatch($url, array('return'=> 1));
@$controller = $dispatcher->dispatch($url, array('return' => 1));
set_error_handler('simpleTestErrorHandler');
$expected = 'TestDispatchPages';
$this->assertEqual($expected, $controller->name);
$expected = array('param'=>'value', 'param2'=>'value2');
$expected = array('param' => 'value', 'param2' => 'value2');
$this->assertIdentical($expected, $controller->namedArgs);
$expected = 'admin';
$this->assertIdentical($expected, $controller->params['admin']);
$this->assertTrue($controller->params['admin']);
$expected = '/cake/repo/branches/1.2.x.x/index.php/admin/test_dispatch_pages/index/param:value/param2:value2';
$this->assertIdentical($expected, $controller->here);
@ -597,6 +584,25 @@ class DispatcherTest extends UnitTestCase {
$this->assertIdentical($expected, $controller);
}
function testPrefixProtection() {
$_POST = array();
$_SERVER['PHP_SELF'] = '/cake/repo/branches/1.2.x.x/index.php';
Router::reload();
Router::connect('/admin/:controller/:action/*', array('prefix'=>'admin'), array('controller', 'action'));
$dispatcher =& new TestDispatcher();
$dispatcher->base = false;
$url = 'test_dispatch_pages/admin_index/param:value/param2:value2';
restore_error_handler();
@$controller = $dispatcher->dispatch($url, array('return' => 1));
set_error_handler('simpleTestErrorHandler');
$expected = 'privateAction';
$this->assertIdentical($expected, $controller);
}
function tearDown() {
$_GET = $this->_get;
}

View file

@ -315,12 +315,13 @@ class ModelTest extends CakeTestCase {
function start() {
parent::start();
$this->debug = Configure::read('debug');
Configure::write('debug', 2);
}
function end() {
parent::end();
Configure::write('debug', DEBUG);
Configure::write('debug', $this->debug);
}
function testFindAllRecursiveSelfJoin() {

View file

@ -376,8 +376,13 @@ class RouterTest extends UnitTestCase {
}
function testAdminRouting() {
$out = $this->router->url(array(CAKE_ADMIN => true, 'controller' => 'posts', 'action'=>'index', '0', '?' => 'var=test&var2=test2'));
$expected = '/' . CAKE_ADMIN . '/posts/index/0?var=test&var2=test2';
Configure::write('Routing.admin', 'admin');
$this->router->reload();
$this->router->parse('/');
$this->router->testing = true;
$out = $this->router->url(array('admin' => true, 'controller' => 'posts', 'action' => 'index', '0', '?' => 'var=test&var2=test2'));
$expected = '/admin/posts/index/0?var=test&var2=test2';
$this->assertEqual($out, $expected);
}
@ -467,11 +472,11 @@ class RouterTest extends UnitTestCase {
Router::connect('/pages/*', array('controller' => 'pages', 'action' => 'display'));
$result = $this->router->parse('/');
$expected = array('pass'=>array('home'), 'plugin'=> null, 'controller'=>'pages', 'action'=>'display');
$expected = array('pass'=>array('home'), 'plugin' => null, 'controller' => 'pages', 'action' => 'display');
$this->assertEqual($result, $expected);
$result = $this->router->parse('/pages/home/');
$expected = array('pass'=>array('home'), 'plugin'=> null, 'controller'=>'pages', 'action'=>'display');
$expected = array('pass' => array('home'), 'plugin' => null, 'controller' => 'pages', 'action' => 'display');
$this->assertEqual($result, $expected);
$this->router->routes = array();
@ -482,6 +487,36 @@ class RouterTest extends UnitTestCase {
$expected = array('pass'=>array('contact'), 'plugin'=> null, 'controller'=>'pages', 'action'=>'display');
$this->assertEqual($result, $expected);
}
function testParsingWithPrefixes() {
$this->router->reload();
$this->router->testing = true;
$adminParams = array('prefix' => 'admin', 'admin' => true);
$this->router->connect('/admin/:controller', $adminParams);
$this->router->connect('/admin/:controller/:action', $adminParams);
$this->router->connect('/admin/:controller/:action/*', $adminParams);
$this->router->setRequestInfo(array(
array('controller' => 'controller', 'action' => 'index', 'form' => array(), 'url' => array (), 'bare' => 0, 'webservices' => null, 'plugin' => null),
array ('base' => '/base', 'here' => '/', 'webroot' => '/base/', 'passedArgs' => array (), 'argSeparator' => ':', 'namedArgs' => array (), 'webservices' => null)
));
$result = $this->router->parse('/admin/posts/');
$expected = array('pass' => array(), 'prefix' => 'admin', 'plugin' => null, 'controller' => 'posts', 'action' => 'index', 'admin' => true);
$this->assertEqual($result, $expected);
$result = $this->router->parse('/admin/posts');
$this->assertEqual($result, $expected);
$result = $this->router->url(array('admin' => true, 'controller' => 'posts'));
$expected = '/base/admin/posts';
$this->assertEqual($result, $expected);
$result = $this->router->prefixes();
$expected = array('admin');
$this->assertEqual($result, $expected);
unset($this->router->testing);
}
}
?>