diff --git a/cake/dispatcher.php b/cake/dispatcher.php index d04826593..da301e473 100644 --- a/cake/dispatcher.php +++ b/cake/dispatcher.php @@ -366,29 +366,6 @@ class Dispatcher extends Object { } return $base . $file; } -/** - * 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 If true all the params are shifted one forward, so plugin becomes - * controller, controller becomes action etc. If false, plugin is made equal to controller - * @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']) - )); - } else { - $params['plugin'] = $params['controller']; - } - return $params; - } /** * Get controller to use, either plugin controller or application controller @@ -398,32 +375,14 @@ class Dispatcher extends Object { * @access private */ function &__getController() { - $original = $params = $this->params; - $controller = false; - $ctrlClass = $this->__loadController($params); + $ctrlClass = $this->__loadController($this->params); if (!$ctrlClass) { - if (!isset($params['plugin'])) { - $params = $this->_restructureParams($params); - } else { - $params = $this->_restructureParams($params, true); - } - $ctrlClass = $this->__loadController($params); - if (!$ctrlClass) { - $this->params = $original; - return $controller; - } + return $controller; } $name = $ctrlClass; $ctrlClass .= 'Controller'; if (class_exists($ctrlClass)) { - if ( - empty($params['plugin']) && - strtolower(get_parent_class($ctrlClass)) === strtolower($name . 'AppController') - ) { - $params = $this->_restructureParams($params); - } - $this->params = $params; $controller =& new $ctrlClass(); } return $controller; diff --git a/cake/libs/router.php b/cake/libs/router.php index 0c988ac35..2239d59e5 100644 --- a/cake/libs/router.php +++ b/cake/libs/router.php @@ -540,17 +540,29 @@ class Router { * Connects the default, built-in routes, including prefix and plugin routes. The following routes are created * in the order below: * + * For each of the Routing.prefixes the following routes are created. Routes containing `:plugin` are only + * created when your application has one or more plugins. + * + * - `/:prefix/:plugin` a plugin shortcut route. + * - `/:prefix/:plugin/:action/*` a plugin shortcut route. * - `/:prefix/:plugin/:controller` * - `/:prefix/:plugin/:controller/:action/*` * - `/:prefix/:controller` * - `/:prefix/:controller/:action/*` + * + * If plugins are found in your application the following routes are created: + * + * - `/:plugin` a plugin shortcut route. + * - `/:plugin/:action/*` a plugin shortcut route. * - `/:plugin/:controller` * - `/:plugin/:controller/:action/*` + * + * And lastly the following catch-all routes are connected. + * * - `/:controller' * - `/:controller/:action/*' * - * A prefix route is generated for each Routing.prefixes declared in core.php. You can disable the - * connection of default routes with Router::defaults(). + * You can disable the connection of default routes with Router::defaults(). * * @return void * @access private @@ -560,7 +572,8 @@ class Router { foreach ($plugins as $key => $value) { $plugins[$key] = Inflector::underscore($value); } - $match = array('plugin' => implode('|', $plugins)); + $pluginPattern = implode('|', $plugins); + $match = array('plugin' => $pluginPattern); foreach ($this->__prefixes as $prefix) { $params = array('prefix' => $prefix, $prefix => true); @@ -568,6 +581,8 @@ class Router { $this->connect("/{$prefix}/:plugin/:controller", $indexParams, $match); $this->connect("/{$prefix}/:plugin/:controller/:action/*", $params, $match); } + $shortParams = array('routeClass' => 'PluginShortRoute', 'plugin' => $pluginPattern); + $this->connect('/:plugin', array('action' => 'index'), $shortParams); $this->connect('/:plugin/:controller', array('action' => 'index'), $match); $this->connect('/:plugin/:controller/:action/*', array(), $match); } @@ -907,7 +922,7 @@ class Router { $urlOut = array_filter(array($url['controller'], $url['action'])); - if (isset($url['plugin']) && $url['plugin'] != $url['controller']) { + if (isset($url['plugin'])) { array_unshift($urlOut, $url['plugin']); } @@ -1411,6 +1426,7 @@ class CakeRoute { $route['pass'] = $route['named'] = array(); $route += $this->defaults; + //move numerically indexed elements from the defaults into pass. foreach ($route as $key => $value) { if (is_integer($key)) { $route['pass'][] = $value; @@ -1531,10 +1547,6 @@ class CakeRoute { * @access protected */ function _writeUrl($params) { - if (isset($params['plugin'], $params['controller']) && $params['plugin'] === $params['controller']) { - unset($params['controller']); - } - if (isset($params['prefix'], $params['action'])) { $params['action'] = str_replace($params['prefix'] . '_', '', $params['action']); unset($params['prefix']); @@ -1576,4 +1588,46 @@ class CakeRoute { return $out; } } + +/** + * Plugin short route, that copies the plugin param to the controller parameters + * It is used for supporting /:plugin routes. + * + * @package cake.libs + */ +class PluginShortRoute extends CakeRoute { +/** + * Parses a string url into an array. If a plugin key is found, it will be copied to the + * controller parameter + * + * @param string $url The url to parse + * @return mixed false on failure, or an array of request parameters + */ + function parse($url) { + $params = parent::parse($url); + if (!$params) { + return false; + } + $params['controller'] = $params['plugin']; + return $params; + } + +/** + * Reverse route plugin shortcut urls. If the plugin and controller + * are not the same the match is an auto fail. + * + * @param array $url Array of parameters to convert to a string. + * @return mixed either false or a string url. + */ + function match($url) { + if (isset($url['controller']) && isset($url['plugin']) && $url['plugin'] != $url['controller']) { + return false; + } + $this->defaults['controller'] = $url['controller']; + $result = parent::match($url); + unset($this->defaults['controller']); + return $result; + } +} + ?> \ No newline at end of file diff --git a/cake/tests/cases/dispatcher.test.php b/cake/tests/cases/dispatcher.test.php index c737d0e53..03128d4c3 100644 --- a/cake/tests/cases/dispatcher.test.php +++ b/cake/tests/cases/dispatcher.test.php @@ -340,6 +340,14 @@ class ArticlesTestController extends ArticlesTestAppController { function admin_index() { return true; } +/** + * fake index method. + * + * @return void + */ + function index() { + return true; + } } /** @@ -1397,11 +1405,9 @@ class DispatcherTest extends CakeTestCase { $Router =& Router::getInstance(); $controller = $Dispatcher->dispatch($url, array('return' => 1)); - $expected = 'TestDispatchPages'; - $this->assertEqual($expected, $controller->name); + $this->assertEqual($controller->name, 'TestDispatchPages'); - $expected = array('param' => 'value', 'param2' => 'value2'); - $this->assertIdentical($expected, $controller->passedArgs); + $this->assertIdentical($controller->passedArgs, array('param' => 'value', 'param2' => 'value2')); $this->assertTrue($controller->params['admin']); $expected = '/cake/repo/branches/1.2.x.x/index.php/admin/test_dispatch_pages/index/param:value/param2:value2'; @@ -1423,7 +1429,10 @@ class DispatcherTest extends CakeTestCase { Router::reload(); $Dispatcher =& new TestDispatcher(); - Router::connect('/my_plugin/:controller/*', array('plugin'=>'my_plugin', 'controller'=>'pages', 'action'=>'display')); + Router::connect( + '/my_plugin/:controller/*', + array('plugin' => 'my_plugin', 'controller' => 'pages', 'action' => 'display') + ); $Dispatcher->base = false; $url = 'my_plugin/some_pages/home/param:value/param2:value2'; @@ -1441,17 +1450,10 @@ class DispatcherTest extends CakeTestCase { $this->assertEqual($expected, $result); - $expected = 'my_plugin'; - $this->assertIdentical($expected, $controller->plugin); - - $expected = 'SomePages'; - $this->assertIdentical($expected, $controller->name); - - $expected = 'some_pages'; - $this->assertIdentical($expected, $controller->params['controller']); - - $expected = array('0' => 'home', 'param'=>'value', 'param2'=>'value2'); - $this->assertIdentical($expected, $controller->passedArgs); + $this->assertIdentical($controller->plugin, 'my_plugin'); + $this->assertIdentical($controller->name, 'SomePages'); + $this->assertIdentical($controller->params['controller'], 'some_pages'); + $this->assertIdentical($controller->passedArgs, array('0' => 'home', 'param'=>'value', 'param2'=>'value2')); $expected = '/cake/repo/branches/1.2.x.x/my_plugin/some_pages/home/param:value/param2:value2'; $this->assertIdentical($expected, $controller->here); @@ -1472,24 +1474,20 @@ class DispatcherTest extends CakeTestCase { Router::reload(); $Dispatcher =& new TestDispatcher(); - Router::connect('/my_plugin/:controller/:action/*', array('plugin'=>'my_plugin', 'controller'=>'pages', 'action'=>'display')); + Router::connect( + '/my_plugin/:controller/:action/*', + array('plugin' => 'my_plugin', 'controller' => 'pages', 'action' => 'display') + ); $Dispatcher->base = false; $url = 'my_plugin/other_pages/index/param:value/param2:value2'; $controller = $Dispatcher->dispatch($url, array('return' => 1)); - $expected = 'my_plugin'; - $this->assertIdentical($expected, $controller->plugin); - - $expected = 'OtherPages'; - $this->assertIdentical($expected, $controller->name); - - $expected = 'index'; - $this->assertIdentical($expected, $controller->action); - - $expected = array('param' => 'value', 'param2' => 'value2'); - $this->assertIdentical($expected, $controller->passedArgs); + $this->assertIdentical($controller->plugin, 'my_plugin'); + $this->assertIdentical($controller->name, 'OtherPages'); + $this->assertIdentical($controller->action, 'index'); + $this->assertIdentical($controller->passedArgs, array('param' => 'value', 'param2' => 'value2')); $expected = '/cake/repo/branches/1.2.x.x/my_plugin/other_pages/index/param:value/param2:value2'; $this->assertIdentical($expected, $controller->here); @@ -1508,6 +1506,13 @@ class DispatcherTest extends CakeTestCase { $_POST = array(); $_SERVER['PHP_SELF'] = '/cake/repo/branches/1.2.x.x/index.php'; + $plugins = App::objects('plugin'); + $plugins[] = 'MyPlugin'; + $plugins[] = 'ArticlesTest'; + + $app = App::getInstance(); + $app->__objects['plugin'] = $plugins; + Router::reload(); $Dispatcher =& new TestDispatcher(); $Dispatcher->base = false; @@ -1526,7 +1531,7 @@ class DispatcherTest extends CakeTestCase { $Dispatcher =& new TestDispatcher(); $Dispatcher->base = false; - /* Simulates the Route for a real plugin, installed in APP/plugins */ + // Simulates the Route for a real plugin, installed in APP/plugins Router::connect('/my_plugin/:controller/:action/*', array('plugin' => 'my_plugin')); $plugin = 'MyPlugin'; @@ -1534,15 +1539,9 @@ class DispatcherTest extends CakeTestCase { $url = $pluginUrl; $controller = $Dispatcher->dispatch($url, array('return' => 1)); - - $expected = $pluginUrl; - $this->assertIdentical($controller->plugin, $expected); - - $expected = $plugin; - $this->assertIdentical($controller->name, $expected); - - $expected = 'index'; - $this->assertIdentical($controller->action, $expected); + $this->assertIdentical($controller->plugin, 'my_plugin'); + $this->assertIdentical($controller->name, 'MyPlugin'); + $this->assertIdentical($controller->action, 'index'); $expected = $pluginUrl; $this->assertEqual($controller->params['controller'], $expected); @@ -1557,37 +1556,40 @@ class DispatcherTest extends CakeTestCase { $url = 'admin/my_plugin/add/5/param:value/param2:value2'; $controller = $Dispatcher->dispatch($url, array('return' => 1)); - $expected = 'my_plugin'; - $this->assertIdentical($controller->plugin, $expected); - - $expected = 'MyPlugin'; - $this->assertIdentical($controller->name, $expected); - - $expected = 'admin_add'; - $this->assertIdentical($controller->action, $expected); + $this->assertEqual($controller->params['plugin'], 'my_plugin'); + $this->assertEqual($controller->params['controller'], 'my_plugin'); + $this->assertEqual($controller->params['action'], 'admin_add'); + $this->assertEqual($controller->params['pass'], array(5)); + $this->assertEqual($controller->params['named'], array('param' => 'value', 'param2' => 'value2')); + $this->assertIdentical($controller->plugin, 'my_plugin'); + $this->assertIdentical($controller->name, 'MyPlugin'); + $this->assertIdentical($controller->action, 'admin_add'); $expected = array(0 => 5, 'param'=>'value', 'param2'=>'value2'); $this->assertEqual($controller->passedArgs, $expected); + Configure::write('Routing.prefixes', array('admin')); Router::reload(); $Dispatcher =& new TestDispatcher(); $Dispatcher->base = false; $controller = $Dispatcher->dispatch('admin/articles_test', array('return' => 1)); - - $expected = 'articles_test'; - $this->assertIdentical($controller->plugin, $expected); - - $expected = 'ArticlesTest'; - $this->assertIdentical($controller->name, $expected); - - $expected = 'admin_index'; - $this->assertIdentical($controller->action, $expected); + $this->assertIdentical($controller->plugin, 'articles_test'); + $this->assertIdentical($controller->name, 'ArticlesTest'); + $this->assertIdentical($controller->action, 'admin_index'); $expected = array( - 'pass'=> array(), 'named' => array(), 'controller' => 'articles_test', 'plugin' => 'articles_test', 'action' => 'admin_index', - 'prefix' => 'admin', 'admin' => true, 'form' => array(), 'url' => array('url' => 'admin/articles_test'), 'return' => 1 + 'pass'=> array(), + 'named' => array(), + 'controller' => 'articles_test', + 'plugin' => 'articles_test', + 'action' => 'admin_index', + 'prefix' => 'admin', + 'admin' => true, + 'form' => array(), + 'url' => array('url' => 'admin/articles_test'), + 'return' => 1 ); $this->assertEqual($controller->params, $expected); } @@ -1602,9 +1604,13 @@ class DispatcherTest extends CakeTestCase { function testAutomaticPluginDispatchWithShortAccess() { $_POST = array(); $_SERVER['PHP_SELF'] = '/cake/repo/branches/1.2.x.x/index.php'; + $plugins = App::objects('plugin'); + $plugins[] = 'MyPlugin'; + + $app = App::getInstance(); + $app->__objects['plugin'] = $plugins; Router::reload(); - Router::connect('/my_plugin/:controller/:action/*', array('plugin' => 'my_plugin')); $Dispatcher =& new TestDispatcher(); $Dispatcher->base = false; @@ -1633,6 +1639,9 @@ class DispatcherTest extends CakeTestCase { $url = 'my_plugin/add'; $controller = $Dispatcher->dispatch($url, array('return' => 1)); $this->assertFalse(isset($controller->params['pass'][0])); + $this->assertEqual($controller->params['controller'], 'my_plugin'); + $this->assertEqual($controller->params['action'], 'add'); + $this->assertEqual($controller->params['plugin'], 'my_plugin'); $Dispatcher =& new TestDispatcher(); $Dispatcher->base = false; diff --git a/cake/tests/cases/libs/router.test.php b/cake/tests/cases/libs/router.test.php index 57b450434..6e0689abe 100644 --- a/cake/tests/cases/libs/router.test.php +++ b/cake/tests/cases/libs/router.test.php @@ -17,7 +17,7 @@ * @since CakePHP(tm) v 1.2.0.4206 * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License */ -App::import('Core', array('Router', 'Debugger')); +App::import('Core', array('Router')); if (!defined('FULL_BASE_URL')) { define('FULL_BASE_URL', 'http://cakephp.org'); @@ -741,31 +741,10 @@ class RouterTest extends CakeTestCase { 'lang' => 'en', 'controller' => 'shows', 'action' => 'index', 'page' => '1', )); - $expected = '/en/shows/page:1'; + $expected = '/en/shows/shows/page:1'; $this->assertEqual($result, $expected); } -/** - * test that plugin short cut routes behave properly. Parse and reverse route correctly. - * - * @return void - */ - function testPluginShortcutRoutes() { - $result = Router::url(array('plugin' => 'test_plugin', 'controller' => 'test_plugin', 'action' => 'index')); - $this->assertEqual($result, '/test_plugin', 'Plugin shortcut index action failed.'); - - $result = Router::url(array('plugin' => 'test_plugin', 'controller' => 'test_plugin', 'action' => 'view', 1)); - $this->assertEqual($result, '/test_plugin/view/1', 'Plugin shortcut with passed args failed.'); - - $result = Router::url(array( - 'plugin' => 'test_plugin', 'controller' => 'test_plugin', 'action' => 'view', - 1, 'sort' => 'title', 'dir' => 'asc' - )); - $this->assertEqual( - $result, '/test_plugin/view/1/sort:title/dir:asc', 'Plugin shortcut with passed + named args failed.' - ); - } - /** * test that you can leave active plugin routes with plugin = null * @@ -1146,7 +1125,7 @@ class RouterTest extends CakeTestCase { Router::parse('/'); $result = Router::url(array('plugin' => 'test_plugin', 'controller' => 'test_plugin', 'action' => 'index')); - $expected = '/admin/test_plugin'; + $expected = '/admin/test_plugin/test_plugin'; $this->assertEqual($result, $expected); Router::reload(); @@ -1964,6 +1943,7 @@ class RouterTest extends CakeTestCase { ) ), true); App::objects('plugin', null, false); + Router::reload(); $plugins = App::objects('plugin'); $plugin = Inflector::underscore($plugins[0]); @@ -1976,6 +1956,17 @@ class RouterTest extends CakeTestCase { 'named' => array(), 'pass' => array() ); $this->assertEqual($result, $expected); + + $result = Router::url(array('plugin' => 'test_plugin', 'controller' => 'test_plugin', 'action' => 'index')); + $this->assertEqual($result, '/test_plugin'); + + $result = Router::parse('/test_plugin'); + $expected = array( + 'plugin' => 'test_plugin', 'controller' => 'test_plugin', 'action' => 'index', + 'named' => array(), 'pass' => array() + ); + + $this->assertEqual($result, $expected, 'Plugin shortcut route broken. %s'); } /** @@ -2437,4 +2428,64 @@ class CakeRouteTestCase extends CakeTestCase { } } +/** + * test case for PluginShortRoute + * + * @package cake.tests.libs + */ +class PluginShortRouteTestCase extends CakeTestCase { +/** + * startTest method + * + * @access public + * @return void + */ + function startTest() { + $this->_routing = Configure::read('Routing'); + Configure::write('Routing', array('admin' => null, 'prefixes' => array())); + Router::reload(); + } + +/** + * end the test and reset the environment + * + * @return void + **/ + function endTest() { + Configure::write('Routing', $this->_routing); + } + +/** + * test the parsing of routes. + * + * @return void + */ + function testParsing() { + $route =& new PluginShortRoute('/:plugin', array('action' => 'index'), array('plugin' => 'foo|bar')); + + $result = $route->parse('/foo'); + $this->assertEqual($result['plugin'], 'foo'); + $this->assertEqual($result['controller'], 'foo'); + $this->assertEqual($result['action'], 'index'); + + $result = $route->parse('/wrong'); + $this->assertFalse($result, 'Wrong plugin name matched %s'); + } + +/** + * test the reverse routing of the plugin shortcut urls. + * + * @return void + */ + function testMatch() { + $route =& new PluginShortRoute('/:plugin', array('action' => 'index'), array('plugin' => 'foo|bar')); + + $result = $route->match(array('plugin' => 'foo', 'controller' => 'posts', 'action' => 'index')); + $this->assertFalse($result, 'plugin controller mismatch was converted. %s'); + + $result = $route->match(array('plugin' => 'foo', 'controller' => 'foo', 'action' => 'index')); + $this->assertEqual($result, '/foo'); + } +} + ?> \ No newline at end of file