1: <?php
2: /**
3: * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
4: * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
5: *
6: * Licensed under The MIT License
7: * For full copyright and license information, please see the LICENSE.txt
8: * Redistributions of files must retain the above copyright notice.
9: *
10: * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
11: * @link https://cakephp.org CakePHP(tm) Project
12: * @since 2.0.0
13: * @license https://opensource.org/licenses/mit-license.php MIT License
14: */
15: namespace Cake\Controller\Component;
16:
17: use Cake\Controller\Component;
18: use Cake\Controller\ComponentRegistry;
19: use Cake\Datasource\Exception\PageOutOfBoundsException;
20: use Cake\Datasource\Paginator;
21: use Cake\Http\Exception\NotFoundException;
22: use InvalidArgumentException;
23:
24: /**
25: * This component is used to handle automatic model data pagination. The primary way to use this
26: * component is to call the paginate() method. There is a convenience wrapper on Controller as well.
27: *
28: * ### Configuring pagination
29: *
30: * You configure pagination when calling paginate(). See that method for more details.
31: *
32: * @link https://book.cakephp.org/3.0/en/controllers/components/pagination.html
33: * @mixin \Cake\Datasource\Paginator
34: */
35: class PaginatorComponent extends Component
36: {
37: /**
38: * Default pagination settings.
39: *
40: * When calling paginate() these settings will be merged with the configuration
41: * you provide.
42: *
43: * - `maxLimit` - The maximum limit users can choose to view. Defaults to 100
44: * - `limit` - The initial number of items per page. Defaults to 20.
45: * - `page` - The starting page, defaults to 1.
46: * - `whitelist` - A list of parameters users are allowed to set using request
47: * parameters. Modifying this list will allow users to have more influence
48: * over pagination, be careful with what you permit.
49: *
50: * @var array
51: */
52: protected $_defaultConfig = [
53: 'page' => 1,
54: 'limit' => 20,
55: 'maxLimit' => 100,
56: 'whitelist' => ['limit', 'sort', 'page', 'direction']
57: ];
58:
59: /**
60: * Datasource paginator instance.
61: *
62: * @var \Cake\Datasource\Paginator
63: */
64: protected $_paginator;
65:
66: /**
67: * {@inheritDoc}
68: */
69: public function __construct(ComponentRegistry $registry, array $config = [])
70: {
71: if (isset($config['paginator'])) {
72: if (!$config['paginator'] instanceof Paginator) {
73: throw new InvalidArgumentException('Paginator must be an instance of ' . Paginator::class);
74: }
75: $this->_paginator = $config['paginator'];
76: unset($config['paginator']);
77: } else {
78: $this->_paginator = new Paginator();
79: }
80:
81: parent::__construct($registry, $config);
82: }
83:
84: /**
85: * Events supported by this component.
86: *
87: * @return array
88: */
89: public function implementedEvents()
90: {
91: return [];
92: }
93:
94: /**
95: * Handles automatic pagination of model records.
96: *
97: * ### Configuring pagination
98: *
99: * When calling `paginate()` you can use the $settings parameter to pass in pagination settings.
100: * These settings are used to build the queries made and control other pagination settings.
101: *
102: * If your settings contain a key with the current table's alias. The data inside that key will be used.
103: * Otherwise the top level configuration will be used.
104: *
105: * ```
106: * $settings = [
107: * 'limit' => 20,
108: * 'maxLimit' => 100
109: * ];
110: * $results = $paginator->paginate($table, $settings);
111: * ```
112: *
113: * The above settings will be used to paginate any Table. You can configure Table specific settings by
114: * keying the settings with the Table alias.
115: *
116: * ```
117: * $settings = [
118: * 'Articles' => [
119: * 'limit' => 20,
120: * 'maxLimit' => 100
121: * ],
122: * 'Comments' => [ ... ]
123: * ];
124: * $results = $paginator->paginate($table, $settings);
125: * ```
126: *
127: * This would allow you to have different pagination settings for `Articles` and `Comments` tables.
128: *
129: * ### Controlling sort fields
130: *
131: * By default CakePHP will automatically allow sorting on any column on the table object being
132: * paginated. Often times you will want to allow sorting on either associated columns or calculated
133: * fields. In these cases you will need to define a whitelist of all the columns you wish to allow
134: * sorting on. You can define the whitelist in the `$settings` parameter:
135: *
136: * ```
137: * $settings = [
138: * 'Articles' => [
139: * 'finder' => 'custom',
140: * 'sortWhitelist' => ['title', 'author_id', 'comment_count'],
141: * ]
142: * ];
143: * ```
144: *
145: * Passing an empty array as whitelist disallows sorting altogether.
146: *
147: * ### Paginating with custom finders
148: *
149: * You can paginate with any find type defined on your table using the `finder` option.
150: *
151: * ```
152: * $settings = [
153: * 'Articles' => [
154: * 'finder' => 'popular'
155: * ]
156: * ];
157: * $results = $paginator->paginate($table, $settings);
158: * ```
159: *
160: * Would paginate using the `find('popular')` method.
161: *
162: * You can also pass an already created instance of a query to this method:
163: *
164: * ```
165: * $query = $this->Articles->find('popular')->matching('Tags', function ($q) {
166: * return $q->where(['name' => 'CakePHP'])
167: * });
168: * $results = $paginator->paginate($query);
169: * ```
170: *
171: * ### Scoping Request parameters
172: *
173: * By using request parameter scopes you can paginate multiple queries in the same controller action:
174: *
175: * ```
176: * $articles = $paginator->paginate($articlesQuery, ['scope' => 'articles']);
177: * $tags = $paginator->paginate($tagsQuery, ['scope' => 'tags']);
178: * ```
179: *
180: * Each of the above queries will use different query string parameter sets
181: * for pagination data. An example URL paginating both results would be:
182: *
183: * ```
184: * /dashboard?articles[page]=1&tags[page]=2
185: * ```
186: *
187: * @param \Cake\Datasource\RepositoryInterface|\Cake\Datasource\QueryInterface $object The table or query to paginate.
188: * @param array $settings The settings/configuration used for pagination.
189: * @return \Cake\Datasource\ResultSetInterface Query results
190: * @throws \Cake\Http\Exception\NotFoundException
191: */
192: public function paginate($object, array $settings = [])
193: {
194: $request = $this->_registry->getController()->getRequest();
195:
196: try {
197: $results = $this->_paginator->paginate(
198: $object,
199: $request->getQueryParams(),
200: $settings
201: );
202:
203: $this->_setPagingParams();
204: } catch (PageOutOfBoundsException $e) {
205: $this->_setPagingParams();
206:
207: throw new NotFoundException(null, null, $e);
208: }
209:
210: return $results;
211: }
212:
213: /**
214: * Merges the various options that Pagination uses.
215: * Pulls settings together from the following places:
216: *
217: * - General pagination settings
218: * - Model specific settings.
219: * - Request parameters
220: *
221: * The result of this method is the aggregate of all the option sets combined together. You can change
222: * config value `whitelist` to modify which options/values can be set using request parameters.
223: *
224: * @param string $alias Model alias being paginated, if the general settings has a key with this value
225: * that key's settings will be used for pagination instead of the general ones.
226: * @param array $settings The settings to merge with the request data.
227: * @return array Array of merged options.
228: */
229: public function mergeOptions($alias, $settings)
230: {
231: $request = $this->_registry->getController()->getRequest();
232:
233: return $this->_paginator->mergeOptions(
234: $request->getQueryParams(),
235: $this->_paginator->getDefaults($alias, $settings)
236: );
237: }
238:
239: /**
240: * Set paginator instance.
241: *
242: * @param \Cake\Datasource\Paginator $paginator Paginator instance.
243: * @return self
244: */
245: public function setPaginator(Paginator $paginator)
246: {
247: $this->_paginator = $paginator;
248:
249: return $this;
250: }
251:
252: /**
253: * Get paginator instance.
254: *
255: * @return \Cake\Datasource\Paginator
256: */
257: public function getPaginator()
258: {
259: return $this->_paginator;
260: }
261:
262: /**
263: * Set paging params to request instance.
264: *
265: * @return void
266: */
267: protected function _setPagingParams()
268: {
269: $controller = $this->getController();
270: $request = $controller->getRequest();
271: $paging = $this->_paginator->getPagingParams() + (array)$request->getParam('paging', []);
272:
273: $controller->setRequest($request->withParam('paging', $paging));
274: }
275:
276: /**
277: * Proxy getting/setting config options to Paginator.
278: *
279: * @deprecated 3.5.0 use setConfig()/getConfig() instead.
280: * @param string|array|null $key The key to get/set, or a complete array of configs.
281: * @param mixed|null $value The value to set.
282: * @param bool $merge Whether to recursively merge or overwrite existing config, defaults to true.
283: * @return mixed Config value being read, or the object itself on write operations.
284: */
285: public function config($key = null, $value = null, $merge = true)
286: {
287: deprecationWarning('PaginatorComponent::config() is deprecated. Use getConfig()/setConfig() instead.');
288: $return = $this->_paginator->config($key, $value, $merge);
289: if ($return instanceof Paginator) {
290: $return = $this;
291: }
292:
293: return $return;
294: }
295:
296: /**
297: * Proxy setting config options to Paginator.
298: *
299: * @param string|array $key The key to set, or a complete array of configs.
300: * @param mixed|null $value The value to set.
301: * @param bool $merge Whether to recursively merge or overwrite existing config, defaults to true.
302: * @return $this
303: */
304: public function setConfig($key, $value = null, $merge = true)
305: {
306: $this->_paginator->setConfig($key, $value, $merge);
307:
308: return $this;
309: }
310:
311: /**
312: * Proxy getting config options to Paginator.
313: *
314: * @param string|null $key The key to get or null for the whole config.
315: * @param mixed $default The return value when the key does not exist.
316: * @return mixed Config value being read.
317: */
318: public function getConfig($key = null, $default = null)
319: {
320: return $this->_paginator->getConfig($key, $default);
321: }
322:
323: /**
324: * Proxy setting config options to Paginator.
325: *
326: * @param string|array $key The key to set, or a complete array of configs.
327: * @param mixed|null $value The value to set.
328: * @return $this
329: */
330: public function configShallow($key, $value = null)
331: {
332: $this->_paginator->configShallow($key, null);
333:
334: return $this;
335: }
336:
337: /**
338: * Proxy method calls to Paginator.
339: *
340: * @param string $method Method name.
341: * @param array $args Method arguments.
342: * @return mixed
343: */
344: public function __call($method, $args)
345: {
346: return call_user_func_array([$this->_paginator, $method], $args);
347: }
348: }
349: