From 20da4484deb8e6bc228428d78e44b2f89ea72c75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20P=C3=A9rez?= Date: Thu, 3 Nov 2016 11:14:51 +0000 Subject: [PATCH 1/2] refs #backport-paginate-multiple-queries Backporting cakephp 3.3 feature to paginate multiple queries --- .../Component/PaginatorComponent.php | 27 ++++- .../Component/PaginatorComponentTest.php | 114 ++++++++++++++++-- .../Test/Case/Controller/ControllerTest.php | 16 ++- .../Case/View/Helper/PaginatorHelperTest.php | 113 +++++++++++++++++ lib/Cake/View/Helper/PaginatorHelper.php | 28 ++++- 5 files changed, 277 insertions(+), 21 deletions(-) diff --git a/lib/Cake/Controller/Component/PaginatorComponent.php b/lib/Cake/Controller/Component/PaginatorComponent.php index 6e34fa6d7..f422623c0 100644 --- a/lib/Cake/Controller/Component/PaginatorComponent.php +++ b/lib/Cake/Controller/Component/PaginatorComponent.php @@ -80,6 +80,21 @@ class PaginatorComponent extends Component { * - `paramType` What type of parameters you want pagination to use? * - `named` Use named parameters / routed parameters. * - `querystring` Use query string parameters. + * - `queryScope` By using request parameter scopes you can paginate multiple queries in the same controller action. + * + * ``` + * $paginator->paginate = array( + * 'Article' => array('queryScope' => 'articles'), + * 'Tag' => array('queryScope' => 'tags'), + * ); + * ``` + * + * Each of the above queries will use different query string parameter sets + * for pagination data. An example URL paginating both results would be: + * + * ``` + * /dashboard/articles[page]:1/tags[page]:2 + * ``` * * @var array */ @@ -87,7 +102,8 @@ class PaginatorComponent extends Component { 'page' => 1, 'limit' => 20, 'maxLimit' => 100, - 'paramType' => 'named' + 'paramType' => 'named', + 'queryScope' => null ); /** @@ -225,7 +241,8 @@ class PaginatorComponent extends Component { 'order' => $order, 'limit' => $limit, 'options' => Hash::diff($options, $defaults), - 'paramType' => $options['paramType'] + 'paramType' => $options['paramType'], + 'queryScope' => $options['queryScope'], ); if (!isset($this->Controller->request['paging'])) { @@ -317,6 +334,9 @@ class PaginatorComponent extends Component { $request = $this->Controller->request->query; break; } + if ($defaults['queryScope']) { + $request = Hash::get($request, $defaults['queryScope'], array()); + } $request = array_intersect_key($request, array_flip($this->whitelist)); return array_merge($defaults, $request); } @@ -337,7 +357,8 @@ class PaginatorComponent extends Component { 'page' => 1, 'limit' => 20, 'maxLimit' => 100, - 'paramType' => 'named' + 'paramType' => 'named', + 'queryScope' => null ); return $defaults; } diff --git a/lib/Cake/Test/Case/Controller/Component/PaginatorComponentTest.php b/lib/Cake/Test/Case/Controller/Component/PaginatorComponentTest.php index 087b92029..b5775c4c6 100644 --- a/lib/Cake/Test/Case/Controller/Component/PaginatorComponentTest.php +++ b/lib/Cake/Test/Case/Controller/Component/PaginatorComponentTest.php @@ -509,7 +509,8 @@ class PaginatorComponentTest extends CakeTestCase { 'contain' => array('ControllerPaginateModel'), 'group' => 'Comment.author_id', 'maxLimit' => 10, - 'paramType' => 'named' + 'paramType' => 'named', + 'queryScope' => null ); $this->assertEquals($expected, $Controller->ControllerPaginateModel->extra); $this->assertEquals($expected, $Controller->ControllerPaginateModel->extraCount); @@ -519,7 +520,8 @@ class PaginatorComponentTest extends CakeTestCase { 'foo', 'contain' => array('ControllerPaginateModel'), 'group' => 'Comment.author_id', 'maxLimit' => 10, - 'paramType' => 'named' + 'paramType' => 'named', + 'queryScope' => null ) ); $Controller->Paginator->paginate('ControllerPaginateModel'); @@ -528,7 +530,8 @@ class PaginatorComponentTest extends CakeTestCase { 'group' => 'Comment.author_id', 'type' => 'foo', 'maxLimit' => 10, - 'paramType' => 'named' + 'paramType' => 'named', + 'queryScope' => null ); $this->assertEquals($expected, $Controller->ControllerPaginateModel->extra); $this->assertEquals($expected, $Controller->ControllerPaginateModel->extraCount); @@ -706,6 +709,7 @@ class PaginatorComponentTest extends CakeTestCase { 'limit' => 20, 'maxLimit' => 100, 'paramType' => 'named', + 'queryScope' => null, 'Post' => array( 'page' => 1, 'limit' => 10, @@ -717,7 +721,7 @@ class PaginatorComponentTest extends CakeTestCase { $this->assertEquals($this->Paginator->settings, $result); $result = $this->Paginator->mergeOptions('Post'); - $expected = array('page' => 1, 'limit' => 10, 'paramType' => 'named', 'maxLimit' => 50); + $expected = array('page' => 1, 'limit' => 10, 'paramType' => 'named', 'maxLimit' => 50, 'queryScope' => null); $this->assertEquals($expected, $result); } @@ -738,7 +742,75 @@ class PaginatorComponentTest extends CakeTestCase { 'paramType' => 'named', ); $result = $this->Paginator->mergeOptions('Post'); - $expected = array('page' => 10, 'limit' => 10, 'maxLimit' => 100, 'paramType' => 'named'); + $expected = array('page' => 10, 'limit' => 10, 'maxLimit' => 100, 'paramType' => 'named', 'queryScope' => null); + $this->assertEquals($expected, $result); + } + +/** + * test mergeOptions with custom scope + * + * @return void + */ + public function testMergeOptionsCustomScope() { + $this->request->params['named'] = array( + 'page' => 10, + 'limit' => 10, + 'scope' => array( + 'page' => 2, + 'limit' => 5, + ) + ); + $this->Paginator->settings = array( + 'page' => 1, + 'limit' => 20, + 'maxLimit' => 100, + 'findType' => 'myCustomFind', + ); + $result = $this->Paginator->mergeOptions('Post'); + $expected = array( + 'page' => 10, + 'limit' => 10, + 'maxLimit' => 100, + 'findType' => 'myCustomFind', + 'paramType' => 'named', + 'queryScope' => null + ); + $this->assertEquals($expected, $result); + + $this->Paginator->settings = array( + 'page' => 1, + 'limit' => 20, + 'maxLimit' => 100, + 'findType' => 'myCustomFind', + 'queryScope' => 'non-existent', + ); + $result = $this->Paginator->mergeOptions('Post'); + $expected = array( + 'page' => 1, + 'limit' => 20, + 'maxLimit' => 100, + 'findType' => 'myCustomFind', + 'paramType' => 'named', + 'queryScope' => 'non-existent', + ); + $this->assertEquals($expected, $result); + + $this->Paginator->settings = array( + 'page' => 1, + 'limit' => 20, + 'maxLimit' => 100, + 'findType' => 'myCustomFind', + 'queryScope' => 'scope', + ); + $result = $this->Paginator->mergeOptions('Post'); + $expected = array( + 'page' => 2, + 'limit' => 5, + 'maxLimit' => 100, + 'findType' => 'myCustomFind', + 'paramType' => 'named', + 'queryScope' => 'scope', + ); $this->assertEquals($expected, $result); } @@ -760,7 +832,14 @@ class PaginatorComponentTest extends CakeTestCase { 'findType' => 'myCustomFind' ); $result = $this->Paginator->mergeOptions('Post'); - $expected = array('page' => 10, 'limit' => 10, 'maxLimit' => 100, 'paramType' => 'named', 'findType' => 'myCustomFind'); + $expected = array( + 'page' => 10, + 'limit' => 10, + 'maxLimit' => 100, + 'paramType' => 'named', + 'findType' => 'myCustomFind', + 'queryScope' => null + ); $this->assertEquals($expected, $result); } @@ -785,7 +864,13 @@ class PaginatorComponentTest extends CakeTestCase { 'paramType' => 'querystring', ); $result = $this->Paginator->mergeOptions('Post'); - $expected = array('page' => 99, 'limit' => 75, 'maxLimit' => 100, 'paramType' => 'querystring'); + $expected = array( + 'page' => 99, + 'limit' => 75, + 'maxLimit' => 100, + 'paramType' => 'querystring', + 'queryScope' => null + ); $this->assertEquals($expected, $result); } @@ -810,7 +895,7 @@ class PaginatorComponentTest extends CakeTestCase { 'paramType' => 'named', ); $result = $this->Paginator->mergeOptions('Post'); - $expected = array('page' => 10, 'limit' => 10, 'maxLimit' => 100, 'paramType' => 'named'); + $expected = array('page' => 10, 'limit' => 10, 'maxLimit' => 100, 'paramType' => 'named', 'queryScope' => null); $this->assertEquals($expected, $result); } @@ -837,7 +922,12 @@ class PaginatorComponentTest extends CakeTestCase { $this->Paginator->whitelist[] = 'fields'; $result = $this->Paginator->mergeOptions('Post'); $expected = array( - 'page' => 10, 'limit' => 10, 'maxLimit' => 100, 'paramType' => 'named', 'fields' => array('bad.stuff') + 'page' => 10, + 'limit' => 10, + 'maxLimit' => 100, + 'paramType' => 'named', + 'queryScope' => null, + 'fields' => array('bad.stuff') ); $this->assertEquals($expected, $result); } @@ -853,7 +943,7 @@ class PaginatorComponentTest extends CakeTestCase { 'paramType' => 'named', ); $result = $this->Paginator->mergeOptions('Post'); - $expected = array('page' => 1, 'limit' => 200, 'maxLimit' => 100, 'paramType' => 'named'); + $expected = array('page' => 1, 'limit' => 200, 'maxLimit' => 100, 'paramType' => 'named', 'queryScope' => null); $this->assertEquals($expected, $result); $this->Paginator->settings = array( @@ -861,7 +951,7 @@ class PaginatorComponentTest extends CakeTestCase { 'paramType' => 'named', ); $result = $this->Paginator->mergeOptions('Post'); - $expected = array('page' => 1, 'limit' => 20, 'maxLimit' => 10, 'paramType' => 'named'); + $expected = array('page' => 1, 'limit' => 20, 'maxLimit' => 10, 'paramType' => 'named', 'queryScope' => null); $this->assertEquals($expected, $result); $this->request->params['named'] = array( @@ -872,7 +962,7 @@ class PaginatorComponentTest extends CakeTestCase { 'paramType' => 'named', ); $result = $this->Paginator->mergeOptions('Post'); - $expected = array('page' => 1, 'limit' => 500, 'maxLimit' => 100, 'paramType' => 'named'); + $expected = array('page' => 1, 'limit' => 500, 'maxLimit' => 100, 'paramType' => 'named', 'queryScope' => null); $this->assertEquals($expected, $result); } diff --git a/lib/Cake/Test/Case/Controller/ControllerTest.php b/lib/Cake/Test/Case/Controller/ControllerTest.php index 6290bd7c9..bde42c134 100644 --- a/lib/Cake/Test/Case/Controller/ControllerTest.php +++ b/lib/Cake/Test/Case/Controller/ControllerTest.php @@ -1332,10 +1332,15 @@ class ControllerTest extends CakeTestCase { $Controller->uses = array('ControllerPost', 'ControllerComment'); $Controller->passedArgs[] = '1'; $Controller->params['url'] = array(); + $Controller->params['named'] = array( + 'posts' => array( + 'page' => 2, + 'limit' => 2, + ), + ); $Controller->constructClasses(); - $expected = array('page' => 1, 'limit' => 20, 'maxLimit' => 100, 'paramType' => 'named'); + $expected = array('page' => 1, 'limit' => 20, 'maxLimit' => 100, 'paramType' => 'named', 'queryScope' => null); $this->assertEquals($expected, $Controller->paginate); - $results = Hash::extract($Controller->paginate('ControllerPost'), '{n}.ControllerPost.id'); $this->assertEquals(array(1, 2, 3), $results); @@ -1347,6 +1352,13 @@ class ControllerTest extends CakeTestCase { $this->assertSame($Controller->params['paging']['ControllerPost']['pageCount'], 3); $this->assertFalse($Controller->params['paging']['ControllerPost']['prevPage']); $this->assertTrue($Controller->params['paging']['ControllerPost']['nextPage']); + $this->assertNull($Controller->params['paging']['ControllerPost']['queryScope']); + + $Controller->paginate = array('queryScope' => 'posts'); + $Controller->paginate('ControllerPost'); + $this->assertSame($Controller->params['paging']['ControllerPost']['page'], 2); + $this->assertSame($Controller->params['paging']['ControllerPost']['pageCount'], 2); + $this->assertSame($Controller->params['paging']['ControllerPost']['queryScope'], 'posts'); } /** diff --git a/lib/Cake/Test/Case/View/Helper/PaginatorHelperTest.php b/lib/Cake/Test/Case/View/Helper/PaginatorHelperTest.php index 246f368be..f29e2d21e 100644 --- a/lib/Cake/Test/Case/View/Helper/PaginatorHelperTest.php +++ b/lib/Cake/Test/Case/View/Helper/PaginatorHelperTest.php @@ -406,6 +406,52 @@ class PaginatorHelperTest extends CakeTestCase { $this->assertTags($result, $expected); } +/** + * test multiple pagination sort links + * + * @return void + */ + public function testSortLinksMultiplePagination() { + 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('base' => '', 'here' => '/accounts/', 'webroot' => '/') + )); + + $this->Paginator->options(array('model' => 'Articles')); + $this->Paginator->request['paging'] = array( + 'Articles' => array( + 'current' => 9, + 'count' => 62, + 'prevPage' => false, + 'nextPage' => true, + 'pageCount' => 7, + 'order' => null, + 'options' => array( + 'page' => 1, + ), + 'paramType' => 'named', + 'queryScope' => 'article' + ) + ); + $result = $this->Paginator->sort('title'); + $expected = array( + 'a' => array('href' => '/accounts/index/article%5Bsort%5D:title/article%5Bdirection%5D:asc/article%5Border%5D:', 'model' => 'Articles'), + 'Title', + '/a' + ); + $this->assertTags($result, $expected); + } + /** * testSortKey method * @@ -691,6 +737,59 @@ class PaginatorHelperTest extends CakeTestCase { $this->assertEquals($expected, $result); } +/** + * test url with multiple pagination + * + * @return void + */ + public function testUrlMultiplePagination() { + Router::reload(); + Router::parse('/'); + Router::setRequestInfo(array( + array('controller' => 'posts', 'action' => 'index', 'form' => array(), 'url' => array(), 'plugin' => null), + array('base' => '', 'here' => 'posts/index', 'webroot' => '/') + )); + $this->Paginator->request->params['paging']['Article']['queryScope'] = 'article'; + $this->Paginator->request->params['paging']['Article']['page'] = 3; + $this->Paginator->request->params['paging']['Article']['options']['page'] = 3; + $this->Paginator->request->params['paging']['Article']['prevPage'] = true; + $this->Paginator->options(array('model' => 'Article')); + $result = $this->Paginator->url(array()); + $expected = '/posts/index/article%5Bpage%5D:3'; + $this->assertEquals($expected, $result); + $result = $this->Paginator->sort('name'); + $expected = array( + 'a' => array( + 'href' => '/posts/index/article%5Bpage%5D:3/article%5Bsort%5D:name/article%5Bdirection%5D:asc/article%5Border%5D:', + 'model' => 'Article' + ), + 'Name', + '/a' + ); + $this->assertTags($result, $expected); + $result = $this->Paginator->next('next'); + $expected = array( + 'span' => array('class' => 'next'), + 'a' => array('href' => '/posts/index/article%5Bpage%5D:4', 'rel' => 'next', 'model' => 'Article'), + 'next', + '/a', + '/span' + ); + $this->assertTags($result, $expected); + $result = $this->Paginator->prev('prev'); + $expected = array( + 'span' => array('class' => 'prev'), + 'a' => array('href' => '/posts/index/article%5Bpage%5D:2', 'rel' => 'prev', 'model' => 'Article'), + 'prev', + '/a', + '/span' + ); + $this->assertTags($result, $expected); + $result = $this->Paginator->url(array('sort' => 'name')); + $expected = '/posts/index/article%5Bpage%5D:3/article%5Bsort%5D:name'; + $this->assertEquals($expected, $result); + } + /** * testOptions method * @@ -2831,6 +2930,20 @@ class PaginatorHelperTest extends CakeTestCase { $this->assertNull($this->Paginator->defaultModel()); } +/** + * test the defaultModel() method + * + * @return void + */ + public function testDefaultModel() { + $this->Paginator->request = new CakeRequest(null, false); + $this->Paginator->defaultModel('Article'); + $this->assertEquals('Article', $this->Paginator->defaultModel()); + + $this->Paginator->options(array('model' => 'Client')); + $this->assertEquals('Client', $this->Paginator->defaultModel()); + } + /** * test the numbers() method when there is only one page * diff --git a/lib/Cake/View/Helper/PaginatorHelper.php b/lib/Cake/View/Helper/PaginatorHelper.php index 917eb7f7f..096640889 100644 --- a/lib/Cake/View/Helper/PaginatorHelper.php +++ b/lib/Cake/View/Helper/PaginatorHelper.php @@ -191,6 +191,9 @@ class PaginatorHelper extends AppHelper { $options['convertKeys'] = array_merge($this->options['convertKeys'], $options['convertKeys']); } $this->options = array_filter(array_merge($this->options, $options)); + if (!empty($this->options['model'])) { + $this->defaultModel($this->options['model']); + } } /** @@ -359,10 +362,16 @@ class PaginatorHelper extends AppHelper { $sortKey = $this->sortKey($options['model']); $defaultModel = $this->defaultModel(); + $model = $options['model'] ?: $defaultModel; + list($table, $field) = explode('.', $key . '.'); + if (!$field) { + $field = $table; + $table = $model; + } $isSorted = ( - $sortKey === $key || + $sortKey === $table . '.' . $field || $sortKey === $defaultModel . '.' . $key || - $key === $defaultModel . '.' . $sortKey + $table . '.' . $field === $defaultModel . '.' . $sortKey ); $dir = $defaultDir; @@ -451,6 +460,13 @@ class PaginatorHelper extends AppHelper { if (!empty($url['?']['page']) && $url['?']['page'] == 1) { unset($url['?']['page']); } + if (!empty($paging['queryScope'])) { + $url = [$paging['queryScope'] => $url]; + if (empty($url[$paging['queryScope']]['page'])) { + unset($url[$paging['queryScope']]['page']); + } + } + if ($asArray) { return $url; } @@ -599,12 +615,16 @@ class PaginatorHelper extends AppHelper { } /** - * Gets the default model of the paged sets + * Gets or sets the default model of the paged sets * + * @param string|null $model Model name to set * @return string|null Model name or null if the pagination isn't initialized. * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/paginator.html#PaginatorHelper::defaultModel */ - public function defaultModel() { + public function defaultModel($model = null) { + if ($model !== null) { + $this->_defaultModel = $model; + } if ($this->_defaultModel) { return $this->_defaultModel; } From c0aa418984e1e03d454e793477fb058adc951d3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20P=C3=A9rez?= Date: Fri, 4 Nov 2016 11:19:43 +0000 Subject: [PATCH 2/2] refs #backport-paginate-multiple-queries fix array notation to be compatible with php 5.3 --- lib/Cake/View/Helper/PaginatorHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Cake/View/Helper/PaginatorHelper.php b/lib/Cake/View/Helper/PaginatorHelper.php index 096640889..1ff055716 100644 --- a/lib/Cake/View/Helper/PaginatorHelper.php +++ b/lib/Cake/View/Helper/PaginatorHelper.php @@ -461,7 +461,7 @@ class PaginatorHelper extends AppHelper { unset($url['?']['page']); } if (!empty($paging['queryScope'])) { - $url = [$paging['queryScope'] => $url]; + $url = array($paging['queryScope'] => $url); if (empty($url[$paging['queryScope']]['page'])) { unset($url[$paging['queryScope']]['page']); }