From 3e7044d4b891b17ceb9696b99bdb5314df79a2a6 Mon Sep 17 00:00:00 2001 From: nate Date: Thu, 16 Aug 2007 05:44:06 +0000 Subject: [PATCH] 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 --- app/config/core.php | 9 +- app/webroot/test.php | 2 +- cake/basics.php | 41 +++++- cake/bootstrap.php | 7 +- .../libs/templates/skel/config/core.php | 4 +- .../libs/templates/skel/webroot/test.php | 2 +- cake/dispatcher.php | 17 ++- cake/libs/configure.php | 16 ++- cake/libs/router.php | 125 +++++++++--------- cake/libs/set.php | 11 +- cake/libs/xml.php | 4 +- cake/tests/cases/dispatcher.test.php | 48 ++++--- cake/tests/cases/libs/model/model.test.php | 3 +- cake/tests/cases/libs/router.test.php | 43 +++++- 14 files changed, 219 insertions(+), 113 deletions(-) diff --git a/app/config/core.php b/app/config/core.php index dad3b1a34..ff1062227 100644 --- a/app/config/core.php +++ b/app/config/core.php @@ -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. diff --git a/app/webroot/test.php b/app/webroot/test.php index 57abdee41..2cc122a79 100644 --- a/app/webroot/test.php +++ b/app/webroot/test.php @@ -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.'); } diff --git a/cake/basics.php b/cake/basics.php index 10121bb84..9b6252c6f 100644 --- a/cake/basics.php +++ b/cake/basics.php @@ -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: diff --git a/cake/bootstrap.php b/cake/bootstrap.php index c7086df1c..bdbbc6529 100644 --- a/cake/bootstrap.php +++ b/cake/bootstrap.php @@ -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 */ diff --git a/cake/console/libs/templates/skel/config/core.php b/cake/console/libs/templates/skel/config/core.php index dad3b1a34..561d40d67 100644 --- a/cake/console/libs/templates/skel/config/core.php +++ b/cake/console/libs/templates/skel/config/core.php @@ -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. diff --git a/cake/console/libs/templates/skel/webroot/test.php b/cake/console/libs/templates/skel/webroot/test.php index f67e4cd9f..7533fd73f 100644 --- a/cake/console/libs/templates/skel/webroot/test.php +++ b/cake/console/libs/templates/skel/webroot/test.php @@ -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.'); } diff --git a/cake/dispatcher.php b/cake/dispatcher.php index 6852bdd43..c2509fcd7 100644 --- a/cake/dispatcher.php +++ b/cake/dispatcher.php @@ -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); } } diff --git a/cake/libs/configure.php b/cake/libs/configure.php index eb3b3e702..13ba1927a 100644 --- a/cake/libs/configure.php +++ b/cake/libs/configure.php @@ -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; diff --git a/cake/libs/router.php b/cake/libs/router.php index 98f5c7ab9..597cd6045 100644 --- a/cake/libs/router.php +++ b/cake/libs/router.php @@ -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); - } -} ?> \ No newline at end of file diff --git a/cake/libs/set.php b/cake/libs/set.php index b9aa2564b..43717c8f6 100644 --- a/cake/libs/set.php +++ b/cake/libs/set.php @@ -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; } diff --git a/cake/libs/xml.php b/cake/libs/xml.php index d7b6b0ee9..5a9efede9 100644 --- a/cake/libs/xml.php +++ b/cake/libs/xml.php @@ -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; } } diff --git a/cake/tests/cases/dispatcher.test.php b/cake/tests/cases/dispatcher.test.php index deee7b337..647d9d41a 100644 --- a/cake/tests/cases/dispatcher.test.php +++ b/cake/tests/cases/dispatcher.test.php @@ -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; } diff --git a/cake/tests/cases/libs/model/model.test.php b/cake/tests/cases/libs/model/model.test.php index 4b07d79ad..80fe5ead8 100644 --- a/cake/tests/cases/libs/model/model.test.php +++ b/cake/tests/cases/libs/model/model.test.php @@ -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() { diff --git a/cake/tests/cases/libs/router.test.php b/cake/tests/cases/libs/router.test.php index ef692b4b9..209debd01 100644 --- a/cake/tests/cases/libs/router.test.php +++ b/cake/tests/cases/libs/router.test.php @@ -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); + } } ?> \ No newline at end of file