From c5bab541251b1ae8a846907d4ac4fa9b30ba1925 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sat, 18 Dec 2010 00:15:09 -0500 Subject: [PATCH 01/59] Starting to try and re-factor named params to perform better and be more explicit with how they are used. --- cake/libs/route/cake_route.php | 11 ++++++++++- cake/libs/router.php | 4 ++-- cake/tests/cases/libs/route/cake_route.test.php | 4 ++-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/cake/libs/route/cake_route.php b/cake/libs/route/cake_route.php index 979f90dcf..0dbe085f8 100644 --- a/cake/libs/route/cake_route.php +++ b/cake/libs/route/cake_route.php @@ -263,6 +263,15 @@ class CakeRoute { if (array_intersect_key($keyNames, $url) != $keyNames) { return false; } + + //pull out named params so comparisons later on are faster. + $named = array(); + foreach ($url as $key => $value) { + if ($key[0] === ':') { + $named[$key] = $value; + unset($url[$key]); + } + } $diffUnfiltered = Set::diff($url, $defaults); $diff = array(); @@ -289,7 +298,7 @@ class CakeRoute { return false; } - $passedArgsAndParams = array_diff_key($diff, $filteredDefaults, $keyNames); + $passedArgsAndParams = array_diff_key($diff, $filteredDefaults, $keyNames) + $named; list($named, $params) = Router::getNamedElements($passedArgsAndParams, $url['controller'], $url['action']); //remove any pass params, they have numeric indexes, skip any params that are in the defaults diff --git a/cake/libs/router.php b/cake/libs/router.php index 611301867..85491ebc0 100644 --- a/cake/libs/router.php +++ b/cake/libs/router.php @@ -89,7 +89,7 @@ class Router { * @access public */ public static $named = array( - 'default' => array('page', 'fields', 'order', 'limit', 'recursive', 'sort', 'direction', 'step'), + 'default' => array(':page', ':fields', ':order', ':limit', ':recursive', ':sort', ':direction', ':step'), 'greedy' => true, 'separator' => ':', 'rules' => false, @@ -972,7 +972,7 @@ class Router { if (isset(self::$named['rules'][$param])) { $rule = self::$named['rules'][$param]; if (Router::matchNamed($param, $val, $rule, compact('controller', 'action'))) { - $named[$param] = $val; + $named[substr($param, 1)] = $val; unset($params[$param]); } } diff --git a/cake/tests/cases/libs/route/cake_route.test.php b/cake/tests/cases/libs/route/cake_route.test.php index dae305b23..a5c13c62b 100644 --- a/cake/tests/cases/libs/route/cake_route.test.php +++ b/cake/tests/cases/libs/route/cake_route.test.php @@ -298,13 +298,13 @@ class CakeRouteTestCase extends CakeTestCase { Router::connectNamed(true); $route = new CakeRoute('/:controller/:action/*', array('plugin' => null)); - $result = $route->match(array('controller' => 'posts', 'action' => 'index', 'plugin' => null, 'page' => 1)); + $result = $route->match(array('controller' => 'posts', 'action' => 'index', 'plugin' => null, ':page' => 1)); $this->assertEqual($result, '/posts/index/page:1'); $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'plugin' => null, 5)); $this->assertEqual($result, '/posts/view/5'); - $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'plugin' => null, 5, 'page' => 1, 'limit' => 20, 'order' => 'title')); + $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'plugin' => null, 5, ':page' => 1, ':limit' => 20, ':order' => 'title')); $this->assertEqual($result, '/posts/view/5/page:1/limit:20/order:title'); From 456a14cf37e4b03680aabb8cd346124fbe0c8f35 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sat, 18 Dec 2010 12:34:48 -0500 Subject: [PATCH 02/59] Refactored CakeRoute::match() to not use Set::diff(). This was the slowest part of reverse routing and this change should make things faster. Added additional tests for the 0 edge case. --- cake/libs/route/cake_route.php | 55 +++++++++---------- .../cases/libs/route/cake_route.test.php | 6 ++ 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/cake/libs/route/cake_route.php b/cake/libs/route/cake_route.php index 0dbe085f8..8803a3201 100644 --- a/cake/libs/route/cake_route.php +++ b/cake/libs/route/cake_route.php @@ -263,27 +263,40 @@ class CakeRoute { if (array_intersect_key($keyNames, $url) != $keyNames) { return false; } - - //pull out named params so comparisons later on are faster. - $named = array(); + + $named = $pass = $diff = array(); foreach ($url as $key => $value) { + // pull out named params so comparisons later on are faster. if ($key[0] === ':') { - $named[$key] = $value; + $named[substr($key, 1)] = $value; unset($url[$key]); + continue; } - } - $diffUnfiltered = Set::diff($url, $defaults); - $diff = array(); - - foreach ($diffUnfiltered as $key => $var) { - if ($var === 0 || $var === '0' || !empty($var)) { - $diff[$key] = $var; + // keys that exist in the defaults and have different values cause match failures. + $keyExists = array_key_exists($key, $defaults); + if ($keyExists && $defaults[$key] != $value) { + $diff[$key] = $value; + continue; + } + // keys that don't exist are different. + if (!$keyExists) { + $diff[$key] = $value; + } + + // pull out passed args + $numeric = is_numeric($key); + if ($numeric && isset($defaults[$key]) && $defaults[$key] == $value) { + continue; + } elseif ($numeric) { + $pass[] = $value; + unset($url[$key]); + continue; } } //if a not a greedy route, no extra params are allowed. - if (!$this->_greedy && array_diff_key($diff, $keyNames) != array()) { + if (!$this->_greedy && (!empty($pass) || array_diff_key($diff, $keyNames) != array())) { return false; } @@ -294,26 +307,10 @@ class CakeRoute { $filteredDefaults = array_filter($defaults); //if the difference between the url diff and defaults contains keys from defaults its not a match - if (array_intersect_key($filteredDefaults, $diffUnfiltered) !== array()) { + if (array_intersect_key($filteredDefaults, $diff) !== array()) { return false; } - $passedArgsAndParams = array_diff_key($diff, $filteredDefaults, $keyNames) + $named; - list($named, $params) = Router::getNamedElements($passedArgsAndParams, $url['controller'], $url['action']); - - //remove any pass params, they have numeric indexes, skip any params that are in the defaults - $pass = array(); - $i = 0; - while (isset($url[$i])) { - if (!isset($diff[$i])) { - $i++; - continue; - } - $pass[] = $url[$i]; - unset($url[$i], $params[$i]); - $i++; - } - //still some left over parameters that weren't named or passed args, bail. if (!empty($params)) { return false; diff --git a/cake/tests/cases/libs/route/cake_route.test.php b/cake/tests/cases/libs/route/cake_route.test.php index a5c13c62b..37fd17d1c 100644 --- a/cake/tests/cases/libs/route/cake_route.test.php +++ b/cake/tests/cases/libs/route/cake_route.test.php @@ -304,6 +304,12 @@ class CakeRouteTestCase extends CakeTestCase { $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'plugin' => null, 5)); $this->assertEqual($result, '/posts/view/5'); + $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'plugin' => null, 0)); + $this->assertEqual($result, '/posts/view/0'); + + $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'plugin' => null, '0')); + $this->assertEqual($result, '/posts/view/0'); + $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'plugin' => null, 5, ':page' => 1, ':limit' => 20, ':order' => 'title')); $this->assertEqual($result, '/posts/view/5/page:1/limit:20/order:title'); From 6ef8203d54199bcb438e6e402af911b18c6b1522 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sat, 18 Dec 2010 13:32:05 -0500 Subject: [PATCH 03/59] Adding another case that makes false/null to not cause match failure. --- cake/libs/route/cake_route.php | 2 +- cake/tests/cases/libs/route/cake_route.test.php | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/cake/libs/route/cake_route.php b/cake/libs/route/cake_route.php index 8803a3201..5fa934347 100644 --- a/cake/libs/route/cake_route.php +++ b/cake/libs/route/cake_route.php @@ -280,7 +280,7 @@ class CakeRoute { continue; } // keys that don't exist are different. - if (!$keyExists) { + if (!$keyExists && !empty($value)) { $diff[$key] = $value; } diff --git a/cake/tests/cases/libs/route/cake_route.test.php b/cake/tests/cases/libs/route/cake_route.test.php index 37fd17d1c..a8c5404fa 100644 --- a/cake/tests/cases/libs/route/cake_route.test.php +++ b/cake/tests/cases/libs/route/cake_route.test.php @@ -289,6 +289,19 @@ class CakeRouteTestCase extends CakeTestCase { $this->assertEqual($result, $expected); } +/** + * test that falsey values do not interrupt a match. + * + * @return void + */ + function testMatchWithFalseyValues() { + $route = new CakeRoute('/:controller/:action/*', array('plugin' => null)); + $result = $route->match(array( + 'controller' => 'posts', 'action' => 'index', 'plugin' => null, 'admin' => false + )); + $this->assertEqual($result, '/posts/index/'); + } + /** * test match() with greedy routes, named parameters and passed args. * From 8d404332a279ebe9cfe59de8a0a9ef571c6c1ffa Mon Sep 17 00:00:00 2001 From: mark_story Date: Sat, 18 Dec 2010 13:40:07 -0500 Subject: [PATCH 04/59] Fixing issue where named params equal to null/false would be part of the generated url. --- cake/libs/route/cake_route.php | 2 +- cake/tests/cases/libs/route/cake_route.test.php | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/cake/libs/route/cake_route.php b/cake/libs/route/cake_route.php index 5fa934347..93c48897b 100644 --- a/cake/libs/route/cake_route.php +++ b/cake/libs/route/cake_route.php @@ -267,7 +267,7 @@ class CakeRoute { $named = $pass = $diff = array(); foreach ($url as $key => $value) { // pull out named params so comparisons later on are faster. - if ($key[0] === ':') { + if ($key[0] === ':' && ($value !== false && $value !== null)) { $named[substr($key, 1)] = $value; unset($url[$key]); continue; diff --git a/cake/tests/cases/libs/route/cake_route.test.php b/cake/tests/cases/libs/route/cake_route.test.php index a8c5404fa..bf58d8295 100644 --- a/cake/tests/cases/libs/route/cake_route.test.php +++ b/cake/tests/cases/libs/route/cake_route.test.php @@ -338,6 +338,17 @@ class CakeRouteTestCase extends CakeTestCase { $this->assertFalse($result); } +/** + * test that named params with null/false are excluded + * + * @return void + */ + function testNamedParamsWithNullFalse() { + $route = new CakeRoute('/:controller/:action/*', array('plugin' => null)); + $result = $route->match(array('controller' => 'posts', 'action' => 'index', ':page' => null, 'sort' => false)); + $this->assertEquals('/posts/index/', $result); + } + /** * test that match with patterns works. * From 328db0c36b12ca8ceb59dd374ac82fba1c17e9e5 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sat, 18 Dec 2010 14:16:00 -0500 Subject: [PATCH 05/59] Fixed a number of tests, there are still a few issues with prefix routes. Moved removing defaults that are also keys to the compile step. This removes quite a few repetitive loops. --- cake/libs/route/cake_route.php | 36 ++++++++++--------- cake/libs/router.php | 3 +- .../cases/libs/route/cake_route.test.php | 33 ++++++++++++++--- cake/tests/cases/libs/router.test.php | 30 ++++++++-------- 4 files changed, 65 insertions(+), 37 deletions(-) diff --git a/cake/libs/route/cake_route.php b/cake/libs/route/cake_route.php index 93c48897b..9d1fb97c7 100644 --- a/cake/libs/route/cake_route.php +++ b/cake/libs/route/cake_route.php @@ -165,6 +165,11 @@ class CakeRoute { $parsed = str_replace(array_keys($routeParams), array_values($routeParams), $parsed); $this->_compiledRoute = '#^' . $parsed . '[/]*$#'; $this->keys = $names; + + //remove defaults that are also keys. They can cause match failures + foreach ($this->keys as $key) { + unset($this->defaults[$key]); + } } /** @@ -265,6 +270,7 @@ class CakeRoute { } $named = $pass = $diff = array(); + foreach ($url as $key => $value) { // pull out named params so comparisons later on are faster. if ($key[0] === ':' && ($value !== false && $value !== null)) { @@ -279,11 +285,12 @@ class CakeRoute { $diff[$key] = $value; continue; } - // keys that don't exist are different. - if (!$keyExists && !empty($value)) { - $diff[$key] = $value; - } + // If the key is a routed key, its not different yet. + if (array_key_exists($key, $keyNames)) { + continue; + } + // pull out passed args $numeric = is_numeric($key); if ($numeric && isset($defaults[$key]) && $defaults[$key] == $value) { @@ -293,26 +300,21 @@ class CakeRoute { unset($url[$key]); continue; } + + // keys that don't exist are different. + if (!$keyExists && !empty($value)) { + $diff[$key] = $value; + } } + //if a not a greedy route, no extra params are allowed. - if (!$this->_greedy && (!empty($pass) || array_diff_key($diff, $keyNames) != array())) { - return false; - } - - //remove defaults that are also keys. They can cause match failures - foreach ($this->keys as $key) { - unset($defaults[$key]); - } - $filteredDefaults = array_filter($defaults); - - //if the difference between the url diff and defaults contains keys from defaults its not a match - if (array_intersect_key($filteredDefaults, $diff) !== array()) { + if (!$this->_greedy && ( (!empty($pass) || !empty($named)) || array_diff_key($diff, $keyNames) != array()) ) { return false; } //still some left over parameters that weren't named or passed args, bail. - if (!empty($params)) { + if (!empty($diff)) { return false; } diff --git a/cake/libs/router.php b/cake/libs/router.php index 85491ebc0..43c9590bf 100644 --- a/cake/libs/router.php +++ b/cake/libs/router.php @@ -943,7 +943,8 @@ class Router { } if (!empty($named)) { - foreach ($named as $name => $value) { + foreach ($named as $name => $value) { + $name = trim($name, ':'); if (is_array($value)) { $flattend = Set::flatten($value, ']['); foreach ($flattend as $namedKey => $namedValue) { diff --git a/cake/tests/cases/libs/route/cake_route.test.php b/cake/tests/cases/libs/route/cake_route.test.php index bf58d8295..aa6a7246b 100644 --- a/cake/tests/cases/libs/route/cake_route.test.php +++ b/cake/tests/cases/libs/route/cake_route.test.php @@ -191,8 +191,7 @@ class CakeRouteTestCase extends CakeTestCase { $this->assertEqual($route->options, array('extra' => '[a-z1-9_]*', 'slug' => '[a-z1-9_]+', 'action' => 'view')); $expected = array( 'controller' => 'pages', - 'action' => 'view', - 'extra' => null, + 'action' => 'view' ); $this->assertEqual($route->defaults, $expected); @@ -220,7 +219,7 @@ class CakeRouteTestCase extends CakeTestCase { * @return void **/ function testMatchBasic() { - $route = new CakeRoute('/:controller/:action/:id', array('plugin' => null)); +/* $route = new CakeRoute('/:controller/:action/:id', array('plugin' => null)); $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'plugin' => null)); $this->assertFalse($result); @@ -236,7 +235,7 @@ class CakeRouteTestCase extends CakeTestCase { $result = $route->match(array('controller' => 'pages', 'action' => 'display', 'about')); $this->assertFalse($result); - +*/ $route = new CakeRoute('/pages/*', array('controller' => 'pages', 'action' => 'display')); $result = $route->match(array('controller' => 'pages', 'action' => 'display', 'home')); @@ -289,6 +288,32 @@ class CakeRouteTestCase extends CakeTestCase { $this->assertEqual($result, $expected); } +/** + * test that non-greedy routes fail with extra passed args + * + * @return void + */ + function testGreedyRouteFailurePassedArg() { + $route = new CakeRoute('/:controller/:action', array('plugin' => null)); + $result = $route->match(array('controller' => 'posts', 'action' => 'view', '0')); + $this->assertFalse($result); + + $route = new CakeRoute('/:controller/:action', array('plugin' => null)); + $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'test')); + $this->assertFalse($result); + } + +/** + * test that non-greedy routes fail with extra passed args + * + * @return void + */ + function testGreedyRouteFailureNamedParam() { + $route = new CakeRoute('/:controller/:action', array('plugin' => null)); + $result = $route->match(array('controller' => 'posts', 'action' => 'view', ':page' => 1)); + $this->assertFalse($result); + } + /** * test that falsey values do not interrupt a match. * diff --git a/cake/tests/cases/libs/router.test.php b/cake/tests/cases/libs/router.test.php index e0def56f9..31539a185 100644 --- a/cake/tests/cases/libs/router.test.php +++ b/cake/tests/cases/libs/router.test.php @@ -328,7 +328,7 @@ class RouterTest extends CakeTestCase { Router::connect('short_controller_name/:action/*', array('controller' => 'real_controller_name')); Router::parse('/'); - $result = Router::url(array('controller' => 'real_controller_name', 'page' => '1')); + $result = Router::url(array('controller' => 'real_controller_name', ':page' => '1')); $expected = '/short_controller_name/index/page:1'; $this->assertEqual($result, $expected); @@ -389,14 +389,14 @@ class RouterTest extends CakeTestCase { * @return void */ function testArrayNamedParameters() { - $result = Router::url(array('controller' => 'tests', 'pages' => array( + $result = Router::url(array('controller' => 'tests', ':pages' => array( 1, 2, 3 ))); $expected = '/tests/index/pages[0]:1/pages[1]:2/pages[2]:3'; $this->assertEqual($result, $expected); $result = Router::url(array('controller' => 'tests', - 'pages' => array( + ':pages' => array( 'param1' => array( 'one', 'two' @@ -408,7 +408,7 @@ class RouterTest extends CakeTestCase { $this->assertEqual($result, $expected); $result = Router::url(array('controller' => 'tests', - 'pages' => array( + ':pages' => array( 'param1' => array( 'one' => 1, 'two' => 2 @@ -420,7 +420,7 @@ class RouterTest extends CakeTestCase { $this->assertEqual($result, $expected); $result = Router::url(array('controller' => 'tests', - 'super' => array( + ':super' => array( 'nested' => array( 'array' => 'awesome', 'something' => 'else' @@ -431,7 +431,7 @@ class RouterTest extends CakeTestCase { $expected = '/tests/index/super[nested][array]:awesome/super[nested][something]:else/super[0]:cool'; $this->assertEqual($result, $expected); - $result = Router::url(array('controller' => 'tests', 'namedParam' => array( + $result = Router::url(array('controller' => 'tests', ':namedParam' => array( 'keyed' => 'is an array', 'test' ))); @@ -648,9 +648,9 @@ class RouterTest extends CakeTestCase { Router::parse('/'); - $result = Router::url(array('page' => 3)); + $result = Router::url(array(':page' => 3)); $expected = '/magazine/admin/subscriptions/index/page:3'; - $this->assertEqual($result, $expected); + $this->assertEquals($expected, $result); Router::reload(); Router::connect('/admin/subscriptions/:action/*', array('controller' => 'subscribe', 'admin' => true, 'prefix' => 'admin')); @@ -860,7 +860,7 @@ class RouterTest extends CakeTestCase { $result = Router::url(array( 'lang' => 'en', - 'controller' => 'shows', 'action' => 'index', 'page' => '1', + 'controller' => 'shows', 'action' => 'index', ':page' => '1', )); $expected = '/en/shows/shows/page:1'; $this->assertEqual($result, $expected); @@ -1361,11 +1361,11 @@ class RouterTest extends CakeTestCase { * @return void */ function testNamedArgsUrlGeneration() { - $result = Router::url(array('controller' => 'posts', 'action' => 'index', 'published' => 1, 'deleted' => 1)); + $result = Router::url(array('controller' => 'posts', 'action' => 'index', ':published' => 1, ':deleted' => 1)); $expected = '/posts/index/published:1/deleted:1'; $this->assertEqual($result, $expected); - $result = Router::url(array('controller' => 'posts', 'action' => 'index', 'published' => 0, 'deleted' => 0)); + $result = Router::url(array('controller' => 'posts', 'action' => 'index', ':published' => 0, ':deleted' => 0)); $expected = '/posts/index/published:0/deleted:0'; $this->assertEqual($result, $expected); @@ -1375,11 +1375,11 @@ class RouterTest extends CakeTestCase { Router::connect('/', array('controller' => 'graphs', 'action' => 'index')); Router::connect('/:id/*', array('controller' => 'graphs', 'action' => 'view'), array('id' => $ID)); - $result = Router::url(array('controller' => 'graphs', 'action' => 'view', 'id' => 12, 'file' => 'asdf.png')); + $result = Router::url(array('controller' => 'graphs', 'action' => 'view', 'id' => 12, ':file' => 'asdf.png')); $expected = '/12/file:asdf.png'; $this->assertEqual($result, $expected); - $result = Router::url(array('controller' => 'graphs', 'action' => 'view', 12, 'file' => 'asdf.foo')); + $result = Router::url(array('controller' => 'graphs', 'action' => 'view', 12, ':file' => 'asdf.foo')); $expected = '/graphs/view/12/file:asdf.foo'; $this->assertEqual($result, $expected); @@ -1398,7 +1398,7 @@ class RouterTest extends CakeTestCase { ); Router::parse('/'); - $result = Router::url(array('page' => 1, 0 => null, 'sort' => 'controller', 'direction' => 'asc', 'order' => null)); + $result = Router::url(array(':page' => 1, 0 => null, ':sort' => 'controller', ':direction' => 'asc', ':order' => null)); $expected = "/admin/controller/index/page:1/sort:controller/direction:asc"; $this->assertEqual($result, $expected); @@ -1411,7 +1411,7 @@ class RouterTest extends CakeTestCase { Router::setRequestInfo($request); $result = Router::parse('/admin/controller/index/type:whatever'); - $result = Router::url(array('type'=> 'new')); + $result = Router::url(array(':type'=> 'new')); $expected = "/admin/controller/index/type:new"; $this->assertEqual($result, $expected); } From 756b09849fd9febd9c202260b1f3063ae6a76da0 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sat, 18 Dec 2010 14:26:45 -0500 Subject: [PATCH 06/59] Router tests all pass now. --- cake/libs/route/cake_route.php | 6 +++++- cake/tests/cases/libs/router.test.php | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cake/libs/route/cake_route.php b/cake/libs/route/cake_route.php index 9d1fb97c7..27e453ea9 100644 --- a/cake/libs/route/cake_route.php +++ b/cake/libs/route/cake_route.php @@ -269,6 +269,11 @@ class CakeRoute { return false; } + // Missing defaults is a fail. + if (array_diff_key($defaults, $url) !== array()) { + return false; + } + $named = $pass = $diff = array(); foreach ($url as $key => $value) { @@ -307,7 +312,6 @@ class CakeRoute { } } - //if a not a greedy route, no extra params are allowed. if (!$this->_greedy && ( (!empty($pass) || !empty($named)) || array_diff_key($diff, $keyNames) != array()) ) { return false; diff --git a/cake/tests/cases/libs/router.test.php b/cake/tests/cases/libs/router.test.php index 31539a185..6d2f700f6 100644 --- a/cake/tests/cases/libs/router.test.php +++ b/cake/tests/cases/libs/router.test.php @@ -1516,7 +1516,7 @@ class RouterTest extends CakeTestCase { $result = Router::url(array('controller' => 'images', 'action' => 'add')); $expected = '/images/add'; - $this->assertEqual($result, $expected); + $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'images', 'action' => 'add', 'protected' => true)); $expected = '/protected/images/add'; From 3b0a3d4109dc8550603fc4412dd2ec8d7cae0d93 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sat, 18 Dec 2010 14:36:11 -0500 Subject: [PATCH 07/59] Making route matching fail even faster. This gives significant performance boosts to routes not matching. --- cake/libs/route/cake_route.php | 4 +++- cake/tests/cases/libs/route/cake_route.test.php | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cake/libs/route/cake_route.php b/cake/libs/route/cake_route.php index 27e453ea9..bbe70bc63 100644 --- a/cake/libs/route/cake_route.php +++ b/cake/libs/route/cake_route.php @@ -265,7 +265,7 @@ class CakeRoute { //check that all the key names are in the url $keyNames = array_flip($this->keys); - if (array_intersect_key($keyNames, $url) != $keyNames) { + if (array_intersect_key($keyNames, $url) !== $keyNames) { return false; } @@ -287,6 +287,7 @@ class CakeRoute { // keys that exist in the defaults and have different values cause match failures. $keyExists = array_key_exists($key, $defaults); if ($keyExists && $defaults[$key] != $value) { + return false; $diff[$key] = $value; continue; } @@ -308,6 +309,7 @@ class CakeRoute { // keys that don't exist are different. if (!$keyExists && !empty($value)) { + return false; $diff[$key] = $value; } } diff --git a/cake/tests/cases/libs/route/cake_route.test.php b/cake/tests/cases/libs/route/cake_route.test.php index aa6a7246b..279e305f1 100644 --- a/cake/tests/cases/libs/route/cake_route.test.php +++ b/cake/tests/cases/libs/route/cake_route.test.php @@ -219,7 +219,7 @@ class CakeRouteTestCase extends CakeTestCase { * @return void **/ function testMatchBasic() { -/* $route = new CakeRoute('/:controller/:action/:id', array('plugin' => null)); + $route = new CakeRoute('/:controller/:action/:id', array('plugin' => null)); $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'plugin' => null)); $this->assertFalse($result); @@ -235,7 +235,7 @@ class CakeRouteTestCase extends CakeTestCase { $result = $route->match(array('controller' => 'pages', 'action' => 'display', 'about')); $this->assertFalse($result); -*/ + $route = new CakeRoute('/pages/*', array('controller' => 'pages', 'action' => 'display')); $result = $route->match(array('controller' => 'pages', 'action' => 'display', 'home')); @@ -369,7 +369,7 @@ class CakeRouteTestCase extends CakeTestCase { * @return void */ function testNamedParamsWithNullFalse() { - $route = new CakeRoute('/:controller/:action/*', array('plugin' => null)); + $route = new CakeRoute('/:controller/:action/*'); $result = $route->match(array('controller' => 'posts', 'action' => 'index', ':page' => null, 'sort' => false)); $this->assertEquals('/posts/index/', $result); } From 5255b8fc9e9dabb490d758d4073cbd2d9d16fb66 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sat, 18 Dec 2010 15:36:22 -0500 Subject: [PATCH 08/59] Updating doc block for Router::redirect() Updating RedirectRoute to not use defaults for where the route will redirect. --- cake/libs/route/redirect_route.php | 31 +++++++++++++++++++++++++----- cake/libs/router.php | 7 +++++-- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/cake/libs/route/redirect_route.php b/cake/libs/route/redirect_route.php index 148ce358f..e385dec0d 100644 --- a/cake/libs/route/redirect_route.php +++ b/cake/libs/route/redirect_route.php @@ -2,7 +2,9 @@ App::import('Core', 'CakeResponse'); App::import('Core', 'route/CakeRoute'); /** - * Redirect route will perform an immediate redirect + * Redirect route will perform an immediate redirect. Redirect routes + * are useful when you want to have Routing layer redirects occur in your + * application, for when URLs move. * * PHP5 * @@ -28,6 +30,25 @@ class RedirectRoute extends CakeRoute { */ public $response = null; +/** + * The location to redirect to. Either a string or a cake array url. + * + * @var mixed + */ + public $redirect; + +/** + * Constructor + * + * @param string $template Template string with parameter placeholders + * @param array $defaults Array of defaults for the route. + * @param string $params Array of parameters and additional options for the Route + */ + public function __construct($template, $defaults = array(), $options = array()) { + parent::__construct($template, $defaults, $options); + $this->redirect = (array)$defaults; + } + /** * Parses a string url into an array. Parsed urls will result in an automatic * redirection @@ -43,16 +64,16 @@ class RedirectRoute extends CakeRoute { if (!$this->response) { $this->response = new CakeResponse(); } - $redirect = $this->defaults; - if (count($this->defaults) == 1 && !isset($this->defaults['controller'])) { - $redirect = $this->defaults[0]; + $redirect = $this->redirect; + if (count($this->redirect) == 1 && !isset($this->redirect['controller'])) { + $redirect = $this->redirect[0]; } if (isset($this->options['persist']) && is_array($redirect)) { $argOptions['context'] = array('action' => $redirect['action'], 'controller' => $redirect['controller']); $args = Router::getArgs($params['_args_'], $argOptions); $redirect += $args['pass']; $redirect += $args['named']; - } + } $status = 301; if (isset($this->options['status']) && ($this->options['status'] >= 300 && $this->options['status'] < 400)) { $status = $this->options['status']; diff --git a/cake/libs/router.php b/cake/libs/router.php index 43c9590bf..506bdb2b0 100644 --- a/cake/libs/router.php +++ b/cake/libs/router.php @@ -269,15 +269,18 @@ class Router { * * `Router::redirect('/home/*', array('controller' => 'posts', 'action' => 'view', array('persist' => true));` * - * Redirects /home/* to /posts/view and passes the parameters to /posts/view + * Redirects /home/* to /posts/view and passes the parameters to /posts/view. Using an array as the + * redirect destination allows you to use other routes to define where a url string should be redirected ot. * * `Router::redirect('/posts/*', 'http://google.com', array('status' => 302));` * * Redirects /posts/* to http://google.com with a HTTP status of 302 * * ### Options: + * * - `status` Sets the HTTP status (default 301) - * - `persist` Passes the params to the redirected route, if it can + * - `persist` Passes the params to the redirected route, if it can. This is useful with greedy routes, + * routes that end in `*` are greedy. As you can remap urls and not loose any passed/named args. * * @param string $route A string describing the template of the route * @param array $url A url to redirect to. Can be a string or a Cake array-based url From b1630d4f07b6cbb3cb27c57d5752eb4e998b9406 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sat, 18 Dec 2010 15:48:14 -0500 Subject: [PATCH 09/59] Removing garbage from Paginator test cases. --- .../libs/view/helpers/paginator.test.php | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/cake/tests/cases/libs/view/helpers/paginator.test.php b/cake/tests/cases/libs/view/helpers/paginator.test.php index 2d0bda4a9..2e60552c5 100644 --- a/cake/tests/cases/libs/view/helpers/paginator.test.php +++ b/cake/tests/cases/libs/view/helpers/paginator.test.php @@ -139,8 +139,8 @@ class PaginatorHelperTest extends CakeTestCase { Router::reload(); Router::parse('/'); Router::setRequestInfo(array( - array('plugin' => null, 'controller' => 'accounts', 'action' => 'index', 'pass' => array(), 'form' => array(), 'url' => array('url' => 'accounts/'), 'bare' => 0), - array('plugin' => null, 'controller' => null, 'action' => null, 'base' => '/officespace', 'here' => '/officespace/accounts/', 'webroot' => '/officespace/', 'passedArgs' => array()) + array('plugin' => null, 'controller' => 'accounts', 'action' => 'index', 'pass' => array(), 'url' => array('url' => 'accounts/')), + array('base' => '/officespace', 'here' => '/officespace/accounts/', 'webroot' => '/officespace/') )); $this->Paginator->options(array('url' => array('param'))); $result = $this->Paginator->sort('title'); @@ -279,9 +279,8 @@ class PaginatorHelperTest extends CakeTestCase { 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' => '/', 'here' => '/accounts/', - 'webroot' => '/', 'passedArgs' => array()) + 'url' => array('url' => 'accounts/', 'mod_rewrite' => 'true')), + array('base' => '/', 'here' => '/accounts/', 'webroot' => '/',) )); $this->Paginator->options(array('url' => array('param'))); @@ -313,7 +312,7 @@ class PaginatorHelperTest extends CakeTestCase { 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()) + array('base' => '/officespace', 'here' => '/officespace/accounts/', 'webroot' => '/officespace/') )); $this->Paginator->request->params['paging']['Article']['options']['order'] = array('Article.title' => 'desc'); @@ -444,7 +443,7 @@ class PaginatorHelperTest extends CakeTestCase { Router::reload(); Router::setRequestInfo(array( - array('pass' => array(), 'named' => array(), 'controller' => 'users', 'plugin' => null, 'action' => 'admin_index', 'prefix' => 'admin', 'admin' => true, 'url' => array('ext' => 'html', 'url' => 'admin/users'), 'form' => array()), + array('pass' => array(), 'named' => array(), 'controller' => 'users', 'plugin' => null, 'action' => 'admin_index', 'prefix' => 'admin', 'admin' => true, 'url' => array('ext' => 'html', 'url' => 'admin/users')), array('base' => '', 'here' => '/admin/users', 'webroot' => '/') )); Router::parse('/admin/users'); @@ -461,8 +460,8 @@ class PaginatorHelperTest extends CakeTestCase { Router::reload(); Router::setRequestInfo(array( - array('plugin' => null, 'controller' => 'test', 'action' => 'admin_index', 'pass' => array(), 'prefix' => 'admin', 'admin' => true, 'form' => array(), 'url' => array('url' => 'admin/test')), - array('plugin' => null, 'controller' => null, 'action' => null, 'base' => '', 'here' => '/admin/test', 'webroot' => '/') + array('plugin' => null, 'controller' => 'test', 'action' => 'admin_index', 'pass' => array(), 'prefix' => 'admin', 'admin' => true, 'url' => array('url' => 'admin/test')), + array('base' => '', 'here' => '/admin/test', 'webroot' => '/') )); Router::parse('/'); $this->Paginator->options(array('url' => array('param'))); @@ -532,7 +531,7 @@ class PaginatorHelperTest extends CakeTestCase { Router::setRequestInfo( array( array('controller' => 'posts', 'action' => 'index', 'form' => array(), 'url' => array(), 'plugin' => null), - array('plugin' => null, 'controller' => null, 'action' => null, 'base' => '', 'here' => 'posts/index', 'webroot' => '/') + array('base' => '', 'here' => 'posts/index', 'webroot' => '/') )); $this->Paginator->request->params['paging']['Article']['options']['page'] = 2; @@ -643,8 +642,8 @@ class PaginatorHelperTest extends CakeTestCase { Router::reload(); Router::parse('/'); Router::setRequestInfo(array( - array('plugin' => null, 'controller' => 'articles', 'action' => 'index', 'pass' => array('2'), 'named' => array('foo' => 'bar'), 'form' => array(), 'url' => array('url' => 'articles/index/2/foo:bar'), 'bare' => 0), - array('plugin' => null, 'controller' => null, 'action' => null, 'base' => '/', 'here' => '/articles/', 'webroot' => '/', 'passedArgs' => array(0 => '2', 'foo' => 'bar')) + array('plugin' => null, 'controller' => 'articles', 'action' => 'index', 'pass' => array('2'), 'named' => array('foo' => 'bar'), 'url' => array('url' => 'articles/index/2/foo:bar')), + array('base' => '/', 'here' => '/articles/', 'webroot' => '/', 'passedArgs' => array(0 => '2', 'foo' => 'bar')) )); $this->Paginator->request->params['paging'] = array( 'Article' => array( @@ -1932,8 +1931,8 @@ class PaginatorHelperTest extends CakeTestCase { 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()) + array('plugin' => null, 'controller' => 'accounts', 'action' => 'index', 'pass' => array(), 'url' => array('url' => 'accounts/', 'mod_rewrite' => 'true')), + array('base' => '/officespace', 'here' => '/officespace/accounts/', 'webroot' => '/officespace/', 'passedArgs' => array()) )); $this->Paginator->request->params['paging']['Article']['options']['order'] = array('Article.title' => 'asc'); From 9c1516e6a80589124b8111f74176e89edbea8b62 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sat, 18 Dec 2010 15:52:23 -0500 Subject: [PATCH 10/59] Fixing calltime pass by reference deprecation warnings. --- cake/libs/http_socket.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cake/libs/http_socket.php b/cake/libs/http_socket.php index b1df3f25d..ba56034bd 100644 --- a/cake/libs/http_socket.php +++ b/cake/libs/http_socket.php @@ -539,7 +539,7 @@ class HttpSocket extends CakeSocket { if (!method_exists($authClass, 'authentication')) { throw new SocketException(sprintf(__('The %s do not support authentication.'), $authClass)); } - call_user_func("$authClass::authentication", $this, &$this->_auth[$method]); + call_user_func("$authClass::authentication", $this, $this->_auth[$method]); } /** @@ -565,7 +565,7 @@ class HttpSocket extends CakeSocket { if (!method_exists($authClass, 'proxyAuthentication')) { throw new SocketException(sprintf(__('The %s do not support proxy authentication.'), $authClass)); } - call_user_func("$authClass::proxyAuthentication", $this, &$this->_proxy); + call_user_func("$authClass::proxyAuthentication", $this, $this->_proxy); } /** From eb9fe07472bb35c98b0186e94776a9d9e7330387 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sat, 18 Dec 2010 16:44:21 -0500 Subject: [PATCH 11/59] Adding query string generation into CakeRoute. This removes one more task from Router. Tests added Adding constants for the named param and querystring param sigils. --- cake/libs/route/cake_route.php | 53 ++++++++++++++----- .../cases/libs/route/cake_route.test.php | 24 +++++++++ 2 files changed, 65 insertions(+), 12 deletions(-) diff --git a/cake/libs/route/cake_route.php b/cake/libs/route/cake_route.php index bbe70bc63..7e4af3384 100644 --- a/cake/libs/route/cake_route.php +++ b/cake/libs/route/cake_route.php @@ -73,6 +73,16 @@ class CakeRoute { */ protected $_compiledRoute = null; +/** + * Constant for the sigil that indicates a route param is a named parameter. + */ + const SIGIL_NAMED = ':'; + +/** + * Constant for the sigil that indicates a route param is a query string parameter. + */ + const SIGIL_QUERYSTRING = '?'; + /** * HTTP header shortcut map. Used for evaluating header-based route expressions. * @@ -274,22 +284,27 @@ class CakeRoute { return false; } - $named = $pass = $diff = array(); + $named = $pass = $_query = array(); foreach ($url as $key => $value) { // pull out named params so comparisons later on are faster. - if ($key[0] === ':' && ($value !== false && $value !== null)) { + if ($key[0] === CakeRoute::SIGIL_NAMED && ($value !== false && $value !== null)) { $named[substr($key, 1)] = $value; unset($url[$key]); continue; } + + // pull out querystring params + if ($key[0] === CakeRoute::SIGIL_QUERYSTRING && ($value !== false && $value !== null)) { + $_query[substr($key, 1)] = $value; + unset($url[$key]); + continue; + } // keys that exist in the defaults and have different values cause match failures. $keyExists = array_key_exists($key, $defaults); if ($keyExists && $defaults[$key] != $value) { return false; - $diff[$key] = $value; - continue; } // If the key is a routed key, its not different yet. @@ -310,17 +325,11 @@ class CakeRoute { // keys that don't exist are different. if (!$keyExists && !empty($value)) { return false; - $diff[$key] = $value; } } //if a not a greedy route, no extra params are allowed. - if (!$this->_greedy && ( (!empty($pass) || !empty($named)) || array_diff_key($diff, $keyNames) != array()) ) { - return false; - } - - //still some left over parameters that weren't named or passed args, bail. - if (!empty($diff)) { + if (!$this->_greedy && (!empty($pass) || !empty($named))) { return false; } @@ -332,7 +341,7 @@ class CakeRoute { } } } - return $this->_writeUrl(array_merge($url, compact('pass', 'named'))); + return $this->_writeUrl(array_merge($url, compact('pass', 'named', '_query'))); } /** @@ -385,7 +394,27 @@ class CakeRoute { if (strpos($this->template, '*')) { $out = str_replace('*', $params['pass'], $out); } + if (!empty($params['_query'])) { + $out .= $this->queryString($params['_query']); + } $out = str_replace('//', '/', $out); return $out; } + +/** + * Generates a well-formed querystring from $q + * + * Will compose an array or nested array into a proper querystring. + * + * @param mixed $q An array of parameters to compose into a query string. + * @param bool $escape Whether or not to use escaped & + * @return string + */ + public function queryString($q, $escape = false) { + $join = '&'; + if ($escape === true) { + $join = '&'; + } + return '?' . http_build_query($q, null, $join); + } } \ No newline at end of file diff --git a/cake/tests/cases/libs/route/cake_route.test.php b/cake/tests/cases/libs/route/cake_route.test.php index 279e305f1..5e6860fba 100644 --- a/cake/tests/cases/libs/route/cake_route.test.php +++ b/cake/tests/cases/libs/route/cake_route.test.php @@ -472,4 +472,28 @@ class CakeRouteTestCase extends CakeTestCase { $result = $route->parse('/blog/foobar'); $this->assertFalse($result); } + +/** + * test sigil based query string params + * + * @return void + */ + function testQueryStringParams() { + $route = new CakeRoute('/:controller/:action/*'); + $result = $route->match(array('controller' => 'posts', 'action' => 'index', '?test' => 'value')); + $expected = '/posts/index/?test=value'; + $this->assertEquals($expected, $result); + + $result = $route->match(array( + 'controller' => 'posts', 'action' => 'index', '?test' => array(1, 2, 3) + )); + $expected = '/posts/index/?test%5B0%5D=1&test%5B1%5D=2&test%5B2%5D=3'; + $this->assertEquals($expected, $result); + + $result = $route->match(array( + 'controller' => 'posts', 'action' => 'index', '?test' => 'value', '?other' => 'value' + )); + $expected = '/posts/index/?test=value&other=value'; + $this->assertEquals($expected, $result); + } } \ No newline at end of file From 319e622151669ad2bfe0f6ec27629bd21073229c Mon Sep 17 00:00:00 2001 From: mark_story Date: Sat, 18 Dec 2010 16:51:54 -0500 Subject: [PATCH 12/59] Added another test for querystring params. Querystring params should not be affected by greedy routes, as they are not really controlled by internal routing. --- cake/tests/cases/libs/route/cake_route.test.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/cake/tests/cases/libs/route/cake_route.test.php b/cake/tests/cases/libs/route/cake_route.test.php index 5e6860fba..3107e0084 100644 --- a/cake/tests/cases/libs/route/cake_route.test.php +++ b/cake/tests/cases/libs/route/cake_route.test.php @@ -496,4 +496,16 @@ class CakeRouteTestCase extends CakeTestCase { $expected = '/posts/index/?test=value&other=value'; $this->assertEquals($expected, $result); } + +/** + * test that querystring params work with non greedy routes. + * + * @return void + */ + function testQueryStringNonGreedy() { + $route = new CakeRoute('/:controller/:action'); + $result = $route->match(array('controller' => 'posts', 'action' => 'index', '?test' => 'value')); + $expected = '/posts/index?test=value'; + $this->assertEquals($expected, $result); + } } \ No newline at end of file From 7c6af5bfec02caa636f5722890f22ff501dac64b Mon Sep 17 00:00:00 2001 From: mark_story Date: Sat, 18 Dec 2010 17:01:26 -0500 Subject: [PATCH 13/59] Making a test actually test what its supposed to. --- cake/tests/cases/libs/router.test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cake/tests/cases/libs/router.test.php b/cake/tests/cases/libs/router.test.php index 6d2f700f6..e05e50149 100644 --- a/cake/tests/cases/libs/router.test.php +++ b/cake/tests/cases/libs/router.test.php @@ -1350,7 +1350,7 @@ class RouterTest extends CakeTestCase { Router::reload(); Router::connect('/:controller/:action/*'); Router::connectNamed(array('page'), array('default' => false, 'greedy' => false)); - $result = Router::parse('/categories/index?limit=5'); + $result = Router::parse('/categories/index/limit=5'); $this->assertTrue(empty($result['named'])); } From a6cca7c03613dd007ccf8f21c36af1b63f7cd2df Mon Sep 17 00:00:00 2001 From: mark_story Date: Sat, 18 Dec 2010 17:09:30 -0500 Subject: [PATCH 14/59] Extracting a method from paginator component. --- cake/libs/controller/components/paginator.php | 83 +++++++++++-------- 1 file changed, 47 insertions(+), 36 deletions(-) diff --git a/cake/libs/controller/components/paginator.php b/cake/libs/controller/components/paginator.php index 9bf89f991..4a0bd2ffc 100644 --- a/cake/libs/controller/components/paginator.php +++ b/cake/libs/controller/components/paginator.php @@ -64,42 +64,7 @@ class PaginatorComponent extends Component { } $assoc = null; - if (is_string($object)) { - $assoc = null; - if (strpos($object, '.') !== false) { - list($object, $assoc) = pluginSplit($object); - } - - if ($assoc && isset($this->Controller->{$object}->{$assoc})) { - $object = $this->Controller->{$object}->{$assoc}; - } elseif ( - $assoc && isset($this->Controller->{$this->Controller->modelClass}) && - isset($this->Controller->{$this->Controller->modelClass}->{$assoc} - )) { - $object = $this->Controller->{$this->Controller->modelClass}->{$assoc}; - } elseif (isset($this->Controller->{$object})) { - $object = $this->Controller->{$object}; - } elseif ( - isset($this->Controller->{$this->Controller->modelClass}) && isset($this->Controller->{$this->Controller->modelClass}->{$object} - )) { - $object = $this->Controller->{$this->Controller->modelClass}->{$object}; - } - } elseif (empty($object) || $object === null) { - if (isset($this->Controller->{$this->Controller->modelClass})) { - $object = $this->Controller->{$this->Controller->modelClass}; - } else { - $className = null; - $name = $this->Controller->uses[0]; - if (strpos($this->Controller->uses[0], '.') !== false) { - list($name, $className) = explode('.', $this->Controller->uses[0]); - } - if ($className) { - $object = $this->Controller->{$className}; - } else { - $object = $this->Controller->{$name}; - } - } - } + $object = $this->_getObject($object); if (!is_object($object)) { throw new MissingModelException($object); @@ -252,4 +217,50 @@ class PaginatorComponent extends Component { } return $results; } + +/** + * Get the object pagination will occur on. + * + * @param mixed $object The object you are looking for. + * @return mixed The model object to paginate on. + */ + protected function _getObject($object) { + if (is_string($object)) { + $assoc = null; + if (strpos($object, '.') !== false) { + list($object, $assoc) = pluginSplit($object); + } + + if ($assoc && isset($this->Controller->{$object}->{$assoc})) { + $object = $this->Controller->{$object}->{$assoc}; + } elseif ( + $assoc && isset($this->Controller->{$this->Controller->modelClass}) && + isset($this->Controller->{$this->Controller->modelClass}->{$assoc} + )) { + $object = $this->Controller->{$this->Controller->modelClass}->{$assoc}; + } elseif (isset($this->Controller->{$object})) { + $object = $this->Controller->{$object}; + } elseif ( + isset($this->Controller->{$this->Controller->modelClass}) && isset($this->Controller->{$this->Controller->modelClass}->{$object} + )) { + $object = $this->Controller->{$this->Controller->modelClass}->{$object}; + } + } elseif (empty($object) || $object === null) { + if (isset($this->Controller->{$this->Controller->modelClass})) { + $object = $this->Controller->{$this->Controller->modelClass}; + } else { + $className = null; + $name = $this->Controller->uses[0]; + if (strpos($this->Controller->uses[0], '.') !== false) { + list($name, $className) = explode('.', $this->Controller->uses[0]); + } + if ($className) { + $object = $this->Controller->{$className}; + } else { + $object = $this->Controller->{$name}; + } + } + } + return $object; + } } \ No newline at end of file From 6b9d9f4aea1f542eda766ea6bb68520739bfdaaf Mon Sep 17 00:00:00 2001 From: mark_story Date: Sat, 18 Dec 2010 17:17:43 -0500 Subject: [PATCH 15/59] Reapplying changes in [33d2f9a6ed7adfee0aedd0fd20d9f31368ef1836] as they got lost when the paginator component was extracted. --- cake/libs/controller/components/paginator.php | 5 ++- .../controller/components/paginator.test.php | 39 +++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/cake/libs/controller/components/paginator.php b/cake/libs/controller/components/paginator.php index 4a0bd2ffc..26e36b367 100644 --- a/cake/libs/controller/components/paginator.php +++ b/cake/libs/controller/components/paginator.php @@ -43,7 +43,7 @@ class PaginatorComponent extends Component { * @param array $settings Array of configuration settings. */ public function __construct(ComponentCollection $collection, $settings = array()) { - $settings = array_merge(array('page' => 1, 'limit' => 20), (array)$settings); + $settings = array_merge(array('page' => 1, 'limit' => 20, 'maxLimit' => 100), (array)$settings); $this->Controller = $collection->getController(); parent::__construct($collection, $settings); } @@ -146,6 +146,7 @@ class PaginatorComponent extends Component { if (empty($options['limit']) || $options['limit'] < 1) { $options['limit'] = 1; } + $options['limit'] = min((int)$options['limit'], $options['maxLimit']); extract($options); @@ -181,7 +182,7 @@ class PaginatorComponent extends Component { } elseif (intval($page) < 1) { $options['page'] = $page = 1; } - $page = $options['page'] = (integer)$page; + $page = $options['page'] = (int)$page; if (method_exists($object, 'paginate')) { $results = $object->paginate( diff --git a/cake/tests/cases/libs/controller/components/paginator.test.php b/cake/tests/cases/libs/controller/components/paginator.test.php index 20abfd8da..82efa4b02 100644 --- a/cake/tests/cases/libs/controller/components/paginator.test.php +++ b/cake/tests/cases/libs/controller/components/paginator.test.php @@ -486,4 +486,43 @@ class PaginatorTest extends CakeTestCase { $Controller->constructClasses(); $Controller->Paginator->paginate('MissingModel'); } + +/** + * testPaginateMaxLimit + * + * @return void + * @access public + */ + function testPaginateMaxLimit() { + $request = new CakeRequest('controller_posts/index'); + $request->params['pass'] = $request->params['named'] = array(); + + $Controller = new Controller($request); + + $Controller->uses = array('ControllerPost', 'ControllerComment'); + $Controller->passedArgs[] = '1'; + $Controller->params['url'] = array(); + $Controller->constructClasses(); + + $Controller->passedArgs = array('contain' => array('ControllerComment'), 'limit' => '1000'); + $result = $Controller->paginate('ControllerPost'); + $this->assertEqual($Controller->params['paging']['ControllerPost']['options']['limit'], 100); + + $Controller->passedArgs = array('contain' => array('ControllerComment'), 'limit' => '1000', 'maxLimit' => 1000); + $result = $Controller->paginate('ControllerPost'); + $this->assertEqual($Controller->params['paging']['ControllerPost']['options']['limit'], 100); + + $Controller->passedArgs = array('contain' => array('ControllerComment'), 'limit' => '10'); + $result = $Controller->paginate('ControllerPost'); + $this->assertEqual($Controller->params['paging']['ControllerPost']['options']['limit'], 10); + + $Controller->passedArgs = array('contain' => array('ControllerComment'), 'limit' => '1000'); + $Controller->paginate = array('maxLimit' => 2000); + $result = $Controller->paginate('ControllerPost'); + $this->assertEqual($Controller->params['paging']['ControllerPost']['options']['limit'], 1000); + + $Controller->passedArgs = array('contain' => array('ControllerComment'), 'limit' => '5000'); + $result = $Controller->paginate('ControllerPost'); + $this->assertEqual($Controller->params['paging']['ControllerPost']['options']['limit'], 2000); + } } \ No newline at end of file From 54c52d85fb559fb25dfa25c6864b0f4eb0d4e0cf Mon Sep 17 00:00:00 2001 From: mark_story Date: Sat, 18 Dec 2010 17:28:28 -0500 Subject: [PATCH 16/59] Fixing failing tests in PaginatorComponent. --- cake/libs/controller/components/paginator.php | 2 +- .../controller/components/paginator.test.php | 92 ++++++++++++------- 2 files changed, 58 insertions(+), 36 deletions(-) diff --git a/cake/libs/controller/components/paginator.php b/cake/libs/controller/components/paginator.php index 26e36b367..1dcd23bb5 100644 --- a/cake/libs/controller/components/paginator.php +++ b/cake/libs/controller/components/paginator.php @@ -141,7 +141,7 @@ class PaginatorComponent extends Component { unset($defaults[0]); } - $options = array_merge(array('page' => 1, 'limit' => 20), $defaults, $options); + $options = array_merge(array('page' => 1, 'limit' => 20, 'maxLimit' => 100), $defaults, $options); $options['limit'] = (int) $options['limit']; if (empty($options['limit']) || $options['limit'] < 1) { $options['limit'] = 1; diff --git a/cake/tests/cases/libs/controller/components/paginator.test.php b/cake/tests/cases/libs/controller/components/paginator.test.php index 82efa4b02..4efa5e785 100644 --- a/cake/tests/cases/libs/controller/components/paginator.test.php +++ b/cake/tests/cases/libs/controller/components/paginator.test.php @@ -269,13 +269,13 @@ class PaginatorTest extends CakeTestCase { $this->assertEqual($results, array(1, 3, 2)); $Controller->passedArgs = array('page' => '1 " onclick="alert(\'xss\');">'); - $Controller->Paginator->settings = array('limit' => 1); + $Controller->Paginator->settings = array('limit' => 1, 'maxLimit' => 10); $Controller->Paginator->paginate('PaginatorControllerPost'); $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['page'], 1, 'XSS exploit opened %s'); $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['options']['page'], 1, 'XSS exploit opened %s'); $Controller->passedArgs = array(); - $Controller->Paginator->settings = array('limit' => 0); + $Controller->Paginator->settings = array('limit' => 0, 'maxLimit' => 10); $Controller->Paginator->paginate('PaginatorControllerPost'); $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['page'], 1); $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['pageCount'], 3); @@ -283,7 +283,7 @@ class PaginatorTest extends CakeTestCase { $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['nextPage'], true); $Controller->passedArgs = array(); - $Controller->Paginator->settings = array('limit' => 'garbage!'); + $Controller->Paginator->settings = array('limit' => 'garbage!', 'maxLimit' => 10); $Controller->Paginator->paginate('PaginatorControllerPost'); $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['page'], 1); $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['pageCount'], 3); @@ -291,7 +291,7 @@ class PaginatorTest extends CakeTestCase { $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['nextPage'], true); $Controller->passedArgs = array(); - $Controller->Paginator->settings = array('limit' => '-1'); + $Controller->Paginator->settings = array('limit' => '-1', 'maxLimit' => 10); $Controller->Paginator->paginate('PaginatorControllerPost'); $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['page'], 1); $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['pageCount'], 3); @@ -323,19 +323,23 @@ class PaginatorTest extends CakeTestCase { $this->assertTrue(!isset($Controller->PaginatorControllerPost->lastQuery['contain'])); $Controller->passedArgs = array('page' => '-1'); - $Controller->Paginator->settings = array('PaginatorControllerPost' => array('contain' => array('PaginatorControllerComment'))); + $Controller->Paginator->settings = array( + 'PaginatorControllerPost' => array('contain' => array('PaginatorControllerComment'), 'maxLimit' => 10), + ); $result = $Controller->Paginator->paginate('PaginatorControllerPost'); $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['page'], 1); $this->assertEqual(Set::extract($result, '{n}.PaginatorControllerPost.id'), array(1, 2, 3)); $this->assertTrue(isset($Controller->PaginatorControllerPost->lastQuery['contain'])); - $Controller->Paginator->settings = array('PaginatorControllerPost' => array('popular', 'fields' => array('id', 'title'))); + $Controller->Paginator->settings = array( + 'PaginatorControllerPost' => array('popular', 'fields' => array('id', 'title'), 'maxLimit' => 10), + ); $result = $Controller->Paginator->paginate('PaginatorControllerPost'); $this->assertEqual(Set::extract($result, '{n}.PaginatorControllerPost.id'), array(2, 3)); $this->assertEqual($Controller->PaginatorControllerPost->lastQuery['conditions'], array('PaginatorControllerPost.id > ' => '1')); $Controller->passedArgs = array('limit' => 12); - $Controller->Paginator->settings = array('limit' => 30); + $Controller->Paginator->settings = array('limit' => 30, 'maxLimit' => 100); $result = $Controller->Paginator->paginate('PaginatorControllerPost'); $paging = $Controller->params['paging']['PaginatorControllerPost']; @@ -347,18 +351,31 @@ class PaginatorTest extends CakeTestCase { $Controller->params['url'] = array(); $Controller->constructClasses(); $Controller->Paginator->settings = array( - 'ControllerPaginateModel' => array('contain' => array('ControllerPaginateModel'), 'group' => 'Comment.author_id') + 'ControllerPaginateModel' => array( + 'contain' => array('ControllerPaginateModel'), + 'group' => 'Comment.author_id', + 'maxLimit' => 10 + ) ); $result = $Controller->Paginator->paginate('ControllerPaginateModel'); - $expected = array('contain' => array('ControllerPaginateModel'), 'group' => 'Comment.author_id'); + $expected = array('contain' => array('ControllerPaginateModel'), 'group' => 'Comment.author_id', 'maxLimit' => 10); $this->assertEqual($Controller->ControllerPaginateModel->extra, $expected); $this->assertEqual($Controller->ControllerPaginateModel->extraCount, $expected); $Controller->Paginator->settings = array( - 'ControllerPaginateModel' => array('foo', 'contain' => array('ControllerPaginateModel'), 'group' => 'Comment.author_id') + 'ControllerPaginateModel' => array( + 'foo', 'contain' => array('ControllerPaginateModel'), + 'group' => 'Comment.author_id', + 'maxLimit' => 10 + ) ); $Controller->Paginator->paginate('ControllerPaginateModel'); - $expected = array('contain' => array('ControllerPaginateModel'), 'group' => 'Comment.author_id', 'type' => 'foo'); + $expected = array( + 'contain' => array('ControllerPaginateModel'), + 'group' => 'Comment.author_id', + 'type' => 'foo', + 'maxLimit' => 10 + ); $this->assertEqual($Controller->ControllerPaginateModel->extra, $expected); $this->assertEqual($Controller->ControllerPaginateModel->extraCount, $expected); } @@ -383,15 +400,17 @@ class PaginatorTest extends CakeTestCase { 'order' => '', 'limit' => 5, 'page' => 1, - 'recursive' => -1 + 'recursive' => -1, + 'maxLimit' => 10 ); $conditions = array(); - $Controller->Paginator->paginate('PaginatorControllerPost',$conditions); + $Controller->Paginator->paginate('PaginatorControllerPost', $conditions); $expected = array( 'fields' => array(), 'order' => '', 'limit' => 5, + 'maxLimit' => 10, 'page' => 1, 'recursive' => -1, 'conditions' => array() @@ -414,7 +433,9 @@ class PaginatorTest extends CakeTestCase { $Controller->params['url'] = array(); $Controller->constructClasses(); - $Controller->Paginator->settings = array('PaginatorControllerPost' => array('popular', 'fields' => array('id', 'title'))); + $Controller->Paginator->settings = array( + 'PaginatorControllerPost' => array('popular', 'fields' => array('id', 'title'), 'maxLimit' => 10) + ); $result = $Controller->Paginator->paginate('PaginatorControllerPost'); $this->assertEqual(Set::extract($result, '{n}.PaginatorControllerPost.id'), array(2, 3)); @@ -437,7 +458,7 @@ class PaginatorTest extends CakeTestCase { $Controller->modelClass = 'PaginatorControllerPost'; $Controller->params['url'] = array(); $Controller->constructClasses(); - $Controller->Paginator->settings = array('order' => 'PaginatorControllerPost.id DESC'); + $Controller->Paginator->settings = array('order' => 'PaginatorControllerPost.id DESC', 'maxLimit' => 10); $results = Set::extract($Controller->Paginator->paginate('PaginatorControllerPost'), '{n}.PaginatorControllerPost.id'); $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['defaults']['order'], 'PaginatorControllerPost.id DESC'); $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['order'], 'PaginatorControllerPost.id DESC'); @@ -463,7 +484,8 @@ class PaginatorTest extends CakeTestCase { $Controller->Paginator->settings = array( 'fields' => array('id', 'title', 'offset_test'), - 'order' => array('offset_test' => 'DESC') + 'order' => array('offset_test' => 'DESC'), + 'maxLimit' => 10 ); $result = $Controller->Paginator->paginate('PaginatorControllerPost'); $this->assertEqual(Set::extract($result, '{n}.PaginatorControllerPost.offset_test'), array(4, 3, 2)); @@ -484,7 +506,7 @@ class PaginatorTest extends CakeTestCase { $Controller = new PaginatorTestController($request); $Controller->constructClasses(); - $Controller->Paginator->paginate('MissingModel'); + $Controller->Paginator->paginate('MissingModel'); } /** @@ -496,33 +518,33 @@ class PaginatorTest extends CakeTestCase { function testPaginateMaxLimit() { $request = new CakeRequest('controller_posts/index'); $request->params['pass'] = $request->params['named'] = array(); - + $Controller = new Controller($request); - - $Controller->uses = array('ControllerPost', 'ControllerComment'); + + $Controller->uses = array('PaginatorControllerPost', 'ControllerComment'); $Controller->passedArgs[] = '1'; $Controller->params['url'] = array(); $Controller->constructClasses(); - + $Controller->passedArgs = array('contain' => array('ControllerComment'), 'limit' => '1000'); - $result = $Controller->paginate('ControllerPost'); - $this->assertEqual($Controller->params['paging']['ControllerPost']['options']['limit'], 100); - + $result = $Controller->paginate('PaginatorControllerPost'); + $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['limit'], 100); + $Controller->passedArgs = array('contain' => array('ControllerComment'), 'limit' => '1000', 'maxLimit' => 1000); - $result = $Controller->paginate('ControllerPost'); - $this->assertEqual($Controller->params['paging']['ControllerPost']['options']['limit'], 100); - + $result = $Controller->paginate('PaginatorControllerPost'); + $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['limit'], 100); + $Controller->passedArgs = array('contain' => array('ControllerComment'), 'limit' => '10'); - $result = $Controller->paginate('ControllerPost'); - $this->assertEqual($Controller->params['paging']['ControllerPost']['options']['limit'], 10); - + $result = $Controller->paginate('PaginatorControllerPost'); + $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['limit'], 10); + $Controller->passedArgs = array('contain' => array('ControllerComment'), 'limit' => '1000'); $Controller->paginate = array('maxLimit' => 2000); - $result = $Controller->paginate('ControllerPost'); - $this->assertEqual($Controller->params['paging']['ControllerPost']['options']['limit'], 1000); - + $result = $Controller->paginate('PaginatorControllerPost'); + $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['limit'], 1000); + $Controller->passedArgs = array('contain' => array('ControllerComment'), 'limit' => '5000'); - $result = $Controller->paginate('ControllerPost'); - $this->assertEqual($Controller->params['paging']['ControllerPost']['options']['limit'], 2000); + $result = $Controller->paginate('PaginatorControllerPost'); + $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['limit'], 2000); } } \ No newline at end of file From 7585b2941efb44b2dc47333b46ab9ede18ec7755 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 19 Dec 2010 00:38:20 -0500 Subject: [PATCH 17/59] Adding paramType to the test cases. --- cake/libs/controller/components/paginator.php | 23 ++++++-- .../controller/components/paginator.test.php | 55 +++++++++++++------ 2 files changed, 57 insertions(+), 21 deletions(-) diff --git a/cake/libs/controller/components/paginator.php b/cake/libs/controller/components/paginator.php index 1dcd23bb5..554e58d99 100644 --- a/cake/libs/controller/components/paginator.php +++ b/cake/libs/controller/components/paginator.php @@ -30,11 +30,25 @@ class PaginatorComponent extends Component { /** - * Pagination settings + * Pagination settings. These settings control pagination at a general level. + * You can also define sub arrays for pagination settings for specific models. + * + * - `maxLimit` The maximum limit users can choose to view. Defaults to 100 + * - `limit` The initial number of items per page. Defaults to 20. + * - `page` The starting page, defaults to 1. + * - `paramType` What type of parameters you want pagination to use? + * - `named` Use named parameters. + * - `querystring` Use query string parameters. + * - `route` Use routed parameters, these require you to setup routes that include the pagination params * * @var array */ - public $settings = array(); + public $settings = array( + 'page' => 1, + 'limit' => 20, + 'maxLimit' => 100, + 'paramType' => 'named' + ); /** * Constructor @@ -43,7 +57,7 @@ class PaginatorComponent extends Component { * @param array $settings Array of configuration settings. */ public function __construct(ComponentCollection $collection, $settings = array()) { - $settings = array_merge(array('page' => 1, 'limit' => 20, 'maxLimit' => 100), (array)$settings); + $settings = array_merge($this->settings, (array)$settings); $this->Controller = $collection->getController(); parent::__construct($collection, $settings); } @@ -203,7 +217,8 @@ class PaginatorComponent extends Component { 'nextPage' => ($count > ($page * $limit)), 'pageCount' => $pageCount, 'defaults' => array_merge(array('limit' => 20, 'step' => 1), $defaults), - 'options' => $options + 'options' => $options, + 'paramType' => $options['paramType'] ); if (!isset($this->Controller->request['paging'])) { $this->Controller->request['paging'] = array(); diff --git a/cake/tests/cases/libs/controller/components/paginator.test.php b/cake/tests/cases/libs/controller/components/paginator.test.php index 4efa5e785..096c523c5 100644 --- a/cake/tests/cases/libs/controller/components/paginator.test.php +++ b/cake/tests/cases/libs/controller/components/paginator.test.php @@ -269,13 +269,13 @@ class PaginatorTest extends CakeTestCase { $this->assertEqual($results, array(1, 3, 2)); $Controller->passedArgs = array('page' => '1 " onclick="alert(\'xss\');">'); - $Controller->Paginator->settings = array('limit' => 1, 'maxLimit' => 10); + $Controller->Paginator->settings = array('limit' => 1, 'maxLimit' => 10, 'paramType' => 'named'); $Controller->Paginator->paginate('PaginatorControllerPost'); $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['page'], 1, 'XSS exploit opened %s'); $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['options']['page'], 1, 'XSS exploit opened %s'); $Controller->passedArgs = array(); - $Controller->Paginator->settings = array('limit' => 0, 'maxLimit' => 10); + $Controller->Paginator->settings = array('limit' => 0, 'maxLimit' => 10, 'paramType' => 'named'); $Controller->Paginator->paginate('PaginatorControllerPost'); $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['page'], 1); $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['pageCount'], 3); @@ -283,7 +283,7 @@ class PaginatorTest extends CakeTestCase { $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['nextPage'], true); $Controller->passedArgs = array(); - $Controller->Paginator->settings = array('limit' => 'garbage!', 'maxLimit' => 10); + $Controller->Paginator->settings = array('limit' => 'garbage!', 'maxLimit' => 10, 'paramType' => 'named'); $Controller->Paginator->paginate('PaginatorControllerPost'); $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['page'], 1); $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['pageCount'], 3); @@ -291,7 +291,7 @@ class PaginatorTest extends CakeTestCase { $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['nextPage'], true); $Controller->passedArgs = array(); - $Controller->Paginator->settings = array('limit' => '-1', 'maxLimit' => 10); + $Controller->Paginator->settings = array('limit' => '-1', 'maxLimit' => 10, 'paramType' => 'named'); $Controller->Paginator->paginate('PaginatorControllerPost'); $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['page'], 1); $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['pageCount'], 3); @@ -324,7 +324,11 @@ class PaginatorTest extends CakeTestCase { $Controller->passedArgs = array('page' => '-1'); $Controller->Paginator->settings = array( - 'PaginatorControllerPost' => array('contain' => array('PaginatorControllerComment'), 'maxLimit' => 10), + 'PaginatorControllerPost' => array( + 'contain' => array('PaginatorControllerComment'), + 'maxLimit' => 10, + 'paramType' => 'named' + ), ); $result = $Controller->Paginator->paginate('PaginatorControllerPost'); $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['page'], 1); @@ -332,14 +336,16 @@ class PaginatorTest extends CakeTestCase { $this->assertTrue(isset($Controller->PaginatorControllerPost->lastQuery['contain'])); $Controller->Paginator->settings = array( - 'PaginatorControllerPost' => array('popular', 'fields' => array('id', 'title'), 'maxLimit' => 10), + 'PaginatorControllerPost' => array( + 'popular', 'fields' => array('id', 'title'), 'maxLimit' => 10, 'paramType' => 'named' + ), ); $result = $Controller->Paginator->paginate('PaginatorControllerPost'); $this->assertEqual(Set::extract($result, '{n}.PaginatorControllerPost.id'), array(2, 3)); $this->assertEqual($Controller->PaginatorControllerPost->lastQuery['conditions'], array('PaginatorControllerPost.id > ' => '1')); $Controller->passedArgs = array('limit' => 12); - $Controller->Paginator->settings = array('limit' => 30, 'maxLimit' => 100); + $Controller->Paginator->settings = array('limit' => 30, 'maxLimit' => 100, 'paramType' => 'named'); $result = $Controller->Paginator->paginate('PaginatorControllerPost'); $paging = $Controller->params['paging']['PaginatorControllerPost']; @@ -354,11 +360,17 @@ class PaginatorTest extends CakeTestCase { 'ControllerPaginateModel' => array( 'contain' => array('ControllerPaginateModel'), 'group' => 'Comment.author_id', - 'maxLimit' => 10 + 'maxLimit' => 10, + 'paramType' => 'named' ) ); $result = $Controller->Paginator->paginate('ControllerPaginateModel'); - $expected = array('contain' => array('ControllerPaginateModel'), 'group' => 'Comment.author_id', 'maxLimit' => 10); + $expected = array( + 'contain' => array('ControllerPaginateModel'), + 'group' => 'Comment.author_id', + 'maxLimit' => 10, + 'paramType' => 'named' + ); $this->assertEqual($Controller->ControllerPaginateModel->extra, $expected); $this->assertEqual($Controller->ControllerPaginateModel->extraCount, $expected); @@ -366,7 +378,8 @@ class PaginatorTest extends CakeTestCase { 'ControllerPaginateModel' => array( 'foo', 'contain' => array('ControllerPaginateModel'), 'group' => 'Comment.author_id', - 'maxLimit' => 10 + 'maxLimit' => 10, + 'paramType' => 'named' ) ); $Controller->Paginator->paginate('ControllerPaginateModel'); @@ -374,7 +387,8 @@ class PaginatorTest extends CakeTestCase { 'contain' => array('ControllerPaginateModel'), 'group' => 'Comment.author_id', 'type' => 'foo', - 'maxLimit' => 10 + 'maxLimit' => 10, + 'paramType' => 'named' ); $this->assertEqual($Controller->ControllerPaginateModel->extra, $expected); $this->assertEqual($Controller->ControllerPaginateModel->extraCount, $expected); @@ -401,7 +415,8 @@ class PaginatorTest extends CakeTestCase { 'limit' => 5, 'page' => 1, 'recursive' => -1, - 'maxLimit' => 10 + 'maxLimit' => 10, + 'paramType' => 'named' ); $conditions = array(); $Controller->Paginator->paginate('PaginatorControllerPost', $conditions); @@ -413,7 +428,8 @@ class PaginatorTest extends CakeTestCase { 'maxLimit' => 10, 'page' => 1, 'recursive' => -1, - 'conditions' => array() + 'conditions' => array(), + 'paramType' => 'named' ); $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options'],$expected); } @@ -434,7 +450,9 @@ class PaginatorTest extends CakeTestCase { $Controller->constructClasses(); $Controller->Paginator->settings = array( - 'PaginatorControllerPost' => array('popular', 'fields' => array('id', 'title'), 'maxLimit' => 10) + 'PaginatorControllerPost' => array( + 'popular', 'fields' => array('id', 'title'), 'maxLimit' => 10, 'paramType' => 'named' + ) ); $result = $Controller->Paginator->paginate('PaginatorControllerPost'); @@ -458,7 +476,9 @@ class PaginatorTest extends CakeTestCase { $Controller->modelClass = 'PaginatorControllerPost'; $Controller->params['url'] = array(); $Controller->constructClasses(); - $Controller->Paginator->settings = array('order' => 'PaginatorControllerPost.id DESC', 'maxLimit' => 10); + $Controller->Paginator->settings = array( + 'order' => 'PaginatorControllerPost.id DESC', 'maxLimit' => 10, 'paramType' => 'named' + ); $results = Set::extract($Controller->Paginator->paginate('PaginatorControllerPost'), '{n}.PaginatorControllerPost.id'); $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['defaults']['order'], 'PaginatorControllerPost.id DESC'); $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['order'], 'PaginatorControllerPost.id DESC'); @@ -485,7 +505,8 @@ class PaginatorTest extends CakeTestCase { $Controller->Paginator->settings = array( 'fields' => array('id', 'title', 'offset_test'), 'order' => array('offset_test' => 'DESC'), - 'maxLimit' => 10 + 'maxLimit' => 10, + 'paramType' => 'named' ); $result = $Controller->Paginator->paginate('PaginatorControllerPost'); $this->assertEqual(Set::extract($result, '{n}.PaginatorControllerPost.offset_test'), array(4, 3, 2)); @@ -539,7 +560,7 @@ class PaginatorTest extends CakeTestCase { $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['limit'], 10); $Controller->passedArgs = array('contain' => array('ControllerComment'), 'limit' => '1000'); - $Controller->paginate = array('maxLimit' => 2000); + $Controller->paginate = array('maxLimit' => 2000, 'paramType' => 'named'); $result = $Controller->paginate('PaginatorControllerPost'); $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['limit'], 1000); From 6b3db0a3eb0ce3437b015b5ce07fc7afffc0602f Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 19 Dec 2010 01:42:23 -0500 Subject: [PATCH 18/59] Pulling out parameter merging logic into a method, this allows specific typing of parameter merging (named, querystring, route). Also simplifies whitelisting of request parameters. Tests added for merging and whitelisting. --- cake/libs/controller/components/paginator.php | 59 ++++++ .../controller/components/paginator.test.php | 190 +++++++++++++++--- 2 files changed, 216 insertions(+), 33 deletions(-) diff --git a/cake/libs/controller/components/paginator.php b/cake/libs/controller/components/paginator.php index 554e58d99..c0e237297 100644 --- a/cake/libs/controller/components/paginator.php +++ b/cake/libs/controller/components/paginator.php @@ -50,6 +50,17 @@ class PaginatorComponent extends Component { 'paramType' => 'named' ); +/** + * A list of request parameters users are allowed to set. Modifying + * this list will allow users to have more influence over pagination, + * be careful with what you permit. + * + * @var array + */ + public $whitelist = array( + 'limit', 'sort', 'page', 'direction' + ); + /** * Constructor * @@ -83,6 +94,9 @@ class PaginatorComponent extends Component { if (!is_object($object)) { throw new MissingModelException($object); } + + $options = $this->mergeOptions($object->alias, $scope, $whitelist); + $options = array_merge( $this->Controller->request->params, $this->Controller->request->query, @@ -279,4 +293,49 @@ class PaginatorComponent extends Component { } return $object; } + +/** + * Merges the various options that Pagination uses. + * Pulls settings together from the following places: + * + * - General pagination settings + * - Model specific settings. + * - Request parameters + * - $options argument. + * + * The result of this method is the aggregate of all the option sets combined together. + * + * @param string $alias Model alias being paginated, if the general settings has a key with this value + * that key's settings will be used for pagination instead of the general ones. + * @param string $options Per call options. + * @param string $whitelist A whitelist of options that are allowed from the request parameters. Modifying + * this array will allow you to permit more or less input from the user. + * @return array Array of merged options. + */ + public function mergeOptions($alias, $options, $whitelist = array()) { + if (isset($this->settings[$alias])) { + $defaults = $this->settings[$alias]; + } else { + $defaults = $this->settings; + } + if (empty($defaults['paramType'])) { + $defaults['paramType'] = 'named'; + } + switch ($defaults['paramType']) { + case 'named': + $request = $this->Controller->request->params['named']; + break; + case 'querystring': + $request = $this->Controller->request->query; + break; + case 'route': + $request = $this->Controller->request->params; + unset($request['pass'], $request['named']); + } + + $whitelist = array_flip(array_merge($this->whitelist, $whitelist)); + $request = array_intersect_key($request, $whitelist); + + return array_merge($defaults, $request, $options); + } } \ No newline at end of file diff --git a/cake/tests/cases/libs/controller/components/paginator.test.php b/cake/tests/cases/libs/controller/components/paginator.test.php index 096c523c5..25bf12a99 100644 --- a/cake/tests/cases/libs/controller/components/paginator.test.php +++ b/cake/tests/cases/libs/controller/components/paginator.test.php @@ -20,6 +20,7 @@ * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ App::import('Controller', 'Controller', false); +App::import('Component', 'Paginator'); App::import('Core', array('CakeRequest', 'CakeResponse')); /** @@ -210,6 +211,20 @@ class PaginatorTest extends CakeTestCase { */ public $fixtures = array('core.post', 'core.comment'); +/** + * setup + * + * @return void + */ + function setUp() { + parent::setUp(); + $this->request = new CakeRequest('controller_posts/index'); + $this->request->params['pass'] = $this->request->params['named'] = array(); + $this->Controller = new Controller($this->request); + $this->Paginator = new PaginatorComponent($this->getMock('ComponentCollection'), array()); + $this->Paginator->Controller = $this->Controller; + } + /** * testPaginate method * @@ -217,10 +232,7 @@ class PaginatorTest extends CakeTestCase { * @return void */ function testPaginate() { - $request = new CakeRequest('controller_posts/index'); - $request->params['pass'] = $request->params['named'] = array(); - - $Controller = new PaginatorTestController($request); + $Controller = new PaginatorTestController($this->request); $Controller->uses = array('PaginatorControllerPost', 'PaginatorControllerComment'); $Controller->passedArgs[] = '1'; $Controller->params['url'] = array(); @@ -306,10 +318,7 @@ class PaginatorTest extends CakeTestCase { * @return void */ function testPaginateExtraParams() { - $request = new CakeRequest('controller_posts/index'); - $request->params['pass'] = $request->params['named'] = array(); - - $Controller = new PaginatorTestController($request); + $Controller = new PaginatorTestController($this->request); $Controller->uses = array('PaginatorControllerPost', 'PaginatorControllerComment'); $Controller->passedArgs[] = '1'; @@ -352,7 +361,7 @@ class PaginatorTest extends CakeTestCase { $this->assertEqual($Controller->PaginatorControllerPost->lastQuery['limit'], 12); $this->assertEqual($paging['options']['limit'], 12); - $Controller = new PaginatorTestController($request); + $Controller = new PaginatorTestController($this->request); $Controller->uses = array('ControllerPaginateModel'); $Controller->params['url'] = array(); $Controller->constructClasses(); @@ -400,10 +409,7 @@ class PaginatorTest extends CakeTestCase { * @return void */ public function testPaginatePassedArgs() { - $request = new CakeRequest('controller_posts/index'); - $request->params['pass'] = $request->params['named'] = array(); - - $Controller = new PaginatorTestController($request); + $Controller = new PaginatorTestController($this->request); $Controller->uses = array('PaginatorControllerPost'); $Controller->passedArgs[] = array('1', '2', '3'); $Controller->params['url'] = array(); @@ -440,10 +446,7 @@ class PaginatorTest extends CakeTestCase { * @return void */ function testPaginateSpecialType() { - $request = new CakeRequest('controller_posts/index'); - $request->params['pass'] = $request->params['named'] = array(); - - $Controller = new PaginatorTestController($request); + $Controller = new PaginatorTestController($this->request); $Controller->uses = array('PaginatorControllerPost', 'PaginatorControllerComment'); $Controller->passedArgs[] = '1'; $Controller->params['url'] = array(); @@ -469,10 +472,7 @@ class PaginatorTest extends CakeTestCase { * @return void */ function testDefaultPaginateParams() { - $request = new CakeRequest('controller_posts/index'); - $request->params['pass'] = $request->params['named'] = array(); - - $Controller = new PaginatorTestController($request); + $Controller = new PaginatorTestController($this->request); $Controller->modelClass = 'PaginatorControllerPost'; $Controller->params['url'] = array(); $Controller->constructClasses(); @@ -491,10 +491,7 @@ class PaginatorTest extends CakeTestCase { * @return void */ function testPaginateOrderVirtualField() { - $request = new CakeRequest('controller_posts/index'); - $request->params['pass'] = $request->params['named'] = array(); - - $Controller = new PaginatorTestController($request); + $Controller = new PaginatorTestController($this->request); $Controller->uses = array('PaginatorControllerPost', 'PaginatorControllerComment'); $Controller->params['url'] = array(); $Controller->constructClasses(); @@ -522,10 +519,7 @@ class PaginatorTest extends CakeTestCase { * @expectedException MissingModelException */ function testPaginateMissingModel() { - $request = new CakeRequest('controller_posts/index'); - $request->params['pass'] = $request->params['named'] = array(); - - $Controller = new PaginatorTestController($request); + $Controller = new PaginatorTestController($this->request); $Controller->constructClasses(); $Controller->Paginator->paginate('MissingModel'); } @@ -537,10 +531,7 @@ class PaginatorTest extends CakeTestCase { * @access public */ function testPaginateMaxLimit() { - $request = new CakeRequest('controller_posts/index'); - $request->params['pass'] = $request->params['named'] = array(); - - $Controller = new Controller($request); + $Controller = new Controller($this->request); $Controller->uses = array('PaginatorControllerPost', 'ControllerComment'); $Controller->passedArgs[] = '1'; @@ -568,4 +559,137 @@ class PaginatorTest extends CakeTestCase { $result = $Controller->paginate('PaginatorControllerPost'); $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['limit'], 2000); } + +/** + * test that option merging prefers specific models + * + * @return void + */ + function testMergeOptionsModelSpecific() { + $this->Paginator->settings = array( + 'page' => 1, + 'limit' => 20, + 'maxLimit' => 100, + 'paramType' => 'named', + 'Post' => array( + 'page' => 1, + 'limit' => 10, + 'maxLimit' => 50, + 'paramType' => 'named', + ) + ); + $result = $this->Paginator->mergeOptions('Silly', array()); + $this->assertEquals($this->Paginator->settings, $result); + + $result = $this->Paginator->mergeOptions('Silly', array('limit' => 10)); + $this->assertEquals(10, $result['limit']); + + $result = $this->Paginator->mergeOptions('Post', array('sort' => 'title')); + $expected = array('page' => 1, 'limit' => 10, 'paramType' => 'named', 'sort' => 'title', 'maxLimit' => 50); + $this->assertEquals($expected, $result); + } + +/** + * test mergeOptions with named params. + * + * @return void + */ + function testMergeOptionsNamedParams() { + $this->request->params['named'] = array( + 'page' => 10, + 'limit' => 10 + ); + $this->Paginator->settings = array( + 'page' => 1, + 'limit' => 20, + 'maxLimit' => 100, + 'paramType' => 'named', + ); + $result = $this->Paginator->mergeOptions('Post', array()); + $expected = array('page' => 10, 'limit' => 10, 'maxLimit' => 100, 'paramType' => 'named'); + $this->assertEquals($expected, $result); + + $result = $this->Paginator->mergeOptions('Post', array('page' => 100)); + $this->assertEquals(100, $result['page'], 'Passed options should replace request params'); + } + +/** + * test merging options from the querystring. + * + * @return void + */ + function testMergeOptionsQueryString() { + $this->request->params['named'] = array( + 'page' => 10, + 'limit' => 10 + ); + $this->request->query = array( + 'page' => 99, + 'limit' => 75 + ); + $this->Paginator->settings = array( + 'page' => 1, + 'limit' => 20, + 'maxLimit' => 100, + 'paramType' => 'querystring', + ); + $result = $this->Paginator->mergeOptions('Post', array()); + $expected = array('page' => 99, 'limit' => 75, 'maxLimit' => 100, 'paramType' => 'querystring'); + $this->assertEquals($expected, $result); + + $result = $this->Paginator->mergeOptions('Post', array('page' => 100)); + $this->assertEquals(100, $result['page'], 'Passed options should replace request params'); + } + +/** + * test that the default whitelist doesn't let people screw with things they should not be allowed to. + * + * @return void + */ + function testMergeOptionsDefaultWhiteList() { + $this->request->params['named'] = array( + 'page' => 10, + 'limit' => 10, + 'fields' => array('bad.stuff'), + 'recursive' => 1000, + 'conditions' => array('bad.stuff'), + 'contain' => array('bad') + ); + $this->Paginator->settings = array( + 'page' => 1, + 'limit' => 20, + 'maxLimit' => 100, + 'paramType' => 'named', + ); + $result = $this->Paginator->mergeOptions('Post', array()); + $expected = array('page' => 10, 'limit' => 10, 'maxLimit' => 100, 'paramType' => 'named'); + $this->assertEquals($expected, $result); + } + +/** + * test that modifying the whitelist works. + * + * @return void + */ + function testMergeOptionsExtraWhitelist() { + $this->request->params['named'] = array( + 'page' => 10, + 'limit' => 10, + 'fields' => array('bad.stuff'), + 'recursive' => 1000, + 'conditions' => array('bad.stuff'), + 'contain' => array('bad') + ); + $this->Paginator->settings = array( + 'page' => 1, + 'limit' => 20, + 'maxLimit' => 100, + 'paramType' => 'named', + ); + $result = $this->Paginator->mergeOptions('Post', array(), array('fields')); + $expected = array( + 'page' => 10, 'limit' => 10, 'maxLimit' => 100, 'paramType' => 'named', 'fields' => array('bad.stuff') + ); + $this->assertEquals($expected, $result); + } } \ No newline at end of file From 7b11eeb6e001a9dfaa3d41b36cf350e2cd977ccd Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 19 Dec 2010 01:57:51 -0500 Subject: [PATCH 19/59] Updating tests to not use the deprecated Controller::$passedArgs. Removing messy and hard to understand defaults + whitelisting from paginate() now that it has a separate method. --- cake/libs/controller/components/paginator.php | 42 +++----------- .../controller/components/paginator.test.php | 56 ++++++++++--------- 2 files changed, 38 insertions(+), 60 deletions(-) diff --git a/cake/libs/controller/components/paginator.php b/cake/libs/controller/components/paginator.php index c0e237297..bb33177e1 100644 --- a/cake/libs/controller/components/paginator.php +++ b/cake/libs/controller/components/paginator.php @@ -94,21 +94,9 @@ class PaginatorComponent extends Component { if (!is_object($object)) { throw new MissingModelException($object); } - + $options = $this->mergeOptions($object->alias, $scope, $whitelist); - $options = array_merge( - $this->Controller->request->params, - $this->Controller->request->query, - $this->Controller->passedArgs - ); - - if (isset($this->settings[$object->alias])) { - $defaults = $this->settings[$object->alias]; - } else { - $defaults = $this->settings; - } - if (isset($options['show'])) { $options['limit'] = $options['show']; } @@ -142,34 +130,21 @@ class PaginatorComponent extends Component { $options['order'][$alias . '.' . $field] = $value; } } - $vars = array('fields', 'order', 'limit', 'page', 'recursive'); - $keys = array_keys($options); - $count = count($keys); - for ($i = 0; $i < $count; $i++) { - if (!in_array($keys[$i], $vars, true)) { - unset($options[$keys[$i]]); - } - if (empty($whitelist) && ($keys[$i] === 'fields' || $keys[$i] === 'recursive')) { - unset($options[$keys[$i]]); - } elseif (!empty($whitelist) && !in_array($keys[$i], $whitelist)) { - unset($options[$keys[$i]]); - } - } $conditions = $fields = $order = $limit = $page = $recursive = null; - if (!isset($defaults['conditions'])) { - $defaults['conditions'] = array(); + if (!isset($options['conditions'])) { + $options['conditions'] = array(); } $type = 'all'; - if (isset($defaults[0])) { - $type = $defaults[0]; - unset($defaults[0]); + if (isset($options[0])) { + $type = $options[0]; + unset($options[0]); } - $options = array_merge(array('page' => 1, 'limit' => 20, 'maxLimit' => 100), $defaults, $options); + $options = array_merge(array('page' => 1, 'limit' => 20, 'maxLimit' => 100), $options); $options['limit'] = (int) $options['limit']; if (empty($options['limit']) || $options['limit'] < 1) { $options['limit'] = 1; @@ -187,7 +162,7 @@ class PaginatorComponent extends Component { $recursive = $object->recursive; } - $extra = array_diff_key($defaults, compact( + $extra = array_diff_key($options, compact( 'conditions', 'fields', 'order', 'limit', 'page', 'recursive' )); if ($type !== 'all') { @@ -230,7 +205,6 @@ class PaginatorComponent extends Component { 'prevPage' => ($page > 1), 'nextPage' => ($count > ($page * $limit)), 'pageCount' => $pageCount, - 'defaults' => array_merge(array('limit' => 20, 'step' => 1), $defaults), 'options' => $options, 'paramType' => $options['paramType'] ); diff --git a/cake/tests/cases/libs/controller/components/paginator.test.php b/cake/tests/cases/libs/controller/components/paginator.test.php index 25bf12a99..063dfaf02 100644 --- a/cake/tests/cases/libs/controller/components/paginator.test.php +++ b/cake/tests/cases/libs/controller/components/paginator.test.php @@ -234,8 +234,8 @@ class PaginatorTest extends CakeTestCase { function testPaginate() { $Controller = new PaginatorTestController($this->request); $Controller->uses = array('PaginatorControllerPost', 'PaginatorControllerComment'); - $Controller->passedArgs[] = '1'; - $Controller->params['url'] = array(); + $Controller->request->params['pass'] = array('1'); + $Controller->request->query = array(); $Controller->constructClasses(); $results = Set::extract($Controller->Paginator->paginate('PaginatorControllerPost'), '{n}.PaginatorControllerPost.id'); @@ -250,43 +250,45 @@ class PaginatorTest extends CakeTestCase { $results = Set::extract($Controller->Paginator->paginate(), '{n}.PaginatorControllerPost.id'); $this->assertEqual($results, array(1, 2, 3)); - $Controller->passedArgs = array('page' => '-1'); + $Controller->request->params['named'] = array('page' => '-1'); $results = Set::extract($Controller->Paginator->paginate('PaginatorControllerPost'), '{n}.PaginatorControllerPost.id'); $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['page'], 1); $this->assertEqual($results, array(1, 2, 3)); - $Controller->passedArgs = array('sort' => 'PaginatorControllerPost.id', 'direction' => 'asc'); + $Controller->request->params['named'] = array('sort' => 'PaginatorControllerPost.id', 'direction' => 'asc'); $results = Set::extract($Controller->Paginator->paginate('PaginatorControllerPost'), '{n}.PaginatorControllerPost.id'); $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['page'], 1); $this->assertEqual($results, array(1, 2, 3)); - $Controller->passedArgs = array('sort' => 'PaginatorControllerPost.id', 'direction' => 'desc'); + $Controller->request->params['named'] = array('sort' => 'PaginatorControllerPost.id', 'direction' => 'desc'); $results = Set::extract($Controller->Paginator->paginate('PaginatorControllerPost'), '{n}.PaginatorControllerPost.id'); $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['page'], 1); $this->assertEqual($results, array(3, 2, 1)); - $Controller->passedArgs = array('sort' => 'id', 'direction' => 'desc'); + $Controller->request->params['named'] = array('sort' => 'id', 'direction' => 'desc'); $results = Set::extract($Controller->Paginator->paginate('PaginatorControllerPost'), '{n}.PaginatorControllerPost.id'); $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['page'], 1); $this->assertEqual($results, array(3, 2, 1)); - $Controller->passedArgs = array('sort' => 'NotExisting.field', 'direction' => 'desc'); + $Controller->request->params['named'] = array('sort' => 'NotExisting.field', 'direction' => 'desc'); $results = Set::extract($Controller->Paginator->paginate('PaginatorControllerPost'), '{n}.PaginatorControllerPost.id'); $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['page'], 1, 'Invalid field in query %s'); $this->assertEqual($results, array(1, 2, 3)); - $Controller->passedArgs = array('sort' => 'PaginatorControllerPost.author_id', 'direction' => 'allYourBase'); + $Controller->request->params['named'] = array( + 'sort' => 'PaginatorControllerPost.author_id', 'direction' => 'allYourBase' + ); $results = Set::extract($Controller->Paginator->paginate('PaginatorControllerPost'), '{n}.PaginatorControllerPost.id'); $this->assertEqual($Controller->PaginatorControllerPost->lastQuery['order'][0], array('PaginatorControllerPost.author_id' => 'asc')); $this->assertEqual($results, array(1, 3, 2)); - $Controller->passedArgs = array('page' => '1 " onclick="alert(\'xss\');">'); + $Controller->request->params['named'] = array('page' => '1 " onclick="alert(\'xss\');">'); $Controller->Paginator->settings = array('limit' => 1, 'maxLimit' => 10, 'paramType' => 'named'); $Controller->Paginator->paginate('PaginatorControllerPost'); $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['page'], 1, 'XSS exploit opened %s'); $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['options']['page'], 1, 'XSS exploit opened %s'); - $Controller->passedArgs = array(); + $Controller->request->params['named'] = array(); $Controller->Paginator->settings = array('limit' => 0, 'maxLimit' => 10, 'paramType' => 'named'); $Controller->Paginator->paginate('PaginatorControllerPost'); $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['page'], 1); @@ -294,7 +296,7 @@ class PaginatorTest extends CakeTestCase { $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['prevPage'], false); $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['nextPage'], true); - $Controller->passedArgs = array(); + $Controller->request->params['named'] = array(); $Controller->Paginator->settings = array('limit' => 'garbage!', 'maxLimit' => 10, 'paramType' => 'named'); $Controller->Paginator->paginate('PaginatorControllerPost'); $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['page'], 1); @@ -302,7 +304,7 @@ class PaginatorTest extends CakeTestCase { $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['prevPage'], false); $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['nextPage'], true); - $Controller->passedArgs = array(); + $Controller->request->params['named'] = array(); $Controller->Paginator->settings = array('limit' => '-1', 'maxLimit' => 10, 'paramType' => 'named'); $Controller->Paginator->paginate('PaginatorControllerPost'); $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['page'], 1); @@ -321,17 +323,17 @@ class PaginatorTest extends CakeTestCase { $Controller = new PaginatorTestController($this->request); $Controller->uses = array('PaginatorControllerPost', 'PaginatorControllerComment'); - $Controller->passedArgs[] = '1'; + $Controller->request->params['pass'] = array('1'); $Controller->params['url'] = array(); $Controller->constructClasses(); - $Controller->passedArgs = array('page' => '-1', 'contain' => array('PaginatorControllerComment')); + $Controller->request->params['named'] = array('page' => '-1', 'contain' => array('PaginatorControllerComment')); $result = $Controller->Paginator->paginate('PaginatorControllerPost'); $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['page'], 1); $this->assertEqual(Set::extract($result, '{n}.PaginatorControllerPost.id'), array(1, 2, 3)); $this->assertTrue(!isset($Controller->PaginatorControllerPost->lastQuery['contain'])); - $Controller->passedArgs = array('page' => '-1'); + $Controller->request->params['named'] = array('page' => '-1'); $Controller->Paginator->settings = array( 'PaginatorControllerPost' => array( 'contain' => array('PaginatorControllerComment'), @@ -353,7 +355,7 @@ class PaginatorTest extends CakeTestCase { $this->assertEqual(Set::extract($result, '{n}.PaginatorControllerPost.id'), array(2, 3)); $this->assertEqual($Controller->PaginatorControllerPost->lastQuery['conditions'], array('PaginatorControllerPost.id > ' => '1')); - $Controller->passedArgs = array('limit' => 12); + $Controller->request->params['named'] = array('limit' => 12); $Controller->Paginator->settings = array('limit' => 30, 'maxLimit' => 100, 'paramType' => 'named'); $result = $Controller->Paginator->paginate('PaginatorControllerPost'); $paging = $Controller->params['paging']['PaginatorControllerPost']; @@ -363,7 +365,7 @@ class PaginatorTest extends CakeTestCase { $Controller = new PaginatorTestController($this->request); $Controller->uses = array('ControllerPaginateModel'); - $Controller->params['url'] = array(); + $Controller->request->query = array(); $Controller->constructClasses(); $Controller->Paginator->settings = array( 'ControllerPaginateModel' => array( @@ -411,7 +413,7 @@ class PaginatorTest extends CakeTestCase { public function testPaginatePassedArgs() { $Controller = new PaginatorTestController($this->request); $Controller->uses = array('PaginatorControllerPost'); - $Controller->passedArgs[] = array('1', '2', '3'); + $Controller->request->params['pass'] = array('1', '2', '3'); $Controller->params['url'] = array(); $Controller->constructClasses(); @@ -461,7 +463,6 @@ class PaginatorTest extends CakeTestCase { $this->assertEqual(Set::extract($result, '{n}.PaginatorControllerPost.id'), array(2, 3)); $this->assertEqual($Controller->PaginatorControllerPost->lastQuery['conditions'], array('PaginatorControllerPost.id > ' => '1')); - $this->assertFalse(isset($Controller->params['paging']['PaginatorControllerPost']['defaults'][0])); $this->assertFalse(isset($Controller->params['paging']['PaginatorControllerPost']['options'][0])); } @@ -480,7 +481,6 @@ class PaginatorTest extends CakeTestCase { 'order' => 'PaginatorControllerPost.id DESC', 'maxLimit' => 10, 'paramType' => 'named' ); $results = Set::extract($Controller->Paginator->paginate('PaginatorControllerPost'), '{n}.PaginatorControllerPost.id'); - $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['defaults']['order'], 'PaginatorControllerPost.id DESC'); $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['order'], 'PaginatorControllerPost.id DESC'); $this->assertEqual($results, array(3, 2, 1)); } @@ -508,7 +508,7 @@ class PaginatorTest extends CakeTestCase { $result = $Controller->Paginator->paginate('PaginatorControllerPost'); $this->assertEqual(Set::extract($result, '{n}.PaginatorControllerPost.offset_test'), array(4, 3, 2)); - $Controller->passedArgs = array('sort' => 'offset_test', 'direction' => 'asc'); + $Controller->request->params['named'] = array('sort' => 'offset_test', 'direction' => 'asc'); $result = $Controller->Paginator->paginate('PaginatorControllerPost'); $this->assertEqual(Set::extract($result, '{n}.PaginatorControllerPost.offset_test'), array(2, 3, 4)); } @@ -538,24 +538,28 @@ class PaginatorTest extends CakeTestCase { $Controller->params['url'] = array(); $Controller->constructClasses(); - $Controller->passedArgs = array('contain' => array('ControllerComment'), 'limit' => '1000'); + $Controller->request->params['named'] = array( + 'contain' => array('ControllerComment'), 'limit' => '1000' + ); $result = $Controller->paginate('PaginatorControllerPost'); $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['limit'], 100); - $Controller->passedArgs = array('contain' => array('ControllerComment'), 'limit' => '1000', 'maxLimit' => 1000); + $Controller->request->params['named'] = array( + 'contain' => array('ControllerComment'), 'limit' => '1000', 'maxLimit' => 1000 + ); $result = $Controller->paginate('PaginatorControllerPost'); $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['limit'], 100); - $Controller->passedArgs = array('contain' => array('ControllerComment'), 'limit' => '10'); + $Controller->request->params['named'] = array('contain' => array('ControllerComment'), 'limit' => '10'); $result = $Controller->paginate('PaginatorControllerPost'); $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['limit'], 10); - $Controller->passedArgs = array('contain' => array('ControllerComment'), 'limit' => '1000'); + $Controller->request->params['named'] = array('contain' => array('ControllerComment'), 'limit' => '1000'); $Controller->paginate = array('maxLimit' => 2000, 'paramType' => 'named'); $result = $Controller->paginate('PaginatorControllerPost'); $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['limit'], 1000); - $Controller->passedArgs = array('contain' => array('ControllerComment'), 'limit' => '5000'); + $Controller->request->params['named'] = array('contain' => array('ControllerComment'), 'limit' => '5000'); $result = $Controller->paginate('PaginatorControllerPost'); $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['limit'], 2000); } From 108a6611a8f7945f491952060e9c1c9152ee066d Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 19 Dec 2010 02:11:39 -0500 Subject: [PATCH 20/59] Moving validation of sorting to a separate method. This makes it easier to test, extends and read. Tests added. --- cake/libs/controller/components/paginator.php | 76 +++++++++++-------- .../controller/components/paginator.test.php | 41 ++++++++++ 2 files changed, 86 insertions(+), 31 deletions(-) diff --git a/cake/libs/controller/components/paginator.php b/cake/libs/controller/components/paginator.php index bb33177e1..9755d47f2 100644 --- a/cake/libs/controller/components/paginator.php +++ b/cake/libs/controller/components/paginator.php @@ -95,41 +95,12 @@ class PaginatorComponent extends Component { throw new MissingModelException($object); } - $options = $this->mergeOptions($object->alias, $scope, $whitelist); - if (isset($options['show'])) { $options['limit'] = $options['show']; } - if (isset($options['sort'])) { - $direction = null; - if (isset($options['direction'])) { - $direction = strtolower($options['direction']); - } - if ($direction != 'asc' && $direction != 'desc') { - $direction = 'asc'; - } - $options['order'] = array($options['sort'] => $direction); - } - - if (!empty($options['order']) && is_array($options['order'])) { - $alias = $object->alias ; - $key = $field = key($options['order']); - - if (strpos($key, '.') !== false) { - list($alias, $field) = explode('.', $key); - } - $value = $options['order'][$key]; - unset($options['order'][$key]); - - if ($object->hasField($field)) { - $options['order'][$alias . '.' . $field] = $value; - } elseif ($object->hasField($field, true)) { - $options['order'][$field] = $value; - } elseif (isset($object->{$alias}) && $object->{$alias}->hasField($field)) { - $options['order'][$alias . '.' . $field] = $value; - } - } + $options = $this->mergeOptions($object->alias, $scope, $whitelist); + $options = $this->validateSort($object, $options); $conditions = $fields = $order = $limit = $page = $recursive = null; @@ -312,4 +283,47 @@ class PaginatorComponent extends Component { return array_merge($defaults, $request, $options); } + +/** + * Validate that the desired sorting can be performed on the $object. Only fields or + * virtualFields can be sorted on. The direction param will also be sanitized. Lastly + * sort + direction keys will be converted into the model friendly order key. + * + * @param Model $object The model being paginated. + * @param array $options The pagination options being used for this request. + * @return array An array of options with sort + direction removed and replaced with order if possible. + */ + public function validateSort($object, $options) { + if (isset($options['sort'])) { + $direction = null; + if (isset($options['direction'])) { + $direction = strtolower($options['direction']); + } + if ($direction != 'asc' && $direction != 'desc') { + $direction = 'asc'; + } + $options['order'] = array($options['sort'] => $direction); + } + + if (!empty($options['order']) && is_array($options['order'])) { + $alias = $object->alias ; + $key = $field = key($options['order']); + + if (strpos($key, '.') !== false) { + list($alias, $field) = explode('.', $key); + } + $value = $options['order'][$key]; + unset($options['order'][$key]); + + if ($object->hasField($field)) { + $options['order'][$alias . '.' . $field] = $value; + } elseif ($object->hasField($field, true)) { + $options['order'][$field] = $value; + } elseif (isset($object->{$alias}) && $object->{$alias}->hasField($field)) { + $options['order'][$alias . '.' . $field] = $value; + } + } + + return $options; + } } \ No newline at end of file diff --git a/cake/tests/cases/libs/controller/components/paginator.test.php b/cake/tests/cases/libs/controller/components/paginator.test.php index 063dfaf02..9fb22993e 100644 --- a/cake/tests/cases/libs/controller/components/paginator.test.php +++ b/cake/tests/cases/libs/controller/components/paginator.test.php @@ -696,4 +696,45 @@ class PaginatorTest extends CakeTestCase { ); $this->assertEquals($expected, $result); } + +/** + * test that invalid directions are ignored. + * + * @return void + */ + function testValidateSortInvalidDirection() { + $model = $this->getMock('Model'); + $model->alias = 'model'; + $model->expects($this->any())->method('hasField')->will($this->returnValue(true)); + + $options = array('sort' => 'something', 'direction' => 'boogers'); + $result = $this->Paginator->validateSort($model, $options); + + $this->assertEquals('asc', $result['order']['model.something']); + } + +/** + * test that virtual fields work. + * + * @return void + */ + function testValidateSortVirtualField() { + $model = $this->getMock('Model'); + $model->alias = 'model'; + + $model->expects($this->at(0)) + ->method('hasField') + ->with('something') + ->will($this->returnValue(false)); + + $model->expects($this->at(1)) + ->method('hasField') + ->with('something', true) + ->will($this->returnValue(true)); + + $options = array('sort' => 'something', 'direction' => 'desc'); + $result = $this->Paginator->validateSort($model, $options); + + $this->assertEquals('desc', $result['order']['something']); + } } \ No newline at end of file From 1e741de84bbb822bd131f459c5cbe5651987b967 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 19 Dec 2010 02:14:37 -0500 Subject: [PATCH 21/59] Removing show alias, it was undocumented and untested. --- cake/libs/controller/components/paginator.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cake/libs/controller/components/paginator.php b/cake/libs/controller/components/paginator.php index 9755d47f2..847ea3ead 100644 --- a/cake/libs/controller/components/paginator.php +++ b/cake/libs/controller/components/paginator.php @@ -95,10 +95,6 @@ class PaginatorComponent extends Component { throw new MissingModelException($object); } - if (isset($options['show'])) { - $options['limit'] = $options['show']; - } - $options = $this->mergeOptions($object->alias, $scope, $whitelist); $options = $this->validateSort($object, $options); From e9d3fcf5cfe6fc1b3e40c5041e92e4ca8c2f7516 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 19 Dec 2010 02:28:38 -0500 Subject: [PATCH 22/59] Moving limit checking into a separate method, and adding tests. Removing $scope from being passed down to the options, it previously only allowed additional conditions to be set. Updated tests. --- cake/libs/controller/components/paginator.php | 40 ++++++++++------- .../controller/components/paginator.test.php | 45 ++++++++++++------- 2 files changed, 53 insertions(+), 32 deletions(-) diff --git a/cake/libs/controller/components/paginator.php b/cake/libs/controller/components/paginator.php index 847ea3ead..5d05a1a9e 100644 --- a/cake/libs/controller/components/paginator.php +++ b/cake/libs/controller/components/paginator.php @@ -77,7 +77,7 @@ class PaginatorComponent extends Component { * Handles automatic pagination of model records. * * @param mixed $object Model to paginate (e.g: model instance, or 'Model', or 'Model.InnerModel') - * @param mixed $scope Conditions to use while paginating + * @param mixed $scope Additional find conditions to use while paginating * @param array $whitelist List of allowed options for paging * @return array Model query results */ @@ -87,7 +87,6 @@ class PaginatorComponent extends Component { $scope = $object; $object = null; } - $assoc = null; $object = $this->_getObject($object); @@ -95,8 +94,9 @@ class PaginatorComponent extends Component { throw new MissingModelException($object); } - $options = $this->mergeOptions($object->alias, $scope, $whitelist); + $options = $this->mergeOptions($object->alias, $whitelist); $options = $this->validateSort($object, $options); + $options = $this->checkLimit($options); $conditions = $fields = $order = $limit = $page = $recursive = null; @@ -111,13 +111,6 @@ class PaginatorComponent extends Component { unset($options[0]); } - $options = array_merge(array('page' => 1, 'limit' => 20, 'maxLimit' => 100), $options); - $options['limit'] = (int) $options['limit']; - if (empty($options['limit']) || $options['limit'] < 1) { - $options['limit'] = 1; - } - $options['limit'] = min((int)$options['limit'], $options['maxLimit']); - extract($options); if (is_array($scope) && !empty($scope)) { @@ -248,20 +241,20 @@ class PaginatorComponent extends Component { * * @param string $alias Model alias being paginated, if the general settings has a key with this value * that key's settings will be used for pagination instead of the general ones. - * @param string $options Per call options. * @param string $whitelist A whitelist of options that are allowed from the request parameters. Modifying * this array will allow you to permit more or less input from the user. * @return array Array of merged options. */ - public function mergeOptions($alias, $options, $whitelist = array()) { + public function mergeOptions($alias, $whitelist = array()) { if (isset($this->settings[$alias])) { $defaults = $this->settings[$alias]; } else { $defaults = $this->settings; } - if (empty($defaults['paramType'])) { - $defaults['paramType'] = 'named'; - } + $defaults = array_merge( + array('page' => 1, 'limit' => 20, 'maxLimit' => 100, 'paramType' => 'named'), + $defaults + ); switch ($defaults['paramType']) { case 'named': $request = $this->Controller->request->params['named']; @@ -277,7 +270,7 @@ class PaginatorComponent extends Component { $whitelist = array_flip(array_merge($this->whitelist, $whitelist)); $request = array_intersect_key($request, $whitelist); - return array_merge($defaults, $request, $options); + return array_merge($defaults, $request); } /** @@ -322,4 +315,19 @@ class PaginatorComponent extends Component { return $options; } + +/** + * Check the limit parameter and ensure its within the maxLimit bounds. + * + * @param array $options An array of options with a limit key to be checked. + * @return array An array of options for pagination + */ + public function checkLimit($options) { + $options['limit'] = (int) $options['limit']; + if (empty($options['limit']) || $options['limit'] < 1) { + $options['limit'] = 1; + } + $options['limit'] = min((int)$options['limit'], $options['maxLimit']); + return $options; + } } \ No newline at end of file diff --git a/cake/tests/cases/libs/controller/components/paginator.test.php b/cake/tests/cases/libs/controller/components/paginator.test.php index 9fb22993e..ecb9f812f 100644 --- a/cake/tests/cases/libs/controller/components/paginator.test.php +++ b/cake/tests/cases/libs/controller/components/paginator.test.php @@ -582,14 +582,11 @@ class PaginatorTest extends CakeTestCase { 'paramType' => 'named', ) ); - $result = $this->Paginator->mergeOptions('Silly', array()); + $result = $this->Paginator->mergeOptions('Silly'); $this->assertEquals($this->Paginator->settings, $result); - $result = $this->Paginator->mergeOptions('Silly', array('limit' => 10)); - $this->assertEquals(10, $result['limit']); - - $result = $this->Paginator->mergeOptions('Post', array('sort' => 'title')); - $expected = array('page' => 1, 'limit' => 10, 'paramType' => 'named', 'sort' => 'title', 'maxLimit' => 50); + $result = $this->Paginator->mergeOptions('Post'); + $expected = array('page' => 1, 'limit' => 10, 'paramType' => 'named', 'maxLimit' => 50); $this->assertEquals($expected, $result); } @@ -609,12 +606,9 @@ class PaginatorTest extends CakeTestCase { 'maxLimit' => 100, 'paramType' => 'named', ); - $result = $this->Paginator->mergeOptions('Post', array()); + $result = $this->Paginator->mergeOptions('Post'); $expected = array('page' => 10, 'limit' => 10, 'maxLimit' => 100, 'paramType' => 'named'); $this->assertEquals($expected, $result); - - $result = $this->Paginator->mergeOptions('Post', array('page' => 100)); - $this->assertEquals(100, $result['page'], 'Passed options should replace request params'); } /** @@ -637,12 +631,9 @@ class PaginatorTest extends CakeTestCase { 'maxLimit' => 100, 'paramType' => 'querystring', ); - $result = $this->Paginator->mergeOptions('Post', array()); + $result = $this->Paginator->mergeOptions('Post'); $expected = array('page' => 99, 'limit' => 75, 'maxLimit' => 100, 'paramType' => 'querystring'); $this->assertEquals($expected, $result); - - $result = $this->Paginator->mergeOptions('Post', array('page' => 100)); - $this->assertEquals(100, $result['page'], 'Passed options should replace request params'); } /** @@ -665,7 +656,7 @@ class PaginatorTest extends CakeTestCase { 'maxLimit' => 100, 'paramType' => 'named', ); - $result = $this->Paginator->mergeOptions('Post', array()); + $result = $this->Paginator->mergeOptions('Post'); $expected = array('page' => 10, 'limit' => 10, 'maxLimit' => 100, 'paramType' => 'named'); $this->assertEquals($expected, $result); } @@ -690,7 +681,7 @@ class PaginatorTest extends CakeTestCase { 'maxLimit' => 100, 'paramType' => 'named', ); - $result = $this->Paginator->mergeOptions('Post', array(), array('fields')); + $result = $this->Paginator->mergeOptions('Post', array('fields')); $expected = array( 'page' => 10, 'limit' => 10, 'maxLimit' => 100, 'paramType' => 'named', 'fields' => array('bad.stuff') ); @@ -737,4 +728,26 @@ class PaginatorTest extends CakeTestCase { $this->assertEquals('desc', $result['order']['something']); } + +/** + * test that maxLimit is respected + * + * @return void + */ + function testCheckLimit() { + $result = $this->Paginator->checkLimit(array('limit' => 1000000, 'maxLimit' => 100)); + $this->assertEquals(100, $result['limit']); + + $result = $this->Paginator->checkLimit(array('limit' => 'sheep!', 'maxLimit' => 100)); + $this->assertEquals(1, $result['limit']); + + $result = $this->Paginator->checkLimit(array('limit' => '-1', 'maxLimit' => 100)); + $this->assertEquals(1, $result['limit']); + + $result = $this->Paginator->checkLimit(array('limit' => null, 'maxLimit' => 100)); + $this->assertEquals(1, $result['limit']); + + $result = $this->Paginator->checkLimit(array('limit' => 0, 'maxLimit' => 100)); + $this->assertEquals(1, $result['limit']); + } } \ No newline at end of file From f54479e5665fbc01eb23262b6c6a75a3babbd061 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 19 Dec 2010 12:20:12 -0500 Subject: [PATCH 23/59] Adding some docblock info about pagination. --- cake/libs/controller/components/paginator.php | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/cake/libs/controller/components/paginator.php b/cake/libs/controller/components/paginator.php index 5d05a1a9e..c2fefef55 100644 --- a/cake/libs/controller/components/paginator.php +++ b/cake/libs/controller/components/paginator.php @@ -19,13 +19,39 @@ */ /** - * PaginatorComponent + * This component is used to handle automatic model data pagination. The primary way to use this + * component is to call the paginate() method. There is a convience wrapper on Controller as well. * - * This component is used to handle automatic model data pagination + * ### Configuring pagination + * + * You configure pagination using the PaginatorComponent::$settings. This allows you to configure + * the default pagination behavior in general or for a specific model. General settings are used when there + * are no specific model configuration, or the model you are paginating does not have specific settings. + * + * {{{ + * $this->Paginator->settings = array( + * 'limit' => 20, + * 'maxLimit' => 100 + * ); + * }}} + * + * The above settings will be used to paginate any model. You can configure model specific settings by + * keying the settings with the model name. + * + * {{{ + * $this->Paginator->settings = array( + * 'Post' => array( + * 'limit' => 20, + * 'maxLimit' => 100 + * ), + * 'Comment' => array( ... ) + * ); + * }}} + * + * This would allow you to have different pagination settings for `Comment` and `Post` models. * * @package cake * @subpackage cake.cake.libs.controller.components - * */ class PaginatorComponent extends Component { From ef84d86cf537afec5000982f892f2c60432e0146 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 19 Dec 2010 12:26:28 -0500 Subject: [PATCH 24/59] Reformatting code, and removing merging of defaults key in the helper. It no longer exists. --- cake/libs/controller/components/paginator.php | 5 ++++- cake/libs/view/helpers/paginator.php | 5 ++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cake/libs/controller/components/paginator.php b/cake/libs/controller/components/paginator.php index c2fefef55..a774e934e 100644 --- a/cake/libs/controller/components/paginator.php +++ b/cake/libs/controller/components/paginator.php @@ -202,7 +202,10 @@ class PaginatorComponent extends Component { array($object->alias => $paging) ); - if (!in_array('Paginator', $this->Controller->helpers) && !array_key_exists('Paginator', $this->Controller->helpers)) { + if ( + !in_array('Paginator', $this->Controller->helpers) && + !array_key_exists('Paginator', $this->Controller->helpers) + ) { $this->Controller->helpers[] = 'Paginator'; } return $results; diff --git a/cake/libs/view/helpers/paginator.php b/cake/libs/view/helpers/paginator.php index 11b897226..6fde140e6 100644 --- a/cake/libs/view/helpers/paginator.php +++ b/cake/libs/view/helpers/paginator.php @@ -112,7 +112,6 @@ class PaginatorHelper extends AppHelper { */ public function beforeRender($viewFile) { $this->options['url'] = array_merge($this->request->params['pass'], $this->request->params['named']); - parent::beforeRender($viewFile); } @@ -191,7 +190,7 @@ class PaginatorHelper extends AppHelper { public function sortKey($model = null, $options = array()) { if (empty($options)) { $params = $this->params($model); - $options = array_merge($params['defaults'], $params['options']); + $options = $params['options']; } if (isset($options['sort']) && !empty($options['sort'])) { @@ -217,7 +216,7 @@ class PaginatorHelper extends AppHelper { if (empty($options)) { $params = $this->params($model); - $options = array_merge($params['defaults'], $params['options']); + $options = $params['options']; } if (isset($options['direction'])) { From 833bdbcc0b0ffa050605034d20b1f4ef2b87b665 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 19 Dec 2010 12:43:29 -0500 Subject: [PATCH 25/59] Reformatting paging params. Removing defaults from the paging params as they are no longer being used. --- cake/libs/view/helpers/paginator.php | 4 +- .../libs/view/helpers/paginator.test.php | 392 ++++++++++++++---- 2 files changed, 301 insertions(+), 95 deletions(-) diff --git a/cake/libs/view/helpers/paginator.php b/cake/libs/view/helpers/paginator.php index 6fde140e6..5708b9afe 100644 --- a/cake/libs/view/helpers/paginator.php +++ b/cake/libs/view/helpers/paginator.php @@ -367,9 +367,7 @@ class PaginatorHelper extends AppHelper { */ public function url($options = array(), $asArray = false, $model = null) { $paging = $this->params($model); - $url = array_merge(array_filter(Set::diff(array_merge( - $paging['defaults'], $paging['options']), $paging['defaults'])), $options - ); + $url = array_merge(array_filter($paging['options']), $options); if (isset($url['order'])) { $sort = $direction = null; diff --git a/cake/tests/cases/libs/view/helpers/paginator.test.php b/cake/tests/cases/libs/view/helpers/paginator.test.php index 2e60552c5..fa6718d55 100644 --- a/cake/tests/cases/libs/view/helpers/paginator.test.php +++ b/cake/tests/cases/libs/view/helpers/paginator.test.php @@ -52,11 +52,6 @@ class PaginatorHelperTest extends CakeTestCase { 'prevPage' => false, 'nextPage' => true, 'pageCount' => 7, - 'defaults' => array( - 'order' => array('Article.date' => 'asc'), - 'limit' => 9, - 'conditions' => array() - ), 'options' => array( 'order' => array('Article.date' => 'asc'), 'limit' => 9, @@ -649,9 +644,6 @@ class PaginatorHelperTest extends CakeTestCase { 'Article' => array( 'page' => 1, 'current' => 3, 'count' => 13, 'prevPage' => false, 'nextPage' => true, 'pageCount' => 8, - 'defaults' => array( - 'limit' => 3, 'step' => 1, 'order' => array(), 'conditions' => array() - ), 'options' => array( 'page' => 1, 'limit' => 3, 'order' => array(), 'conditions' => array() ) @@ -706,10 +698,21 @@ class PaginatorHelperTest extends CakeTestCase { * @return void */ function testPagingLinks() { - $this->Paginator->request->params['paging'] = array('Client' => array( - 'page' => 1, 'current' => 3, 'count' => 13, 'prevPage' => false, 'nextPage' => true, 'pageCount' => 5, - 'defaults' => array('limit' => 3, 'step' => 1, 'order' => array('Client.name' => 'DESC'), 'conditions' => array()), - 'options' => array('page' => 1, 'limit' => 3, 'order' => array('Client.name' => 'DESC'), 'conditions' => array())) + $this->Paginator->request->params['paging'] = array( + 'Client' => array( + 'page' => 1, + 'current' => 3, + 'count' => 13, + 'prevPage' => false, + 'nextPage' => true, + 'pageCount' => 5, + 'options' => array( + 'page' => 1, + 'limit' => 3, + 'order' => array('Client.name' => 'DESC'), + 'conditions' => array() + ) + ) ); $result = $this->Paginator->prev('<< Previous', null, null, array('class' => 'disabled')); $expected = array( @@ -779,10 +782,21 @@ class PaginatorHelperTest extends CakeTestCase { ); $this->assertTags($result, $expected); - $this->Paginator->request->params['paging'] = array('Client' => array( - 'page' => 1, 'current' => 1, 'count' => 13, 'prevPage' => false, 'nextPage' => true, 'pageCount' => 5, - 'defaults' => array(), - 'options' => array('page' => 1, 'limit' => 3, 'order' => array('Client.name' => 'DESC'), 'conditions' => array())) + $this->Paginator->request->params['paging'] = array( + 'Client' => array( + 'page' => 1, + 'current' => 1, + 'count' => 13, + 'prevPage' => false, + 'nextPage' => true, + 'pageCount' => 5, + 'options' => array( + 'page' => 1, + 'limit' => 3, + 'order' => array('Client.name' => 'DESC'), + 'conditions' => array() + ) + ) ); $result = $this->Paginator->prev('<< Previous', null, 'Disabled'); @@ -809,10 +823,21 @@ class PaginatorHelperTest extends CakeTestCase { ); $this->assertTags($result, $expected); - $this->Paginator->request->params['paging'] = array('Client' => array( - 'page' => 1, 'current' => 3, 'count' => 13, 'prevPage' => false, 'nextPage' => true, 'pageCount' => 5, - 'defaults' => array(), - 'options' => array('page' => 1, 'limit' => 3, 'order' => array('Client.name' => 'DESC'), 'conditions' => array())) + $this->Paginator->request->params['paging'] = array( + 'Client' => array( + 'page' => 1, + 'current' => 3, + 'count' => 13, + 'prevPage' => false, + 'nextPage' => true, + 'pageCount' => 5, + 'options' => array( + 'page' => 1, + 'limit' => 3, + 'order' => array('Client.name' => 'DESC'), + 'conditions' => array() + ) + ) ); $this->Paginator->request->params['paging']['Client']['page'] = 2; @@ -837,11 +862,22 @@ class PaginatorHelperTest extends CakeTestCase { ); $this->assertTags($result, $expected); - $this->Paginator->request->params['paging'] = array('Client' => array( - 'page' => 2, 'current' => 1, 'count' => 13, 'prevPage' => true, 'nextPage' => false, 'pageCount' => 2, - 'defaults' => array(), - 'options' => array('page' => 2, 'limit' => 10, 'order' => array(), 'conditions' => array()) - )); + $this->Paginator->request->params['paging'] = array( + 'Client' => array( + 'page' => 2, + 'current' => 1, + 'count' => 13, + 'prevPage' => true, + 'nextPage' => false, + 'pageCount' => 2, + 'options' => array( + 'page' => 2, + 'limit' => 10, + 'order' => array(), + 'conditions' => array() + ) + ) + ); $result = $this->Paginator->prev('Prev'); $expected = array( 'Paginator->request->params['paging'] = array( 'Client' => array( - 'page' => 1, 'current' => 3, 'count' => 13, 'prevPage' => false, - 'nextPage' => true, 'pageCount' => 5, - 'defaults' => array( - 'limit' => 3, 'step' => 1, 'order' => array('Client.name' => 'DESC'), 'conditions' => array() - ), + 'page' => 1, + 'current' => 3, + 'count' => 13, + 'prevPage' => false, + 'nextPage' => true, + 'pageCount' => 5, 'options' => array( 'page' => 1, 'limit' => 3, 'order' => array('Client.name' => 'DESC'), 'conditions' => array() ) @@ -924,14 +961,32 @@ class PaginatorHelperTest extends CakeTestCase { // Multiple Model Paginate $this->Paginator->request->params['paging'] = array( 'Client' => array( - 'page' => 1, 'current' => 3, 'count' => 13, 'prevPage' => false, 'nextPage' => true, 'pageCount' => 5, - 'defaults' => array( 'limit'=>3, 'order' => array('Client.name' => 'DESC')), - 'options' => array('page' => 1, 'limit' => 3, 'order' => array('Client.name' => 'DESC'), 'conditions' => array()) + 'page' => 1, + 'current' => 3, + 'count' => 13, + 'prevPage' => false, + 'nextPage' => true, + 'pageCount' => 5, + 'options' => array( + 'page' => 1, + 'limit' => 3, + 'order' => array('Client.name' => 'DESC'), + 'conditions' => array() + ) ), 'Server' => array( - 'page' => 1, 'current' => 1, 'count' => 5, 'prevPage' => false, 'nextPage' => false, 'pageCount' => 5, - 'defaults' => array(), - 'options' => array('page' => 1, 'limit' => 5, 'order' => array('Server.name' => 'ASC'), 'conditions' => array()) + 'page' => 1, + 'current' => 1, + 'count' => 5, + 'prevPage' => false, + 'nextPage' => false, + 'pageCount' => 5, + 'options' => array( + 'page' => 1, + 'limit' => 5, + 'order' => array('Server.name' => 'ASC'), + 'conditions' => array() + ) ) ); $result = $this->Paginator->next('Next', array('model' => 'Client')); @@ -1030,10 +1085,21 @@ class PaginatorHelperTest extends CakeTestCase { * @return void */ function testNumbers() { - $this->Paginator->request->params['paging'] = array('Client' => array( - 'page' => 8, 'current' => 3, 'count' => 30, 'prevPage' => false, 'nextPage' => 2, 'pageCount' => 15, - 'defaults' => array('limit' => 3, 'step' => 1, 'order' => array('Client.name' => 'DESC'), 'conditions' => array()), - 'options' => array('page' => 1, 'limit' => 3, 'order' => array('Client.name' => 'DESC'), 'conditions' => array())) + $this->Paginator->request->params['paging'] = array( + 'Client' => array( + 'page' => 8, + 'current' => 3, + 'count' => 30, + 'prevPage' => false, + 'nextPage' => 2, + 'pageCount' => 15, + 'options' => array( + 'page' => 1, + 'limit' => 3, + 'order' => array('Client.name' => 'DESC'), + 'conditions' => array() + ) + ) ); $result = $this->Paginator->numbers(); $expected = array( @@ -1119,10 +1185,21 @@ class PaginatorHelperTest extends CakeTestCase { ); $this->assertTags($result, $expected); - $this->Paginator->request->params['paging'] = array('Client' => array( - 'page' => 1, 'current' => 3, 'count' => 30, 'prevPage' => false, 'nextPage' => 2, 'pageCount' => 15, - 'defaults' => array('limit' => 3, 'step' => 1, 'order' => array('Client.name' => 'DESC'), 'conditions' => array()), - 'options' => array('page' => 1, 'limit' => 3, 'order' => array('Client.name' => 'DESC'), 'conditions' => array())) + $this->Paginator->request->params['paging'] = array( + 'Client' => array( + 'page' => 1, + 'current' => 3, + 'count' => 30, + 'prevPage' => false, + 'nextPage' => 2, + 'pageCount' => 15, + 'options' => array( + 'page' => 1, + 'limit' => 3, + 'order' => array('Client.name' => 'DESC'), + 'conditions' => array() + ) + ) ); $result = $this->Paginator->numbers(); $expected = array( @@ -1147,10 +1224,21 @@ class PaginatorHelperTest extends CakeTestCase { $this->assertTags($result, $expected); - $this->Paginator->request->params['paging'] = array('Client' => array( - 'page' => 14, 'current' => 3, 'count' => 30, 'prevPage' => false, 'nextPage' => 2, 'pageCount' => 15, - 'defaults' => array('limit' => 3, 'step' => 1, 'order' => array('Client.name' => 'DESC'), 'conditions' => array()), - 'options' => array('page' => 1, 'limit' => 3, 'order' => array('Client.name' => 'DESC'), 'conditions' => array())) + $this->Paginator->request->params['paging'] = array( + 'Client' => array( + 'page' => 14, + 'current' => 3, + 'count' => 30, + 'prevPage' => false, + 'nextPage' => 2, + 'pageCount' => 15, + 'options' => array( + 'page' => 1, + 'limit' => 3, + 'order' => array('Client.name' => 'DESC'), + 'conditions' => array() + ) + ) ); $result = $this->Paginator->numbers(); $expected = array( @@ -1174,10 +1262,21 @@ class PaginatorHelperTest extends CakeTestCase { ); $this->assertTags($result, $expected); - $this->Paginator->request->params['paging'] = array('Client' => array( - 'page' => 2, 'current' => 3, 'count' => 27, 'prevPage' => false, 'nextPage' => 2, 'pageCount' => 9, - 'defaults' => array('limit' => 3, 'step' => 1, 'order' => array('Client.name' => 'DESC'), 'conditions' => array()), - 'options' => array('page' => 1, 'limit' => 3, 'order' => array('Client.name' => 'DESC'), 'conditions' => array())) + $this->Paginator->request->params['paging'] = array( + 'Client' => array( + 'page' => 2, + 'current' => 3, + 'count' => 27, + 'prevPage' => false, + 'nextPage' => 2, + 'pageCount' => 9, + 'options' => array( + 'page' => 1, + 'limit' => 3, + 'order' => array('Client.name' => 'DESC'), + 'conditions' => array() + ) + ) ); $result = $this->Paginator->numbers(array('first' => 1)); @@ -1224,10 +1323,21 @@ class PaginatorHelperTest extends CakeTestCase { ); $this->assertTags($result, $expected); - $this->Paginator->request->params['paging'] = array('Client' => array( - 'page' => 15, 'current' => 3, 'count' => 30, 'prevPage' => false, 'nextPage' => 2, 'pageCount' => 15, - 'defaults' => array('limit' => 3, 'step' => 1, 'order' => array('Client.name' => 'DESC'), 'conditions' => array()), - 'options' => array('page' => 1, 'limit' => 3, 'order' => array('Client.name' => 'DESC'), 'conditions' => array())) + $this->Paginator->request->params['paging'] = array( + 'Client' => array( + 'page' => 15, + 'current' => 3, + 'count' => 30, + 'prevPage' => false, + 'nextPage' => 2, + 'pageCount' => 15, + 'options' => array( + 'page' => 1, + 'limit' => 3, + 'order' => array('Client.name' => 'DESC'), + 'conditions' => array() + ) + ) ); $result = $this->Paginator->numbers(array('first' => 1)); @@ -1255,10 +1365,21 @@ class PaginatorHelperTest extends CakeTestCase { ); $this->assertTags($result, $expected); - $this->Paginator->request->params['paging'] = array('Client' => array( - 'page' => 10, 'current' => 3, 'count' => 30, 'prevPage' => false, 'nextPage' => 2, 'pageCount' => 15, - 'defaults' => array('limit' => 3, 'step' => 1, 'order' => array('Client.name' => 'DESC'), 'conditions' => array()), - 'options' => array('page' => 1, 'limit' => 3, 'order' => array('Client.name' => 'DESC'), 'conditions' => array())) + $this->Paginator->request->params['paging'] = array( + 'Client' => array( + 'page' => 10, + 'current' => 3, + 'count' => 30, + 'prevPage' => false, + 'nextPage' => 2, + 'pageCount' => 15, + 'options' => array( + 'page' => 1, + 'limit' => 3, + 'order' => array('Client.name' => 'DESC'), + 'conditions' => array() + ) + ) ); $result = $this->Paginator->numbers(array('first' => 1, 'last' => 1)); @@ -1287,10 +1408,21 @@ class PaginatorHelperTest extends CakeTestCase { ); $this->assertTags($result, $expected); - $this->Paginator->request->params['paging'] = array('Client' => array( - 'page' => 6, 'current' => 15, 'count' => 623, 'prevPage' => 1, 'nextPage' => 1, 'pageCount' => 42, - 'defaults' => array('limit' => 15, 'step' => 1, 'page' => 1, 'order' => array('Client.name' => 'DESC'), 'conditions' => array()), - 'options' => array('page' => 6, 'limit' => 15, 'order' => array('Client.name' => 'DESC'), 'conditions' => array())) + $this->Paginator->request->params['paging'] = array( + 'Client' => array( + 'page' => 6, + 'current' => 15, + 'count' => 623, + 'prevPage' => 1, + 'nextPage' => 1, + 'pageCount' => 42, + 'options' => array( + 'page' => 6, + 'limit' => 15, + 'order' => array('Client.name' => 'DESC'), + 'conditions' => array() + ) + ) ); $result = $this->Paginator->numbers(array('first' => 1, 'last' => 1)); @@ -1319,10 +1451,21 @@ class PaginatorHelperTest extends CakeTestCase { ); $this->assertTags($result, $expected); - $this->Paginator->request->params['paging'] = array('Client' => array( - 'page' => 37, 'current' => 15, 'count' => 623, 'prevPage' => 1, 'nextPage' => 1, 'pageCount' => 42, - 'defaults' => array('limit' => 15, 'step' => 1, 'page' => 1, 'order' => array('Client.name' => 'DESC'), 'conditions' => array()), - 'options' => array('page' => 37, 'limit' => 15, 'order' => array('Client.name' => 'DESC'), 'conditions' => array())) + $this->Paginator->request->params['paging'] = array( + 'Client' => array( + 'page' => 37, + 'current' => 15, + 'count' => 623, + 'prevPage' => 1, + 'nextPage' => 1, + 'pageCount' => 42, + 'options' => array( + 'page' => 37, + 'limit' => 15, + 'order' => array('Client.name' => 'DESC'), + 'conditions' => array() + ) + ) ); $result = $this->Paginator->numbers(array('first' => 1, 'last' => 1)); @@ -1384,10 +1527,20 @@ class PaginatorHelperTest extends CakeTestCase { ); $this->assertTags($result, $expected); - $this->Paginator->request->params['paging'] = array('Client' => array( - 'page' => 2, 'current' => 10, 'count' => 31, 'prevPage' => true, 'nextPage' => true, 'pageCount' => 4, - 'defaults' => array('limit' => 10), - 'options' => array('page' => 1, 'order' => array('Client.name' => 'DESC'), 'conditions' => array())) + $this->Paginator->request->params['paging'] = array( + 'Client' => array( + 'page' => 2, + 'current' => 10, + 'count' => 31, + 'prevPage' => true, + 'nextPage' => true, + 'pageCount' => 4, + 'options' => array( + 'page' => 1, + 'order' => array('Client.name' => 'DESC'), + 'conditions' => array() + ) + ) ); $result = $this->Paginator->numbers(); $expected = array( @@ -1402,10 +1555,21 @@ class PaginatorHelperTest extends CakeTestCase { $this->assertTags($result, $expected); - $this->Paginator->request->params['paging'] = array('Client' => array( - 'page' => 4895, 'current' => 10, 'count' => 48962, 'prevPage' => 1, 'nextPage' => 1, 'pageCount' => 4897, - 'defaults' => array('limit' => 10), - 'options' => array('page' => 4894, 'limit' => 10, 'order' => 'Client.name DESC', 'conditions' => array())) + $this->Paginator->request->params['paging'] = array( + 'Client' => array( + 'page' => 4895, + 'current' => 10, + 'count' => 48962, + 'prevPage' => 1, + 'nextPage' => 1, + 'pageCount' => 4897, + 'options' => array( + 'page' => 4894, + 'limit' => 10, + 'order' => 'Client.name DESC', + 'conditions' => array() + ) + ) ); $result = $this->Paginator->numbers(array('first' => 2, 'modulus' => 2, 'last' => 2)); @@ -1619,20 +1783,42 @@ class PaginatorHelperTest extends CakeTestCase { * @return void */ function testFirstAndLast() { - $this->Paginator->request->params['paging'] = array('Client' => array( - 'page' => 1, 'current' => 3, 'count' => 30, 'prevPage' => false, 'nextPage' => 2, 'pageCount' => 15, - 'defaults' => array('limit' => 3, 'step' => 1, 'order' => array('Client.name' => 'DESC'), 'conditions' => array()), - 'options' => array('page' => 1, 'limit' => 3, 'order' => array('Client.name' => 'DESC'), 'conditions' => array())) + $this->Paginator->request->params['paging'] = array( + 'Client' => array( + 'page' => 1, + 'current' => 3, + 'count' => 30, + 'prevPage' => false, + 'nextPage' => 2, + 'pageCount' => 15, + 'options' => array( + 'page' => 1, + 'limit' => 3, + 'order' => array('Client.name' => 'DESC'), + 'conditions' => array() + ) + ) ); $result = $this->Paginator->first(); $expected = ''; $this->assertEqual($result, $expected); - $this->Paginator->request->params['paging'] = array('Client' => array( - 'page' => 4, 'current' => 3, 'count' => 30, 'prevPage' => false, 'nextPage' => 2, 'pageCount' => 15, - 'defaults' => array('limit' => 3, 'step' => 1, 'order' => array('Client.name' => 'DESC'), 'conditions' => array()), - 'options' => array('page' => 1, 'limit' => 3, 'order' => array('Client.name' => 'DESC'), 'conditions' => array())) + $this->Paginator->request->params['paging'] = array( + 'Client' => array( + 'page' => 4, + 'current' => 3, + 'count' => 30, + 'prevPage' => false, + 'nextPage' => 2, + 'pageCount' => 15, + 'options' => array( + 'page' => 1, + 'limit' => 3, + 'order' => array('Client.name' => 'DESC'), + 'conditions' => array() + ) + ) ); $result = $this->Paginator->first(); @@ -1702,19 +1888,41 @@ class PaginatorHelperTest extends CakeTestCase { ); $this->assertTags($result, $expected); - $this->Paginator->request->params['paging'] = array('Client' => array( - 'page' => 15, 'current' => 3, 'count' => 30, 'prevPage' => false, 'nextPage' => 2, 'pageCount' => 15, - 'defaults' => array('limit' => 3, 'step' => 1, 'order' => array('Client.name' => 'DESC'), 'conditions' => array()), - 'options' => array('page' => 1, 'limit' => 3, 'order' => array('Client.name' => 'DESC'), 'conditions' => array())) + $this->Paginator->request->params['paging'] = array( + 'Client' => array( + 'page' => 15, + 'current' => 3, + 'count' => 30, + 'prevPage' => false, + 'nextPage' => 2, + 'pageCount' => 15, + 'options' => array( + 'page' => 1, + 'limit' => 3, + 'order' => array('Client.name' => 'DESC'), + 'conditions' => array() + ) + ) ); $result = $this->Paginator->last(); $expected = ''; $this->assertEqual($result, $expected); - $this->Paginator->request->params['paging'] = array('Client' => array( - 'page' => 4, 'current' => 3, 'count' => 30, 'prevPage' => false, 'nextPage' => 2, 'pageCount' => 15, - 'defaults' => array('limit' => 3), - 'options' => array('page' => 1, 'limit' => 3, 'order' => array('Client.name' => 'DESC'), 'conditions' => array())) + $this->Paginator->request->params['paging'] = array( + 'Client' => array( + 'page' => 4, + 'current' => 3, + 'count' => 30, + 'prevPage' => false, + 'nextPage' => 2, + 'pageCount' => 15, + 'options' => array( + 'page' => 1, + 'limit' => 3, + 'order' => array('Client.name' => 'DESC'), + 'conditions' => array() + ) + ) ); $result = $this->Paginator->first(); From 8c3ceff50d0bf8b7042954156300cec2b4382095 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 19 Dec 2010 12:58:07 -0500 Subject: [PATCH 26/59] Making paging.options only contain options that are not in the defaults. This replaces the many diffs that were calculated on each url generation between paging.options and paging.defaults. --- cake/libs/controller/components/paginator.php | 47 ++++++++++------ .../controller/components/paginator.test.php | 56 +++++-------------- 2 files changed, 44 insertions(+), 59 deletions(-) diff --git a/cake/libs/controller/components/paginator.php b/cake/libs/controller/components/paginator.php index a774e934e..e660276e3 100644 --- a/cake/libs/controller/components/paginator.php +++ b/cake/libs/controller/components/paginator.php @@ -184,14 +184,18 @@ class PaginatorComponent extends Component { } $results = $object->find($type, array_merge($parameters, $extra)); } + $defaults = $this->getDefaults($object->alias); + unset($defaults[0]); + $paging = array( - 'page' => $page, - 'current' => count($results), - 'count' => $count, - 'prevPage' => ($page > 1), - 'nextPage' => ($count > ($page * $limit)), - 'pageCount' => $pageCount, - 'options' => $options, + 'page' => $page, + 'current' => count($results), + 'count' => $count, + 'prevPage' => ($page > 1), + 'nextPage' => ($count > ($page * $limit)), + 'pageCount' => $pageCount, + 'order' => $order, + 'options' => Set::diff($options, $defaults), 'paramType' => $options['paramType'] ); if (!isset($this->Controller->request['paging'])) { @@ -275,15 +279,7 @@ class PaginatorComponent extends Component { * @return array Array of merged options. */ public function mergeOptions($alias, $whitelist = array()) { - if (isset($this->settings[$alias])) { - $defaults = $this->settings[$alias]; - } else { - $defaults = $this->settings; - } - $defaults = array_merge( - array('page' => 1, 'limit' => 20, 'maxLimit' => 100, 'paramType' => 'named'), - $defaults - ); + $defaults = $this->getDefaults($alias); switch ($defaults['paramType']) { case 'named': $request = $this->Controller->request->params['named']; @@ -302,6 +298,25 @@ class PaginatorComponent extends Component { return array_merge($defaults, $request); } +/** + * Get the default settings for a $model. If there are no settings for a specific model, the general settings + * will be used. + * + * @param string $alias Model name to get default settings for. + * @return array + */ + public function getDefaults($alias) { + if (isset($this->settings[$alias])) { + $defaults = $this->settings[$alias]; + } else { + $defaults = $this->settings; + } + return array_merge( + array('page' => 1, 'limit' => 20, 'maxLimit' => 100, 'paramType' => 'named'), + $defaults + ); + } + /** * Validate that the desired sorting can be performed on the $object. Only fields or * virtualFields can be sorted on. The direction param will also be sanitized. Lastly diff --git a/cake/tests/cases/libs/controller/components/paginator.test.php b/cake/tests/cases/libs/controller/components/paginator.test.php index ecb9f812f..9268d9615 100644 --- a/cake/tests/cases/libs/controller/components/paginator.test.php +++ b/cake/tests/cases/libs/controller/components/paginator.test.php @@ -285,8 +285,7 @@ class PaginatorTest extends CakeTestCase { $Controller->request->params['named'] = array('page' => '1 " onclick="alert(\'xss\');">'); $Controller->Paginator->settings = array('limit' => 1, 'maxLimit' => 10, 'paramType' => 'named'); $Controller->Paginator->paginate('PaginatorControllerPost'); - $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['page'], 1, 'XSS exploit opened %s'); - $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['options']['page'], 1, 'XSS exploit opened %s'); + $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['page'], 1, 'XSS exploit opened'); $Controller->request->params['named'] = array(); $Controller->Paginator->settings = array('limit' => 0, 'maxLimit' => 10, 'paramType' => 'named'); @@ -405,43 +404,6 @@ class PaginatorTest extends CakeTestCase { $this->assertEqual($Controller->ControllerPaginateModel->extraCount, $expected); } -/** - * testPaginatePassedArgs method - * - * @return void - */ - public function testPaginatePassedArgs() { - $Controller = new PaginatorTestController($this->request); - $Controller->uses = array('PaginatorControllerPost'); - $Controller->request->params['pass'] = array('1', '2', '3'); - $Controller->params['url'] = array(); - $Controller->constructClasses(); - - $Controller->Paginator->settings = array( - 'fields' => array(), - 'order' => '', - 'limit' => 5, - 'page' => 1, - 'recursive' => -1, - 'maxLimit' => 10, - 'paramType' => 'named' - ); - $conditions = array(); - $Controller->Paginator->paginate('PaginatorControllerPost', $conditions); - - $expected = array( - 'fields' => array(), - 'order' => '', - 'limit' => 5, - 'maxLimit' => 10, - 'page' => 1, - 'recursive' => -1, - 'conditions' => array(), - 'paramType' => 'named' - ); - $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options'],$expected); - } - /** * Test that special paginate types are called and that the type param doesn't leak out into defaults or options. * @@ -456,13 +418,19 @@ class PaginatorTest extends CakeTestCase { $Controller->Paginator->settings = array( 'PaginatorControllerPost' => array( - 'popular', 'fields' => array('id', 'title'), 'maxLimit' => 10, 'paramType' => 'named' + 'popular', + 'fields' => array('id', 'title'), + 'maxLimit' => 10, + 'paramType' => 'named' ) ); $result = $Controller->Paginator->paginate('PaginatorControllerPost'); $this->assertEqual(Set::extract($result, '{n}.PaginatorControllerPost.id'), array(2, 3)); - $this->assertEqual($Controller->PaginatorControllerPost->lastQuery['conditions'], array('PaginatorControllerPost.id > ' => '1')); + $this->assertEqual( + $Controller->PaginatorControllerPost->lastQuery['conditions'], + array('PaginatorControllerPost.id > ' => '1') + ); $this->assertFalse(isset($Controller->params['paging']['PaginatorControllerPost']['options'][0])); } @@ -478,10 +446,12 @@ class PaginatorTest extends CakeTestCase { $Controller->params['url'] = array(); $Controller->constructClasses(); $Controller->Paginator->settings = array( - 'order' => 'PaginatorControllerPost.id DESC', 'maxLimit' => 10, 'paramType' => 'named' + 'order' => 'PaginatorControllerPost.id DESC', + 'maxLimit' => 10, + 'paramType' => 'named' ); $results = Set::extract($Controller->Paginator->paginate('PaginatorControllerPost'), '{n}.PaginatorControllerPost.id'); - $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['order'], 'PaginatorControllerPost.id DESC'); + $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['order'], 'PaginatorControllerPost.id DESC'); $this->assertEqual($results, array(3, 2, 1)); } From 176d5520f624f821de8bb7ce1ddd95b63eacba99 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 19 Dec 2010 13:09:52 -0500 Subject: [PATCH 27/59] Making paging params match those that would be generated by PaginatorComponent. --- .../libs/view/helpers/paginator.test.php | 112 +++++------------- 1 file changed, 32 insertions(+), 80 deletions(-) diff --git a/cake/tests/cases/libs/view/helpers/paginator.test.php b/cake/tests/cases/libs/view/helpers/paginator.test.php index fa6718d55..c8ca15a05 100644 --- a/cake/tests/cases/libs/view/helpers/paginator.test.php +++ b/cake/tests/cases/libs/view/helpers/paginator.test.php @@ -53,8 +53,6 @@ class PaginatorHelperTest extends CakeTestCase { 'nextPage' => true, 'pageCount' => 7, 'options' => array( - 'order' => array('Article.date' => 'asc'), - 'limit' => 9, 'page' => 1, 'conditions' => array() ) @@ -138,6 +136,21 @@ class PaginatorHelperTest extends CakeTestCase { array('base' => '/officespace', 'here' => '/officespace/accounts/', 'webroot' => '/officespace/') )); $this->Paginator->options(array('url' => array('param'))); + $this->Paginator->request['paging'] = array( + 'Article' => array( + 'current' => 9, + 'count' => 62, + 'prevPage' => false, + 'nextPage' => true, + 'pageCount' => 7, + 'options' => array( + 'page' => 1, + 'order' => array('date' => 'asc'), + 'conditions' => array() + ) + ) + ); + $result = $this->Paginator->sort('title'); $expected = array( 'a' => array('href' => '/officespace/accounts/index/param/page:1/sort:title/direction:asc'), @@ -645,7 +658,9 @@ class PaginatorHelperTest extends CakeTestCase { 'page' => 1, 'current' => 3, 'count' => 13, 'prevPage' => false, 'nextPage' => true, 'pageCount' => 8, 'options' => array( - 'page' => 1, 'limit' => 3, 'order' => array(), 'conditions' => array() + 'page' => 1, + 'order' => array(), + 'conditions' => array() ) ) ); @@ -708,9 +723,6 @@ class PaginatorHelperTest extends CakeTestCase { 'pageCount' => 5, 'options' => array( 'page' => 1, - 'limit' => 3, - 'order' => array('Client.name' => 'DESC'), - 'conditions' => array() ) ) ); @@ -792,9 +804,6 @@ class PaginatorHelperTest extends CakeTestCase { 'pageCount' => 5, 'options' => array( 'page' => 1, - 'limit' => 3, - 'order' => array('Client.name' => 'DESC'), - 'conditions' => array() ) ) ); @@ -834,8 +843,7 @@ class PaginatorHelperTest extends CakeTestCase { 'options' => array( 'page' => 1, 'limit' => 3, - 'order' => array('Client.name' => 'DESC'), - 'conditions' => array() + 'order' => array('Client.name' => 'DESC'), ) ) ); @@ -873,7 +881,7 @@ class PaginatorHelperTest extends CakeTestCase { 'options' => array( 'page' => 2, 'limit' => 10, - 'order' => array(), + 'order' => array(), 'conditions' => array() ) ) @@ -926,7 +934,7 @@ class PaginatorHelperTest extends CakeTestCase { 'nextPage' => true, 'pageCount' => 5, 'options' => array( - 'page' => 1, 'limit' => 3, 'order' => array('Client.name' => 'DESC'), 'conditions' => array() + 'page' => 1, ) ) ); @@ -969,9 +977,6 @@ class PaginatorHelperTest extends CakeTestCase { 'pageCount' => 5, 'options' => array( 'page' => 1, - 'limit' => 3, - 'order' => array('Client.name' => 'DESC'), - 'conditions' => array() ) ), 'Server' => array( @@ -983,9 +988,6 @@ class PaginatorHelperTest extends CakeTestCase { 'pageCount' => 5, 'options' => array( 'page' => 1, - 'limit' => 5, - 'order' => array('Server.name' => 'ASC'), - 'conditions' => array() ) ) ); @@ -1094,10 +1096,7 @@ class PaginatorHelperTest extends CakeTestCase { 'nextPage' => 2, 'pageCount' => 15, 'options' => array( - 'page' => 1, - 'limit' => 3, - 'order' => array('Client.name' => 'DESC'), - 'conditions' => array() + 'page' => 1, ) ) ); @@ -1194,10 +1193,7 @@ class PaginatorHelperTest extends CakeTestCase { 'nextPage' => 2, 'pageCount' => 15, 'options' => array( - 'page' => 1, - 'limit' => 3, - 'order' => array('Client.name' => 'DESC'), - 'conditions' => array() + 'page' => 1, ) ) ); @@ -1233,10 +1229,7 @@ class PaginatorHelperTest extends CakeTestCase { 'nextPage' => 2, 'pageCount' => 15, 'options' => array( - 'page' => 1, - 'limit' => 3, - 'order' => array('Client.name' => 'DESC'), - 'conditions' => array() + 'page' => 1, ) ) ); @@ -1271,10 +1264,7 @@ class PaginatorHelperTest extends CakeTestCase { 'nextPage' => 2, 'pageCount' => 9, 'options' => array( - 'page' => 1, - 'limit' => 3, - 'order' => array('Client.name' => 'DESC'), - 'conditions' => array() + 'page' => 1, ) ) ); @@ -1332,10 +1322,7 @@ class PaginatorHelperTest extends CakeTestCase { 'nextPage' => 2, 'pageCount' => 15, 'options' => array( - 'page' => 1, - 'limit' => 3, - 'order' => array('Client.name' => 'DESC'), - 'conditions' => array() + 'page' => 1, ) ) ); @@ -1374,10 +1361,7 @@ class PaginatorHelperTest extends CakeTestCase { 'nextPage' => 2, 'pageCount' => 15, 'options' => array( - 'page' => 1, - 'limit' => 3, - 'order' => array('Client.name' => 'DESC'), - 'conditions' => array() + 'page' => 1, ) ) ); @@ -1418,9 +1402,6 @@ class PaginatorHelperTest extends CakeTestCase { 'pageCount' => 42, 'options' => array( 'page' => 6, - 'limit' => 15, - 'order' => array('Client.name' => 'DESC'), - 'conditions' => array() ) ) ); @@ -1460,10 +1441,7 @@ class PaginatorHelperTest extends CakeTestCase { 'nextPage' => 1, 'pageCount' => 42, 'options' => array( - 'page' => 37, - 'limit' => 15, - 'order' => array('Client.name' => 'DESC'), - 'conditions' => array() + 'page' => 37, ) ) ); @@ -1502,17 +1480,8 @@ class PaginatorHelperTest extends CakeTestCase { 'prevPage' => false, 'nextPage' => 2, 'pageCount' => 3, - 'defaults' => array( - 'limit' => 3, - 'step' => 1, - 'order' => array('Client.name' => 'DESC'), - 'conditions' => array() - ), 'options' => array( 'page' => 1, - 'limit' => 3, - 'order' => array('Client.name' => 'DESC'), - 'conditions' => array() ) ) ); @@ -1538,7 +1507,6 @@ class PaginatorHelperTest extends CakeTestCase { 'options' => array( 'page' => 1, 'order' => array('Client.name' => 'DESC'), - 'conditions' => array() ) ) ); @@ -1565,9 +1533,6 @@ class PaginatorHelperTest extends CakeTestCase { 'pageCount' => 4897, 'options' => array( 'page' => 4894, - 'limit' => 10, - 'order' => 'Client.name DESC', - 'conditions' => array() ) ) ); @@ -1791,12 +1756,7 @@ class PaginatorHelperTest extends CakeTestCase { 'prevPage' => false, 'nextPage' => 2, 'pageCount' => 15, - 'options' => array( - 'page' => 1, - 'limit' => 3, - 'order' => array('Client.name' => 'DESC'), - 'conditions' => array() - ) + 'options' => array() ) ); @@ -1814,9 +1774,6 @@ class PaginatorHelperTest extends CakeTestCase { 'pageCount' => 15, 'options' => array( 'page' => 1, - 'limit' => 3, - 'order' => array('Client.name' => 'DESC'), - 'conditions' => array() ) ) ); @@ -1898,9 +1855,6 @@ class PaginatorHelperTest extends CakeTestCase { 'pageCount' => 15, 'options' => array( 'page' => 1, - 'limit' => 3, - 'order' => array('Client.name' => 'DESC'), - 'conditions' => array() ) ) ); @@ -1913,14 +1867,12 @@ class PaginatorHelperTest extends CakeTestCase { 'page' => 4, 'current' => 3, 'count' => 30, - 'prevPage' => false, + 'prevPage' => false, 'nextPage' => 2, 'pageCount' => 15, 'options' => array( - 'page' => 1, - 'limit' => 3, + 'page' => 1, 'order' => array('Client.name' => 'DESC'), - 'conditions' => array() ) ) ); @@ -2139,7 +2091,7 @@ class PaginatorHelperTest extends CakeTestCase { Router::reload(); Router::parse('/'); Router::setRequestInfo(array( - array('plugin' => null, 'controller' => 'accounts', 'action' => 'index', 'pass' => array(), 'url' => array('url' => 'accounts/', 'mod_rewrite' => 'true')), + array('plugin' => null, 'controller' => 'accounts', 'action' => 'index', 'pass' => array(), 'url' => array('url' => 'accounts/')), array('base' => '/officespace', 'here' => '/officespace/accounts/', 'webroot' => '/officespace/', 'passedArgs' => array()) )); From cc2d8e2fece90d9024ea54a249081293f67ee249 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 19 Dec 2010 13:15:04 -0500 Subject: [PATCH 28/59] Moving limit from the options to the normal paging params. This fixes a few notice errors. --- cake/libs/controller/components/paginator.php | 1 + cake/libs/view/helpers/paginator.php | 4 ++-- .../libs/controller/components/paginator.test.php | 2 ++ cake/tests/cases/libs/view/helpers/paginator.test.php | 10 +--------- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/cake/libs/controller/components/paginator.php b/cake/libs/controller/components/paginator.php index e660276e3..caa911c06 100644 --- a/cake/libs/controller/components/paginator.php +++ b/cake/libs/controller/components/paginator.php @@ -195,6 +195,7 @@ class PaginatorComponent extends Component { 'nextPage' => ($count > ($page * $limit)), 'pageCount' => $pageCount, 'order' => $order, + 'limit' => $limit, 'options' => Set::diff($options, $defaults), 'paramType' => $options['paramType'] ); diff --git a/cake/libs/view/helpers/paginator.php b/cake/libs/view/helpers/paginator.php index 5708b9afe..2651579f5 100644 --- a/cake/libs/view/helpers/paginator.php +++ b/cake/libs/view/helpers/paginator.php @@ -525,9 +525,9 @@ class PaginatorHelper extends AppHelper { } $start = 0; if ($paging['count'] >= 1) { - $start = (($paging['page'] - 1) * $paging['options']['limit']) + 1; + $start = (($paging['page'] - 1) * $paging['limit']) + 1; } - $end = $start + $paging['options']['limit'] - 1; + $end = $start + $paging['limit'] - 1; if ($paging['count'] < $end) { $end = $paging['count']; } diff --git a/cake/tests/cases/libs/controller/components/paginator.test.php b/cake/tests/cases/libs/controller/components/paginator.test.php index 9268d9615..1f6168b66 100644 --- a/cake/tests/cases/libs/controller/components/paginator.test.php +++ b/cake/tests/cases/libs/controller/components/paginator.test.php @@ -306,6 +306,8 @@ class PaginatorTest extends CakeTestCase { $Controller->request->params['named'] = array(); $Controller->Paginator->settings = array('limit' => '-1', 'maxLimit' => 10, 'paramType' => 'named'); $Controller->Paginator->paginate('PaginatorControllerPost'); + + $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['limit'], 1); $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['page'], 1); $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['pageCount'], 3); $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['prevPage'], false); diff --git a/cake/tests/cases/libs/view/helpers/paginator.test.php b/cake/tests/cases/libs/view/helpers/paginator.test.php index c8ca15a05..27e9f94a9 100644 --- a/cake/tests/cases/libs/view/helpers/paginator.test.php +++ b/cake/tests/cases/libs/view/helpers/paginator.test.php @@ -1966,18 +1966,10 @@ class PaginatorHelperTest extends CakeTestCase { 'prevPage' => false, 'nextPage' => true, 'pageCount' => 5, - 'defaults' => array( - 'limit' => 3, - 'step' => 1, - 'order' => array('Client.name' => 'DESC'), - 'conditions' => array() - ), + 'limit' => 3, 'options' => array( 'page' => 1, - 'limit' => 3, 'order' => array('Client.name' => 'DESC'), - 'conditions' => array(), - 'separator' => 'of' ), ) ); From da46ad494b4a3c3ce171de39c68e586e22a77e3f Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 19 Dec 2010 13:53:11 -0500 Subject: [PATCH 29/59] Deleting duplicate tests. Moving a test out into a separate method. --- .../controller/components/paginator.test.php | 107 ++++++++++-------- 1 file changed, 62 insertions(+), 45 deletions(-) diff --git a/cake/tests/cases/libs/controller/components/paginator.test.php b/cake/tests/cases/libs/controller/components/paginator.test.php index 1f6168b66..df3779f1e 100644 --- a/cake/tests/cases/libs/controller/components/paginator.test.php +++ b/cake/tests/cases/libs/controller/components/paginator.test.php @@ -223,6 +223,8 @@ class PaginatorTest extends CakeTestCase { $this->Controller = new Controller($this->request); $this->Paginator = new PaginatorComponent($this->getMock('ComponentCollection'), array()); $this->Paginator->Controller = $this->Controller; + $this->Controller->Post = $this->getMock('Model'); + $this->Controller->Post->alias = 'Post'; } /** @@ -282,11 +284,6 @@ class PaginatorTest extends CakeTestCase { $this->assertEqual($Controller->PaginatorControllerPost->lastQuery['order'][0], array('PaginatorControllerPost.author_id' => 'asc')); $this->assertEqual($results, array(1, 3, 2)); - $Controller->request->params['named'] = array('page' => '1 " onclick="alert(\'xss\');">'); - $Controller->Paginator->settings = array('limit' => 1, 'maxLimit' => 10, 'paramType' => 'named'); - $Controller->Paginator->paginate('PaginatorControllerPost'); - $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['page'], 1, 'XSS exploit opened'); - $Controller->request->params['named'] = array(); $Controller->Paginator->settings = array('limit' => 0, 'maxLimit' => 10, 'paramType' => 'named'); $Controller->Paginator->paginate('PaginatorControllerPost'); @@ -314,6 +311,26 @@ class PaginatorTest extends CakeTestCase { $this->assertIdentical($Controller->params['paging']['PaginatorControllerPost']['nextPage'], true); } +/** + * Test that non-numeric values are rejected for page, and limit + * + * @return void + */ + function testPageParamCasting() { + $this->Controller->Post->expects($this->at(0)) + ->method('find') + ->will($this->returnValue(2)); + + $this->Controller->Post->expects($this->at(1)) + ->method('find') + ->will($this->returnValue(array('stuff'))); + + $this->request->params['named'] = array('page' => '1 " onclick="alert(\'xss\');">'); + $this->Paginator->settings = array('limit' => 1, 'maxLimit' => 10, 'paramType' => 'named'); + $this->Paginator->paginate('Post'); + $this->assertSame(1, $this->request->params['paging']['Post']['page'], 'XSS exploit opened'); + } + /** * testPaginateExtraParams method * @@ -496,46 +513,6 @@ class PaginatorTest extends CakeTestCase { $Controller->Paginator->paginate('MissingModel'); } -/** - * testPaginateMaxLimit - * - * @return void - * @access public - */ - function testPaginateMaxLimit() { - $Controller = new Controller($this->request); - - $Controller->uses = array('PaginatorControllerPost', 'ControllerComment'); - $Controller->passedArgs[] = '1'; - $Controller->params['url'] = array(); - $Controller->constructClasses(); - - $Controller->request->params['named'] = array( - 'contain' => array('ControllerComment'), 'limit' => '1000' - ); - $result = $Controller->paginate('PaginatorControllerPost'); - $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['limit'], 100); - - $Controller->request->params['named'] = array( - 'contain' => array('ControllerComment'), 'limit' => '1000', 'maxLimit' => 1000 - ); - $result = $Controller->paginate('PaginatorControllerPost'); - $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['limit'], 100); - - $Controller->request->params['named'] = array('contain' => array('ControllerComment'), 'limit' => '10'); - $result = $Controller->paginate('PaginatorControllerPost'); - $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['limit'], 10); - - $Controller->request->params['named'] = array('contain' => array('ControllerComment'), 'limit' => '1000'); - $Controller->paginate = array('maxLimit' => 2000, 'paramType' => 'named'); - $result = $Controller->paginate('PaginatorControllerPost'); - $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['limit'], 1000); - - $Controller->request->params['named'] = array('contain' => array('ControllerComment'), 'limit' => '5000'); - $result = $Controller->paginate('PaginatorControllerPost'); - $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['limit'], 2000); - } - /** * test that option merging prefers specific models * @@ -722,4 +699,44 @@ class PaginatorTest extends CakeTestCase { $result = $this->Paginator->checkLimit(array('limit' => 0, 'maxLimit' => 100)); $this->assertEquals(1, $result['limit']); } + +/** + * testPaginateMaxLimit + * + * @return void + * @access public + */ + function testPaginateMaxLimit() { + $Controller = new Controller($this->request); + + $Controller->uses = array('PaginatorControllerPost', 'ControllerComment'); + $Controller->passedArgs[] = '1'; + $Controller->params['url'] = array(); + $Controller->constructClasses(); + + $Controller->request->params['named'] = array( + 'contain' => array('ControllerComment'), 'limit' => '1000' + ); + $result = $Controller->paginate('PaginatorControllerPost'); + $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['limit'], 100); + + $Controller->request->params['named'] = array( + 'contain' => array('ControllerComment'), 'limit' => '1000', 'maxLimit' => 1000 + ); + $result = $Controller->paginate('PaginatorControllerPost'); + $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['limit'], 100); + + $Controller->request->params['named'] = array('contain' => array('ControllerComment'), 'limit' => '10'); + $result = $Controller->paginate('PaginatorControllerPost'); + $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['limit'], 10); + + $Controller->request->params['named'] = array('contain' => array('ControllerComment'), 'limit' => '1000'); + $Controller->paginate = array('maxLimit' => 2000, 'paramType' => 'named'); + $result = $Controller->paginate('PaginatorControllerPost'); + $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['limit'], 1000); + + $Controller->request->params['named'] = array('contain' => array('ControllerComment'), 'limit' => '5000'); + $result = $Controller->paginate('PaginatorControllerPost'); + $this->assertEqual($Controller->params['paging']['PaginatorControllerPost']['options']['limit'], 2000); + } } \ No newline at end of file From c83a4703a3779db9446a93fe15938a64a8ca18fb Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 19 Dec 2010 16:47:22 -0500 Subject: [PATCH 30/59] Implementing ability to change pagination params to use querystring variables. Tests added. --- cake/libs/view/helpers/paginator.php | 45 ++++++- .../libs/view/helpers/paginator.test.php | 117 ++++++++++++------ 2 files changed, 121 insertions(+), 41 deletions(-) diff --git a/cake/libs/view/helpers/paginator.php b/cake/libs/view/helpers/paginator.php index 2651579f5..5caaf7ed9 100644 --- a/cake/libs/view/helpers/paginator.php +++ b/cake/libs/view/helpers/paginator.php @@ -68,14 +68,22 @@ class PaginatorHelper extends AppHelper { * - `$options['escape']` Defines if the title field for the link should be escaped (default: true). * - `$options['update']` DOM id of the element updated with the results of the AJAX call. * If this key isn't specified Paginator will use plain HTML links. - * - `$options['indicator']` DOM id of the element that will be shown when doing AJAX requests. **Only supported by - * AjaxHelper** + * - `$options['paramType']` The type of parameters to use when creating links. Valid options are + * 'querystring', 'named', and 'route'. See PaginatorComponent::$settings for more information. * * @var array * @access public */ public $options = array(); +/** + * A list of keys that will be turned into `$this->options['paramType']` url parameters when links + * are generated + * + * @var array + */ + public $convertKeys = array('page', 'limit', 'sort', 'direction'); + /** * Constructor for the helper. Sets up the helper that is used for creating 'AJAX' links. * @@ -111,7 +119,12 @@ class PaginatorHelper extends AppHelper { * @return void */ public function beforeRender($viewFile) { - $this->options['url'] = array_merge($this->request->params['pass'], $this->request->params['named']); + $named = $this->request->params['named']; + foreach ($named as $key => $val) { + $named[CakeRoute::SIGIL_NAMED . $key] = $val; + unset($named[$key]); + } + $this->options['url'] = array_merge($this->request->params['pass'], $named); parent::beforeRender($viewFile); } @@ -377,6 +390,7 @@ class PaginatorHelper extends AppHelper { unset($url['order']); $url = array_merge($url, compact('sort', 'direction')); } + $url = $this->_convertUrlKeys($url, $paging['paramType']); if ($asArray) { return $url; @@ -384,6 +398,31 @@ class PaginatorHelper extends AppHelper { return parent::url($url); } +/** + * Converts the keys being used into the format set by options.paramType + * + * @param array $url Array of url params to convert + * @return converted url params. + */ + protected function _convertUrlKeys($url, $type) { + $prefix = ''; + switch ($type) { + case 'named': + $prefix = CakeRoute::SIGIL_NAMED; + break; + case 'querystring': + $prefix = CakeRoute::SIGIL_QUERYSTRING; + break; + } + foreach ($this->convertKeys as $key) { + if (isset($url[$key])) { + $url[$prefix . $key] = $url[$key]; + unset($url[$key]); + } + } + return $url; + } + /** * Protected method for generating prev/next links * diff --git a/cake/tests/cases/libs/view/helpers/paginator.test.php b/cake/tests/cases/libs/view/helpers/paginator.test.php index 27e9f94a9..ab400702b 100644 --- a/cake/tests/cases/libs/view/helpers/paginator.test.php +++ b/cake/tests/cases/libs/view/helpers/paginator.test.php @@ -55,7 +55,8 @@ class PaginatorHelperTest extends CakeTestCase { 'options' => array( 'page' => 1, 'conditions' => array() - ) + ), + 'paramType' => 'named' ) ) )); @@ -147,7 +148,8 @@ class PaginatorHelperTest extends CakeTestCase { 'page' => 1, 'order' => array('date' => 'asc'), 'conditions' => array() - ) + ), + 'paramType' => 'named' ) ); @@ -651,7 +653,7 @@ class PaginatorHelperTest extends CakeTestCase { Router::parse('/'); Router::setRequestInfo(array( array('plugin' => null, 'controller' => 'articles', 'action' => 'index', 'pass' => array('2'), 'named' => array('foo' => 'bar'), 'url' => array('url' => 'articles/index/2/foo:bar')), - array('base' => '/', 'here' => '/articles/', 'webroot' => '/', 'passedArgs' => array(0 => '2', 'foo' => 'bar')) + array('base' => '/', 'here' => '/articles/', 'webroot' => '/') )); $this->Paginator->request->params['paging'] = array( 'Article' => array( @@ -661,7 +663,8 @@ class PaginatorHelperTest extends CakeTestCase { 'page' => 1, 'order' => array(), 'conditions' => array() - ) + ), + 'paramType' => 'named' ) ); @@ -671,7 +674,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->sort('title'); $expected = array( - 'a' => array('href' => '/articles/index/2/page:1/foo:bar/sort:title/direction:asc'), + 'a' => array('href' => '/articles/index/2/foo:bar/page:1/sort:title/direction:asc'), 'Title', '/a' ); @@ -681,24 +684,24 @@ class PaginatorHelperTest extends CakeTestCase { $expected = array( array('span' => array('class' => 'current')), '1', '/span', ' | ', - array('span' => array()), array('a' => array('href' => '/articles/index/2/page:2/foo:bar')), '2', '/a', '/span', + array('span' => array()), array('a' => array('href' => '/articles/index/2/foo:bar/page:2')), '2', '/a', '/span', ' | ', - array('span' => array()), array('a' => array('href' => '/articles/index/2/page:3/foo:bar')), '3', '/a', '/span', + array('span' => array()), array('a' => array('href' => '/articles/index/2/foo:bar/page:3')), '3', '/a', '/span', ' | ', - array('span' => array()), array('a' => array('href' => '/articles/index/2/page:4/foo:bar')), '4', '/a', '/span', + array('span' => array()), array('a' => array('href' => '/articles/index/2/foo:bar/page:4')), '4', '/a', '/span', ' | ', - array('span' => array()), array('a' => array('href' => '/articles/index/2/page:5/foo:bar')), '5', '/a', '/span', + array('span' => array()), array('a' => array('href' => '/articles/index/2/foo:bar/page:5')), '5', '/a', '/span', ' | ', - array('span' => array()), array('a' => array('href' => '/articles/index/2/page:6/foo:bar')), '6', '/a', '/span', + array('span' => array()), array('a' => array('href' => '/articles/index/2/foo:bar/page:6')), '6', '/a', '/span', ' | ', - array('span' => array()), array('a' => array('href' => '/articles/index/2/page:7/foo:bar')), '7', '/a', '/span', + array('span' => array()), array('a' => array('href' => '/articles/index/2/foo:bar/page:7')), '7', '/a', '/span', ); $this->assertTags($result, $expected); $result = $this->Paginator->next('Next'); $expected = array( ' array('href' => '/articles/index/2/page:2/foo:bar', 'class' => 'next'), + 'a' => array('href' => '/articles/index/2/foo:bar/page:2', 'class' => 'next'), 'Next', '/a', '/span' @@ -723,7 +726,8 @@ class PaginatorHelperTest extends CakeTestCase { 'pageCount' => 5, 'options' => array( 'page' => 1, - ) + ), + 'paramType' => 'named' ) ); $result = $this->Paginator->prev('<< Previous', null, null, array('class' => 'disabled')); @@ -804,7 +808,8 @@ class PaginatorHelperTest extends CakeTestCase { 'pageCount' => 5, 'options' => array( 'page' => 1, - ) + ), + 'paramType' => 'named' ) ); @@ -844,7 +849,8 @@ class PaginatorHelperTest extends CakeTestCase { 'page' => 1, 'limit' => 3, 'order' => array('Client.name' => 'DESC'), - ) + ), + 'paramType' => 'named' ) ); @@ -883,7 +889,8 @@ class PaginatorHelperTest extends CakeTestCase { 'limit' => 10, 'order' => array(), 'conditions' => array() - ) + ), + 'paramType' => 'named' ) ); $result = $this->Paginator->prev('Prev'); @@ -903,14 +910,15 @@ class PaginatorHelperTest extends CakeTestCase { 'defaults' => array(), 'options' => array( 'page' => 2, 'limit' => 10, 'order' => array(), 'conditions' => array() - ) + ), + 'paramType' => 'named' ) ); $this->Paginator->options(array('url' => array(12, 'page' => 3))); - $result = $this->Paginator->prev('Prev', array('url' => array('foo' => 'bar'))); + $result = $this->Paginator->prev('Prev', array('url' => array(':foo' => 'bar'))); $expected = array( ' array('href' => '/index/12/page:1/limit:10/foo:bar', 'class' => 'prev'), + 'a' => array('href' => '/index/12/foo:bar/page:1/limit:10', 'class' => 'prev'), 'Prev', '/a', '/span' @@ -935,7 +943,8 @@ class PaginatorHelperTest extends CakeTestCase { 'pageCount' => 5, 'options' => array( 'page' => 1, - ) + ), + 'paramType' => 'named' ) ); $result = $this->Paginator->prev('<< Previous', array('escape' => false)); @@ -977,7 +986,8 @@ class PaginatorHelperTest extends CakeTestCase { 'pageCount' => 5, 'options' => array( 'page' => 1, - ) + ), + 'paramType' => 'named' ), 'Server' => array( 'page' => 1, @@ -988,7 +998,8 @@ class PaginatorHelperTest extends CakeTestCase { 'pageCount' => 5, 'options' => array( 'page' => 1, - ) + ), + 'paramType' => 'named' ) ); $result = $this->Paginator->next('Next', array('model' => 'Client')); @@ -1097,7 +1108,8 @@ class PaginatorHelperTest extends CakeTestCase { 'pageCount' => 15, 'options' => array( 'page' => 1, - ) + ), + 'paramType' => 'named' ) ); $result = $this->Paginator->numbers(); @@ -1194,7 +1206,8 @@ class PaginatorHelperTest extends CakeTestCase { 'pageCount' => 15, 'options' => array( 'page' => 1, - ) + ), + 'paramType' => 'named' ) ); $result = $this->Paginator->numbers(); @@ -1230,7 +1243,8 @@ class PaginatorHelperTest extends CakeTestCase { 'pageCount' => 15, 'options' => array( 'page' => 1, - ) + ), + 'paramType' => 'named' ) ); $result = $this->Paginator->numbers(); @@ -1265,7 +1279,8 @@ class PaginatorHelperTest extends CakeTestCase { 'pageCount' => 9, 'options' => array( 'page' => 1, - ) + ), + 'paramType' => 'named' ) ); @@ -1323,7 +1338,8 @@ class PaginatorHelperTest extends CakeTestCase { 'pageCount' => 15, 'options' => array( 'page' => 1, - ) + ), + 'paramType' => 'named' ) ); @@ -1362,7 +1378,8 @@ class PaginatorHelperTest extends CakeTestCase { 'pageCount' => 15, 'options' => array( 'page' => 1, - ) + ), + 'paramType' => 'named' ) ); @@ -1402,7 +1419,8 @@ class PaginatorHelperTest extends CakeTestCase { 'pageCount' => 42, 'options' => array( 'page' => 6, - ) + ), + 'paramType' => 'named' ) ); @@ -1442,7 +1460,8 @@ class PaginatorHelperTest extends CakeTestCase { 'pageCount' => 42, 'options' => array( 'page' => 37, - ) + ), + 'paramType' => 'named' ) ); @@ -1482,7 +1501,8 @@ class PaginatorHelperTest extends CakeTestCase { 'pageCount' => 3, 'options' => array( 'page' => 1, - ) + ), + 'paramType' => 'named' ) ); $options = array('modulus' => 10); @@ -1507,7 +1527,8 @@ class PaginatorHelperTest extends CakeTestCase { 'options' => array( 'page' => 1, 'order' => array('Client.name' => 'DESC'), - ) + ), + 'paramType' => 'named' ) ); $result = $this->Paginator->numbers(); @@ -1533,7 +1554,8 @@ class PaginatorHelperTest extends CakeTestCase { 'pageCount' => 4897, 'options' => array( 'page' => 4894, - ) + ), + 'paramType' => 'named' ) ); @@ -1756,7 +1778,8 @@ class PaginatorHelperTest extends CakeTestCase { 'prevPage' => false, 'nextPage' => 2, 'pageCount' => 15, - 'options' => array() + 'options' => array(), + 'paramType' => 'named' ) ); @@ -1774,7 +1797,8 @@ class PaginatorHelperTest extends CakeTestCase { 'pageCount' => 15, 'options' => array( 'page' => 1, - ) + ), + 'paramType' => 'named' ) ); @@ -1855,7 +1879,8 @@ class PaginatorHelperTest extends CakeTestCase { 'pageCount' => 15, 'options' => array( 'page' => 1, - ) + ), + 'paramType' => 'named' ) ); $result = $this->Paginator->last(); @@ -1873,7 +1898,8 @@ class PaginatorHelperTest extends CakeTestCase { 'options' => array( 'page' => 1, 'order' => array('Client.name' => 'DESC'), - ) + ), + 'paramType' => 'named' ) ); @@ -1971,6 +1997,7 @@ class PaginatorHelperTest extends CakeTestCase { 'page' => 1, 'order' => array('Client.name' => 'DESC'), ), + 'paramType' => 'named' ) ); $input = 'Page %page% of %pages%, showing %current% records out of %count% total, '; @@ -2152,7 +2179,8 @@ class PaginatorHelperTest extends CakeTestCase { 'nextPage' => true, 'pageCount' => 7, 'defaults' => array(), - 'options' => array() + 'options' => array(), + 'paramType' => 'named' ) ); $Paginator->PaginatorMockJs = $mock; @@ -2162,4 +2190,17 @@ class PaginatorHelperTest extends CakeTestCase { $this->expectException(); $Paginator = new PaginatorHelper($this->View, array('ajax' => 'Form')); } + +/** + * test that querystring links can be generated. + * + * @return void + */ + function testQuerystringLinkGeneration() { + $this->Paginator->request->params['paging']['Article']['paramType'] = 'querystring'; + $result = $this->Paginator->url(array('page' => '4')); + $expected = '/index?page=4'; + $this->assertEquals($expected, $result); + } + } From 5df2678ba9fce1728d5f6a885a166fb25cd724a2 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 19 Dec 2010 17:09:34 -0500 Subject: [PATCH 31/59] Fixing named params that were missing the : and fixing Router::reverse() so it adds in the : --- cake/libs/router.php | 21 +++++++++++++++------ cake/tests/cases/libs/router.test.php | 16 ++++++++-------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/cake/libs/router.php b/cake/libs/router.php index 506bdb2b0..f4cfa32fd 100644 --- a/cake/libs/router.php +++ b/cake/libs/router.php @@ -897,7 +897,7 @@ class Router { * @see Router::url() */ protected static function _handleNoRoute($url) { - $named = $args = array(); + $named = $args = $query = array(); $skip = array_merge( array('bare', 'action', 'controller', 'plugin', 'prefix'), self::$_prefixes @@ -908,10 +908,13 @@ class Router { // Remove this once parsed URL parameters can be inserted into 'pass' for ($i = 0; $i < $count; $i++) { + $key = $keys[$i]; if (is_numeric($keys[$i])) { - $args[] = $url[$keys[$i]]; - } else { - $named[$keys[$i]] = $url[$keys[$i]]; + $args[] = $url[$key]; + } elseif ($key[0] === CakeRoute::SIGIL_NAMED) { + $named[substr($key, 1)] = $url[$key]; + } elseif ($key[0] === CakeRoute::SIGIL_QUERYSTRING) { + $query[substr($key, 1)] = $url[$key]; } } @@ -923,7 +926,7 @@ class Router { } } - if (empty($named) && empty($args) && (!isset($url['action']) || $url['action'] === 'index')) { + if (empty($named) && empty($args) && empty($query) && (!isset($url['action']) || $url['action'] === 'index')) { $url['action'] = null; } @@ -947,7 +950,6 @@ class Router { if (!empty($named)) { foreach ($named as $name => $value) { - $name = trim($name, ':'); if (is_array($value)) { $flattend = Set::flatten($value, ']['); foreach ($flattend as $namedKey => $namedValue) { @@ -958,6 +960,9 @@ class Router { } } } + if (!empty($query)) { + $output .= Router::queryString($query); + } return $output; } @@ -1070,6 +1075,10 @@ class Router { $params['pass'], $params['named'], $params['paging'], $params['models'], $params['url'], $url['url'], $params['autoRender'], $params['bare'], $params['requested'], $params['return'] ); + foreach ($named as $key => $value) { + $named[CakeRoute::SIGIL_NAMED . $key] = $value; + unset($named[$key]); + } $params = array_merge($params, $pass, $named); if (!empty($url)) { $params['?'] = $url; diff --git a/cake/tests/cases/libs/router.test.php b/cake/tests/cases/libs/router.test.php index e05e50149..d9a57afc8 100644 --- a/cake/tests/cases/libs/router.test.php +++ b/cake/tests/cases/libs/router.test.php @@ -630,7 +630,7 @@ class RouterTest extends CakeTestCase { $request->webroot = '/'; Router::setRequestInfo($request); - $result = Router::url(array('page' => 2)); + $result = Router::url(array(':page' => 2)); $expected = '/admin/registrations/index/page:2'; $this->assertEqual($result, $expected); @@ -1546,12 +1546,12 @@ class RouterTest extends CakeTestCase { $expected = '/protected/others/edit/1'; $this->assertEqual($result, $expected); - $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1, 'protected' => true, 'page' => 1)); + $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1, 'protected' => true, ':page' => 1)); $expected = '/protected/others/edit/1/page:1'; $this->assertEqual($result, $expected); Router::connectNamed(array('random')); - $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1, 'protected' => true, 'random' => 'my-value')); + $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1, 'protected' => true, ':random' => 'my-value')); $expected = '/protected/others/edit/1/random:my-value'; $this->assertEqual($result, $expected); } @@ -1610,12 +1610,12 @@ class RouterTest extends CakeTestCase { $expected = '/protected/others/edit/1'; $this->assertEqual($result, $expected); - $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1, 'protected' => true, 'page' => 1)); + $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1, 'protected' => true, ':page' => 1)); $expected = '/protected/others/edit/1/page:1'; $this->assertEqual($result, $expected); Router::connectNamed(array('random')); - $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1, 'protected' => true, 'random' => 'my-value')); + $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1, 'protected' => true, ':random' => 'my-value')); $expected = '/protected/others/edit/1/random:my-value'; $this->assertEqual($result, $expected); } @@ -1721,7 +1721,7 @@ class RouterTest extends CakeTestCase { $this->assertEqual($result, $expected); $result = Router::url(array('controller' => 'my_controller', 'action' => 'my_action', 'base' => true)); - $expected = '/base/my_controller/my_action/base:1'; + $expected = '/base/my_controller/my_action'; $this->assertEqual($result, $expected); } @@ -1908,7 +1908,7 @@ class RouterTest extends CakeTestCase { $expected = array('pass' => array(), 'named' => array(), 'prefix' => 'members', 'plugin' => null, 'controller' => 'posts', 'action' => 'index', 'members' => true); $this->assertEqual($result, $expected); - $result = Router::url(array('members' => true, 'controller' => 'posts', 'action' =>'index', 'page' => 2)); + $result = Router::url(array('members' => true, 'controller' => 'posts', 'action' =>'index', ':page' => 2)); $expected = '/base/members/posts/index/page:2'; $this->assertEqual($result, $expected); @@ -2083,7 +2083,7 @@ class RouterTest extends CakeTestCase { $this->assertEqual($result, $expected); $result = Router::url(array('action' => 'test_another_action', 'locale' => 'badness')); - $expected = '/test/test_another_action/locale:badness'; + $expected = '/test/test_another_action'; $this->assertEqual($result, $expected); } From 51e2b16d46d96023d5ec4e11fcb32e9c52d91ce5 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 19 Dec 2010 17:24:38 -0500 Subject: [PATCH 32/59] Removing pagination test from Containable test case, it doesn't make sense there. --- .../libs/model/behaviors/containable.test.php | 106 ------------------ 1 file changed, 106 deletions(-) diff --git a/cake/tests/cases/libs/model/behaviors/containable.test.php b/cake/tests/cases/libs/model/behaviors/containable.test.php index d2ccdc5aa..b5dbabe52 100644 --- a/cake/tests/cases/libs/model/behaviors/containable.test.php +++ b/cake/tests/cases/libs/model/behaviors/containable.test.php @@ -3191,112 +3191,6 @@ class ContainableBehaviorTest extends CakeTestCase { $this->assertEqual($result, $expected); } -/** - * testPaginate method - * - * @access public - * @return void - */ - function testPaginate() { - App::import('Core', 'Controller'); - $Controller = new Controller($this->getMock('CakeRequest')); - $Controller->uses = array('Article'); - $Controller->passedArgs[] = '1'; - $Controller->request->params['url'] = array(); - $Controller->constructClasses(); - - $Controller->paginate = array('Article' => array('fields' => array('title'), 'contain' => array('User(user)'))); - $result = $Controller->paginate('Article'); - $expected = array( - array('Article' => array('title' => 'First Article'), 'User' => array('user' => 'mariano', 'id' => 1)), - array('Article' => array('title' => 'Second Article'), 'User' => array('user' => 'larry', 'id' => 3)), - array('Article' => array('title' => 'Third Article'), 'User' => array('user' => 'mariano', 'id' => 1)), - ); - $this->assertEqual($result, $expected); - - $r = $Controller->Article->find('all'); - $this->assertTrue(Set::matches('/Article[id=1]', $r)); - $this->assertTrue(Set::matches('/User[id=1]', $r)); - $this->assertTrue(Set::matches('/Tag[id=1]', $r)); - - $Controller->paginate = array('Article' => array('contain' => array('Comment(comment)' => 'User(user)'), 'fields' => array('title'))); - $result = $Controller->paginate('Article'); - $expected = array( - array( - 'Article' => array('title' => 'First Article', 'id' => 1), - 'Comment' => array( - array( - 'comment' => 'First Comment for First Article', - 'user_id' => 2, - 'article_id' => 1, - 'User' => array('user' => 'nate') - ), - array( - 'comment' => 'Second Comment for First Article', - 'user_id' => 4, - 'article_id' => 1, - 'User' => array('user' => 'garrett') - ), - array( - 'comment' => 'Third Comment for First Article', - 'user_id' => 1, - 'article_id' => 1, - 'User' => array('user' => 'mariano') - ), - array( - 'comment' => 'Fourth Comment for First Article', - 'user_id' => 1, - 'article_id' => 1, - 'User' => array('user' => 'mariano') - ) - ) - ), - array( - 'Article' => array('title' => 'Second Article', 'id' => 2), - 'Comment' => array( - array( - 'comment' => 'First Comment for Second Article', - 'user_id' => 1, - 'article_id' => 2, - 'User' => array('user' => 'mariano') - ), - array( - 'comment' => 'Second Comment for Second Article', - 'user_id' => 2, - 'article_id' => 2, - 'User' => array('user' => 'nate') - ) - ) - ), - array( - 'Article' => array('title' => 'Third Article', 'id' => 3), - 'Comment' => array() - ), - ); - $this->assertEqual($result, $expected); - - $r = $Controller->Article->find('all'); - $this->assertTrue(Set::matches('/Article[id=1]', $r)); - $this->assertTrue(Set::matches('/User[id=1]', $r)); - $this->assertTrue(Set::matches('/Tag[id=1]', $r)); - - $Controller->Article->unbindModel(array('hasMany' => array('Comment'), 'belongsTo' => array('User'), 'hasAndBelongsToMany' => array('Tag')), false); - $Controller->Article->bindModel(array('hasMany' => array('Comment'), 'belongsTo' => array('User')), false); - - $Controller->paginate = array('Article' => array('contain' => array('Comment(comment)', 'User(user)'), 'fields' => array('title'))); - $r = $Controller->paginate('Article'); - $this->assertTrue(Set::matches('/Article[id=1]', $r)); - $this->assertTrue(Set::matches('/User[id=1]', $r)); - $this->assertTrue(Set::matches('/Comment[article_id=1]', $r)); - $this->assertFalse(Set::matches('/Comment[id=1]', $r)); - - $r = $this->Article->find('all'); - $this->assertTrue(Set::matches('/Article[id=1]', $r)); - $this->assertTrue(Set::matches('/User[id=1]', $r)); - $this->assertTrue(Set::matches('/Comment[article_id=1]', $r)); - $this->assertTrue(Set::matches('/Comment[id=1]', $r)); - } - /** * testOriginalAssociations method * From 4c3736a68aa16c7c6d815c86b9ed2dec12433fa2 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 19 Dec 2010 17:38:21 -0500 Subject: [PATCH 33/59] Making more tests pass with the named parameter changes. --- cake/libs/http_socket.php | 4 ++-- cake/libs/route/redirect_route.php | 7 +++---- cake/tests/cases/libs/controller/controller.test.php | 3 ++- cake/tests/cases/libs/view/helper.test.php | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cake/libs/http_socket.php b/cake/libs/http_socket.php index ba56034bd..1a486742c 100644 --- a/cake/libs/http_socket.php +++ b/cake/libs/http_socket.php @@ -539,7 +539,7 @@ class HttpSocket extends CakeSocket { if (!method_exists($authClass, 'authentication')) { throw new SocketException(sprintf(__('The %s do not support authentication.'), $authClass)); } - call_user_func("$authClass::authentication", $this, $this->_auth[$method]); + call_user_func_array("$authClass::authentication", array($this, &$this->_auth[$method])); } /** @@ -565,7 +565,7 @@ class HttpSocket extends CakeSocket { if (!method_exists($authClass, 'proxyAuthentication')) { throw new SocketException(sprintf(__('The %s do not support proxy authentication.'), $authClass)); } - call_user_func("$authClass::proxyAuthentication", $this, $this->_proxy); + call_user_func_array("$authClass::proxyAuthentication", array($this, &$this->_proxy)); } /** diff --git a/cake/libs/route/redirect_route.php b/cake/libs/route/redirect_route.php index e385dec0d..d9beaa42d 100644 --- a/cake/libs/route/redirect_route.php +++ b/cake/libs/route/redirect_route.php @@ -70,16 +70,15 @@ class RedirectRoute extends CakeRoute { } if (isset($this->options['persist']) && is_array($redirect)) { $argOptions['context'] = array('action' => $redirect['action'], 'controller' => $redirect['controller']); - $args = Router::getArgs($params['_args_'], $argOptions); - $redirect += $args['pass']; - $redirect += $args['named']; + $redirect += Router::getArgs($params['_args_'], $argOptions) + array('url' => array()); + $redirect = Router::reverse($redirect); } $status = 301; if (isset($this->options['status']) && ($this->options['status'] >= 300 && $this->options['status'] < 400)) { $status = $this->options['status']; } $this->response->header(array('Location' => Router::url($redirect, true))); - $this->response->statusCode($status); + $this->response->statusCode($status); $this->response->send(); } diff --git a/cake/tests/cases/libs/controller/controller.test.php b/cake/tests/cases/libs/controller/controller.test.php index 39563931b..91165cd22 100644 --- a/cake/tests/cases/libs/controller/controller.test.php +++ b/cake/tests/cases/libs/controller/controller.test.php @@ -1266,7 +1266,8 @@ class ControllerTest extends CakeTestCase { $Controller->passedArgs[] = '1'; $Controller->params['url'] = array(); $Controller->constructClasses(); - $this->assertEqual($Controller->paginate, array('page' => 1, 'limit' => 20)); + $expected = array('page' => 1, 'limit' => 20, 'maxLimit' => 100, 'paramType' => 'named'); + $this->assertEqual($Controller->paginate, $expected); $results = Set::extract($Controller->paginate('ControllerPost'), '{n}.ControllerPost.id'); $this->assertEqual($results, array(1, 2, 3)); diff --git a/cake/tests/cases/libs/view/helper.test.php b/cake/tests/cases/libs/view/helper.test.php index 4ef761091..9e57e3028 100644 --- a/cake/tests/cases/libs/view/helper.test.php +++ b/cake/tests/cases/libs/view/helper.test.php @@ -477,7 +477,7 @@ class HelperTest extends CakeTestCase { $result = $this->Helper->url('/controller/action/1?one=1&two=2'); $this->assertEqual($result, '/controller/action/1?one=1&two=2'); - $result = $this->Helper->url(array('controller' => 'posts', 'action' => 'index', 'page' => '1" onclick="alert(\'XSS\');"')); + $result = $this->Helper->url(array('controller' => 'posts', 'action' => 'index', ':page' => '1" onclick="alert(\'XSS\');"')); $this->assertEqual($result, "/posts/index/page:1" onclick="alert('XSS');""); $result = $this->Helper->url('/controller/action/1/param:this+one+more'); @@ -490,12 +490,12 @@ class HelperTest extends CakeTestCase { $this->assertEqual($result, '/controller/action/1/param:%7Baround%20here%7D%5Bthings%5D%5Bare%5D%24%24'); $result = $this->Helper->url(array( - 'controller' => 'posts', 'action' => 'index', 'param' => '%7Baround%20here%7D%5Bthings%5D%5Bare%5D%24%24' + 'controller' => 'posts', 'action' => 'index', ':param' => '%7Baround%20here%7D%5Bthings%5D%5Bare%5D%24%24' )); $this->assertEqual($result, "/posts/index/param:%7Baround%20here%7D%5Bthings%5D%5Bare%5D%24%24"); $result = $this->Helper->url(array( - 'controller' => 'posts', 'action' => 'index', 'page' => '1', + 'controller' => 'posts', 'action' => 'index', ':page' => '1', '?' => array('one' => 'value', 'two' => 'value', 'three' => 'purple') )); $this->assertEqual($result, "/posts/index/page:1?one=value&two=value&three=purple"); From 025ba238863cb52c51ce916491f3da11c6010a03 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 19 Dec 2010 20:05:57 -0500 Subject: [PATCH 34/59] Removing whitespace and adding some more documentation. --- cake/libs/router.php | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/cake/libs/router.php b/cake/libs/router.php index f4cfa32fd..8d76a7ba5 100644 --- a/cake/libs/router.php +++ b/cake/libs/router.php @@ -300,7 +300,9 @@ class Router { } /** - * Specifies what named parameters CakePHP should be parsing. The most common setups are: + * Specifies what named parameters CakePHP should be parsing out of incoming urls. By default + * CakePHP will parse every named parameter out of incoming URLs. However, if you want to take more + * control over how named parameters are parsed you can use one of the following setups: * * Do not parse any named parameters: * @@ -342,8 +344,6 @@ class Router { * @return array */ public static function connectNamed($named, $options = array()) { - - if (isset($options['argSeparator'])) { self::$named['separator'] = $options['argSeparator']; unset($options['argSeparator']); @@ -385,7 +385,6 @@ class Router { * @return void */ public static function defaults($connect = true) { - self::$_connectDefaults = $connect; } @@ -403,7 +402,6 @@ class Router { * @return array Array of mapped resources */ public static function mapResources($controller, $options = array()) { - $options = array_merge(array('prefix' => '/', 'id' => self::$__named['ID'] . '|' . self::$__named['UUID']), $options); $prefix = $options['prefix']; @@ -430,7 +428,6 @@ class Router { * @return array A list of prefixes used in connected routes */ public static function prefixes() { - return self::$_prefixes; } @@ -441,8 +438,6 @@ class Router { * @return array Parsed elements from URL */ public static function parse($url) { - - if (!self::$_defaultsMapped && self::$_connectDefaults) { self::__connectDefaultRoutes(); } From d3fc29c8e83642a20fe9f96d0d782e903d524180 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 19 Dec 2010 21:49:17 -0500 Subject: [PATCH 35/59] Adding more documentation to connectNamed(). Changing argSeparator -> separator, as it is less typing and easier to remember. --- cake/libs/router.php | 18 +++++++++++++----- cake/tests/cases/libs/router.test.php | 4 ++-- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/cake/libs/router.php b/cake/libs/router.php index 8d76a7ba5..29e38dc3a 100644 --- a/cake/libs/router.php +++ b/cake/libs/router.php @@ -89,7 +89,7 @@ class Router { * @access public */ public static $named = array( - 'default' => array(':page', ':fields', ':order', ':limit', ':recursive', ':sort', ':direction', ':step'), + 'default' => array('page', 'fields', 'order', 'limit', 'recursive', 'sort', 'direction', 'step'), 'greedy' => true, 'separator' => ':', 'rules' => false, @@ -316,7 +316,7 @@ class Router { * * {{{ Router::connectNamed(array('page' => '[\d]+'), array('default' => false, 'greedy' => false)); }}} * - * Parse only the page parameter no mater what. + * Parse only the page parameter no matter what. * * {{{ Router::connectNamed(array('page'), array('default' => false, 'greedy' => false)); }}} * @@ -338,15 +338,23 @@ class Router { * ); * }}} * + * ### Options + * + * - `greedy` Setting this to true will make Router parse all named params. Setting it to false will + * parse only the connected named params. + * - `default` Set this to true to merge in the default set of named parameters. + * - `reset` Set to true to clear existing rules and start fresh. + * - `separator` Change the string used to separate the key & value in a named parameter. Defaults to `:` + * * @param array $named A list of named parameters. Key value pairs are accepted where values are * either regex strings to match, or arrays as seen above. * @param array $options Allows to control all settings: separator, greedy, reset, default * @return array */ public static function connectNamed($named, $options = array()) { - if (isset($options['argSeparator'])) { - self::$named['separator'] = $options['argSeparator']; - unset($options['argSeparator']); + if (isset($options['separator'])) { + self::$named['separator'] = $options['separator']; + unset($options['separator']); } if ($named === true || $named === false) { diff --git a/cake/tests/cases/libs/router.test.php b/cake/tests/cases/libs/router.test.php index d9a57afc8..c44f27619 100644 --- a/cake/tests/cases/libs/router.test.php +++ b/cake/tests/cases/libs/router.test.php @@ -1335,14 +1335,14 @@ class RouterTest extends CakeTestCase { Router::reload(); Router::connect('/foo/*', array('controller' => 'bar', 'action' => 'fubar')); - Router::connectNamed(array(), array('argSeparator' => '=')); + Router::connectNamed(array(), array('separator' => '=')); $result = Router::parse('/foo/param1=value1/param2=value2'); $expected = array('pass' => array(), 'named' => array('param1' => 'value1', 'param2' => 'value2'), 'controller' => 'bar', 'action' => 'fubar', 'plugin' => null); $this->assertEqual($result, $expected); Router::reload(); Router::connect('/controller/action/*', array('controller' => 'controller', 'action' => 'action'), array('named' => array('param1' => 'value[\d]'))); - Router::connectNamed(array(), array('greedy' => false, 'argSeparator' => '=')); + Router::connectNamed(array(), array('greedy' => false, 'separator' => '=')); $result = Router::parse('/controller/action/param1=value1/param2=value2'); $expected = array('pass' => array('param2=value2'), 'named' => array('param1' => 'value1'), 'controller' => 'controller', 'action' => 'action', 'plugin' => null); $this->assertEqual($result, $expected); From b49b49a5ef4656f23d51bcfcf8553338a50df9bb Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 19 Dec 2010 22:17:57 -0500 Subject: [PATCH 36/59] Removing named parameter sigils. --- cake/libs/route/cake_route.php | 42 +++++++++++-------- cake/libs/router.php | 4 +- .../cases/libs/route/cake_route.test.php | 12 +++--- 3 files changed, 33 insertions(+), 25 deletions(-) diff --git a/cake/libs/route/cake_route.php b/cake/libs/route/cake_route.php index 7e4af3384..364b18641 100644 --- a/cake/libs/route/cake_route.php +++ b/cake/libs/route/cake_route.php @@ -284,27 +284,19 @@ class CakeRoute { return false; } + $greedyNamed = Router::$named['greedy']; + $allowedNamedParams = Router::$named['rules']; + $named = $pass = $_query = array(); foreach ($url as $key => $value) { - // pull out named params so comparisons later on are faster. - if ($key[0] === CakeRoute::SIGIL_NAMED && ($value !== false && $value !== null)) { - $named[substr($key, 1)] = $value; - unset($url[$key]); - continue; - } - - // pull out querystring params - if ($key[0] === CakeRoute::SIGIL_QUERYSTRING && ($value !== false && $value !== null)) { - $_query[substr($key, 1)] = $value; - unset($url[$key]); - continue; - } - // keys that exist in the defaults and have different values cause match failures. - $keyExists = array_key_exists($key, $defaults); - if ($keyExists && $defaults[$key] != $value) { + // keys that exist in the defaults and have different values is a match failure. + $defaultExists = array_key_exists($key, $defaults); + if ($defaultExists && $defaults[$key] != $value) { return false; + } elseif ($defaultExists) { + continue; } // If the key is a routed key, its not different yet. @@ -322,8 +314,24 @@ class CakeRoute { continue; } + // pull out querystring params + if ($key[0] === CakeRoute::SIGIL_QUERYSTRING && ($value !== false && $value !== null)) { + $_query[substr($key, 1)] = $value; + unset($url[$key]); + continue; + } + + // pull out named params if named params are greedy or a rule exists. + if ( + ($greedyNamed || isset($allowedNamedParams[$key])) && + ($value !== false && $value !== null) + ) { + $named[$key] = $value; + continue; + } + // keys that don't exist are different. - if (!$keyExists && !empty($value)) { + if (!$defaultExists && !empty($value)) { return false; } } diff --git a/cake/libs/router.php b/cake/libs/router.php index 29e38dc3a..76e801aec 100644 --- a/cake/libs/router.php +++ b/cake/libs/router.php @@ -914,10 +914,10 @@ class Router { $key = $keys[$i]; if (is_numeric($keys[$i])) { $args[] = $url[$key]; - } elseif ($key[0] === CakeRoute::SIGIL_NAMED) { - $named[substr($key, 1)] = $url[$key]; } elseif ($key[0] === CakeRoute::SIGIL_QUERYSTRING) { $query[substr($key, 1)] = $url[$key]; + } else { + $named[$key] = $url[$key]; } } diff --git a/cake/tests/cases/libs/route/cake_route.test.php b/cake/tests/cases/libs/route/cake_route.test.php index 3107e0084..0831ab814 100644 --- a/cake/tests/cases/libs/route/cake_route.test.php +++ b/cake/tests/cases/libs/route/cake_route.test.php @@ -310,7 +310,7 @@ class CakeRouteTestCase extends CakeTestCase { */ function testGreedyRouteFailureNamedParam() { $route = new CakeRoute('/:controller/:action', array('plugin' => null)); - $result = $route->match(array('controller' => 'posts', 'action' => 'view', ':page' => 1)); + $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'page' => 1)); $this->assertFalse($result); } @@ -334,9 +334,9 @@ class CakeRouteTestCase extends CakeTestCase { */ function testMatchWithNamedParametersAndPassedArgs() { Router::connectNamed(true); - +/* $route = new CakeRoute('/:controller/:action/*', array('plugin' => null)); - $result = $route->match(array('controller' => 'posts', 'action' => 'index', 'plugin' => null, ':page' => 1)); + $result = $route->match(array('controller' => 'posts', 'action' => 'index', 'plugin' => null, 'page' => 1)); $this->assertEqual($result, '/posts/index/page:1'); $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'plugin' => null, 5)); @@ -348,9 +348,9 @@ class CakeRouteTestCase extends CakeTestCase { $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'plugin' => null, '0')); $this->assertEqual($result, '/posts/view/0'); - $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'plugin' => null, 5, ':page' => 1, ':limit' => 20, ':order' => 'title')); + $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'plugin' => null, 5, 'page' => 1, 'limit' => 20, 'order' => 'title')); $this->assertEqual($result, '/posts/view/5/page:1/limit:20/order:title'); - +*/ $route = new CakeRoute('/test2/*', array('controller' => 'pages', 'action' => 'display', 2)); $result = $route->match(array('controller' => 'pages', 'action' => 'display', 1)); @@ -370,7 +370,7 @@ class CakeRouteTestCase extends CakeTestCase { */ function testNamedParamsWithNullFalse() { $route = new CakeRoute('/:controller/:action/*'); - $result = $route->match(array('controller' => 'posts', 'action' => 'index', ':page' => null, 'sort' => false)); + $result = $route->match(array('controller' => 'posts', 'action' => 'index', 'page' => null, 'sort' => false)); $this->assertEquals('/posts/index/', $result); } From e5588f746c9885288a572db5f1f4bd2cd00da597 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 19 Dec 2010 23:11:02 -0500 Subject: [PATCH 37/59] Reversing changes that required a : sigil for named parameters. Also removing ?foo style parameters for querystring args. Having two ways to create querystring args was not sitting well with me. Tests updated. --- cake/libs/controller/components/paginator.php | 3 +- cake/libs/route/cake_route.php | 20 --------- cake/libs/router.php | 6 --- cake/libs/view/helpers/paginator.php | 22 +++------- .../cases/libs/route/cake_route.test.php | 39 +--------------- cake/tests/cases/libs/router.test.php | 44 +++++++++---------- cake/tests/cases/libs/view/helper.test.php | 6 +-- .../libs/view/helpers/paginator.test.php | 22 +++++----- 8 files changed, 46 insertions(+), 116 deletions(-) diff --git a/cake/libs/controller/components/paginator.php b/cake/libs/controller/components/paginator.php index caa911c06..b69f75a2b 100644 --- a/cake/libs/controller/components/paginator.php +++ b/cake/libs/controller/components/paginator.php @@ -63,9 +63,8 @@ class PaginatorComponent extends Component { * - `limit` The initial number of items per page. Defaults to 20. * - `page` The starting page, defaults to 1. * - `paramType` What type of parameters you want pagination to use? - * - `named` Use named parameters. + * - `named` Use named parameters / routed parameters. * - `querystring` Use query string parameters. - * - `route` Use routed parameters, these require you to setup routes that include the pagination params * * @var array */ diff --git a/cake/libs/route/cake_route.php b/cake/libs/route/cake_route.php index 364b18641..a3a9413e9 100644 --- a/cake/libs/route/cake_route.php +++ b/cake/libs/route/cake_route.php @@ -73,16 +73,6 @@ class CakeRoute { */ protected $_compiledRoute = null; -/** - * Constant for the sigil that indicates a route param is a named parameter. - */ - const SIGIL_NAMED = ':'; - -/** - * Constant for the sigil that indicates a route param is a query string parameter. - */ - const SIGIL_QUERYSTRING = '?'; - /** * HTTP header shortcut map. Used for evaluating header-based route expressions. * @@ -314,13 +304,6 @@ class CakeRoute { continue; } - // pull out querystring params - if ($key[0] === CakeRoute::SIGIL_QUERYSTRING && ($value !== false && $value !== null)) { - $_query[substr($key, 1)] = $value; - unset($url[$key]); - continue; - } - // pull out named params if named params are greedy or a rule exists. if ( ($greedyNamed || isset($allowedNamedParams[$key])) && @@ -402,9 +385,6 @@ class CakeRoute { if (strpos($this->template, '*')) { $out = str_replace('*', $params['pass'], $out); } - if (!empty($params['_query'])) { - $out .= $this->queryString($params['_query']); - } $out = str_replace('//', '/', $out); return $out; } diff --git a/cake/libs/router.php b/cake/libs/router.php index 76e801aec..7e9157fde 100644 --- a/cake/libs/router.php +++ b/cake/libs/router.php @@ -914,8 +914,6 @@ class Router { $key = $keys[$i]; if (is_numeric($keys[$i])) { $args[] = $url[$key]; - } elseif ($key[0] === CakeRoute::SIGIL_QUERYSTRING) { - $query[substr($key, 1)] = $url[$key]; } else { $named[$key] = $url[$key]; } @@ -1078,10 +1076,6 @@ class Router { $params['pass'], $params['named'], $params['paging'], $params['models'], $params['url'], $url['url'], $params['autoRender'], $params['bare'], $params['requested'], $params['return'] ); - foreach ($named as $key => $value) { - $named[CakeRoute::SIGIL_NAMED . $key] = $value; - unset($named[$key]); - } $params = array_merge($params, $pass, $named); if (!empty($url)) { $params['?'] = $url; diff --git a/cake/libs/view/helpers/paginator.php b/cake/libs/view/helpers/paginator.php index 5caaf7ed9..69da3a24a 100644 --- a/cake/libs/view/helpers/paginator.php +++ b/cake/libs/view/helpers/paginator.php @@ -119,12 +119,7 @@ class PaginatorHelper extends AppHelper { * @return void */ public function beforeRender($viewFile) { - $named = $this->request->params['named']; - foreach ($named as $key => $val) { - $named[CakeRoute::SIGIL_NAMED . $key] = $val; - unset($named[$key]); - } - $this->options['url'] = array_merge($this->request->params['pass'], $named); + $this->options['url'] = array_merge($this->request->params['pass'], $this->request->params['named']); parent::beforeRender($viewFile); } @@ -405,18 +400,15 @@ class PaginatorHelper extends AppHelper { * @return converted url params. */ protected function _convertUrlKeys($url, $type) { - $prefix = ''; - switch ($type) { - case 'named': - $prefix = CakeRoute::SIGIL_NAMED; - break; - case 'querystring': - $prefix = CakeRoute::SIGIL_QUERYSTRING; - break; + if ($type == 'named') { + return $url; + } + if (!isset($url['?'])) { + $url['?'] = array(); } foreach ($this->convertKeys as $key) { if (isset($url[$key])) { - $url[$prefix . $key] = $url[$key]; + $url['?'][$key] = $url[$key]; unset($url[$key]); } } diff --git a/cake/tests/cases/libs/route/cake_route.test.php b/cake/tests/cases/libs/route/cake_route.test.php index 0831ab814..c1433451c 100644 --- a/cake/tests/cases/libs/route/cake_route.test.php +++ b/cake/tests/cases/libs/route/cake_route.test.php @@ -334,7 +334,7 @@ class CakeRouteTestCase extends CakeTestCase { */ function testMatchWithNamedParametersAndPassedArgs() { Router::connectNamed(true); -/* + $route = new CakeRoute('/:controller/:action/*', array('plugin' => null)); $result = $route->match(array('controller' => 'posts', 'action' => 'index', 'plugin' => null, 'page' => 1)); $this->assertEqual($result, '/posts/index/page:1'); @@ -350,7 +350,7 @@ class CakeRouteTestCase extends CakeTestCase { $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'plugin' => null, 5, 'page' => 1, 'limit' => 20, 'order' => 'title')); $this->assertEqual($result, '/posts/view/5/page:1/limit:20/order:title'); -*/ + $route = new CakeRoute('/test2/*', array('controller' => 'pages', 'action' => 'display', 2)); $result = $route->match(array('controller' => 'pages', 'action' => 'display', 1)); @@ -473,39 +473,4 @@ class CakeRouteTestCase extends CakeTestCase { $this->assertFalse($result); } -/** - * test sigil based query string params - * - * @return void - */ - function testQueryStringParams() { - $route = new CakeRoute('/:controller/:action/*'); - $result = $route->match(array('controller' => 'posts', 'action' => 'index', '?test' => 'value')); - $expected = '/posts/index/?test=value'; - $this->assertEquals($expected, $result); - - $result = $route->match(array( - 'controller' => 'posts', 'action' => 'index', '?test' => array(1, 2, 3) - )); - $expected = '/posts/index/?test%5B0%5D=1&test%5B1%5D=2&test%5B2%5D=3'; - $this->assertEquals($expected, $result); - - $result = $route->match(array( - 'controller' => 'posts', 'action' => 'index', '?test' => 'value', '?other' => 'value' - )); - $expected = '/posts/index/?test=value&other=value'; - $this->assertEquals($expected, $result); - } - -/** - * test that querystring params work with non greedy routes. - * - * @return void - */ - function testQueryStringNonGreedy() { - $route = new CakeRoute('/:controller/:action'); - $result = $route->match(array('controller' => 'posts', 'action' => 'index', '?test' => 'value')); - $expected = '/posts/index?test=value'; - $this->assertEquals($expected, $result); - } } \ No newline at end of file diff --git a/cake/tests/cases/libs/router.test.php b/cake/tests/cases/libs/router.test.php index c44f27619..c1ed8fb43 100644 --- a/cake/tests/cases/libs/router.test.php +++ b/cake/tests/cases/libs/router.test.php @@ -328,7 +328,7 @@ class RouterTest extends CakeTestCase { Router::connect('short_controller_name/:action/*', array('controller' => 'real_controller_name')); Router::parse('/'); - $result = Router::url(array('controller' => 'real_controller_name', ':page' => '1')); + $result = Router::url(array('controller' => 'real_controller_name', 'page' => '1')); $expected = '/short_controller_name/index/page:1'; $this->assertEqual($result, $expected); @@ -389,14 +389,14 @@ class RouterTest extends CakeTestCase { * @return void */ function testArrayNamedParameters() { - $result = Router::url(array('controller' => 'tests', ':pages' => array( + $result = Router::url(array('controller' => 'tests', 'pages' => array( 1, 2, 3 ))); $expected = '/tests/index/pages[0]:1/pages[1]:2/pages[2]:3'; $this->assertEqual($result, $expected); $result = Router::url(array('controller' => 'tests', - ':pages' => array( + 'pages' => array( 'param1' => array( 'one', 'two' @@ -408,7 +408,7 @@ class RouterTest extends CakeTestCase { $this->assertEqual($result, $expected); $result = Router::url(array('controller' => 'tests', - ':pages' => array( + 'pages' => array( 'param1' => array( 'one' => 1, 'two' => 2 @@ -420,7 +420,7 @@ class RouterTest extends CakeTestCase { $this->assertEqual($result, $expected); $result = Router::url(array('controller' => 'tests', - ':super' => array( + 'super' => array( 'nested' => array( 'array' => 'awesome', 'something' => 'else' @@ -431,7 +431,7 @@ class RouterTest extends CakeTestCase { $expected = '/tests/index/super[nested][array]:awesome/super[nested][something]:else/super[0]:cool'; $this->assertEqual($result, $expected); - $result = Router::url(array('controller' => 'tests', ':namedParam' => array( + $result = Router::url(array('controller' => 'tests', 'namedParam' => array( 'keyed' => 'is an array', 'test' ))); @@ -630,7 +630,7 @@ class RouterTest extends CakeTestCase { $request->webroot = '/'; Router::setRequestInfo($request); - $result = Router::url(array(':page' => 2)); + $result = Router::url(array('page' => 2)); $expected = '/admin/registrations/index/page:2'; $this->assertEqual($result, $expected); @@ -648,7 +648,7 @@ class RouterTest extends CakeTestCase { Router::parse('/'); - $result = Router::url(array(':page' => 3)); + $result = Router::url(array('page' => 3)); $expected = '/magazine/admin/subscriptions/index/page:3'; $this->assertEquals($expected, $result); @@ -860,7 +860,7 @@ class RouterTest extends CakeTestCase { $result = Router::url(array( 'lang' => 'en', - 'controller' => 'shows', 'action' => 'index', ':page' => '1', + 'controller' => 'shows', 'action' => 'index', 'page' => '1', )); $expected = '/en/shows/shows/page:1'; $this->assertEqual($result, $expected); @@ -1361,11 +1361,11 @@ class RouterTest extends CakeTestCase { * @return void */ function testNamedArgsUrlGeneration() { - $result = Router::url(array('controller' => 'posts', 'action' => 'index', ':published' => 1, ':deleted' => 1)); + $result = Router::url(array('controller' => 'posts', 'action' => 'index', 'published' => 1, 'deleted' => 1)); $expected = '/posts/index/published:1/deleted:1'; $this->assertEqual($result, $expected); - $result = Router::url(array('controller' => 'posts', 'action' => 'index', ':published' => 0, ':deleted' => 0)); + $result = Router::url(array('controller' => 'posts', 'action' => 'index', 'published' => 0, 'deleted' => 0)); $expected = '/posts/index/published:0/deleted:0'; $this->assertEqual($result, $expected); @@ -1375,11 +1375,11 @@ class RouterTest extends CakeTestCase { Router::connect('/', array('controller' => 'graphs', 'action' => 'index')); Router::connect('/:id/*', array('controller' => 'graphs', 'action' => 'view'), array('id' => $ID)); - $result = Router::url(array('controller' => 'graphs', 'action' => 'view', 'id' => 12, ':file' => 'asdf.png')); + $result = Router::url(array('controller' => 'graphs', 'action' => 'view', 'id' => 12, 'file' => 'asdf.png')); $expected = '/12/file:asdf.png'; $this->assertEqual($result, $expected); - $result = Router::url(array('controller' => 'graphs', 'action' => 'view', 12, ':file' => 'asdf.foo')); + $result = Router::url(array('controller' => 'graphs', 'action' => 'view', 12, 'file' => 'asdf.foo')); $expected = '/graphs/view/12/file:asdf.foo'; $this->assertEqual($result, $expected); @@ -1398,7 +1398,7 @@ class RouterTest extends CakeTestCase { ); Router::parse('/'); - $result = Router::url(array(':page' => 1, 0 => null, ':sort' => 'controller', ':direction' => 'asc', ':order' => null)); + $result = Router::url(array('page' => 1, 0 => null, 'sort' => 'controller', 'direction' => 'asc', 'order' => null)); $expected = "/admin/controller/index/page:1/sort:controller/direction:asc"; $this->assertEqual($result, $expected); @@ -1411,7 +1411,7 @@ class RouterTest extends CakeTestCase { Router::setRequestInfo($request); $result = Router::parse('/admin/controller/index/type:whatever'); - $result = Router::url(array(':type'=> 'new')); + $result = Router::url(array('type'=> 'new')); $expected = "/admin/controller/index/type:new"; $this->assertEqual($result, $expected); } @@ -1546,12 +1546,12 @@ class RouterTest extends CakeTestCase { $expected = '/protected/others/edit/1'; $this->assertEqual($result, $expected); - $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1, 'protected' => true, ':page' => 1)); + $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1, 'protected' => true, 'page' => 1)); $expected = '/protected/others/edit/1/page:1'; $this->assertEqual($result, $expected); Router::connectNamed(array('random')); - $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1, 'protected' => true, ':random' => 'my-value')); + $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1, 'protected' => true, 'random' => 'my-value')); $expected = '/protected/others/edit/1/random:my-value'; $this->assertEqual($result, $expected); } @@ -1610,12 +1610,12 @@ class RouterTest extends CakeTestCase { $expected = '/protected/others/edit/1'; $this->assertEqual($result, $expected); - $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1, 'protected' => true, ':page' => 1)); + $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1, 'protected' => true, 'page' => 1)); $expected = '/protected/others/edit/1/page:1'; $this->assertEqual($result, $expected); Router::connectNamed(array('random')); - $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1, 'protected' => true, ':random' => 'my-value')); + $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1, 'protected' => true, 'random' => 'my-value')); $expected = '/protected/others/edit/1/random:my-value'; $this->assertEqual($result, $expected); } @@ -1721,7 +1721,7 @@ class RouterTest extends CakeTestCase { $this->assertEqual($result, $expected); $result = Router::url(array('controller' => 'my_controller', 'action' => 'my_action', 'base' => true)); - $expected = '/base/my_controller/my_action'; + $expected = '/base/my_controller/my_action/base:1'; $this->assertEqual($result, $expected); } @@ -1908,7 +1908,7 @@ class RouterTest extends CakeTestCase { $expected = array('pass' => array(), 'named' => array(), 'prefix' => 'members', 'plugin' => null, 'controller' => 'posts', 'action' => 'index', 'members' => true); $this->assertEqual($result, $expected); - $result = Router::url(array('members' => true, 'controller' => 'posts', 'action' =>'index', ':page' => 2)); + $result = Router::url(array('members' => true, 'controller' => 'posts', 'action' =>'index', 'page' => 2)); $expected = '/base/members/posts/index/page:2'; $this->assertEqual($result, $expected); @@ -2083,7 +2083,7 @@ class RouterTest extends CakeTestCase { $this->assertEqual($result, $expected); $result = Router::url(array('action' => 'test_another_action', 'locale' => 'badness')); - $expected = '/test/test_another_action'; + $expected = '/test/test_another_action/locale:badness'; $this->assertEqual($result, $expected); } diff --git a/cake/tests/cases/libs/view/helper.test.php b/cake/tests/cases/libs/view/helper.test.php index 9e57e3028..4ef761091 100644 --- a/cake/tests/cases/libs/view/helper.test.php +++ b/cake/tests/cases/libs/view/helper.test.php @@ -477,7 +477,7 @@ class HelperTest extends CakeTestCase { $result = $this->Helper->url('/controller/action/1?one=1&two=2'); $this->assertEqual($result, '/controller/action/1?one=1&two=2'); - $result = $this->Helper->url(array('controller' => 'posts', 'action' => 'index', ':page' => '1" onclick="alert(\'XSS\');"')); + $result = $this->Helper->url(array('controller' => 'posts', 'action' => 'index', 'page' => '1" onclick="alert(\'XSS\');"')); $this->assertEqual($result, "/posts/index/page:1" onclick="alert('XSS');""); $result = $this->Helper->url('/controller/action/1/param:this+one+more'); @@ -490,12 +490,12 @@ class HelperTest extends CakeTestCase { $this->assertEqual($result, '/controller/action/1/param:%7Baround%20here%7D%5Bthings%5D%5Bare%5D%24%24'); $result = $this->Helper->url(array( - 'controller' => 'posts', 'action' => 'index', ':param' => '%7Baround%20here%7D%5Bthings%5D%5Bare%5D%24%24' + 'controller' => 'posts', 'action' => 'index', 'param' => '%7Baround%20here%7D%5Bthings%5D%5Bare%5D%24%24' )); $this->assertEqual($result, "/posts/index/param:%7Baround%20here%7D%5Bthings%5D%5Bare%5D%24%24"); $result = $this->Helper->url(array( - 'controller' => 'posts', 'action' => 'index', ':page' => '1', + 'controller' => 'posts', 'action' => 'index', 'page' => '1', '?' => array('one' => 'value', 'two' => 'value', 'three' => 'purple') )); $this->assertEqual($result, "/posts/index/page:1?one=value&two=value&three=purple"); diff --git a/cake/tests/cases/libs/view/helpers/paginator.test.php b/cake/tests/cases/libs/view/helpers/paginator.test.php index ab400702b..c52ec5e92 100644 --- a/cake/tests/cases/libs/view/helpers/paginator.test.php +++ b/cake/tests/cases/libs/view/helpers/paginator.test.php @@ -674,7 +674,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->sort('title'); $expected = array( - 'a' => array('href' => '/articles/index/2/foo:bar/page:1/sort:title/direction:asc'), + 'a' => array('href' => '/articles/index/2/page:1/foo:bar/sort:title/direction:asc'), 'Title', '/a' ); @@ -684,24 +684,24 @@ class PaginatorHelperTest extends CakeTestCase { $expected = array( array('span' => array('class' => 'current')), '1', '/span', ' | ', - array('span' => array()), array('a' => array('href' => '/articles/index/2/foo:bar/page:2')), '2', '/a', '/span', + array('span' => array()), array('a' => array('href' => '/articles/index/2/page:2/foo:bar')), '2', '/a', '/span', ' | ', - array('span' => array()), array('a' => array('href' => '/articles/index/2/foo:bar/page:3')), '3', '/a', '/span', + array('span' => array()), array('a' => array('href' => '/articles/index/2/page:3/foo:bar')), '3', '/a', '/span', ' | ', - array('span' => array()), array('a' => array('href' => '/articles/index/2/foo:bar/page:4')), '4', '/a', '/span', + array('span' => array()), array('a' => array('href' => '/articles/index/2/page:4/foo:bar')), '4', '/a', '/span', ' | ', - array('span' => array()), array('a' => array('href' => '/articles/index/2/foo:bar/page:5')), '5', '/a', '/span', + array('span' => array()), array('a' => array('href' => '/articles/index/2/page:5/foo:bar')), '5', '/a', '/span', ' | ', - array('span' => array()), array('a' => array('href' => '/articles/index/2/foo:bar/page:6')), '6', '/a', '/span', + array('span' => array()), array('a' => array('href' => '/articles/index/2/page:6/foo:bar')), '6', '/a', '/span', ' | ', - array('span' => array()), array('a' => array('href' => '/articles/index/2/foo:bar/page:7')), '7', '/a', '/span', + array('span' => array()), array('a' => array('href' => '/articles/index/2/page:7/foo:bar')), '7', '/a', '/span', ); $this->assertTags($result, $expected); $result = $this->Paginator->next('Next'); $expected = array( ' array('href' => '/articles/index/2/foo:bar/page:2', 'class' => 'next'), + 'a' => array('href' => '/articles/index/2/page:2/foo:bar', 'class' => 'next'), 'Next', '/a', '/span' @@ -915,10 +915,10 @@ class PaginatorHelperTest extends CakeTestCase { ) ); $this->Paginator->options(array('url' => array(12, 'page' => 3))); - $result = $this->Paginator->prev('Prev', array('url' => array(':foo' => 'bar'))); + $result = $this->Paginator->prev('Prev', array('url' => array('foo' => 'bar'))); $expected = array( ' array('href' => '/index/12/foo:bar/page:1/limit:10', 'class' => 'prev'), + 'a' => array('href' => '/index/12/page:1/limit:10/foo:bar', 'class' => 'prev'), 'Prev', '/a', '/span' @@ -2199,7 +2199,7 @@ class PaginatorHelperTest extends CakeTestCase { function testQuerystringLinkGeneration() { $this->Paginator->request->params['paging']['Article']['paramType'] = 'querystring'; $result = $this->Paginator->url(array('page' => '4')); - $expected = '/index?page=4'; + $expected = '/?page=4'; $this->assertEquals($expected, $result); } From d7e411650fee8ebb1b33b24d63c66c806649807e Mon Sep 17 00:00:00 2001 From: mark_story Date: Mon, 20 Dec 2010 13:39:22 -0500 Subject: [PATCH 38/59] Moving some tests around so its easier to figure out what's being tested. Adding a querystring test. --- .../libs/view/helpers/paginator.test.php | 110 +++++++++--------- 1 file changed, 53 insertions(+), 57 deletions(-) diff --git a/cake/tests/cases/libs/view/helpers/paginator.test.php b/cake/tests/cases/libs/view/helpers/paginator.test.php index c52ec5e92..54602424f 100644 --- a/cake/tests/cases/libs/view/helpers/paginator.test.php +++ b/cake/tests/cases/libs/view/helpers/paginator.test.php @@ -47,6 +47,7 @@ class PaginatorHelperTest extends CakeTestCase { $this->Paginator->request->addParams(array( 'paging' => array( 'Article' => array( + 'page' => 2, 'current' => 9, 'count' => 62, 'prevPage' => false, @@ -1763,6 +1764,51 @@ class PaginatorHelperTest extends CakeTestCase { $this->assertTags($result, $expected); } +/** + * test first() and last() with tag options + * + * @return void + */ + function testFirstAndLastTag() { + $result = $this->Paginator->first('<<', array('tag' => 'li')); + $expected = array( + ' array('href' => '/index/page:1'), + '<<', + '/a', + '/li' + ); + $this->assertTags($result, $expected); + + $result = $this->Paginator->last(2, array('tag' => 'li')); + + $expected = array( + '...', + ' array('href' => '/index/page:6')), '6', '/a', + '/li', + ' | ', + ' array('href' => '/index/page:7')), '7', '/a', + '/li', + ); + $this->assertTags($result, $expected); + } + +/** + * test that on the last page you don't get a link ot the last page. + * + * @return void + */ + function testLastNoOutput() { + $this->Paginator->request->params['paging']['Article']['page'] = 15; + $this->Paginator->request->params['paging']['Article']['pageCount'] = 15; + + $result = $this->Paginator->last(); + $expected = ''; + $this->assertEqual($result, $expected); + } + /** * testFirstAndLast method * @@ -1787,20 +1833,7 @@ class PaginatorHelperTest extends CakeTestCase { $expected = ''; $this->assertEqual($result, $expected); - $this->Paginator->request->params['paging'] = array( - 'Client' => array( - 'page' => 4, - 'current' => 3, - 'count' => 30, - 'prevPage' => false, - 'nextPage' => 2, - 'pageCount' => 15, - 'options' => array( - 'page' => 1, - ), - 'paramType' => 'named' - ) - ); + $this->Paginator->request->params['paging']['Client']['page'] = 4; $result = $this->Paginator->first(); $expected = array( @@ -1812,16 +1845,6 @@ class PaginatorHelperTest extends CakeTestCase { ); $this->assertTags($result, $expected); - $result = $this->Paginator->first('<<', array('tag' => 'li')); - $expected = array( - ' array('href' => '/index/page:1'), - '<<', - '/a', - '/li' - ); - $this->assertTags($result, $expected); - $result = $this->Paginator->last(); $expected = array( 'assertTags($result, $expected); - $result = $this->Paginator->last(2, array('tag' => 'li')); - $expected = array( - '...', - ' array('href' => '/index/page:14')), '14', '/a', - '/li', - ' | ', - ' array('href' => '/index/page:15')), '15', '/a', - '/li', - ); - $this->assertTags($result, $expected); - - $this->Paginator->request->params['paging'] = array( - 'Client' => array( - 'page' => 15, - 'current' => 3, - 'count' => 30, - 'prevPage' => false, - 'nextPage' => 2, - 'pageCount' => 15, - 'options' => array( - 'page' => 1, - ), - 'paramType' => 'named' - ) - ); - $result = $this->Paginator->last(); - $expected = ''; - $this->assertEqual($result, $expected); - $this->Paginator->request->params['paging'] = array( 'Client' => array( 'page' => 4, @@ -2192,15 +2184,19 @@ class PaginatorHelperTest extends CakeTestCase { } /** - * test that querystring links can be generated. + * test that querystring urls can be generated. * * @return void */ - function testQuerystringLinkGeneration() { + function testQuerystringUrlGeneration() { $this->Paginator->request->params['paging']['Article']['paramType'] = 'querystring'; $result = $this->Paginator->url(array('page' => '4')); $expected = '/?page=4'; $this->assertEquals($expected, $result); + + $result = $this->Paginator->url(array('page' => '4', 'limit' => 10, 'something' => 'else')); + $expected = '/index/something:else?page=4&limit=10'; + $this->assertEquals($expected, $result); } } From 0b90195a5256118858c35369b1e2cfef75e025da Mon Sep 17 00:00:00 2001 From: mark_story Date: Mon, 20 Dec 2010 13:59:09 -0500 Subject: [PATCH 39/59] Adding tests for creating next/prev links with querystrings. Removing code that doesn't seem to do anything, no tests fail when its removed. --- cake/libs/view/helpers/paginator.php | 3 +- .../libs/view/helpers/paginator.test.php | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/cake/libs/view/helpers/paginator.php b/cake/libs/view/helpers/paginator.php index 69da3a24a..6f7145643 100644 --- a/cake/libs/view/helpers/paginator.php +++ b/cake/libs/view/helpers/paginator.php @@ -357,11 +357,10 @@ class PaginatorHelper extends AppHelper { $url = array_merge((array)$options['url'], (array)$url); unset($options['url']); } + $url = $this->url($url, true, $model); $obj = isset($options['update']) ? $this->_ajaxHelperClass : 'Html'; - $url = array_merge(array('page' => $this->current($model)), $url); - $url = array_merge(Set::filter($url, true), array_intersect_key($url, array('plugin' => true))); return $this->{$obj}->link($title, $url, $options); } diff --git a/cake/tests/cases/libs/view/helpers/paginator.test.php b/cake/tests/cases/libs/view/helpers/paginator.test.php index 54602424f..2bad399f4 100644 --- a/cake/tests/cases/libs/view/helpers/paginator.test.php +++ b/cake/tests/cases/libs/view/helpers/paginator.test.php @@ -2199,4 +2199,36 @@ class PaginatorHelperTest extends CakeTestCase { $this->assertEquals($expected, $result); } +/** + * test querystring paging link. + * + * @return void + */ + function testQuerystringNextAndPrev() { + $this->Paginator->request->params['paging']['Article']['paramType'] = 'querystring'; + $this->Paginator->request->params['paging']['Article']['page'] = 2; + $this->Paginator->request->params['paging']['Article']['nextPage'] = true; + $this->Paginator->request->params['paging']['Article']['prevPage'] = true; + + $result = $this->Paginator->next('Next'); + $expected = array( + ' array('href' => '/?page=3', 'class' => 'next'), + 'Next', + '/a', + '/span' + ); + $this->assertTags($result, $expected); + + $result = $this->Paginator->prev('Prev'); + $expected = array( + ' array('href' => '/?page=1', 'class' => 'prev'), + 'Prev', + '/a', + '/span' + ); + $this->assertTags($result, $expected); + } + } From abc6a28ecaac6b74972408ae1aceaf39b77725ca Mon Sep 17 00:00:00 2001 From: mark_story Date: Mon, 20 Dec 2010 14:02:12 -0500 Subject: [PATCH 40/59] Increasing code coverage. --- .../cases/libs/view/helpers/paginator.test.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/cake/tests/cases/libs/view/helpers/paginator.test.php b/cake/tests/cases/libs/view/helpers/paginator.test.php index 2bad399f4..b0c5ae18c 100644 --- a/cake/tests/cases/libs/view/helpers/paginator.test.php +++ b/cake/tests/cases/libs/view/helpers/paginator.test.php @@ -2231,4 +2231,17 @@ class PaginatorHelperTest extends CakeTestCase { $this->assertTags($result, $expected); } +/** + * test the current() method + * + * @return void + */ + function testCurrent() { + $result = $this->Paginator->current(); + $this->assertEquals($this->Paginator->request->params['paging']['Article']['page'], $result); + + $result = $this->Paginator->current('Incorrect'); + $this->assertEquals(1, $result); + } + } From 6f62c22cbc0ca1aed6ee80cee1a3ec141171f7aa Mon Sep 17 00:00:00 2001 From: mark_story Date: Mon, 20 Dec 2010 20:27:27 -0500 Subject: [PATCH 41/59] Fixing fencepost errors. Splitting tests up into smaller groups. --- cake/libs/view/helpers/paginator.php | 4 +- .../libs/view/helpers/paginator.test.php | 119 ++++++++++-------- 2 files changed, 68 insertions(+), 55 deletions(-) diff --git a/cake/libs/view/helpers/paginator.php b/cake/libs/view/helpers/paginator.php index 6f7145643..f7369e408 100644 --- a/cake/libs/view/helpers/paginator.php +++ b/cake/libs/view/helpers/paginator.php @@ -754,7 +754,7 @@ class PaginatorHelper extends AppHelper { $out = ''; - if (is_int($first) && $params['page'] > $first) { + if (is_int($first) && $params['page'] >= $first) { if ($after === null) { $after = $ellipsis; } @@ -811,7 +811,7 @@ class PaginatorHelper extends AppHelper { $out = ''; $lower = $params['pageCount'] - $last + 1; - if (is_int($last) && $params['page'] < $lower) { + if (is_int($last) && $params['page'] <= $lower) { if ($before === null) { $before = $ellipsis; } diff --git a/cake/tests/cases/libs/view/helpers/paginator.test.php b/cake/tests/cases/libs/view/helpers/paginator.test.php index b0c5ae18c..b5e37983b 100644 --- a/cake/tests/cases/libs/view/helpers/paginator.test.php +++ b/cake/tests/cases/libs/view/helpers/paginator.test.php @@ -1810,31 +1810,45 @@ class PaginatorHelperTest extends CakeTestCase { } /** - * testFirstAndLast method + * test first() on the first page. * - * @access public * @return void */ - function testFirstAndLast() { - $this->Paginator->request->params['paging'] = array( - 'Client' => array( - 'page' => 1, - 'current' => 3, - 'count' => 30, - 'prevPage' => false, - 'nextPage' => 2, - 'pageCount' => 15, - 'options' => array(), - 'paramType' => 'named' - ) - ); + function testFirstEmpty() { + $this->Paginator->request->params['paging']['Article']['page'] = 1; $result = $this->Paginator->first(); $expected = ''; $this->assertEqual($result, $expected); + } - $this->Paginator->request->params['paging']['Client']['page'] = 4; +/** + * test first() and options() + * + * @return void + */ + function testFirstFullBaseUrl() { + $this->Paginator->request->params['paging']['Article']['options']['order'] = array('Article.title' => 'DESC'); + $this->Paginator->options(array('url' => array('full_base' => true))); + + $result = $this->Paginator->first(); + $expected = array( + ' array('href' => FULL_BASE_URL . '/index/page:1/sort:Article.title/direction:DESC')), + '<< first', + '/a', + '/span', + ); + $this->assertTags($result, $expected); + } + +/** + * test first() on the fence-post + * + * @return void + */ + function testFirstBoundaries() { $result = $this->Paginator->first(); $expected = array( 'assertTags($result, $expected); + $result = $this->Paginator->first(2); + $expected = array( + ' array('href' => '/index/page:1')), '1', '/a', + '/span', + ' | ', + ' array('href' => '/index/page:2')), '2', '/a', + '/span' + ); + $this->assertTags($result, $expected); + } + +/** + * test Last method + * + * @access public + * @return void + */ + function testLast() { $result = $this->Paginator->last(); $expected = array( ' array('href' => '/index/page:15'), + 'a' => array('href' => '/index/page:7'), 'last >>', '/a', '/span' @@ -1859,26 +1893,35 @@ class PaginatorHelperTest extends CakeTestCase { $expected = array( '...', ' array('href' => '/index/page:15'), - '15', + 'a' => array('href' => '/index/page:7'), + '7', '/a', '/span' ); $this->assertTags($result, $expected); + $this->Paginator->request->params['paging']['Article']['page'] = 6; + $result = $this->Paginator->last(2); $expected = array( '...', ' array('href' => '/index/page:14')), '14', '/a', + array('a' => array('href' => '/index/page:6')), '6', '/a', '/span', ' | ', ' array('href' => '/index/page:15')), '15', '/a', + array('a' => array('href' => '/index/page:7')), '7', '/a', '/span', ); $this->assertTags($result, $expected); - + } + +/** + * undocumented function + * + * @return void + */ + function testLastOptions() { $this->Paginator->request->params['paging'] = array( 'Client' => array( 'page' => 4, @@ -1895,14 +1938,6 @@ class PaginatorHelperTest extends CakeTestCase { ) ); - $result = $this->Paginator->first(); - $expected = array( - ' array('href' => '/index/page:1/sort:Client.name/direction:DESC')), '<< first', '/a', - '/span', - ); - $this->assertTags($result, $expected); - $result = $this->Paginator->last(); $expected = array( 'assertTags($result, $expected); - + $result = $this->Paginator->last(1); $expected = array( '...', @@ -1933,19 +1968,6 @@ class PaginatorHelperTest extends CakeTestCase { ); $this->assertTags($result, $expected); - $result = $this->Paginator->last(2, array('ellipsis' => '~~~')); - $expected = array( - '~~~', - ' array('href' => '/index/page:14/sort:Client.name/direction:DESC')), '14', '/a', - '/span', - ' | ', - ' array('href' => '/index/page:15/sort:Client.name/direction:DESC')), '15', '/a', - '/span', - ); - $this->assertTags($result, $expected); - $result = $this->Paginator->last(2, array('ellipsis' => '...')); $expected = array( array('span' => array('class' => 'ellipsis')), '...', '/span', @@ -1958,15 +1980,6 @@ class PaginatorHelperTest extends CakeTestCase { '/span', ); $this->assertTags($result, $expected); - - $this->Paginator->options(array('url' => array('full_base' => true))); - $result = $this->Paginator->first(); - $expected = array( - ' array('href' => FULL_BASE_URL . '/index/page:1/sort:Client.name/direction:DESC')), '<< first', '/a', - '/span', - ); - $this->assertTags($result, $expected); } /** From 83d12ce690e0a2d497ae687ad6d4e6dc20192e2f Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 26 Dec 2010 13:01:20 -0500 Subject: [PATCH 42/59] Removing deprecated $this->params in Helpers, it got missed somehow. Removing PaginatorHelper::$convertKeys. Its just a regular option now. Added some documentation. --- cake/libs/view/helper.php | 1 - cake/libs/view/helpers/paginator.php | 37 +++++++++++++--------------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/cake/libs/view/helper.php b/cake/libs/view/helper.php index c83dd9b03..16ea94798 100644 --- a/cake/libs/view/helper.php +++ b/cake/libs/view/helper.php @@ -137,7 +137,6 @@ class Helper extends Object { */ public function __construct(View $View, $settings = array()) { $this->_View = $View; - $this->params = $View->params; $this->request = $View->request; if (!empty($this->helpers)) { $this->_helperMap = ObjectCollection::normalizeObjectArray($this->helpers); diff --git a/cake/libs/view/helpers/paginator.php b/cake/libs/view/helpers/paginator.php index 90f80baa7..cf0584655 100644 --- a/cake/libs/view/helpers/paginator.php +++ b/cake/libs/view/helpers/paginator.php @@ -53,34 +53,30 @@ class PaginatorHelper extends AppHelper { * * The values that may be specified are: * - * - `$options['format']` Format of the counter. Supported formats are 'range' and 'pages' + * - `$options['format']` Format of the counter. Supported formats are 'range' and 'pages' * and custom (default). In the default mode the supplied string is parsed and constants are replaced * by their actual values. * Constants: %page%, %pages%, %current%, %count%, %start%, %end% . - * - `$options['separator']` The separator of the actual page and number of pages (default: ' of '). - * - `$options['url']` Url of the action. See Router::url() - * - `$options['url']['sort']` the key that the recordset is sorted. - * - `$options['url']['direction']` Direction of the sorting (default: 'asc'). - * - `$options['url']['page']` Page # to display. - * - `$options['model']` The name of the model. - * - `$options['escape']` Defines if the title field for the link should be escaped (default: true). - * - `$options['update']` DOM id of the element updated with the results of the AJAX call. + * - `$options['separator']` The separator of the actual page and number of pages (default: ' of '). + * - `$options['url']` Url of the action. See Router::url() + * - `$options['url']['sort']` the key that the recordset is sorted. + * - `$options['url']['direction']` Direction of the sorting (default: 'asc'). + * - `$options['url']['page']` Page # to display. + * - `$options['model']` The name of the model. + * - `$options['escape']` Defines if the title field for the link should be escaped (default: true). + * - `$options['update']` DOM id of the element updated with the results of the AJAX call. * If this key isn't specified Paginator will use plain HTML links. - * - `$options['paramType']` The type of parameters to use when creating links. Valid options are + * - `$options['paramType']` The type of parameters to use when creating links. Valid options are * 'querystring', 'named', and 'route'. See PaginatorComponent::$settings for more information. + * - `convertKeys` - A list of keys in url arrays that should be converted to querysting params + * if paramType == 'querystring'. * * @var array * @access public */ - public $options = array(); - -/** - * A list of keys that will be turned into `$this->options['paramType']` url parameters when links - * are generated - * - * @var array - */ - public $convertKeys = array('page', 'limit', 'sort', 'direction'); + public $options = array( + 'convertKeys' => array('page', 'limit', 'sort', 'direction') + ); /** * Constructor for the helper. Sets up the helper that is used for creating 'AJAX' links. @@ -355,6 +351,7 @@ class PaginatorHelper extends AppHelper { $url = array_merge((array)$options['url'], (array)$url); unset($options['url']); } + unset($options['convertKeys']); $url = $this->url($url, true, $model); @@ -403,7 +400,7 @@ class PaginatorHelper extends AppHelper { if (!isset($url['?'])) { $url['?'] = array(); } - foreach ($this->convertKeys as $key) { + foreach ($this->options['convertKeys'] as $key) { if (isset($url[$key])) { $url['?'][$key] = $url[$key]; unset($url[$key]); From edf567b9f9d485be69c7d3f5e955cb3901f789b2 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 26 Dec 2010 13:24:05 -0500 Subject: [PATCH 43/59] Adding convertKeys to PaginatorHelper::options(). Added a test case. Fixes #1390 --- cake/libs/view/helpers/paginator.php | 5 ++++- .../cases/libs/view/helpers/paginator.test.php | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/cake/libs/view/helpers/paginator.php b/cake/libs/view/helpers/paginator.php index cf0584655..59acc1f49 100644 --- a/cake/libs/view/helpers/paginator.php +++ b/cake/libs/view/helpers/paginator.php @@ -66,7 +66,7 @@ class PaginatorHelper extends AppHelper { * - `$options['escape']` Defines if the title field for the link should be escaped (default: true). * - `$options['update']` DOM id of the element updated with the results of the AJAX call. * If this key isn't specified Paginator will use plain HTML links. - * - `$options['paramType']` The type of parameters to use when creating links. Valid options are + * - `$options['paging']['paramType']` The type of parameters to use when creating links. Valid options are * 'querystring', 'named', and 'route'. See PaginatorComponent::$settings for more information. * - `convertKeys` - A list of keys in url arrays that should be converted to querysting params * if paramType == 'querystring'. @@ -163,6 +163,9 @@ class PaginatorHelper extends AppHelper { ); unset($options[$model]); } + if (!empty($options['convertKeys'])) { + $options['convertKeys'] = array_merge($this->options['convertKeys'], $options['convertKeys']); + } $this->options = array_filter(array_merge($this->options, $options)); } diff --git a/cake/tests/cases/libs/view/helpers/paginator.test.php b/cake/tests/cases/libs/view/helpers/paginator.test.php index 63272aad7..5176b3340 100644 --- a/cake/tests/cases/libs/view/helpers/paginator.test.php +++ b/cake/tests/cases/libs/view/helpers/paginator.test.php @@ -2242,6 +2242,22 @@ class PaginatorHelperTest extends CakeTestCase { $this->assertTags($result, $expected); } +/** + * test that additional keys can be flagged as query string args. + * + * @return void + */ + function testOptionsConvertKeys() { + $this->Paginator->options(array( + 'convertKeys' => array('something'), + 'Article' => array('paramType' => 'querystring') + )); + $result = $this->Paginator->url(array('page' => '4', 'something' => 'bar')); + $expected = '/?page=4&something=bar'; + $this->assertEquals($expected, $result); + } + + /** * test the current() method * From 769da1a7c87f993968efda28314e8ba76fe5c9fe Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 26 Dec 2010 14:25:57 -0500 Subject: [PATCH 44/59] Adding basic BehaviorCollection::hasMethod implementation. Tests added. --- cake/libs/model/behavior_collection.php | 19 +++++++++ .../libs/model/behavior_collection.test.php | 41 ++++++++++++++++++- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/cake/libs/model/behavior_collection.php b/cake/libs/model/behavior_collection.php index da9970560..848894cb9 100644 --- a/cake/libs/model/behavior_collection.php +++ b/cake/libs/model/behavior_collection.php @@ -237,4 +237,23 @@ class BehaviorCollection extends ObjectCollection { return $this->__methods; } +/** + * Check to see if a behavior in this collection implements the provided method. Will + * also check mappedMethods. + * + * @param string $method The method to find. + * @return boolean Method was found. + */ + public function hasMethod($method) { + if (isset($this->__methods[$method])) { + return true; + } + foreach ($this->__mappedMethods as $pattern => $target) { + if (preg_match($pattern . 'i', $method)) { + return true; + } + } + return false; + } + } diff --git a/cake/tests/cases/libs/model/behavior_collection.test.php b/cake/tests/cases/libs/model/behavior_collection.test.php index efe549297..4b4ab980d 100644 --- a/cake/tests/cases/libs/model/behavior_collection.test.php +++ b/cake/tests/cases/libs/model/behavior_collection.test.php @@ -340,7 +340,16 @@ class TestBehavior extends ModelBehavior { * * @package cake.tests.cases.libs.model */ -class Test2Behavior extends TestBehavior{ +class Test2Behavior extends TestBehavior { + public $mapMethods = array('/mappingRobot(\w+)/' => 'mapped'); + + function resolveMethod($model, $stuff) { + + } + + function mapped($model, $method, $query) { + + } } /** @@ -1116,4 +1125,34 @@ class BehaviorCollectionTest extends CakeTestCase { $Sample->Behaviors->trigger('beforeTest', array(&$Sample)); } + +/** + * test that hasMethod works with basic functions. + * + * @return void + */ + function testHasMethodBasic() { + $Sample = new Sample(); + $Collection = new BehaviorCollection(); + $Collection->init('Sample', array('Test', 'Test2')); + + $this->assertTrue($Collection->hasMethod('testMethod')); + $this->assertTrue($Collection->hasMethod('resolveMethod')); + + $this->assertFalse($Collection->hasMethod('No method')); + } + +/** + * test that hasMethod works with mapped methods. + * + * @return void + */ + function testHasMethodMappedMethods() { + $Sample = new Sample(); + $Collection = new BehaviorCollection(); + $Collection->init('Sample', array('Test', 'Test2')); + + $this->assertTrue($Collection->hasMethod('look for the remote in the couch')); + $this->assertTrue($Collection->hasMethod('mappingRobotOnTheRoof')); + } } From 0c4b665ad020512409c602f3152e3672da510d14 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 26 Dec 2010 17:09:20 -0500 Subject: [PATCH 45/59] Adding ability for BehaviorCollection::hasMethod() to return the callback. Re-factored BehaviorCollection::dispatchMethod to be simpler and faster. Changing now BehaviorCollection stores callbacks so they look like normal php callback arrays. --- cake/libs/model/behavior_collection.php | 63 +++++++++---------- .../libs/model/behavior_collection.test.php | 27 +++++++- 2 files changed, 54 insertions(+), 36 deletions(-) diff --git a/cake/libs/model/behavior_collection.php b/cake/libs/model/behavior_collection.php index 848894cb9..fbf1081fc 100644 --- a/cake/libs/model/behavior_collection.php +++ b/cake/libs/model/behavior_collection.php @@ -128,7 +128,7 @@ class BehaviorCollection extends ObjectCollection { $this->_loaded[$name]->setup(ClassRegistry::getObject($this->modelName), $config); foreach ($this->_loaded[$name]->mapMethods as $method => $alias) { - $this->__mappedMethods[$method] = array($alias, $name); + $this->__mappedMethods[$method] = array($name, $alias); } $methods = get_class_methods($this->_loaded[$name]); $parentMethods = array_flip(get_class_methods('ModelBehavior')); @@ -144,7 +144,7 @@ class BehaviorCollection extends ObjectCollection { !in_array($m, $callbacks) ); if ($methodAllowed) { - $this->__methods[$m] = array($m, $name); + $this->__methods[$m] = array($name, $m); } } } @@ -171,7 +171,7 @@ class BehaviorCollection extends ObjectCollection { unset($this->_loaded[$name]); } foreach ($this->__methods as $m => $callback) { - if (is_array($callback) && $callback[1] == $name) { + if (is_array($callback) && $callback[0] == $name) { unset($this->__methods[$m]); } } @@ -194,42 +194,29 @@ class BehaviorCollection extends ObjectCollection { * * @return array All methods for all behaviors attached to this object */ - public function dispatchMethod(&$model, $method, $params = array(), $strict = false) { - $methods = array_keys($this->__methods); - $check = array_flip($methods); - $found = isset($check[$method]); - $call = null; - - if ($strict && !$found) { + public function dispatchMethod($model, $method, $params = array(), $strict = false) { + $method = $this->hasMethod($method, true); + + if ($strict && empty($method)) { trigger_error(__("BehaviorCollection::dispatchMethod() - Method %s not found in any attached behavior", $method), E_USER_WARNING); return null; - } elseif ($found) { - $methods = array_combine($methods, array_values($this->__methods)); - $call = $methods[$method]; - } else { - $count = count($this->__mappedMethods); - $mapped = array_keys($this->__mappedMethods); - - for ($i = 0; $i < $count; $i++) { - if (preg_match($mapped[$i] . 'i', $method)) { - $call = $this->__mappedMethods[$mapped[$i]]; - array_unshift($params, $method); - break; - } - } } - - if (!empty($call)) { - return call_user_func_array( - array(&$this->_loaded[$call[1]], $call[0]), - array_merge(array(&$model), $params) - ); + if (empty($method)) { + return array('unhandled'); } - return array('unhandled'); + if (count($method) === 3) { + array_unshift($params, $method[2]); + unset($method[2]); + } + return call_user_func_array( + array($this->_loaded[$method[0]], $method[1]), + array_merge(array(&$model), $params) + ); } /** - * Gets the method list for attached behaviors, i.e. all public, non-callback methods + * Gets the method list for attached behaviors, i.e. all public, non-callback methods. + * This does not include mappedMethods. * * @return array All public methods for all behaviors attached to this collection */ @@ -242,14 +229,20 @@ class BehaviorCollection extends ObjectCollection { * also check mappedMethods. * * @param string $method The method to find. - * @return boolean Method was found. + * @param boolean $callback Return the callback for the method. + * @return mixed If $callback is false, a boolean will be returnned, if its true, an array + * containing callback information will be returnned. For mapped methods the array will have 3 elements. */ - public function hasMethod($method) { + public function hasMethod($method, $callback = false) { if (isset($this->__methods[$method])) { - return true; + return $callback ? $this->__methods[$method] : true; } foreach ($this->__mappedMethods as $pattern => $target) { if (preg_match($pattern . 'i', $method)) { + if ($callback) { + $target[] = $method; + return $target; + } return true; } } diff --git a/cake/tests/cases/libs/model/behavior_collection.test.php b/cake/tests/cases/libs/model/behavior_collection.test.php index 4b4ab980d..b7f547d99 100644 --- a/cake/tests/cases/libs/model/behavior_collection.test.php +++ b/cake/tests/cases/libs/model/behavior_collection.test.php @@ -1151,8 +1151,33 @@ class BehaviorCollectionTest extends CakeTestCase { $Sample = new Sample(); $Collection = new BehaviorCollection(); $Collection->init('Sample', array('Test', 'Test2')); - + $this->assertTrue($Collection->hasMethod('look for the remote in the couch')); $this->assertTrue($Collection->hasMethod('mappingRobotOnTheRoof')); } + +/** + * test hasMethod returrning a 'callback' + * + * @return void + */ + function testHasMethodAsCallback() { + $Sample = new Sample(); + $Collection = new BehaviorCollection(); + $Collection->init('Sample', array('Test', 'Test2')); + + $result = $Collection->hasMethod('testMethod', true); + $expected = array('Test', 'testMethod'); + $this->assertEquals($expected, $result); + + $result = $Collection->hasMethod('resolveMethod', true); + $expected = array('Test2', 'resolveMethod'); + $this->assertEquals($expected, $result); + + $result = $Collection->hasMethod('mappingRobotOnTheRoof', true); + $expected = array('Test2', 'mapped', 'mappingRobotOnTheRoof'); + $this->assertEquals($expected, $result); + } + + } From ad5a1ca6b7d28850acd05283aef666cfc54eff67 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 26 Dec 2010 17:10:16 -0500 Subject: [PATCH 46/59] Making __methods and __mappedMethods protected instead of private. --- cake/libs/model/behavior_collection.php | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/cake/libs/model/behavior_collection.php b/cake/libs/model/behavior_collection.php index fbf1081fc..e4823bb11 100644 --- a/cake/libs/model/behavior_collection.php +++ b/cake/libs/model/behavior_collection.php @@ -42,14 +42,14 @@ class BehaviorCollection extends ObjectCollection { * * @var array */ - private $__methods = array(); + protected $_methods = array(); /** * Keeps a list of all methods which have been mapped with regular expressions * * @var array */ - private $__mappedMethods = array(); + protected $_mappedMethods = array(); /** * Attaches a model object and loads a list of behaviors @@ -128,7 +128,7 @@ class BehaviorCollection extends ObjectCollection { $this->_loaded[$name]->setup(ClassRegistry::getObject($this->modelName), $config); foreach ($this->_loaded[$name]->mapMethods as $method => $alias) { - $this->__mappedMethods[$method] = array($name, $alias); + $this->_mappedMethods[$method] = array($name, $alias); } $methods = get_class_methods($this->_loaded[$name]); $parentMethods = array_flip(get_class_methods('ModelBehavior')); @@ -140,11 +140,11 @@ class BehaviorCollection extends ObjectCollection { foreach ($methods as $m) { if (!isset($parentMethods[$m])) { $methodAllowed = ( - $m[0] != '_' && !array_key_exists($m, $this->__methods) && + $m[0] != '_' && !array_key_exists($m, $this->_methods) && !in_array($m, $callbacks) ); if ($methodAllowed) { - $this->__methods[$m] = array($name, $m); + $this->_methods[$m] = array($name, $m); } } } @@ -170,9 +170,9 @@ class BehaviorCollection extends ObjectCollection { $this->_loaded[$name]->cleanup(ClassRegistry::getObject($this->modelName)); unset($this->_loaded[$name]); } - foreach ($this->__methods as $m => $callback) { + foreach ($this->_methods as $m => $callback) { if (is_array($callback) && $callback[0] == $name) { - unset($this->__methods[$m]); + unset($this->_methods[$m]); } } $this->_enabled = array_values(array_diff($this->_enabled, (array)$name)); @@ -221,7 +221,7 @@ class BehaviorCollection extends ObjectCollection { * @return array All public methods for all behaviors attached to this collection */ public function methods() { - return $this->__methods; + return $this->_methods; } /** @@ -234,10 +234,10 @@ class BehaviorCollection extends ObjectCollection { * containing callback information will be returnned. For mapped methods the array will have 3 elements. */ public function hasMethod($method, $callback = false) { - if (isset($this->__methods[$method])) { - return $callback ? $this->__methods[$method] : true; + if (isset($this->_methods[$method])) { + return $callback ? $this->_methods[$method] : true; } - foreach ($this->__mappedMethods as $pattern => $target) { + foreach ($this->_mappedMethods as $pattern => $target) { if (preg_match($pattern . 'i', $method)) { if ($callback) { $target[] = $method; From 3022e2d785268731ae0a7830c1fa93a6c9b24bef Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 26 Dec 2010 17:21:49 -0500 Subject: [PATCH 47/59] Adding documentation. --- cake/libs/model/behavior_collection.php | 9 ++++++++- cake/libs/model/model_behavior.php | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/cake/libs/model/behavior_collection.php b/cake/libs/model/behavior_collection.php index e4823bb11..965c315f4 100644 --- a/cake/libs/model/behavior_collection.php +++ b/cake/libs/model/behavior_collection.php @@ -190,8 +190,15 @@ class BehaviorCollection extends ObjectCollection { } /** - * Dispatches a behavior method + * Dispatches a behavior method. Will call either normal methods or mapped methods. * + * If a method is not handeled by the BehaviorCollection, and $strict is false, a + * special return of `array('unhandled')` will be returned to signal the method was not found. + * + * @param Model $model The model the method was originally called on. + * @param string $method The method called. + * @param array $params Parameters for the called method. + * @param boolean $strict If methods are not found, trigger an error. * @return array All methods for all behaviors attached to this object */ public function dispatchMethod($model, $method, $params = array(), $strict = false) { diff --git a/cake/libs/model/model_behavior.php b/cake/libs/model/model_behavior.php index 482bd2e1e..7ff613b71 100644 --- a/cake/libs/model/model_behavior.php +++ b/cake/libs/model/model_behavior.php @@ -39,6 +39,25 @@ * * Would be called like `$this->Model->doSomething($arg1, $arg2);`. * + * ### Mapped methods + * + * Behaviors can also define mapped methods. Mapped methods use pattern matching for method invocation. This + * allows you to create methods similar to Model::findAllByXXX methods on your behaviors. Mapped methods need to + * be declared in your behaviors `$mapMethods` array. The method signature for a mapped method is slightly different + * than a normal behavior mixin method. + * + * {{{ + * var $mapMethods = array('/do(\w+)/' => 'doSomething'); + * + * function doSomething($model, $method, $arg1, $arg2) { + * //do something + * } + * }}} + * + * The above will map every doXXX() method call to the behavior. As you can see, the model is + * still the first parameter, but the called method name will be the 2nd parameter. This allows + * you to munge the method name for additional information, much like Model::findAllByXX. + * * @package cake.libs.model * @see Model::$actsAs * @see BehaviorCollection::load() From c5fa93b0fb81141b1fcf51d8bc6bbe3436fed54c Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 26 Dec 2010 17:26:18 -0500 Subject: [PATCH 48/59] Removing test that is testing methods covered in ObjectCollection test case. --- .../libs/model/behavior_collection.test.php | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/cake/tests/cases/libs/model/behavior_collection.test.php b/cake/tests/cases/libs/model/behavior_collection.test.php index b7f547d99..6fa7756b7 100644 --- a/cake/tests/cases/libs/model/behavior_collection.test.php +++ b/cake/tests/cases/libs/model/behavior_collection.test.php @@ -1035,34 +1035,6 @@ class BehaviorCollectionTest extends CakeTestCase { $this->assertTrue($Apple->testData('one', 'two', 'three', 'four', 'five', 'six')); } -/** - * testBehaviorTrigger method - * - * @access public - * @return void - */ - function testBehaviorTrigger() { - $Apple = new Apple(); - $Apple->Behaviors->attach('Test'); - $Apple->Behaviors->attach('Test2'); - $Apple->Behaviors->attach('Test3'); - - $Apple->beforeTestResult = array(); - $Apple->Behaviors->trigger('beforeTest', array(&$Apple)); - $expected = array('testbehavior', 'test2behavior', 'test3behavior'); - $this->assertIdentical($Apple->beforeTestResult, $expected); - - $Apple->beforeTestResult = array(); - $Apple->Behaviors->trigger('beforeTest', array(&$Apple), array('break' => true, 'breakOn' => 'test2behavior')); - $expected = array('testbehavior', 'test2behavior'); - $this->assertIdentical($Apple->beforeTestResult, $expected); - - $Apple->beforeTestResult = array(); - $Apple->Behaviors->trigger('beforeTest', array($Apple), array('break' => true, 'breakOn' => array('test2behavior', 'test3behavior'))); - $expected = array('testbehavior', 'test2behavior'); - $this->assertIdentical($Apple->beforeTestResult, $expected); - } - /** * undocumented function * From fd3b4b2cd51ed5f2356156e8fa7d7cdd5d052564 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 26 Dec 2010 17:35:22 -0500 Subject: [PATCH 49/59] Adding Model::hasMethod() and tests. --- cake/libs/model/model.php | 17 ++++++++++++++ .../libs/model/model_integration.test.php | 23 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/cake/libs/model/model.php b/cake/libs/model/model.php index b55616028..396da7d4e 100644 --- a/cake/libs/model/model.php +++ b/cake/libs/model/model.php @@ -1066,6 +1066,23 @@ class Model extends Object { return false; } +/** + * Check that a method is callable on a model. This will check both the model's own methods, its + * inherited methods and methods that could be callable through behaviors. + * + * @param string $method The method to be called. + * @return boolean True on method being callable. + */ + public function hasMethod($method) { + if (method_exists($this, $method)) { + return true; + } + if ($this->Behaviors->hasMethod($method)) { + return true; + } + return false; + } + /** * Returns true if the supplied field is a model Virtual Field * diff --git a/cake/tests/cases/libs/model/model_integration.test.php b/cake/tests/cases/libs/model/model_integration.test.php index fdd410bde..94714a276 100644 --- a/cake/tests/cases/libs/model/model_integration.test.php +++ b/cake/tests/cases/libs/model/model_integration.test.php @@ -2008,4 +2008,27 @@ class ModelIntegrationTest extends BaseModelTest { $expected = $db->name('Domain.DomainHandle'); $this->assertEqual($result, $expected); } + +/** + * test that model->hasMethod checks self and behaviors. + * + * @return void + */ + function testHasMethod() { + $Article = new Article(); + $Article->Behaviors = $this->getMock('BehaviorCollection'); + + $Article->Behaviors->expects($this->at(0)) + ->method('hasMethod') + ->will($this->returnValue(true)); + + $Article->Behaviors->expects($this->at(1)) + ->method('hasMethod') + ->will($this->returnValue(false)); + + $this->assertTrue($Article->hasMethod('find')); + + $this->assertTrue($Article->hasMethod('pass')); + $this->assertFalse($Article->hasMethod('fail')); + } } From f62a067d7cfffb6e711a25abe02287235a36034b Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 26 Dec 2010 17:40:34 -0500 Subject: [PATCH 50/59] Making it possible for behaviors to define paginate and paginateCount. Updated test to ensure the component calls the correct methods. Fixes #1373 --- cake/libs/controller/components/paginator.php | 4 ++-- .../libs/controller/components/paginator.test.php | 12 +++++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/cake/libs/controller/components/paginator.php b/cake/libs/controller/components/paginator.php index 086de0584..7fbae137a 100644 --- a/cake/libs/controller/components/paginator.php +++ b/cake/libs/controller/components/paginator.php @@ -152,7 +152,7 @@ class PaginatorComponent extends Component { $extra['type'] = $type; } - if (method_exists($object, 'paginateCount')) { + if ($object->hasMethod('paginateCount')) { $count = $object->paginateCount($conditions, $recursive, $extra); } else { $parameters = compact('conditions'); @@ -170,7 +170,7 @@ class PaginatorComponent extends Component { } $page = $options['page'] = (int)$page; - if (method_exists($object, 'paginate')) { + if ($object->hasMethod('paginate')) { $results = $object->paginate( $conditions, $fields, $order, $limit, $page, $recursive, $extra ); diff --git a/cake/tests/cases/libs/controller/components/paginator.test.php b/cake/tests/cases/libs/controller/components/paginator.test.php index 6a337eb44..e8c925fc4 100644 --- a/cake/tests/cases/libs/controller/components/paginator.test.php +++ b/cake/tests/cases/libs/controller/components/paginator.test.php @@ -313,10 +313,20 @@ class PaginatorTest extends CakeTestCase { */ function testPageParamCasting() { $this->Controller->Post->expects($this->at(0)) + ->method('hasMethod') + ->with('paginateCount') + ->will($this->returnValue(false)); + + $this->Controller->Post->expects($this->at(1)) ->method('find') ->will($this->returnValue(2)); - $this->Controller->Post->expects($this->at(1)) + $this->Controller->Post->expects($this->at(2)) + ->method('hasMethod') + ->with('paginate') + ->will($this->returnValue(false)); + + $this->Controller->Post->expects($this->at(3)) ->method('find') ->will($this->returnValue(array('stuff'))); From 5ce66d3031a0d0f4ab14ddeef9128d136b1fcaf3 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 26 Dec 2010 21:30:43 -0500 Subject: [PATCH 51/59] Changing how PaginatorComponent::paginate()'s $whitelist param works. It now serves as the whitelist for fields ordering can be done on. It previously allowed you to whitelist things you passed into paginate(), which was kind of useless. Updated tests. Fixes #430 --- cake/libs/controller/components/paginator.php | 31 ++++++++++--------- .../controller/components/paginator.test.php | 19 +++++++++++- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/cake/libs/controller/components/paginator.php b/cake/libs/controller/components/paginator.php index 7fbae137a..c857b573e 100644 --- a/cake/libs/controller/components/paginator.php +++ b/cake/libs/controller/components/paginator.php @@ -74,7 +74,7 @@ class PaginatorComponent extends Component { ); /** - * A list of request parameters users are allowed to set. Modifying + * A list of parameters users are allowed to set using request parameters. Modifying * this list will allow users to have more influence over pagination, * be careful with what you permit. * @@ -101,7 +101,8 @@ class PaginatorComponent extends Component { * * @param mixed $object Model to paginate (e.g: model instance, or 'Model', or 'Model.InnerModel') * @param mixed $scope Additional find conditions to use while paginating - * @param array $whitelist List of allowed options for paging + * @param array $whitelist List of allowed fields for ordering. This allows you to prevent ordering + * on non-indexed, or undesirable columns. * @return array Model query results */ public function paginate($object = null, $scope = array(), $whitelist = array()) { @@ -117,8 +118,8 @@ class PaginatorComponent extends Component { throw new MissingModelException($object); } - $options = $this->mergeOptions($object->alias, $whitelist); - $options = $this->validateSort($object, $options); + $options = $this->mergeOptions($object->alias); + $options = $this->validateSort($object, $options, $whitelist); $options = $this->checkLimit($options); $conditions = $fields = $order = $limit = $page = $recursive = null; @@ -272,11 +273,9 @@ class PaginatorComponent extends Component { * * @param string $alias Model alias being paginated, if the general settings has a key with this value * that key's settings will be used for pagination instead of the general ones. - * @param string $whitelist A whitelist of options that are allowed from the request parameters. Modifying - * this array will allow you to permit more or less input from the user. * @return array Array of merged options. */ - public function mergeOptions($alias, $whitelist = array()) { + public function mergeOptions($alias) { $defaults = $this->getDefaults($alias); switch ($defaults['paramType']) { case 'named': @@ -285,14 +284,8 @@ class PaginatorComponent extends Component { case 'querystring': $request = $this->Controller->request->query; break; - case 'route': - $request = $this->Controller->request->params; - unset($request['pass'], $request['named']); } - - $whitelist = array_flip(array_merge($this->whitelist, $whitelist)); - $request = array_intersect_key($request, $whitelist); - + $request = array_intersect_key($request, array_flip($this->whitelist)); return array_merge($defaults, $request); } @@ -322,9 +315,10 @@ class PaginatorComponent extends Component { * * @param Model $object The model being paginated. * @param array $options The pagination options being used for this request. + * @param array $whitelist The list of columns that can be used for sorting. If empty all keys are allowed. * @return array An array of options with sort + direction removed and replaced with order if possible. */ - public function validateSort($object, $options) { + public function validateSort($object, $options, $whitelist = array()) { if (isset($options['sort'])) { $direction = null; if (isset($options['direction'])) { @@ -335,6 +329,13 @@ class PaginatorComponent extends Component { } $options['order'] = array($options['sort'] => $direction); } + + if (!empty($whitelist)) { + $field = key($options['order']); + if (!in_array($field, $whitelist)) { + $options['order'] = null; + } + } if (!empty($options['order']) && is_array($options['order'])) { $alias = $object->alias ; diff --git a/cake/tests/cases/libs/controller/components/paginator.test.php b/cake/tests/cases/libs/controller/components/paginator.test.php index e8c925fc4..cbaecb2b9 100644 --- a/cake/tests/cases/libs/controller/components/paginator.test.php +++ b/cake/tests/cases/libs/controller/components/paginator.test.php @@ -635,7 +635,8 @@ class PaginatorTest extends CakeTestCase { 'maxLimit' => 100, 'paramType' => 'named', ); - $result = $this->Paginator->mergeOptions('Post', array('fields')); + $this->Paginator->whitelist[] = 'fields'; + $result = $this->Paginator->mergeOptions('Post'); $expected = array( 'page' => 10, 'limit' => 10, 'maxLimit' => 100, 'paramType' => 'named', 'fields' => array('bad.stuff') ); @@ -658,6 +659,22 @@ class PaginatorTest extends CakeTestCase { $this->assertEquals('asc', $result['order']['model.something']); } +/** + * test that fields not in whitelist won't be part of order conditions. + * + * @return void + */ + function testValidateSortWhitelistFailure() { + $model = $this->getMock('Model'); + $model->alias = 'model'; + $model->expects($this->any())->method('hasField')->will($this->returnValue(true)); + + $options = array('sort' => 'body', 'direction' => 'asc'); + $result = $this->Paginator->validateSort($model, $options, array('title', 'id')); + + $this->assertNull($result['order']); + } + /** * test that virtual fields work. * From 65394604a751ccfdc8124f1f29b85a33a3d1b217 Mon Sep 17 00:00:00 2001 From: Dieter Plaetinck Date: Fri, 19 Feb 2010 10:27:16 +0100 Subject: [PATCH 52/59] Give PaginatorHelper's next/prev links the correct 'rel' attribute It's a good idea to give links such as next/prev the 'rel' attribute. See the following pages for more information: http://www.w3.org/TR/html4/struct/links.html#edef-A http://www.w3.org/TR/html4/types.html#type-links Signed-off-by: mark_story --- cake/libs/view/helpers/paginator.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cake/libs/view/helpers/paginator.php b/cake/libs/view/helpers/paginator.php index 59acc1f49..c7377e13e 100644 --- a/cake/libs/view/helpers/paginator.php +++ b/cake/libs/view/helpers/paginator.php @@ -252,6 +252,10 @@ class PaginatorHelper extends AppHelper { * @return string A "previous" link or $disabledTitle text if the link is disabled. */ public function prev($title = '<< Previous', $options = array(), $disabledTitle = null, $disabledOptions = array()) { + $defaults = array( + 'rel' => 'prev' + ); + $options = array_merge($defaults, (array)$options); return $this->__pagingLink('Prev', $title, $options, $disabledTitle, $disabledOptions); } @@ -271,6 +275,10 @@ class PaginatorHelper extends AppHelper { * @return string A "next" link or or $disabledTitle text if the link is disabled. */ public function next($title = 'Next >>', $options = array(), $disabledTitle = null, $disabledOptions = array()) { + $defaults = array( + 'rel' => 'next' + ); + $options = array_merge($defaults, (array)$options); return $this->__pagingLink('Next', $title, $options, $disabledTitle, $disabledOptions); } From af608f68ef70ed17e0663cd37ae2f18bc85fb032 Mon Sep 17 00:00:00 2001 From: mark_story Date: Mon, 27 Dec 2010 10:38:30 -0500 Subject: [PATCH 53/59] Updating test cases for the addition of rel attributes to links generated by PaginatorHelper. Also removed rel attributes for disabled elements, as they are probably not link tags. Fixes #370 --- cake/libs/view/helpers/paginator.php | 1 + .../libs/view/helpers/paginator.test.php | 48 ++++++++++++------- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/cake/libs/view/helpers/paginator.php b/cake/libs/view/helpers/paginator.php index c7377e13e..fc4ba0862 100644 --- a/cake/libs/view/helpers/paginator.php +++ b/cake/libs/view/helpers/paginator.php @@ -454,6 +454,7 @@ class PaginatorHelper extends AppHelper { if ($this->{$check}($model)) { return $this->Html->tag($tag, $this->link($title, $url, array_merge($options, compact('escape', 'class')))); } else { + unset($options['rel']); return $this->Html->tag($tag, $title, array_merge($options, compact('escape', 'class'))); } } diff --git a/cake/tests/cases/libs/view/helpers/paginator.test.php b/cake/tests/cases/libs/view/helpers/paginator.test.php index 5176b3340..671c4f46a 100644 --- a/cake/tests/cases/libs/view/helpers/paginator.test.php +++ b/cake/tests/cases/libs/view/helpers/paginator.test.php @@ -460,7 +460,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->next('Next'); $expected = array( ' array('href' => '/admin/users/index/page:2', 'class' => 'next'), + 'a' => array('href' => '/admin/users/index/page:2', 'class' => 'next', 'rel' => 'next'), 'Next', '/a', '/span' @@ -563,7 +563,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->next('next', array('url' => $options)); $expected = array( ' array('href' => '/members/posts/index/page:3', 'class' => 'next'), + 'a' => array('href' => '/members/posts/index/page:3', 'class' => 'next', 'rel' => 'next'), 'next', '/a', '/span' @@ -573,7 +573,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->prev('prev', array('url' => $options)); $expected = array( ' array('href' => '/members/posts/index/page:1', 'class' => 'prev'), + 'a' => array('href' => '/members/posts/index/page:1', 'class' => 'prev', 'rel' => 'prev'), 'prev', '/a', '/span' @@ -700,7 +700,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->next('Next'); $expected = array( ' array('href' => '/articles/index/2/page:2/foo:bar', 'class' => 'next'), + 'a' => array('href' => '/articles/index/2/page:2/foo:bar', 'class' => 'next', 'rel' => 'next'), 'Next', '/a', '/span' @@ -750,7 +750,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->prev('<< Previous', null, null, array('class' => 'disabled')); $expected = array( ' array('href' => '/index/page:1', 'class' => 'prev'), + 'a' => array('href' => '/index/page:1', 'class' => 'prev', 'rel' => 'prev'), '<< Previous', '/a', '/span' @@ -760,7 +760,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->next('Next'); $expected = array( ' array('href' => '/index/page:3', 'class' => 'next'), + 'a' => array('href' => '/index/page:3', 'class' => 'next', 'rel' => 'next'), 'Next', '/a', '/span' @@ -770,7 +770,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->next('Next', array('tag' => 'li')); $expected = array( ' array('href' => '/index/page:3', 'class' => 'next'), + 'a' => array('href' => '/index/page:3', 'class' => 'next', 'rel' => 'next'), 'Next', '/a', '/li' @@ -780,7 +780,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->prev('<< Previous', array('escape' => true)); $expected = array( ' array('href' => '/index/page:1', 'class' => 'prev'), + 'a' => array('href' => '/index/page:1', 'class' => 'prev', 'rel' => 'prev'), '<< Previous', '/a', '/span' @@ -790,7 +790,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->prev('<< Previous', array('escape' => false)); $expected = array( ' array('href' => '/index/page:1', 'class' => 'prev'), + 'a' => array('href' => '/index/page:1', 'class' => 'prev', 'rel' => 'prev'), 'preg:/<< Previous/', '/a', '/span' @@ -858,7 +858,11 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->prev('<< Previous', null, null, array('class' => 'disabled')); $expected = array( ' array('href' => '/index/page:1/limit:3/sort:Client.name/direction:DESC', 'class' => 'prev'), + 'a' => array( + 'href' => '/index/page:1/limit:3/sort:Client.name/direction:DESC', + 'class' => 'prev', + 'rel' => 'prev' + ), '<< Previous', '/a', '/span' @@ -868,7 +872,11 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->next('Next'); $expected = array( ' array('href' => '/index/page:3/limit:3/sort:Client.name/direction:DESC', 'class' => 'next'), + 'a' => array( + 'href' => '/index/page:3/limit:3/sort:Client.name/direction:DESC', + 'class' => 'next', + 'rel' => 'next' + ), 'Next', '/a', '/span' @@ -895,7 +903,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->prev('Prev'); $expected = array( ' array('href' => '/index/page:1/limit:10', 'class' => 'prev'), + 'a' => array('href' => '/index/page:1/limit:10', 'class' => 'prev', 'rel' => 'prev'), 'Prev', '/a', '/span' @@ -917,7 +925,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->prev('Prev', array('url' => array('foo' => 'bar'))); $expected = array( ' array('href' => '/index/12/page:1/limit:10/foo:bar', 'class' => 'prev'), + 'a' => array('href' => '/index/12/page:1/limit:10/foo:bar', 'class' => 'prev', 'rel' => 'prev'), 'Prev', '/a', '/span' @@ -957,7 +965,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->next('Next >>', array('escape' => false)); $expected = array( ' array('href' => '/index/page:2', 'class' => 'next'), + 'a' => array('href' => '/index/page:2', 'class' => 'next', 'rel' => 'next'), 'preg:/Next >>/', '/a', '/span' @@ -1004,7 +1012,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->next('Next', array('model' => 'Client')); $expected = array( ' array('href' => '/index/page:2', 'class' => 'next'), + 'a' => array('href' => '/index/page:2', 'class' => 'next', 'rel' => 'next'), 'Next', '/a', '/span' @@ -2130,7 +2138,11 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->next('Next'); $expected = array( ' array('href' => '/officespace/accounts/index/page:2/sort:Article.title/direction:asc', 'class' => 'next'), + 'a' => array( + 'href' => '/officespace/accounts/index/page:2/sort:Article.title/direction:asc', + 'class' => 'next', + 'rel' => 'next' + ), 'Next', '/a', '/span', @@ -2224,7 +2236,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->next('Next'); $expected = array( ' array('href' => '/?page=3', 'class' => 'next'), + 'a' => array('href' => '/?page=3', 'class' => 'next', 'rel' => 'next'), 'Next', '/a', '/span' @@ -2234,7 +2246,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->prev('Prev'); $expected = array( ' array('href' => '/?page=1', 'class' => 'prev'), + 'a' => array('href' => '/?page=1', 'class' => 'prev', 'rel' => 'prev'), 'Prev', '/a', '/span' From fd88d575130787dc10e0ccd4c4ba337cfc767c7e Mon Sep 17 00:00:00 2001 From: mark_story Date: Mon, 27 Dec 2010 10:40:21 -0500 Subject: [PATCH 54/59] Fixing option documentation. --- cake/libs/view/helpers/paginator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cake/libs/view/helpers/paginator.php b/cake/libs/view/helpers/paginator.php index fc4ba0862..f217d8fae 100644 --- a/cake/libs/view/helpers/paginator.php +++ b/cake/libs/view/helpers/paginator.php @@ -730,7 +730,7 @@ class PaginatorHelper extends AppHelper { * ### Options: * * - `tag` The tag wrapping tag you want to use, defaults to 'span' - * - `before` Content to insert before the link/tag + * - `after` Content to insert after the link/tag * - `model` The model to use defaults to PaginatorHelper::defaultModel() * - `separator` Content between the generated links, defaults to ' | ' * - `ellipsis` Content for ellipsis, defaults to '...' From e003bd6ea9a74c52b35595cdd859e1546d2e0577 Mon Sep 17 00:00:00 2001 From: mark_story Date: Mon, 27 Dec 2010 10:48:38 -0500 Subject: [PATCH 55/59] Adding rel attributes for first and last links. These attribute values are part of the html5 spec, and fit with the intention of #370 --- cake/libs/view/helpers/paginator.php | 8 +++++--- .../libs/view/helpers/paginator.test.php | 20 ++++++++++++------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/cake/libs/view/helpers/paginator.php b/cake/libs/view/helpers/paginator.php index f217d8fae..5966d11c0 100644 --- a/cake/libs/view/helpers/paginator.php +++ b/cake/libs/view/helpers/paginator.php @@ -743,14 +743,14 @@ class PaginatorHelper extends AppHelper { $options = array_merge( array( 'tag' => 'span', - 'after'=> null, + 'after' => null, 'model' => $this->defaultModel(), 'separator' => ' | ', - 'ellipsis' => '...', + 'ellipsis' => '...' ), (array)$options); - $params = array_merge(array('page'=> 1), (array)$this->params($options['model'])); + $params = array_merge(array('page' => 1), (array)$this->params($options['model'])); unset($options['model']); if ($params['pageCount'] <= 1) { @@ -773,6 +773,7 @@ class PaginatorHelper extends AppHelper { } $out .= $after; } elseif ($params['page'] > 1) { + $options += array('rel' => 'first'); $out = $this->Html->tag($tag, $this->link($first, array('page' => 1), $options)) . $after; } @@ -830,6 +831,7 @@ class PaginatorHelper extends AppHelper { } $out = $before . $out; } elseif ($params['page'] < $params['pageCount']) { + $options += array('rel' => 'last'); $out = $before . $this->Html->tag( $tag, $this->link($last, array('page' => $params['pageCount']), $options )); diff --git a/cake/tests/cases/libs/view/helpers/paginator.test.php b/cake/tests/cases/libs/view/helpers/paginator.test.php index 671c4f46a..7b600819f 100644 --- a/cake/tests/cases/libs/view/helpers/paginator.test.php +++ b/cake/tests/cases/libs/view/helpers/paginator.test.php @@ -1179,7 +1179,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->numbers(true); $expected = array( - array('span' => array()), array('a' => array('href' => '/index/page:1')), 'first', '/a', '/span', + array('span' => array()), array('a' => array('href' => '/index/page:1', 'rel' => 'first')), 'first', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:4')), '4', '/a', '/span', ' | ', @@ -1199,7 +1199,7 @@ class PaginatorHelperTest extends CakeTestCase { ' | ', array('span' => array()), array('a' => array('href' => '/index/page:12')), '12', '/a', '/span', ' | ', - array('span' => array()), array('a' => array('href' => '/index/page:15')), 'last', '/a', '/span', + array('span' => array()), array('a' => array('href' => '/index/page:15', 'rel' => 'last')), 'last', '/a', '/span', ); $this->assertTags($result, $expected); @@ -1779,7 +1779,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->first('<<', array('tag' => 'li')); $expected = array( ' array('href' => '/index/page:1'), + 'a' => array('href' => '/index/page:1', 'rel' => 'first'), '<<', '/a', '/li' @@ -1841,7 +1841,9 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->first(); $expected = array( ' array('href' => FULL_BASE_URL . '/index/page:1/sort:Article.title/direction:DESC')), + array('a' => array( + 'href' => FULL_BASE_URL . '/index/page:1/sort:Article.title/direction:DESC', 'rel' => 'first' + )), '<< first', '/a', '/span', @@ -1858,7 +1860,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->first(); $expected = array( ' array('href' => '/index/page:1'), + 'a' => array('href' => '/index/page:1', 'rel' => 'first'), '<< first', '/a', '/span' @@ -1888,7 +1890,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->last(); $expected = array( ' array('href' => '/index/page:7'), + 'a' => array('href' => '/index/page:7', 'rel' => 'last'), 'last >>', '/a', '/span' @@ -1947,7 +1949,11 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->last(); $expected = array( ' array('href' => '/index/page:15/sort:Client.name/direction:DESC')), 'last >>', '/a', + array('a' => array( + 'href' => '/index/page:15/sort:Client.name/direction:DESC', + 'rel' => 'last' + )), + 'last >>', '/a', '/span', ); $this->assertTags($result, $expected); From 85baa180d9d458cf1f2ccce7a008249bfd824ca9 Mon Sep 17 00:00:00 2001 From: mark_story Date: Mon, 27 Dec 2010 23:30:10 -0500 Subject: [PATCH 56/59] Adding usage and expanding doc blocks for PaginatorHelper. --- cake/libs/view/helpers/paginator.php | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/cake/libs/view/helpers/paginator.php b/cake/libs/view/helpers/paginator.php index 5966d11c0..b83561d24 100644 --- a/cake/libs/view/helpers/paginator.php +++ b/cake/libs/view/helpers/paginator.php @@ -725,7 +725,16 @@ class PaginatorHelper extends AppHelper { } /** - * Returns a first or set of numbers for the first pages + * Returns a first or set of numbers for the first pages. + * + * `echo $this->Paginator->first('< first');` + * + * Creates a single link for the first page. Will output nothing if you are on the first page. + * + * `echo $this->Paginator->first(3);` + * + * Will create links for the first 3 pages, once you get to the third or greater page. Prior to that + * nothing will be output. * * ### Options: * @@ -735,8 +744,9 @@ class PaginatorHelper extends AppHelper { * - `separator` Content between the generated links, defaults to ' | ' * - `ellipsis` Content for ellipsis, defaults to '...' * - * @param mixed $first if string use as label for the link, if numeric print page numbers - * @param mixed $options + * @param mixed $first if string use as label for the link. If numeric, the number of page links + * you want at the beginning of the range. + * @param mixed $options An array of options. * @return string numbers string. */ public function first($first = '<< first', $options = array()) { @@ -781,7 +791,15 @@ class PaginatorHelper extends AppHelper { } /** - * Returns a last or set of numbers for the last pages + * Returns a last or set of numbers for the last pages. + * + * `echo $this->Paginator->last('last >');` + * + * Creates a single link for the last page. Will output nothing if you are on the last page. + * + * `echo $this->Paginator->last(3);` + * + * Will create links for the last 3 pages. Once you enter the page range, no output will be created. * * ### Options: * From eb38b8b60c34d9db2585052a40cf34c89eca89a3 Mon Sep 17 00:00:00 2001 From: mark_story Date: Mon, 27 Dec 2010 23:40:10 -0500 Subject: [PATCH 57/59] Fixing more boundary issues with first() and last(). When you entered a first/last range a wonky page link would be generated. Tests added. --- cake/libs/view/helpers/paginator.php | 4 ++-- cake/tests/cases/libs/view/helpers/paginator.test.php | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/cake/libs/view/helpers/paginator.php b/cake/libs/view/helpers/paginator.php index b83561d24..a28767364 100644 --- a/cake/libs/view/helpers/paginator.php +++ b/cake/libs/view/helpers/paginator.php @@ -782,7 +782,7 @@ class PaginatorHelper extends AppHelper { } } $out .= $after; - } elseif ($params['page'] > 1) { + } elseif ($params['page'] > 1 && is_string($first)) { $options += array('rel' => 'first'); $out = $this->Html->tag($tag, $this->link($first, array('page' => 1), $options)) . $after; @@ -848,7 +848,7 @@ class PaginatorHelper extends AppHelper { } } $out = $before . $out; - } elseif ($params['page'] < $params['pageCount']) { + } elseif ($params['page'] < $params['pageCount'] && is_string($last)) { $options += array('rel' => 'last'); $out = $before . $this->Html->tag( $tag, $this->link($last, array('page' => $params['pageCount']), $options diff --git a/cake/tests/cases/libs/view/helpers/paginator.test.php b/cake/tests/cases/libs/view/helpers/paginator.test.php index 7b600819f..ce4497536 100644 --- a/cake/tests/cases/libs/view/helpers/paginator.test.php +++ b/cake/tests/cases/libs/view/helpers/paginator.test.php @@ -1878,6 +1878,10 @@ class PaginatorHelperTest extends CakeTestCase { '/span' ); $this->assertTags($result, $expected); + + $this->Paginator->request->params['paging']['Article']['page'] = 2; + $result = $this->Paginator->first(3); + $this->assertEquals('', $result, 'When inside the first links range, no links should be made'); } /** @@ -1922,6 +1926,9 @@ class PaginatorHelperTest extends CakeTestCase { '/span', ); $this->assertTags($result, $expected); + + $result = $this->Paginator->last(3); + $this->assertEquals('', $result, 'When inside the last links range, no links should be made'); } /** From ab552c22a1a26eeaceb7fe33bc1835885accf4af Mon Sep 17 00:00:00 2001 From: mark_story Date: Tue, 28 Dec 2010 00:09:47 -0500 Subject: [PATCH 58/59] Adding a usage sample to Paginator->numbers() --- cake/libs/view/helpers/paginator.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/cake/libs/view/helpers/paginator.php b/cake/libs/view/helpers/paginator.php index a28767364..aaba58297 100644 --- a/cake/libs/view/helpers/paginator.php +++ b/cake/libs/view/helpers/paginator.php @@ -602,7 +602,12 @@ class PaginatorHelper extends AppHelper { /** * Returns a set of numbers for the paged result set - * uses a modulus to decide how many numbers to show on each side of the current page (default: 8) + * uses a modulus to decide how many numbers to show on each side of the current page (default: 8). + * + * `$this->Paginator->numbers(array('first' => 2, 'last' => 2));` + * + * Using the first and last options you can create links to the beginning and end of the page set. + * * * ### Options * @@ -613,9 +618,9 @@ class PaginatorHelper extends AppHelper { * - `separator` Separator content defaults to ' | ' * - `tag` The tag to wrap links in, defaults to 'span' * - `first` Whether you want first links generated, set to an integer to define the number of 'first' - * links to generate + * links to generate. * - `last` Whether you want last links generated, set to an integer to define the number of 'last' - * links to generate + * links to generate. * - `ellipsis` Ellipsis content, defaults to '...' * * @param mixed $options Options for the numbers, (before, after, model, modulus, separator) From 7dd1eea28562344a7504fe4b4572ee5e11a3693f Mon Sep 17 00:00:00 2001 From: mark_story Date: Tue, 28 Dec 2010 22:18:31 -0500 Subject: [PATCH 59/59] Updating documentation for paginator helper, and component. --- cake/libs/controller/components/paginator.php | 9 ++++--- cake/libs/view/helpers/paginator.php | 25 ++++++++++--------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/cake/libs/controller/components/paginator.php b/cake/libs/controller/components/paginator.php index c857b573e..5c0c4b940 100644 --- a/cake/libs/controller/components/paginator.php +++ b/cake/libs/controller/components/paginator.php @@ -267,9 +267,9 @@ class PaginatorComponent extends Component { * - General pagination settings * - Model specific settings. * - Request parameters - * - $options argument. * - * The result of this method is the aggregate of all the option sets combined together. + * The result of this method is the aggregate of all the option sets combined together. You can change + * PaginatorComponent::$whitelist to modify which options/values can be set using request parameters. * * @param string $alias Model alias being paginated, if the general settings has a key with this value * that key's settings will be used for pagination instead of the general ones. @@ -294,7 +294,7 @@ class PaginatorComponent extends Component { * will be used. * * @param string $alias Model name to get default settings for. - * @return array + * @return array An array of pagination defaults for a model, or the general settings. */ public function getDefaults($alias) { if (isset($this->settings[$alias])) { @@ -313,6 +313,9 @@ class PaginatorComponent extends Component { * virtualFields can be sorted on. The direction param will also be sanitized. Lastly * sort + direction keys will be converted into the model friendly order key. * + * You can use the whitelist parameter to control which columns/fields are available for sorting. + * This helps prevent users from ordering large result sets on un-indexed values. + * * @param Model $object The model being paginated. * @param array $options The pagination options being used for this request. * @param array $whitelist The list of columns that can be used for sorting. If empty all keys are allowed. diff --git a/cake/libs/view/helpers/paginator.php b/cake/libs/view/helpers/paginator.php index aaba58297..8fe9e40ec 100644 --- a/cake/libs/view/helpers/paginator.php +++ b/cake/libs/view/helpers/paginator.php @@ -42,7 +42,8 @@ class PaginatorHelper extends AppHelper { private $__defaultModel = null; /** - * The class used for 'Ajax' pagination links. + * The class used for 'Ajax' pagination links. Defaults to JsHelper. You should make sure + * that JsHelper is defined as a helper before PaginatorHelper, if you want to customize the JsHelper. * * @var string */ @@ -53,20 +54,20 @@ class PaginatorHelper extends AppHelper { * * The values that may be specified are: * - * - `$options['format']` Format of the counter. Supported formats are 'range' and 'pages' + * - `format` Format of the counter. Supported formats are 'range' and 'pages' * and custom (default). In the default mode the supplied string is parsed and constants are replaced * by their actual values. - * Constants: %page%, %pages%, %current%, %count%, %start%, %end% . - * - `$options['separator']` The separator of the actual page and number of pages (default: ' of '). - * - `$options['url']` Url of the action. See Router::url() - * - `$options['url']['sort']` the key that the recordset is sorted. - * - `$options['url']['direction']` Direction of the sorting (default: 'asc'). - * - `$options['url']['page']` Page # to display. - * - `$options['model']` The name of the model. - * - `$options['escape']` Defines if the title field for the link should be escaped (default: true). - * - `$options['update']` DOM id of the element updated with the results of the AJAX call. + * placeholders: %page%, %pages%, %current%, %count%, %start%, %end% . + * - `separator` The separator of the actual page and number of pages (default: ' of '). + * - `url` Url of the action. See Router::url() + * - `url['sort']` the key that the recordset is sorted. + * - `url['direction']` Direction of the sorting (default: 'asc'). + * - `url['page']` Page number to use in links. + * - `model` The name of the model. + * - `escape` Defines if the title field for the link should be escaped (default: true). + * - `update` DOM id of the element updated with the results of the AJAX call. * If this key isn't specified Paginator will use plain HTML links. - * - `$options['paging']['paramType']` The type of parameters to use when creating links. Valid options are + * - `paging['paramType']` The type of parameters to use when creating links. Valid options are * 'querystring', 'named', and 'route'. See PaginatorComponent::$settings for more information. * - `convertKeys` - A list of keys in url arrays that should be converted to querysting params * if paramType == 'querystring'.