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