PaginatorHelper::generateUrlParams()   F
last analyzed

Complexity

Conditions 20
Paths 288

Size

Total Lines 63

Duplication

Lines 6
Ratio 9.52 %

Importance

Changes 0
Metric Value
cc 20
nc 288
nop 2
dl 6
loc 63
rs 2.2333
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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         1.2.0
13
 * @license       https://opensource.org/licenses/mit-license.php MIT License
14
 */
15
namespace Cake\View\Helper;
16
17
use Cake\Utility\Hash;
18
use Cake\Utility\Inflector;
19
use Cake\View\Helper;
20
use Cake\View\StringTemplateTrait;
21
use Cake\View\View;
22
23
/**
24
 * Pagination Helper class for easy generation of pagination links.
25
 *
26
 * PaginationHelper encloses all methods needed when working with pagination.
27
 *
28
 * @property \Cake\View\Helper\UrlHelper $Url
29
 * @property \Cake\View\Helper\NumberHelper $Number
30
 * @property \Cake\View\Helper\HtmlHelper $Html
31
 * @property \Cake\View\Helper\FormHelper $Form
32
 * @link https://book.cakephp.org/3/en/views/helpers/paginator.html
33
 */
34
class PaginatorHelper extends Helper
35
{
36
    use StringTemplateTrait;
37
38
    /**
39
     * List of helpers used by this helper
40
     *
41
     * @var array
42
     */
43
    public $helpers = ['Url', 'Number', 'Html', 'Form'];
44
45
    /**
46
     * Default config for this class
47
     *
48
     * Options: Holds the default options for pagination links
49
     *
50
     * The values that may be specified are:
51
     *
52
     * - `url` Url of the action. See Router::url()
53
     * - `url['sort']` the key that the recordset is sorted.
54
     * - `url['direction']` Direction of the sorting (default: 'asc').
55
     * - `url['page']` Page number to use in links.
56
     * - `model` The name of the model.
57
     * - `escape` Defines if the title field for the link should be escaped (default: true).
58
     *
59
     * Templates: the templates used by this class
60
     *
61
     * @var array
62
     */
63
    protected $_defaultConfig = [
64
        'options' => [],
65
        'templates' => [
66
            'nextActive' => '<li class="next"><a rel="next" href="{{url}}">{{text}}</a></li>',
67
            'nextDisabled' => '<li class="next disabled"><a href="" onclick="return false;">{{text}}</a></li>',
68
            'prevActive' => '<li class="prev"><a rel="prev" href="{{url}}">{{text}}</a></li>',
69
            'prevDisabled' => '<li class="prev disabled"><a href="" onclick="return false;">{{text}}</a></li>',
70
            'counterRange' => '{{start}} - {{end}} of {{count}}',
71
            'counterPages' => '{{page}} of {{pages}}',
72
            'first' => '<li class="first"><a href="{{url}}">{{text}}</a></li>',
73
            'last' => '<li class="last"><a href="{{url}}">{{text}}</a></li>',
74
            'number' => '<li><a href="{{url}}">{{text}}</a></li>',
75
            'current' => '<li class="active"><a href="">{{text}}</a></li>',
76
            'ellipsis' => '<li class="ellipsis">&hellip;</li>',
77
            'sort' => '<a href="{{url}}">{{text}}</a>',
78
            'sortAsc' => '<a class="asc" href="{{url}}">{{text}}</a>',
79
            'sortDesc' => '<a class="desc" href="{{url}}">{{text}}</a>',
80
            'sortAscLocked' => '<a class="asc locked" href="{{url}}">{{text}}</a>',
81
            'sortDescLocked' => '<a class="desc locked" href="{{url}}">{{text}}</a>',
82
        ],
83
    ];
84
85
    /**
86
     * Default model of the paged sets
87
     *
88
     * @var string
89
     */
90
    protected $_defaultModel;
91
92
    /**
93
     * Constructor. Overridden to merge passed args with URL options.
94
     *
95
     * @param \Cake\View\View $View The View this helper is being attached to.
96
     * @param array $config Configuration settings for the helper.
97
     */
98
    public function __construct(View $View, array $config = [])
99
    {
100
        parent::__construct($View, $config);
101
102
        $query = $this->_View->getRequest()->getQueryParams();
103
        unset($query['page'], $query['limit'], $query['sort'], $query['direction']);
104
        $this->setConfig(
105
            'options.url',
106
            array_merge($this->_View->getRequest()->getParam('pass', []), ['?' => $query])
107
        );
108
    }
109
110
    /**
111
     * Gets the current paging parameters from the resultset for the given model
112
     *
113
     * @param string|null $model Optional model name. Uses the default if none is specified.
114
     * @return array The array of paging parameters for the paginated resultset.
115
     */
116
    public function params($model = null)
117
    {
118
        $request = $this->_View->getRequest();
119
120
        if (empty($model)) {
121
            $model = $this->defaultModel();
122
        }
123
        if (!$request->getParam('paging') || !$request->getParam('paging.' . $model)) {
124
            return [];
125
        }
126
127
        return $request->getParam('paging.' . $model);
128
    }
129
130
    /**
131
     * Convenience access to any of the paginator params.
132
     *
133
     * @param string $key Key of the paginator params array to retrieve.
134
     * @param string|null $model Optional model name. Uses the default if none is specified.
135
     * @return mixed Content of the requested param.
136
     */
137
    public function param($key, $model = null)
138
    {
139
        $params = $this->params($model);
140
        if (!isset($params[$key])) {
141
            return null;
142
        }
143
144
        return $params[$key];
145
    }
146
147
    /**
148
     * Sets default options for all pagination links
149
     *
150
     * @param array $options Default options for pagination links.
151
     *   See PaginatorHelper::$options for list of keys.
152
     * @return void
153
     */
154
    public function options(array $options = [])
155
    {
156
        $request = $this->_View->getRequest();
157
158
        if (!empty($options['paging'])) {
159
            $request = $request->withParam(
160
                'paging',
161
                $options['paging'] + $request->getParam('paging', [])
162
            );
163
            unset($options['paging']);
164
        }
165
166
        $model = $this->defaultModel();
167
        if (!empty($options[$model])) {
168
            $request = $request->withParam(
169
                'paging.' . $model,
170
                $options[$model] + (array)$request->getParam('paging.' . $model, [])
171
            );
172
            unset($options[$model]);
173
        }
174
175
        $this->_View->setRequest($request);
176
177
        $this->_config['options'] = array_filter($options + $this->_config['options']);
178
        if (empty($this->_config['options']['url'])) {
179
            $this->_config['options']['url'] = [];
180
        }
181
        if (!empty($this->_config['options']['model'])) {
182
            $this->defaultModel($this->_config['options']['model']);
183
        }
184
    }
185
186
    /**
187
     * Gets the current page of the recordset for the given model
188
     *
189
     * @param string|null $model Optional model name. Uses the default if none is specified.
190
     * @return int The current page number of the recordset.
191
     * @link https://book.cakephp.org/3/en/views/helpers/paginator.html#checking-the-pagination-state
192
     */
193 View Code Duplication
    public function current($model = null)
194
    {
195
        $params = $this->params($model);
196
197
        if (isset($params['page'])) {
198
            return $params['page'];
199
        }
200
201
        return 1;
202
    }
203
204
    /**
205
     * Gets the total number of pages in the recordset for the given model.
206
     *
207
     * @param string|null $model Optional model name. Uses the default if none is specified.
208
     * @return int The total pages for the recordset.
209
     */
210 View Code Duplication
    public function total($model = null)
211
    {
212
        $params = $this->params($model);
213
214
        if (isset($params['pageCount'])) {
215
            return $params['pageCount'];
216
        }
217
218
        return 0;
219
    }
220
221
    /**
222
     * Gets the current key by which the recordset is sorted
223
     *
224
     * @param string|null $model Optional model name. Uses the default if none is specified.
225
     * @param array $options Options for pagination links. See #options for list of keys.
226
     * @return string|null The name of the key by which the recordset is being sorted, or
227
     *  null if the results are not currently sorted.
228
     * @link https://book.cakephp.org/3/en/views/helpers/paginator.html#creating-sort-links
229
     */
230
    public function sortKey($model = null, array $options = [])
231
    {
232
        if (empty($options)) {
233
            $options = $this->params($model);
234
        }
235
        if (!empty($options['sort'])) {
236
            return $options['sort'];
237
        }
238
239
        return null;
240
    }
241
242
    /**
243
     * Gets the current direction the recordset is sorted
244
     *
245
     * @param string|null $model Optional model name. Uses the default if none is specified.
246
     * @param array $options Options for pagination links. See #options for list of keys.
247
     * @return string The direction by which the recordset is being sorted, or
248
     *  null if the results are not currently sorted.
249
     * @link https://book.cakephp.org/3/en/views/helpers/paginator.html#creating-sort-links
250
     */
251
    public function sortDir($model = null, array $options = [])
252
    {
253
        $dir = null;
254
255
        if (empty($options)) {
256
            $options = $this->params($model);
257
        }
258
259
        if (isset($options['direction'])) {
260
            $dir = strtolower($options['direction']);
261
        }
262
263
        if ($dir === 'desc') {
264
            return 'desc';
265
        }
266
267
        return 'asc';
268
    }
269
270
    /**
271
     * Generate an active/inactive link for next/prev methods.
272
     *
273
     * @param string|bool $text The enabled text for the link.
274
     * @param bool $enabled Whether or not the enabled/disabled version should be created.
275
     * @param array $options An array of options from the calling method.
276
     * @param array $templates An array of templates with the 'active' and 'disabled' keys.
277
     * @return string Generated HTML
278
     */
279
    protected function _toggledLink($text, $enabled, $options, $templates)
280
    {
281
        $template = $templates['active'];
282
        if (!$enabled) {
283
            $text = $options['disabledTitle'];
284
            $template = $templates['disabled'];
285
        }
286
287
        if (!$enabled && $text === false) {
288
            return '';
289
        }
290
        $text = $options['escape'] ? h($text) : $text;
291
292
        $templater = $this->templater();
293
        $newTemplates = !empty($options['templates']) ? $options['templates'] : false;
294 View Code Duplication
        if ($newTemplates) {
295
            $templater->push();
296
            $templateMethod = is_string($options['templates']) ? 'load' : 'add';
297
            $templater->{$templateMethod}($options['templates']);
298
        }
299
300
        if (!$enabled) {
301
            $out = $templater->format($template, [
302
                'text' => $text,
303
            ]);
304
305
            if ($newTemplates) {
306
                $templater->pop();
307
            }
308
309
            return $out;
310
        }
311
        $paging = $this->params($options['model']);
312
313
        $url = array_merge(
314
            $options['url'],
315
            ['page' => $paging['page'] + $options['step']]
316
        );
317
        $url = $this->generateUrl($url, $options['model']);
318
319
        $out = $templater->format($template, [
320
            'url' => $url,
321
            'text' => $text,
322
        ]);
323
324
        if ($newTemplates) {
325
            $templater->pop();
326
        }
327
328
        return $out;
329
    }
330
331
    /**
332
     * Generates a "previous" link for a set of paged records
333
     *
334
     * ### Options:
335
     *
336
     * - `disabledTitle` The text to used when the link is disabled. This
337
     *   defaults to the same text at the active link. Setting to false will cause
338
     *   this method to return ''.
339
     * - `escape` Whether you want the contents html entity encoded, defaults to true
340
     * - `model` The model to use, defaults to PaginatorHelper::defaultModel()
341
     * - `url` An array of additional URL options to use for link generation.
342
     * - `templates` An array of templates, or template file name containing the
343
     *   templates you'd like to use when generating the link for previous page.
344
     *   The helper's original templates will be restored once prev() is done.
345
     *
346
     * @param string $title Title for the link. Defaults to '<< Previous'.
347
     * @param array $options Options for pagination link. See above for list of keys.
348
     * @return string A "previous" link or a disabled link.
349
     * @link https://book.cakephp.org/3/en/views/helpers/paginator.html#creating-jump-links
350
     */
351 View Code Duplication
    public function prev($title = '<< Previous', array $options = [])
352
    {
353
        $defaults = [
354
            'url' => [],
355
            'model' => $this->defaultModel(),
356
            'disabledTitle' => $title,
357
            'escape' => true,
358
        ];
359
        $options += $defaults;
360
        $options['step'] = -1;
361
362
        $enabled = $this->hasPrev($options['model']);
363
        $templates = [
364
            'active' => 'prevActive',
365
            'disabled' => 'prevDisabled',
366
        ];
367
368
        return $this->_toggledLink($title, $enabled, $options, $templates);
369
    }
370
371
    /**
372
     * Generates a "next" link for a set of paged records
373
     *
374
     * ### Options:
375
     *
376
     * - `disabledTitle` The text to used when the link is disabled. This
377
     *   defaults to the same text at the active link. Setting to false will cause
378
     *   this method to return ''.
379
     * - `escape` Whether you want the contents html entity encoded, defaults to true
380
     * - `model` The model to use, defaults to PaginatorHelper::defaultModel()
381
     * - `url` An array of additional URL options to use for link generation.
382
     * - `templates` An array of templates, or template file name containing the
383
     *   templates you'd like to use when generating the link for next page.
384
     *   The helper's original templates will be restored once next() is done.
385
     *
386
     * @param string $title Title for the link. Defaults to 'Next >>'.
387
     * @param array $options Options for pagination link. See above for list of keys.
388
     * @return string A "next" link or $disabledTitle text if the link is disabled.
389
     * @link https://book.cakephp.org/3/en/views/helpers/paginator.html#creating-jump-links
390
     */
391 View Code Duplication
    public function next($title = 'Next >>', array $options = [])
392
    {
393
        $defaults = [
394
            'url' => [],
395
            'model' => $this->defaultModel(),
396
            'disabledTitle' => $title,
397
            'escape' => true,
398
        ];
399
        $options += $defaults;
400
        $options['step'] = 1;
401
402
        $enabled = $this->hasNext($options['model']);
403
        $templates = [
404
            'active' => 'nextActive',
405
            'disabled' => 'nextDisabled',
406
        ];
407
408
        return $this->_toggledLink($title, $enabled, $options, $templates);
409
    }
410
411
    /**
412
     * Generates a sorting link. Sets named parameters for the sort and direction. Handles
413
     * direction switching automatically.
414
     *
415
     * ### Options:
416
     *
417
     * - `escape` Whether you want the contents html entity encoded, defaults to true.
418
     * - `model` The model to use, defaults to PaginatorHelper::defaultModel().
419
     * - `direction` The default direction to use when this link isn't active.
420
     * - `lock` Lock direction. Will only use the default direction then, defaults to false.
421
     *
422
     * @param string $key The name of the key that the recordset should be sorted.
423
     * @param string|array|null $title Title for the link. If $title is null $key will be used
424
     *   for the title and will be generated by inflection. It can also be an array
425
     *   with keys `asc` and `desc` for specifying separate titles based on the direction.
426
     * @param array $options Options for sorting link. See above for list of keys.
427
     * @return string A link sorting default by 'asc'. If the resultset is sorted 'asc' by the specified
428
     *  key the returned link will sort by 'desc'.
429
     * @link https://book.cakephp.org/3/en/views/helpers/paginator.html#creating-sort-links
430
     */
431
    public function sort($key, $title = null, array $options = [])
432
    {
433
        $options += ['url' => [], 'model' => null, 'escape' => true];
434
        $url = $options['url'];
435
        unset($options['url']);
436
437
        if (empty($title)) {
438
            $title = $key;
439
440
            if (strpos($title, '.') !== false) {
441
                $title = str_replace('.', ' ', $title);
442
            }
443
444
            $title = __(Inflector::humanize(preg_replace('/_id$/', '', $title)));
445
        }
446
447
        $defaultDir = isset($options['direction']) ? strtolower($options['direction']) : 'asc';
448
        unset($options['direction']);
449
450
        $locked = isset($options['lock']) ? $options['lock'] : false;
451
        unset($options['lock']);
452
453
        $sortKey = $this->sortKey($options['model']);
454
        $defaultModel = $this->defaultModel();
455
        $model = $options['model'] ?: $defaultModel;
456
        list($table, $field) = explode('.', $key . '.');
457
        if (!$field) {
458
            $field = $table;
459
            $table = $model;
460
        }
461
        $isSorted = (
462
            $sortKey === $table . '.' . $field ||
463
            $sortKey === $model . '.' . $key ||
464
            $table . '.' . $field === $model . '.' . $sortKey
465
        );
466
467
        $template = 'sort';
468
        $dir = $defaultDir;
469
        if ($isSorted) {
470
            if ($locked) {
471
                $template = $dir === 'asc' ? 'sortDescLocked' : 'sortAscLocked';
472
            } else {
473
                $dir = $this->sortDir($options['model']) === 'asc' ? 'desc' : 'asc';
474
                $template = $dir === 'asc' ? 'sortDesc' : 'sortAsc';
475
            }
476
        }
477
        if (is_array($title) && array_key_exists($dir, $title)) {
478
            $title = $title[$dir];
479
        }
480
481
        $url = array_merge(
482
            ['sort' => $key, 'direction' => $dir, 'page' => 1],
483
            $url,
484
            ['order' => null]
485
        );
486
        $vars = [
487
            'text' => $options['escape'] ? h($title) : $title,
488
            'url' => $this->generateUrl($url, $options['model']),
489
        ];
490
491
        return $this->templater()->format($template, $vars);
492
    }
493
494
    /**
495
     * Merges passed URL options with current pagination state to generate a pagination URL.
496
     *
497
     * ### Url options:
498
     *
499
     * - `escape`: If false, the URL will be returned unescaped, do only use if it is manually
500
     *    escaped afterwards before being displayed.
501
     * - `fullBase`: If true, the full base URL will be prepended to the result
502
     *
503
     * @param array $options Pagination/URL options array
504
     * @param string|null $model Which model to paginate on
505
     * @param array $urlOptions Array of options
506
     * The bool version of this argument is *deprecated* and will be removed in 4.0.0
507
     * @return string By default, returns a full pagination URL string for use in non-standard contexts (i.e. JavaScript)
508
     * @link https://book.cakephp.org/3/en/views/helpers/paginator.html#generating-pagination-urls
509
     */
510
    public function generateUrl(array $options = [], $model = null, $urlOptions = [])
511
    {
512
        if (is_bool($urlOptions)) {
513
            $urlOptions = ['fullBase' => $urlOptions];
514
            deprecationWarning(
515
                'Passing a boolean value as third argument into PaginatorHelper::generateUrl() is deprecated ' .
516
                'and will be removed in 4.0.0 . ' .
517
                'Pass an array instead.'
518
            );
519
        }
520
        $urlOptions += [
521
            'escape' => true,
522
            'fullBase' => false,
523
        ];
524
525
        return $this->Url->build($this->generateUrlParams($options, $model), $urlOptions);
526
    }
527
528
    /**
529
     * Merges passed URL options with current pagination state to generate a pagination URL.
530
     *
531
     * @param array $options Pagination/URL options array
532
     * @param string|null $model Which model to paginate on
533
     * @return array An array of URL parameters
534
     */
535
    public function generateUrlParams(array $options = [], $model = null)
536
    {
537
        $paging = $this->params($model);
538
        $paging += ['page' => null, 'sort' => null, 'direction' => null, 'limit' => null];
539
540 View Code Duplication
        if (!empty($paging['sort']) && !empty($options['sort']) && strpos($options['sort'], '.') === false) {
541
            $paging['sort'] = $this->_removeAlias($paging['sort'], null);
542
        }
543 View Code Duplication
        if (!empty($paging['sortDefault']) && !empty($options['sort']) && strpos($options['sort'], '.') === false) {
544
            $paging['sortDefault'] = $this->_removeAlias($paging['sortDefault'], $model);
545
        }
546
547
        $url = [
548
            'page' => $paging['page'],
549
            'limit' => $paging['limit'],
550
            'sort' => $paging['sort'],
551
            'direction' => $paging['direction'],
552
        ];
553
554
        if (!empty($this->_config['options']['url'])) {
555
            $key = implode('.', array_filter(['options.url', Hash::get($paging, 'scope', null)]));
556
            $url = array_merge($url, Hash::get($this->_config, $key, []));
557
        }
558
559
        $url = array_filter($url, function ($value) {
560
            return ($value || is_numeric($value) || $value === false);
561
        });
562
        $url = array_merge($url, $options);
563
564
        if (!empty($url['page']) && $url['page'] == 1) {
565
            $url['page'] = false;
566
        }
567
568
        if (
569
            isset($paging['sortDefault'], $paging['directionDefault'], $url['sort'], $url['direction']) &&
570
            $url['sort'] === $paging['sortDefault'] &&
571
            strtolower($url['direction']) === strtolower($paging['directionDefault'])
572
        ) {
573
            $url['sort'] = $url['direction'] = null;
574
        }
575
576
        if (!empty($paging['scope'])) {
577
            $scope = $paging['scope'];
578
            $currentParams = $this->_config['options']['url'];
579
580
            if (isset($url['#'])) {
581
                $currentParams['#'] = $url['#'];
582
                unset($url['#']);
583
            }
584
585
            // Merge existing query parameters in the scope.
586
            if (isset($currentParams['?'][$scope]) && is_array($currentParams['?'][$scope])) {
587
                $url += $currentParams['?'][$scope];
588
                unset($currentParams['?'][$scope]);
589
            }
590
            $url = [$scope => $url] + $currentParams;
591
            if (empty($url[$scope]['page'])) {
592
                unset($url[$scope]['page']);
593
            }
594
        }
595
596
        return $url;
597
    }
598
599
    /**
600
     * Remove alias if needed.
601
     *
602
     * @param string $field Current field
603
     * @param string|null $model Current model alias
604
     * @return string Unaliased field if applicable
605
     */
606
    protected function _removeAlias($field, $model = null)
607
    {
608
        $currentModel = $model ?: $this->defaultModel();
609
610
        if (strpos($field, '.') === false) {
611
            return $field;
612
        }
613
614
        list ($alias, $currentField) = explode('.', $field);
615
616
        if ($alias === $currentModel) {
617
            return $currentField;
618
        }
619
620
        return $field;
621
    }
622
623
    /**
624
     * Returns true if the given result set is not at the first page
625
     *
626
     * @param string|null $model Optional model name. Uses the default if none is specified.
627
     * @return bool True if the result set is not at the first page.
628
     * @link https://book.cakephp.org/3/en/views/helpers/paginator.html#checking-the-pagination-state
629
     */
630
    public function hasPrev($model = null)
631
    {
632
        return $this->_hasPage($model, 'prev');
633
    }
634
635
    /**
636
     * Returns true if the given result set is not at the last page
637
     *
638
     * @param string|null $model Optional model name. Uses the default if none is specified.
639
     * @return bool True if the result set is not at the last page.
640
     * @link https://book.cakephp.org/3/en/views/helpers/paginator.html#checking-the-pagination-state
641
     */
642
    public function hasNext($model = null)
643
    {
644
        return $this->_hasPage($model, 'next');
645
    }
646
647
    /**
648
     * Returns true if the given result set has the page number given by $page
649
     *
650
     * @param string|null $model Optional model name. Uses the default if none is specified.
651
     * @param int $page The page number - if not set defaults to 1.
652
     * @return bool True if the given result set has the specified page number.
653
     * @link https://book.cakephp.org/3/en/views/helpers/paginator.html#checking-the-pagination-state
654
     */
655
    public function hasPage($model = null, $page = 1)
656
    {
657
        if (is_numeric($model)) {
658
            $page = $model;
659
            $model = null;
660
        }
661
        $paging = $this->params($model);
662
        if ($paging === []) {
663
            return false;
664
        }
665
666
        return $page <= $paging['pageCount'];
667
    }
668
669
    /**
670
     * Does $model have $page in its range?
671
     *
672
     * @param string $model Model name to get parameters for.
673
     * @param int $page Page number you are checking.
674
     * @return bool Whether model has $page
675
     */
676
    protected function _hasPage($model, $page)
677
    {
678
        $params = $this->params($model);
679
680
        return !empty($params) && $params[$page . 'Page'];
681
    }
682
683
    /**
684
     * Gets or sets the default model of the paged sets
685
     *
686
     * @param string|null $model Model name to set
687
     * @return string|null Model name or null if the pagination isn't initialized.
688
     */
689
    public function defaultModel($model = null)
690
    {
691
        if ($model !== null) {
692
            $this->_defaultModel = $model;
693
        }
694
        if ($this->_defaultModel) {
695
            return $this->_defaultModel;
696
        }
697
        if (!$this->_View->getRequest()->getParam('paging')) {
698
            return null;
699
        }
700
        list($this->_defaultModel) = array_keys($this->_View->getRequest()->getParam('paging'));
701
702
        return $this->_defaultModel;
703
    }
704
705
    /**
706
     * Returns a counter string for the paged result set
707
     *
708
     * ### Options
709
     *
710
     * - `model` The model to use, defaults to PaginatorHelper::defaultModel();
711
     * - `format` The format string you want to use, defaults to 'pages' Which generates output like '1 of 5'
712
     *    set to 'range' to generate output like '1 - 3 of 13'. Can also be set to a custom string, containing
713
     *    the following placeholders `{{page}}`, `{{pages}}`, `{{current}}`, `{{count}}`, `{{model}}`, `{{start}}`, `{{end}}` and any
714
     *    custom content you would like.
715
     *
716
     * @param string|array $options Options for the counter string. See #options for list of keys.
717
     *   If string it will be used as format.
718
     * @return string Counter string.
719
     * @link https://book.cakephp.org/3/en/views/helpers/paginator.html#creating-a-page-counter
720
     */
721
    public function counter($options = [])
722
    {
723
        if (is_string($options)) {
724
            $options = ['format' => $options];
725
        }
726
727
        $options += [
728
            'model' => $this->defaultModel(),
729
            'format' => 'pages',
730
        ];
731
732
        $paging = $this->params($options['model']);
733
        if (!$paging['pageCount']) {
734
            $paging['pageCount'] = 1;
735
        }
736
737
        switch ($options['format']) {
738
            case 'range':
739
            case 'pages':
740
                $template = 'counter' . ucfirst($options['format']);
741
                break;
742
            default:
743
                $template = 'counterCustom';
744
                $this->templater()->add([$template => $options['format']]);
745
        }
746
        $map = array_map([$this->Number, 'format'], [
747
            'page' => $paging['page'],
748
            'pages' => $paging['pageCount'],
749
            'current' => $paging['current'],
750
            'count' => $paging['count'],
751
            'start' => $paging['start'],
752
            'end' => $paging['end'],
753
        ]);
754
755
        $map += [
756
            'model' => strtolower(Inflector::humanize(Inflector::tableize($options['model']))),
757
        ];
758
759
        return $this->templater()->format($template, $map);
760
    }
761
762
    /**
763
     * Returns a set of numbers for the paged result set
764
     * uses a modulus to decide how many numbers to show on each side of the current page (default: 8).
765
     *
766
     * ```
767
     * $this->Paginator->numbers(['first' => 2, 'last' => 2]);
768
     * ```
769
     *
770
     * Using the first and last options you can create links to the beginning and end of the page set.
771
     *
772
     * ### Options
773
     *
774
     * - `before` Content to be inserted before the numbers, but after the first links.
775
     * - `after` Content to be inserted after the numbers, but before the last links.
776
     * - `model` Model to create numbers for, defaults to PaginatorHelper::defaultModel()
777
     * - `modulus` How many numbers to include on either side of the current page, defaults to 8.
778
     *    Set to `false` to disable and to show all numbers.
779
     * - `first` Whether you want first links generated, set to an integer to define the number of 'first'
780
     *    links to generate. If a string is set a link to the first page will be generated with the value
781
     *    as the title.
782
     * - `last` Whether you want last links generated, set to an integer to define the number of 'last'
783
     *    links to generate. If a string is set a link to the last page will be generated with the value
784
     *    as the title.
785
     * - `templates` An array of templates, or template file name containing the templates you'd like to
786
     *    use when generating the numbers. The helper's original templates will be restored once
787
     *    numbers() is done.
788
     * - `url` An array of additional URL options to use for link generation.
789
     *
790
     * The generated number links will include the 'ellipsis' template when the `first` and `last` options
791
     * and the number of pages exceed the modulus. For example if you have 25 pages, and use the first/last
792
     * options and a modulus of 8, ellipsis content will be inserted after the first and last link sets.
793
     *
794
     * @param array $options Options for the numbers.
795
     * @return string|false Numbers string.
796
     * @link https://book.cakephp.org/3/en/views/helpers/paginator.html#creating-page-number-links
797
     */
798
    public function numbers(array $options = [])
799
    {
800
        $defaults = [
801
            'before' => null, 'after' => null, 'model' => $this->defaultModel(),
802
            'modulus' => 8, 'first' => null, 'last' => null, 'url' => [],
803
        ];
804
        $options += $defaults;
805
806
        $params = (array)$this->params($options['model']) + ['page' => 1];
807
        if ($params['pageCount'] <= 1) {
808
            return false;
809
        }
810
811
        $templater = $this->templater();
812 View Code Duplication
        if (isset($options['templates'])) {
813
            $templater->push();
814
            $method = is_string($options['templates']) ? 'load' : 'add';
815
            $templater->{$method}($options['templates']);
816
        }
817
818
        if ($options['modulus'] !== false && $params['pageCount'] > $options['modulus']) {
819
            $out = $this->_modulusNumbers($templater, $params, $options);
820
        } else {
821
            $out = $this->_numbers($templater, $params, $options);
822
        }
823
824
        if (isset($options['templates'])) {
825
            $templater->pop();
826
        }
827
828
        return $out;
829
    }
830
831
    /**
832
     * Calculates the start and end for the pagination numbers.
833
     *
834
     * @param array $params Params from the numbers() method.
835
     * @param array $options Options from the numbers() method.
836
     * @return array An array with the start and end numbers.
837
     */
838
    protected function _getNumbersStartAndEnd($params, $options)
839
    {
840
        $half = (int)($options['modulus'] / 2);
841
        $end = max(1 + $options['modulus'], $params['page'] + $half);
842
        $start = min($params['pageCount'] - $options['modulus'], $params['page'] - $half - $options['modulus'] % 2);
843
844
        if ($options['first']) {
845
            $first = is_int($options['first']) ? $options['first'] : 1;
846
847
            if ($start <= $first + 2) {
848
                $start = 1;
849
            }
850
        }
851
852
        if ($options['last']) {
853
            $last = is_int($options['last']) ? $options['last'] : 1;
854
855
            if ($end >= $params['pageCount'] - $last - 1) {
856
                $end = $params['pageCount'];
857
            }
858
        }
859
860
        $end = min($params['pageCount'], $end);
861
        $start = max(1, $start);
862
863
        return [$start, $end];
864
    }
865
866
    /**
867
     * Formats a number for the paginator number output.
868
     *
869
     * @param \Cake\View\StringTemplate $templater StringTemplate instance.
870
     * @param array $options Options from the numbers() method.
871
     * @return string
872
     */
873
    protected function _formatNumber($templater, $options)
874
    {
875
        $url = array_merge($options['url'], ['page' => $options['page']]);
876
        $vars = [
877
            'text' => $options['text'],
878
            'url' => $this->generateUrl($url, $options['model']),
879
        ];
880
881
        return $templater->format('number', $vars);
882
    }
883
884
    /**
885
     * Generates the numbers for the paginator numbers() method.
886
     *
887
     * @param \Cake\View\StringTemplate $templater StringTemplate instance.
888
     * @param array $params Params from the numbers() method.
889
     * @param array $options Options from the numbers() method.
890
     * @return string Markup output.
891
     */
892
    protected function _modulusNumbers($templater, $params, $options)
893
    {
894
        $out = '';
895
        $ellipsis = $templater->format('ellipsis', []);
896
897
        list($start, $end) = $this->_getNumbersStartAndEnd($params, $options);
898
899
        $out .= $this->_firstNumber($ellipsis, $params, $start, $options);
900
        $out .= $options['before'];
901
902
        for ($i = $start; $i < $params['page']; $i++) {
903
            $out .= $this->_formatNumber($templater, [
904
                'text' => $this->Number->format($i),
905
                'page' => $i,
906
                'model' => $options['model'],
907
                'url' => $options['url'],
908
            ]);
909
        }
910
911
        $url = array_merge($options['url'], ['page' => $params['page']]);
912
        $out .= $templater->format('current', [
913
            'text' => $this->Number->format($params['page']),
914
            'url' => $this->generateUrl($url, $options['model']),
915
        ]);
916
917
        $start = $params['page'] + 1;
918
        $i = $start;
919
        while ($i < $end) {
920
            $out .= $this->_formatNumber($templater, [
921
                'text' => $this->Number->format($i),
922
                'page' => $i,
923
                'model' => $options['model'],
924
                'url' => $options['url'],
925
            ]);
926
            $i++;
927
        }
928
929
        if ($end != $params['page']) {
930
            $out .= $this->_formatNumber($templater, [
931
                'text' => $this->Number->format($i),
932
                'page' => $end,
933
                'model' => $options['model'],
934
                'url' => $options['url'],
935
            ]);
936
        }
937
938
        $out .= $options['after'];
939
        $out .= $this->_lastNumber($ellipsis, $params, $end, $options);
940
941
        return $out;
942
    }
943
944
    /**
945
     * Generates the first number for the paginator numbers() method.
946
     *
947
     * @param string $ellipsis Ellipsis character.
948
     * @param array $params Params from the numbers() method.
949
     * @param int $start Start number.
950
     * @param array $options Options from the numbers() method.
951
     * @return string Markup output.
952
     */
953
    protected function _firstNumber($ellipsis, $params, $start, $options)
954
    {
955
        $out = '';
956
        $first = is_int($options['first']) ? $options['first'] : 0;
957
        if ($options['first'] && $start > 1) {
958
            $offset = ($start <= $first) ? $start - 1 : $options['first'];
959
            $out .= $this->first($offset, $options);
960
            if ($first < $start - 1) {
961
                $out .= $ellipsis;
962
            }
963
        }
964
965
        return $out;
966
    }
967
968
    /**
969
     * Generates the last number for the paginator numbers() method.
970
     *
971
     * @param string $ellipsis Ellipsis character.
972
     * @param array $params Params from the numbers() method.
973
     * @param int $end End number.
974
     * @param array $options Options from the numbers() method.
975
     * @return string Markup output.
976
     */
977
    protected function _lastNumber($ellipsis, $params, $end, $options)
978
    {
979
        $out = '';
980
        $last = is_int($options['last']) ? $options['last'] : 0;
981
        if ($options['last'] && $end < $params['pageCount']) {
982
            $offset = ($params['pageCount'] < $end + $last) ? $params['pageCount'] - $end : $options['last'];
983
            if ($offset <= $options['last'] && $params['pageCount'] - $end > $last) {
984
                $out .= $ellipsis;
985
            }
986
            $out .= $this->last($offset, $options);
987
        }
988
989
        return $out;
990
    }
991
992
    /**
993
     * Generates the numbers for the paginator numbers() method.
994
     *
995
     * @param \Cake\View\StringTemplate $templater StringTemplate instance.
996
     * @param array $params Params from the numbers() method.
997
     * @param array $options Options from the numbers() method.
998
     * @return string Markup output.
999
     */
1000
    protected function _numbers($templater, $params, $options)
1001
    {
1002
        $out = '';
1003
        $out .= $options['before'];
1004
        for ($i = 1; $i <= $params['pageCount']; $i++) {
1005
            $url = array_merge($options['url'], ['page' => $i]);
1006
            if ($i == $params['page']) {
1007
                $out .= $templater->format('current', [
1008
                    'text' => $this->Number->format($params['page']),
1009
                    'url' => $this->generateUrl($url, $options['model']),
1010
                ]);
1011
            } else {
1012
                $vars = [
1013
                    'text' => $this->Number->format($i),
1014
                    'url' => $this->generateUrl($url, $options['model']),
1015
                ];
1016
                $out .= $templater->format('number', $vars);
1017
            }
1018
        }
1019
        $out .= $options['after'];
1020
1021
        return $out;
1022
    }
1023
1024
    /**
1025
     * Returns a first or set of numbers for the first pages.
1026
     *
1027
     * ```
1028
     * echo $this->Paginator->first('< first');
1029
     * ```
1030
     *
1031
     * Creates a single link for the first page. Will output nothing if you are on the first page.
1032
     *
1033
     * ```
1034
     * echo $this->Paginator->first(3);
1035
     * ```
1036
     *
1037
     * Will create links for the first 3 pages, once you get to the third or greater page. Prior to that
1038
     * nothing will be output.
1039
     *
1040
     * ### Options:
1041
     *
1042
     * - `model` The model to use defaults to PaginatorHelper::defaultModel()
1043
     * - `escape` Whether or not to HTML escape the text.
1044
     * - `url` An array of additional URL options to use for link generation.
1045
     *
1046
     * @param string|int $first if string use as label for the link. If numeric, the number of page links
1047
     *   you want at the beginning of the range.
1048
     * @param array $options An array of options.
1049
     * @return string|false Numbers string.
1050
     * @link https://book.cakephp.org/3/en/views/helpers/paginator.html#creating-jump-links
1051
     */
1052
    public function first($first = '<< first', array $options = [])
1053
    {
1054
        $options += [
1055
            'url' => [],
1056
            'model' => $this->defaultModel(),
1057
            'escape' => true,
1058
        ];
1059
1060
        $params = $this->params($options['model']);
1061
1062
        if ($params['pageCount'] <= 1) {
1063
            return false;
1064
        }
1065
1066
        $out = '';
1067
1068
        if (is_int($first) && $params['page'] >= $first) {
1069 View Code Duplication
            for ($i = 1; $i <= $first; $i++) {
1070
                $url = array_merge($options['url'], ['page' => $i]);
1071
                $out .= $this->templater()->format('number', [
1072
                    'url' => $this->generateUrl($url, $options['model']),
1073
                    'text' => $this->Number->format($i),
1074
                ]);
1075
            }
1076
        } elseif ($params['page'] > 1 && is_string($first)) {
1077
            $first = $options['escape'] ? h($first) : $first;
1078
            $out .= $this->templater()->format('first', [
1079
                'url' => $this->generateUrl(['page' => 1], $options['model']),
1080
                'text' => $first,
1081
            ]);
1082
        }
1083
1084
        return $out;
1085
    }
1086
1087
    /**
1088
     * Returns a last or set of numbers for the last pages.
1089
     *
1090
     * ```
1091
     * echo $this->Paginator->last('last >');
1092
     * ```
1093
     *
1094
     * Creates a single link for the last page. Will output nothing if you are on the last page.
1095
     *
1096
     * ```
1097
     * echo $this->Paginator->last(3);
1098
     * ```
1099
     *
1100
     * Will create links for the last 3 pages. Once you enter the page range, no output will be created.
1101
     *
1102
     * ### Options:
1103
     *
1104
     * - `model` The model to use defaults to PaginatorHelper::defaultModel()
1105
     * - `escape` Whether or not to HTML escape the text.
1106
     * - `url` An array of additional URL options to use for link generation.
1107
     *
1108
     * @param string|int $last if string use as label for the link, if numeric print page numbers
1109
     * @param array $options Array of options
1110
     * @return string|false Numbers string.
1111
     * @link https://book.cakephp.org/3/en/views/helpers/paginator.html#creating-jump-links
1112
     */
1113
    public function last($last = 'last >>', array $options = [])
1114
    {
1115
        $options += [
1116
            'model' => $this->defaultModel(),
1117
            'escape' => true,
1118
            'url' => [],
1119
        ];
1120
        $params = $this->params($options['model']);
1121
1122
        if ($params['pageCount'] <= 1) {
1123
            return false;
1124
        }
1125
1126
        $out = '';
1127
        $lower = (int)$params['pageCount'] - (int)$last + 1;
1128
1129
        if (is_int($last) && $params['page'] <= $lower) {
1130 View Code Duplication
            for ($i = $lower; $i <= $params['pageCount']; $i++) {
1131
                $url = array_merge($options['url'], ['page' => $i]);
1132
                $out .= $this->templater()->format('number', [
1133
                    'url' => $this->generateUrl($url, $options['model']),
1134
                    'text' => $this->Number->format($i),
1135
                ]);
1136
            }
1137
        } elseif ($params['page'] < $params['pageCount'] && is_string($last)) {
1138
            $last = $options['escape'] ? h($last) : $last;
1139
            $out .= $this->templater()->format('last', [
1140
                'url' => $this->generateUrl(['page' => $params['pageCount']], $options['model']),
1141
                'text' => $last,
1142
            ]);
1143
        }
1144
1145
        return $out;
1146
    }
1147
1148
    /**
1149
     * Returns the meta-links for a paginated result set.
1150
     *
1151
     * ```
1152
     * echo $this->Paginator->meta();
1153
     * ```
1154
     *
1155
     * Echos the links directly, will output nothing if there is neither a previous nor next page.
1156
     *
1157
     * ```
1158
     * $this->Paginator->meta(['block' => true]);
1159
     * ```
1160
     *
1161
     * Will append the output of the meta function to the named block - if true is passed the "meta"
1162
     * block is used.
1163
     *
1164
     * ### Options:
1165
     *
1166
     * - `model` The model to use defaults to PaginatorHelper::defaultModel()
1167
     * - `block` The block name to append the output to, or false/absent to return as a string
1168
     * - `prev` (default True) True to generate meta for previous page
1169
     * - `next` (default True) True to generate meta for next page
1170
     * - `first` (default False) True to generate meta for first page
1171
     * - `last` (default False) True to generate meta for last page
1172
     *
1173
     * @param array $options Array of options
1174
     * @return string|null Meta links
1175
     */
1176
    public function meta(array $options = [])
1177
    {
1178
        $options += [
1179
                'model' => null,
1180
                'block' => false,
1181
                'prev' => true,
1182
                'next' => true,
1183
                'first' => false,
1184
                'last' => false,
1185
            ];
1186
1187
        $model = isset($options['model']) ? $options['model'] : null;
1188
        $params = $this->params($model);
1189
        $links = [];
1190
1191 View Code Duplication
        if ($options['prev'] && $this->hasPrev()) {
1192
            $links[] = $this->Html->meta(
1193
                'prev',
1194
                $this->generateUrl(['page' => $params['page'] - 1], null, ['escape' => false, 'fullBase' => true])
1195
            );
1196
        }
1197
1198 View Code Duplication
        if ($options['next'] && $this->hasNext()) {
1199
            $links[] = $this->Html->meta(
1200
                'next',
1201
                $this->generateUrl(['page' => $params['page'] + 1], null, ['escape' => false, 'fullBase' => true])
1202
            );
1203
        }
1204
1205
        if ($options['first']) {
1206
            $links[] = $this->Html->meta(
1207
                'first',
1208
                $this->generateUrl(['page' => 1], null, ['escape' => false, 'fullBase' => true])
1209
            );
1210
        }
1211
1212
        if ($options['last']) {
1213
            $links[] = $this->Html->meta(
1214
                'last',
1215
                $this->generateUrl(['page' => $params['pageCount']], null, ['escape' => false, 'fullBase' => true])
1216
            );
1217
        }
1218
1219
        $out = implode($links);
1220
1221
        if ($options['block'] === true) {
1222
            $options['block'] = __FUNCTION__;
1223
        }
1224
1225
        if ($options['block']) {
1226
            $this->_View->append($options['block'], $out);
1227
1228
            return null;
1229
        }
1230
1231
        return $out;
1232
    }
1233
1234
    /**
1235
     * Event listeners.
1236
     *
1237
     * @return array
1238
     */
1239
    public function implementedEvents()
1240
    {
1241
        return [];
1242
    }
1243
1244
    /**
1245
     * Dropdown select for pagination limit.
1246
     * This will generate a wrapping form.
1247
     *
1248
     * @param array $limits The options array.
1249
     * @param int|null $default Default option for pagination limit. Defaults to `$this->param('perPage')`.
1250
     * @param array $options Options for Select tag attributes like class, id or event
1251
     * @return string html output.
1252
     */
1253
    public function limitControl(array $limits = [], $default = null, array $options = [])
1254
    {
1255
        $out = $this->Form->create(null, ['type' => 'get']);
1256
1257
        if (empty($default) || !is_numeric($default)) {
1258
            $default = $this->param('perPage');
1259
        }
1260
1261
        if (empty($limits)) {
1262
            $limits = [
1263
                '20' => '20',
1264
                '50' => '50',
1265
                '100' => '100',
1266
            ];
1267
        }
1268
1269
        $out .= $this->Form->control('limit', $options + [
1270
                'type' => 'select',
1271
                'label' => __('View'),
1272
                'default' => $default,
1273
                'value' => $this->_View->getRequest()->getQuery('limit'),
1274
                'options' => $limits,
1275
                'onChange' => 'this.form.submit()',
1276
            ]);
1277
        $out .= $this->Form->end();
1278
1279
        return $out;
1280
    }
1281
}
1282