refs #backport-paginate-multiple-queries Backporting cakephp 3.3 feature to paginate multiple queries

This commit is contained in:
Rodrigo Pérez 2016-11-03 11:14:51 +00:00
parent 25b62b6c5d
commit 20da4484de
5 changed files with 277 additions and 21 deletions

View file

@ -80,6 +80,21 @@ class PaginatorComponent extends Component {
* - `paramType` What type of parameters you want pagination to use? * - `paramType` What type of parameters you want pagination to use?
* - `named` Use named parameters / routed parameters. * - `named` Use named parameters / routed parameters.
* - `querystring` Use query string 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 * @var array
*/ */
@ -87,7 +102,8 @@ class PaginatorComponent extends Component {
'page' => 1, 'page' => 1,
'limit' => 20, 'limit' => 20,
'maxLimit' => 100, 'maxLimit' => 100,
'paramType' => 'named' 'paramType' => 'named',
'queryScope' => null
); );
/** /**
@ -225,7 +241,8 @@ class PaginatorComponent extends Component {
'order' => $order, 'order' => $order,
'limit' => $limit, 'limit' => $limit,
'options' => Hash::diff($options, $defaults), 'options' => Hash::diff($options, $defaults),
'paramType' => $options['paramType'] 'paramType' => $options['paramType'],
'queryScope' => $options['queryScope'],
); );
if (!isset($this->Controller->request['paging'])) { if (!isset($this->Controller->request['paging'])) {
@ -317,6 +334,9 @@ class PaginatorComponent extends Component {
$request = $this->Controller->request->query; $request = $this->Controller->request->query;
break; break;
} }
if ($defaults['queryScope']) {
$request = Hash::get($request, $defaults['queryScope'], array());
}
$request = array_intersect_key($request, array_flip($this->whitelist)); $request = array_intersect_key($request, array_flip($this->whitelist));
return array_merge($defaults, $request); return array_merge($defaults, $request);
} }
@ -337,7 +357,8 @@ class PaginatorComponent extends Component {
'page' => 1, 'page' => 1,
'limit' => 20, 'limit' => 20,
'maxLimit' => 100, 'maxLimit' => 100,
'paramType' => 'named' 'paramType' => 'named',
'queryScope' => null
); );
return $defaults; return $defaults;
} }

View file

@ -509,7 +509,8 @@ class PaginatorComponentTest extends CakeTestCase {
'contain' => array('ControllerPaginateModel'), 'contain' => array('ControllerPaginateModel'),
'group' => 'Comment.author_id', 'group' => 'Comment.author_id',
'maxLimit' => 10, 'maxLimit' => 10,
'paramType' => 'named' 'paramType' => 'named',
'queryScope' => null
); );
$this->assertEquals($expected, $Controller->ControllerPaginateModel->extra); $this->assertEquals($expected, $Controller->ControllerPaginateModel->extra);
$this->assertEquals($expected, $Controller->ControllerPaginateModel->extraCount); $this->assertEquals($expected, $Controller->ControllerPaginateModel->extraCount);
@ -519,7 +520,8 @@ class PaginatorComponentTest extends CakeTestCase {
'foo', 'contain' => array('ControllerPaginateModel'), 'foo', 'contain' => array('ControllerPaginateModel'),
'group' => 'Comment.author_id', 'group' => 'Comment.author_id',
'maxLimit' => 10, 'maxLimit' => 10,
'paramType' => 'named' 'paramType' => 'named',
'queryScope' => null
) )
); );
$Controller->Paginator->paginate('ControllerPaginateModel'); $Controller->Paginator->paginate('ControllerPaginateModel');
@ -528,7 +530,8 @@ class PaginatorComponentTest extends CakeTestCase {
'group' => 'Comment.author_id', 'group' => 'Comment.author_id',
'type' => 'foo', 'type' => 'foo',
'maxLimit' => 10, 'maxLimit' => 10,
'paramType' => 'named' 'paramType' => 'named',
'queryScope' => null
); );
$this->assertEquals($expected, $Controller->ControllerPaginateModel->extra); $this->assertEquals($expected, $Controller->ControllerPaginateModel->extra);
$this->assertEquals($expected, $Controller->ControllerPaginateModel->extraCount); $this->assertEquals($expected, $Controller->ControllerPaginateModel->extraCount);
@ -706,6 +709,7 @@ class PaginatorComponentTest extends CakeTestCase {
'limit' => 20, 'limit' => 20,
'maxLimit' => 100, 'maxLimit' => 100,
'paramType' => 'named', 'paramType' => 'named',
'queryScope' => null,
'Post' => array( 'Post' => array(
'page' => 1, 'page' => 1,
'limit' => 10, 'limit' => 10,
@ -717,7 +721,7 @@ class PaginatorComponentTest extends CakeTestCase {
$this->assertEquals($this->Paginator->settings, $result); $this->assertEquals($this->Paginator->settings, $result);
$result = $this->Paginator->mergeOptions('Post'); $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); $this->assertEquals($expected, $result);
} }
@ -738,7 +742,75 @@ class PaginatorComponentTest extends CakeTestCase {
'paramType' => 'named', 'paramType' => 'named',
); );
$result = $this->Paginator->mergeOptions('Post'); $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); $this->assertEquals($expected, $result);
} }
@ -760,7 +832,14 @@ class PaginatorComponentTest extends CakeTestCase {
'findType' => 'myCustomFind' 'findType' => 'myCustomFind'
); );
$result = $this->Paginator->mergeOptions('Post'); $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); $this->assertEquals($expected, $result);
} }
@ -785,7 +864,13 @@ class PaginatorComponentTest extends CakeTestCase {
'paramType' => 'querystring', 'paramType' => 'querystring',
); );
$result = $this->Paginator->mergeOptions('Post'); $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); $this->assertEquals($expected, $result);
} }
@ -810,7 +895,7 @@ class PaginatorComponentTest extends CakeTestCase {
'paramType' => 'named', 'paramType' => 'named',
); );
$result = $this->Paginator->mergeOptions('Post'); $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); $this->assertEquals($expected, $result);
} }
@ -837,7 +922,12 @@ class PaginatorComponentTest extends CakeTestCase {
$this->Paginator->whitelist[] = 'fields'; $this->Paginator->whitelist[] = 'fields';
$result = $this->Paginator->mergeOptions('Post'); $result = $this->Paginator->mergeOptions('Post');
$expected = array( $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); $this->assertEquals($expected, $result);
} }
@ -853,7 +943,7 @@ class PaginatorComponentTest extends CakeTestCase {
'paramType' => 'named', 'paramType' => 'named',
); );
$result = $this->Paginator->mergeOptions('Post'); $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->assertEquals($expected, $result);
$this->Paginator->settings = array( $this->Paginator->settings = array(
@ -861,7 +951,7 @@ class PaginatorComponentTest extends CakeTestCase {
'paramType' => 'named', 'paramType' => 'named',
); );
$result = $this->Paginator->mergeOptions('Post'); $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->assertEquals($expected, $result);
$this->request->params['named'] = array( $this->request->params['named'] = array(
@ -872,7 +962,7 @@ class PaginatorComponentTest extends CakeTestCase {
'paramType' => 'named', 'paramType' => 'named',
); );
$result = $this->Paginator->mergeOptions('Post'); $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); $this->assertEquals($expected, $result);
} }

View file

@ -1332,10 +1332,15 @@ class ControllerTest extends CakeTestCase {
$Controller->uses = array('ControllerPost', 'ControllerComment'); $Controller->uses = array('ControllerPost', 'ControllerComment');
$Controller->passedArgs[] = '1'; $Controller->passedArgs[] = '1';
$Controller->params['url'] = array(); $Controller->params['url'] = array();
$Controller->params['named'] = array(
'posts' => array(
'page' => 2,
'limit' => 2,
),
);
$Controller->constructClasses(); $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); $this->assertEquals($expected, $Controller->paginate);
$results = Hash::extract($Controller->paginate('ControllerPost'), '{n}.ControllerPost.id'); $results = Hash::extract($Controller->paginate('ControllerPost'), '{n}.ControllerPost.id');
$this->assertEquals(array(1, 2, 3), $results); $this->assertEquals(array(1, 2, 3), $results);
@ -1347,6 +1352,13 @@ class ControllerTest extends CakeTestCase {
$this->assertSame($Controller->params['paging']['ControllerPost']['pageCount'], 3); $this->assertSame($Controller->params['paging']['ControllerPost']['pageCount'], 3);
$this->assertFalse($Controller->params['paging']['ControllerPost']['prevPage']); $this->assertFalse($Controller->params['paging']['ControllerPost']['prevPage']);
$this->assertTrue($Controller->params['paging']['ControllerPost']['nextPage']); $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');
} }
/** /**

View file

@ -406,6 +406,52 @@ class PaginatorHelperTest extends CakeTestCase {
$this->assertTags($result, $expected); $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 * testSortKey method
* *
@ -691,6 +737,59 @@ class PaginatorHelperTest extends CakeTestCase {
$this->assertEquals($expected, $result); $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 * testOptions method
* *
@ -2831,6 +2930,20 @@ class PaginatorHelperTest extends CakeTestCase {
$this->assertNull($this->Paginator->defaultModel()); $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 * test the numbers() method when there is only one page
* *

View file

@ -191,6 +191,9 @@ class PaginatorHelper extends AppHelper {
$options['convertKeys'] = array_merge($this->options['convertKeys'], $options['convertKeys']); $options['convertKeys'] = array_merge($this->options['convertKeys'], $options['convertKeys']);
} }
$this->options = array_filter(array_merge($this->options, $options)); $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']); $sortKey = $this->sortKey($options['model']);
$defaultModel = $this->defaultModel(); $defaultModel = $this->defaultModel();
$model = $options['model'] ?: $defaultModel;
list($table, $field) = explode('.', $key . '.');
if (!$field) {
$field = $table;
$table = $model;
}
$isSorted = ( $isSorted = (
$sortKey === $key || $sortKey === $table . '.' . $field ||
$sortKey === $defaultModel . '.' . $key || $sortKey === $defaultModel . '.' . $key ||
$key === $defaultModel . '.' . $sortKey $table . '.' . $field === $defaultModel . '.' . $sortKey
); );
$dir = $defaultDir; $dir = $defaultDir;
@ -451,6 +460,13 @@ class PaginatorHelper extends AppHelper {
if (!empty($url['?']['page']) && $url['?']['page'] == 1) { if (!empty($url['?']['page']) && $url['?']['page'] == 1) {
unset($url['?']['page']); unset($url['?']['page']);
} }
if (!empty($paging['queryScope'])) {
$url = [$paging['queryScope'] => $url];
if (empty($url[$paging['queryScope']]['page'])) {
unset($url[$paging['queryScope']]['page']);
}
}
if ($asArray) { if ($asArray) {
return $url; 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. * @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 * @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) { if ($this->_defaultModel) {
return $this->_defaultModel; return $this->_defaultModel;
} }