Routing fixes for invalid null value matching and named argument handling (Ticket , , , ). Thanks biesbjerg for all your help!

git-svn-id: https://svn.cakephp.org/repo/branches/1.2.x.x@5554 3807eeeb-6ff5-0310-8944-8be069107fe0
This commit is contained in:
nate 2007-08-20 13:09:11 +00:00
parent 6c1a81eeb8
commit bd194b51e5
5 changed files with 132 additions and 94 deletions
cake
libs
tests/cases/libs

View file

@ -157,13 +157,6 @@ class Router extends Object {
* @access private
*/
var $__paths = array();
/**
* Maintains the mapped elements for array based urls
*
* @var array
* @access private
*/
var $__mapped = array();
/**
* Keeps Router state to determine if default routes have already been connected
*
@ -171,22 +164,6 @@ class Router extends Object {
* @access private
*/
var $__defaultsMapped = false;
/**
* Initialize the Router object
*
*/
function __construct() {
if (defined('CAKE_ADMIN')) {
$admin = CAKE_ADMIN;
if (!empty($admin)) {
$this->__admin = array(
'/:' . $admin . '/:controller/:action/*',
'/^(?:\/(?:(' . $admin . ')(?:\\/([a-zA-Z0-9_\\-\\.\\;\\:]+)(?:\\/([a-zA-Z0-9_\\-\\.\\;\\:]+)(?:[\\/\\?](.*))?)?)?))[\/]*$/',
array($admin, 'controller', 'action'), array()
);
}
}
}
/**
* Gets a reference to the Router object instance
*
@ -648,6 +625,7 @@ class Router extends Object {
function url($url = null, $full = false) {
$_this =& Router::getInstance();
$defaults = $params = array('plugin' => null, 'controller' => null, 'action' => 'index');
$admin = Configure::read('Routing.admin');
if (!empty($_this->__params)) {
if (isset($this) && !isset($this->params['requested'])) {
@ -688,16 +666,16 @@ class Router extends Object {
$url['action'] = 'index';
}
}
$url = am(array('controller' => $params['controller'], 'plugin' => $params['plugin']), $url);
$url = am(array('controller' => $params['controller'], 'plugin' => $params['plugin']), Set::filter($url, true));
if (isset($url['ext'])) {
$extension = '.' . $url['ext'];
unset($url['ext']);
}
if ($admin = Configure::read('Routing.admin')) {
if ($admin) {
if (!isset($url[$admin]) && isset($params[$admin])) {
$url[$admin] = true;
$url['action'] = str_replace("{$admin}_", '', $url['action']);
} elseif ($admin && isset($url[$admin]) && $url[$admin] == false) {
} elseif ($admin && array_key_exists($admin, $url) && !$url[$admin]) {
unset($url[$admin]);
}
}
@ -705,29 +683,23 @@ class Router extends Object {
foreach ($_this->routes as $route) {
if ($match = $_this->mapRouteElements($route, $url)) {
list($output, $url) = $match;
if (strpos($output, '/') === 0) {
$output = substr($output, 1);
}
$output = trim($match, '/');
$url = array();
break;
}
}
$named = $args = array();
$skip = am(array_keys($_this->__mapped), array('bare', 'action', 'controller', 'plugin', 'ext', '?', '#'));
$skip = array('bare', 'action', 'controller', 'plugin', 'ext', '?', '#');
$_this->__mapped = array();
$keys = array_values(array_diff(array_keys($url), $skip));
$count = count($keys);
// Remove this once parsed URL parameters can be inserted into 'pass'
for ($i = 0; $i < $count; $i++) {
if ($i == 0 && is_numeric($keys[$i]) && in_array('id', $keys)) {
$args[0] = $url[$keys[$i]];
} elseif (is_numeric($keys[$i]) || $keys[$i] == 'id') {
$args[] = $url[$keys[$i]];
} elseif (!empty($path['namedArgs']) && in_array($keys[$i], array_keys($path['namedArgs'])) && !empty($url[$keys[$i]])) {
$named[] = $keys[$i] . $_this->__argSeparator . $url[$keys[$i]];
} elseif (!empty($url[$keys[$i]]) || is_numeric($url[$keys[$i]])) {
$named[] = $keys[$i] . $_this->__argSeparator . $url[$keys[$i]];
}
}
@ -742,20 +714,15 @@ class Router extends Object {
if ($url['plugin'] == $url['controller']) {
array_shift($urlOut);
}
if (defined('CAKE_ADMIN') && isset($url[CAKE_ADMIN]) && $url[CAKE_ADMIN]) {
array_unshift($urlOut, CAKE_ADMIN);
}
$output = join('/', $urlOut) . '/';
}
foreach (array('args', 'named') as $var) {
if (!empty(${$var})) {
${$var} = join('/', ${$var});
if ($output{strlen($output) - 1} != '/') {
${$var} = '/'. ${$var};
}
$output .= ${$var};
if (!empty($args)) {
$args = join('/', $args);
if ($output{strlen($output) - 1} != '/') {
$args = '/'. $args;
}
$output .= $args;
}
$output = str_replace('//', '/', $base . '/' . $output);
} else {
@ -765,12 +732,12 @@ class Router extends Object {
if (empty($url)) {
return $path['here'];
} elseif ($url{0} == '/') {
} elseif (substr($url, 0, 1) == '/') {
$output = $base . $url;
} else {
$output = $base . '/';
if (defined('CAKE_ADMIN') && isset($params[CAKE_ADMIN])) {
$output .= CAKE_ADMIN . '/';
if ($admin && isset($params[$admin])) {
$output .= $admin . '/';
}
if (!empty($params['plugin'])) {
$output .= Inflector::underscore($params['plugin']) . '/';
@ -798,12 +765,15 @@ class Router extends Object {
*/
function mapRouteElements($route, $url) {
$_this =& Router::getInstance();
unset($route[3]['prefix']);
if (isset($route[3]['prefix'])) {
$prefix = $route[3]['prefix'];
unset($route[3]['prefix']);
}
$pass = array();
$defaults = $route[3];
$params = Set::diff($url, $defaults);
$routeParams = $route[2];
$params = Set::diff($url, $defaults);
foreach ($params as $key => $value) {
if (is_int($key)) {
@ -817,14 +787,13 @@ class Router extends Object {
return false;
}
if (!empty($params)) {
if (array_diff(array_keys($params), $routeParams) != array()) {
return false;
}
$required = array_diff(array_keys($defaults), array_keys($url));
}
krsort($defaults);
krsort($url);
if (empty($params)) {
return array(Router::__mapRoute($route, am($url, compact('pass', 'named'))), array());
return Router::__mapRoute($route, am($url, compact('pass', 'named', 'prefix')));
} elseif (!empty($routeParams) && !empty($route[3])) {
if (!empty($required)) {
return false;
@ -834,27 +803,26 @@ class Router extends Object {
return false;
}
}
$filled = array_intersect_key($url, array_combine($routeParams, array_keys($routeParams)));
if (array_keys($filled) != $routeParams) {
if (array_diff(array_keys($filled), $routeParams) != array()) {
return false;
}
} else {
if (empty($required) && $defaults['plugin'] == $url['plugin'] && $defaults['controller'] == $url['controller'] && $defaults['action'] == $url['action']) {
return array(Router::__mapRoute($route, am($url, compact('pass', 'named'))), $url);
return Router::__mapRoute($route, am($url, compact('pass', 'named', 'prefix')));
}
return false;
}
if (!empty($route[4])) {
foreach ($route[4] as $key => $reg) {
if (isset($url[$key]) && !preg_match('/' . $reg . '/', $url[$key])) {
if (array_key_exists($key, $url) && !preg_match('/' . $reg . '/', $url[$key])) {
return false;
}
}
}
return array(Router::__mapRoute($route, am($url, compact('pass', 'named'))), $url);
return Router::__mapRoute($route, am($filled, compact('pass', 'named', 'prefix')));
}
/**
* Merges URL parameters into a route string
@ -866,8 +834,13 @@ class Router extends Object {
*/
function __mapRoute($route, $params = array()) {
$_this =& Router::getInstance();
if (isset($params['prefix']) && isset($params['action'])) {
$params['action'] = str_replace($params['prefix'] . '_', '', $params['action']);
unset($params['prefix']);
}
if (isset($params['pass']) && is_array($params['pass'])) {
$_this->__mapped = $params['pass'];
$params['pass'] = implode('/', Set::filter($params['pass'], true));
} elseif (!isset($params['pass'])) {
$params['pass'] = '';
@ -907,11 +880,6 @@ class Router extends Object {
unset($params[$key]);
}
$out = str_replace(':' . $key, $string, $out);
$_this->__mapped[$key] = $string;
}
if (substr($out, -1) == '/') {
$out = substr($out, 0, strlen($out) - 1);
}
return $out;
}

View file

@ -115,8 +115,7 @@ class Set extends Object {
*/
function filter($var, $isArray = false) {
if (is_array($var) && (!empty($var) || $isArray)) {
$set = new Set();
return array_filter($var, array(&$set, 'filter'));
return array_filter($var, array('Set', 'filter'));
} else {
if ($var === 0 || $var === '0' || !empty($var)) {
return true;
@ -531,7 +530,7 @@ class Set extends Object {
}
foreach ($val1 as $key => $val) {
if (isset($val2[$key]) && $val2[$key] != $val) {
if (array_key_exists($key, $val2) && $val2[$key] != $val) {
$out[$key] = $val;
} elseif (!array_key_exists($key, $val2)) {
$out[$key] = $val;
@ -540,7 +539,7 @@ class Set extends Object {
}
foreach ($val2 as $key => $val) {
if (!isset($out[$key])) {
if (!array_key_exists($key, $out)) {
$out[$key] = $val;
}
}

View file

@ -267,7 +267,7 @@ class PaginatorHelper extends AppHelper {
$obj = isset($options['update']) ? 'Ajax' : 'Html';
$url = am(array('page' => $this->current($model)), $url);
return $this->{$obj}->link($title, $url, $options);
return $this->{$obj}->link($title, Set::filter($url, true), $options);
}
/**
* Protected method for generating prev/next links

View file

@ -36,6 +36,11 @@ uses('router', 'debugger');
*/
class RouterTest extends UnitTestCase {
function RouterTest() {
parent::UnitTestCase();
$this->startTime = getMicrotime();
}
function setUp() {
$this->router =& Router::getInstance();
}
@ -130,9 +135,11 @@ class RouterTest extends UnitTestCase {
$this->assertEqual($result, $expected);
$this->router->reload();
$this->router->parse('/');
$this->router->connect('/:plugin/:id/*', array('controller' => 'posts', 'action' => 'view'), array('id' => $ID));
$result = $this->router->url(array('plugin' => 'cake_plugin', 'controller' => 'posts', 'action' => 'view', 'id' => '1'));
$expected = '/cake_plugin/1/';
$expected = '/cake_plugin/1';
$this->assertEqual($result, $expected);
$result = $this->router->url(array('plugin' => 'cake_plugin', 'controller' => 'posts', 'action' => 'view', 'id' => '1', '0'));
@ -140,12 +147,16 @@ class RouterTest extends UnitTestCase {
$this->assertEqual($result, $expected);
$this->router->reload();
$this->router->parse('/');
$this->router->connect('/:controller/:action/:id', array(), array('id' => $ID));
$result = $this->router->url(array('controller' => 'posts', 'action' => 'view', 'id' => '1'));
$expected = '/posts/view/1';
$this->assertEqual($result, $expected);
$this->router->reload();
$this->router->parse('/');
$this->router->connect('/:controller/:id', array('action' => 'view', 'id' => '1'));
$result = $this->router->url(array('controller' => 'posts', 'action' => 'view', 'id' => '1'));
$expected = '/posts/1';
@ -170,14 +181,15 @@ class RouterTest extends UnitTestCase {
$this->assertEqual($result, $expected);
$this->router->connect('/view/*', array('controller' => 'posts', 'action' => 'view'));
$this->router->promote();
$result = $this->router->url(array('controller' => 'posts', 'action' => 'view', '1'));
$expected = '/view/1';
$this->assertEqual($result, $expected);
Configure::write('Routing.admin', 'admin');
$this->router->reload();
$this->router->connect('/admin/subscriptions/:action/*', array('controller' => 'subscribe', 'admin' => true));
Router::setRequestInfo(array(
$this->router->connect('/admin/subscriptions/:action/*', array('controller' => 'subscribe', 'admin' => true, 'prefix' => 'admin'));
$this->router->setRequestInfo(array(
array(
'pass' => array(), 'action' => 'admin_index', 'plugin' => null, 'controller' => 'subscribe',
'admin' => true, 'url' => array('url' => 'admin/subscriptions/index/page:2'), 'bare' => 0, 'webservices' => ''
@ -195,7 +207,7 @@ class RouterTest extends UnitTestCase {
$this->assertEqual($result, $expected);
$this->router->reload();
Router::setRequestInfo(array(
$this->router->setRequestInfo(array(
array(
'pass' => array(), 'action' => 'index', 'plugin' => null, 'controller' => 'real_controller_name',
'url' => array('url' => ''), 'bare' => 0, 'webservices' => ''
@ -214,7 +226,7 @@ class RouterTest extends UnitTestCase {
$this->assertEqual($result, $expected);
$result = $this->router->url(array('action' => 'add'));
$expected = '/short_controller_name/add/';
$expected = '/short_controller_name/add';
$this->assertEqual($result, $expected);
$this->router->reload();
@ -263,11 +275,12 @@ class RouterTest extends UnitTestCase {
$this->assertEqual($result, $expected);
$result = $this->router->url(array('language' => 'eng', 'controller' => 'pages', 'action' => 'add'));
$expected = '/eng/pages/add/';
$expected = '/eng/pages/add';
$this->assertEqual($result, $expected);
$this->router->reload();
Router::setRequestInfo(array(
$this->router->parse('/');
$this->router->setRequestInfo(array(
array(
'pass' => array(), 'action' => 'index', 'plugin' => null, 'controller' => 'users',
'url' => array('url' => 'users'), 'bare' => 0, 'webservices' => ''
@ -280,12 +293,50 @@ class RouterTest extends UnitTestCase {
));
$result = $this->router->url(array('action' => 'login'));
$expected = '/users/login/';
$this->assertEqual($result, $expected);
$expected = '/users/login';
$this->assertEqual($result, $expected);
$this->router->reload();
$this->router->parse('/');
$this->router->connect('/page/*', array('plugin' => null, 'controller' => 'pages', 'action' => 'view'));
$result = $this->router->url(array('plugin' => 'my_plugin', 'controller' => 'pages', 'action' => 'view', 'my-page'));
$expected = '/my_plugin/pages/view/my-page';
$this->assertEqual($result, $expected);
$this->router->reload();
$this->router->parse('/');
$this->router->connect('/forestillinger/:month/:year/*',
array('plugin' => 'shows', 'controller' => 'shows', 'action' => 'calendar'),
array('month' => '0[1-9]|1[012]', 'year' => '[12][0-9]{3}')
);
$result = $this->router->url(array('plugin' => 'shows', 'controller' => 'shows', 'action' => 'calendar', 'month' => 10, 'year' => 2007, 'min-forestilling'));
$expected = '/forestillinger/10/2007/min-forestilling';
$this->assertEqual($result, $expected);
$this->router->reload();
$this->router->parse('/');
$this->router->connect('/contact/:action', array('plugin' => 'contact', 'controller' => 'contact'));
$result = $this->router->url(array('plugin' => 'contact', 'controller' => 'contact', 'action' => 'me'));
$expected = '/contact/me';
$this->assertEqual($result, $expected);
Configure::write('Routing.admin', 'admin');
$this->router->reload();
$this->router->parse('/');
$result = $this->router->url(array('admin' => true, 'controller' => 'users', 'action' => 'login'));
$expected = '/admin/users/login';
$this->assertEqual($result, $expected);
}
function testUrlGenerationWithExtensions() {
$this->router->reload();
$this->router->parse('/');
$result = $this->router->url(array('plugin' => null, 'controller' => 'articles', 'action' => 'add', 'id' => null, 'ext' => 'json'));
$expected = '/articles/add.json';
$this->assertEqual($result, $expected);
@ -307,11 +358,11 @@ class RouterTest extends UnitTestCase {
$this->router->setRequestInfo(array(
array(
'controller' => 'controller', 'action' => 'index', 'form' => array(),
'url' => array (), 'bare' => 0, 'webservices' => null, 'plugin' => 'test'
'url' => array(), 'bare' => 0, 'webservices' => null, 'plugin' => 'test'
),
array (
'base' => '/base', 'here' => '/clients/sage/portal/donations', 'webroot' => '/base/',
'passedArgs' => array (), 'argSeparator' => ':', 'namedArgs' => array (), 'webservices' => null
'passedArgs' => array(), 'argSeparator' => ':', 'namedArgs' => array (), 'webservices' => null
)
));
@ -322,7 +373,6 @@ class RouterTest extends UnitTestCase {
function testUrlParsing() {
extract($this->router->getNamedExpressions());
$this->router->routes = array();
$this->router->connect('/posts/:value/:somevalue/:othervalue/*', array('controller' => 'posts', 'action' => 'view'), array('value','somevalue', 'othervalue'));
$result = $this->router->parse('/posts/2007/08/01/title-of-post-here');
$expected = array('value' => '2007', 'somevalue' => '08', 'othervalue' => '01', 'controller' => 'posts', 'action' => 'view', 'plugin' =>'', 'pass' => array('0' => 'title-of-post-here'));
@ -373,6 +423,17 @@ class RouterTest extends UnitTestCase {
$result = $this->router->parse('/eng/contact');
$expected = array('pass' => array(), 'language' => 'eng', 'plugin' => 'contact', 'controller' => 'contact', 'action' => 'index');
$this->assertEqual($result, $expected);
$this->router->reload();
$this->router->connect('/forestillinger/:month/:year/*',
array('plugin' => 'shows', 'controller' => 'shows', 'action' => 'calendar'),
array('month' => '0[1-9]|1[012]', 'year' => '[12][0-9]{3}')
);
$result = $this->router->parse('/forestillinger/10/2007/min-forestilling');
$expected = array('pass' => array('min-forestilling'), 'plugin' => 'shows', 'controller' => 'shows', 'action' => 'calendar', 'year' => 2007, 'month' => 10);
$this->assertEqual($result, $expected);
}
function testAdminRouting() {
@ -450,7 +511,12 @@ class RouterTest extends UnitTestCase {
}
function testNamedArgsUrlGeneration() {
Router::setRequestInfo(array(null, array('base' => '/', 'argSeparator' => ':')));
$this->router->setRequestInfo(array(null, array('base' => '/', 'argSeparator' => ':')));
$this->router->connectNamed(array(
'published' => array('regex' => '[01]'),
'deleted' => array('regex' => '[01]')
));
$this->router->parse('/');
$result = $this->router->url(array('controller' => 'posts', 'action' => 'index', 'published' => 1, 'deleted' => 1));
$expected = '/posts/index/published:1/deleted:1';
@ -463,17 +529,17 @@ class RouterTest extends UnitTestCase {
function testParamsUrlParsing() {
$this->router->routes = array();
Router::connect('/', array('controller' => 'posts', 'action' => 'index'));
Router::connect('/view/:user/*', array('controller' => 'posts', 'action' => 'view'), array('user'));
$this->router->connect('/', array('controller' => 'posts', 'action' => 'index'));
$this->router->connect('/view/:user/*', array('controller' => 'posts', 'action' => 'view'), array('user'));
$result = $this->router->parse('/view/gwoo/');
$expected = array('user' => 'gwoo', 'controller' => 'posts', 'action' => 'view', 'plugin' =>'', 'pass' => array());
$this->assertEqual($result, $expected);
}
function testPagesUrlParsing() {
$this->router->routes = array();
Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home'));
Router::connect('/pages/*', array('controller' => 'pages', 'action' => 'display'));
$this->router->reload();
$this->router->connect('/', array('controller' => 'pages', 'action' => 'display', 'home'));
$this->router->connect('/pages/*', array('controller' => 'pages', 'action' => 'display'));
$result = $this->router->parse('/');
$expected = array('pass'=>array('home'), 'plugin' => null, 'controller' => 'pages', 'action' => 'display');
@ -483,9 +549,9 @@ class RouterTest extends UnitTestCase {
$expected = array('pass' => array('home'), 'plugin' => null, 'controller' => 'pages', 'action' => 'display');
$this->assertEqual($result, $expected);
$this->router->routes = array();
Router::connect('/', array('controller' => 'posts', 'action' => 'index'));
Router::connect('/pages/*', array('controller' => 'pages', 'action' => 'display'));
$this->router->reload();
$this->router->connect('/', array('controller' => 'posts', 'action' => 'index'));
$this->router->connect('/pages/*', array('controller' => 'pages', 'action' => 'display'));
$result = $this->router->parse('/pages/contact/');
$expected = array('pass'=>array('contact'), 'plugin'=> null, 'controller'=>'pages', 'action'=>'display');
@ -519,6 +585,10 @@ class RouterTest extends UnitTestCase {
$expected = array('admin');
$this->assertEqual($result, $expected);
}
function testTheEnd() {
pr(getMicrotime() - $this->startTime);
}
}
?>

View file

@ -83,6 +83,7 @@ class PaginatorTest extends UnitTestCase {
function testSortLinks() {
Router::reload();
Router::parse('/');
Router::setRequestInfo(array(
array ('plugin' => null, 'controller' => 'accounts', 'action' => 'index', 'pass' => array(), 'form' => array(), 'url' => array('url' => 'accounts/', 'mod_rewrite' => 'true'), 'bare' => 0),
array ('plugin' => null, 'controller' => null, 'action' => null, 'base' => '/officespace', 'here' => '/officespace/accounts/', 'webroot' => '/officespace/', 'passedArgs' => array())