1: <?php
2: /**
3: * Pagination Helper class file.
4: *
5: * Generates pagination links
6: *
7: * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
8: * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
9: *
10: * Licensed under The MIT License
11: * For full copyright and license information, please see the LICENSE.txt
12: * Redistributions of files must retain the above copyright notice.
13: *
14: * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
15: * @link https://cakephp.org CakePHP(tm) Project
16: * @package Cake.View.Helper
17: * @since CakePHP(tm) v 1.2.0
18: * @license https://opensource.org/licenses/mit-license.php MIT License
19: */
20:
21: App::uses('AppHelper', 'View/Helper');
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: * @package Cake.View.Helper
29: * @property HtmlHelper $Html
30: * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/paginator.html
31: */
32: class PaginatorHelper extends AppHelper {
33:
34: /**
35: * Helper dependencies
36: *
37: * @var array
38: */
39: public $helpers = array('Html');
40:
41: /**
42: * The class used for 'Ajax' pagination links. Defaults to JsHelper. You should make sure
43: * that JsHelper is defined as a helper before PaginatorHelper, if you want to customize the JsHelper.
44: *
45: * @var string
46: */
47: protected $_ajaxHelperClass = 'Js';
48:
49: /**
50: * Holds the default options for pagination links
51: *
52: * The values that may be specified are:
53: *
54: * - `format` Format of the counter. Supported formats are 'range' and 'pages'
55: * and custom (default). In the default mode the supplied string is parsed and constants are replaced
56: * by their actual values.
57: * placeholders: %page%, %pages%, %current%, %count%, %start%, %end% .
58: * - `separator` The separator of the actual page and number of pages (default: ' of ').
59: * - `url` Url of the action. See Router::url()
60: * - `url['sort']` the key that the recordset is sorted.
61: * - `url['direction']` Direction of the sorting (default: 'asc').
62: * - `url['page']` Page number to use in links.
63: * - `model` The name of the model.
64: * - `escape` Defines if the title field for the link should be escaped (default: true).
65: * - `update` DOM id of the element updated with the results of the AJAX call.
66: * If this key isn't specified Paginator will use plain HTML links.
67: * - `paging['paramType']` The type of parameters to use when creating links. Valid options are
68: * 'querystring' and 'named'. See PaginatorComponent::$settings for more information.
69: * - `convertKeys` - A list of keys in URL arrays that should be converted to querysting params
70: * if paramType == 'querystring'.
71: *
72: * @var array
73: */
74: public $options = array(
75: 'convertKeys' => array('page', 'limit', 'sort', 'direction')
76: );
77:
78: /**
79: * Constructor for the helper. Sets up the helper that is used for creating 'AJAX' links.
80: *
81: * Use `public $helpers = array('Paginator' => array('ajax' => 'CustomHelper'));` to set a custom Helper
82: * or choose a non JsHelper Helper. If you want to use a specific library with JsHelper declare JsHelper and its
83: * adapter before including PaginatorHelper in your helpers array.
84: *
85: * The chosen custom helper must implement a `link()` method.
86: *
87: * @param View $View the view object the helper is attached to.
88: * @param array $settings Array of settings.
89: * @throws CakeException When the AjaxProvider helper does not implement a link method.
90: */
91: public function __construct(View $View, $settings = array()) {
92: $ajaxProvider = isset($settings['ajax']) ? $settings['ajax'] : 'Js';
93: $this->helpers[] = $ajaxProvider;
94: $this->_ajaxHelperClass = $ajaxProvider;
95: App::uses($ajaxProvider . 'Helper', 'View/Helper');
96: $classname = $ajaxProvider . 'Helper';
97: if (!class_exists($classname) || !method_exists($classname, 'link')) {
98: throw new CakeException(
99: __d('cake_dev', '%s does not implement a %s method, it is incompatible with %s', $classname, 'link()', 'PaginatorHelper')
100: );
101: }
102: parent::__construct($View, $settings);
103: }
104:
105: /**
106: * Before render callback. Overridden to merge passed args with URL options.
107: *
108: * @param string $viewFile View file name.
109: * @return void
110: */
111: public function beforeRender($viewFile) {
112: $this->options['url'] = array_merge($this->request->params['pass'], $this->request->params['named']);
113: if (!empty($this->request->query)) {
114: $this->options['url']['?'] = $this->request->query;
115: }
116: parent::beforeRender($viewFile);
117: }
118:
119: /**
120: * Gets the current paging parameters from the resultset for the given model
121: *
122: * @param string $model Optional model name. Uses the default if none is specified.
123: * @return array The array of paging parameters for the paginated resultset.
124: * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/paginator.html#PaginatorHelper::params
125: */
126: public function params($model = null) {
127: if (empty($model)) {
128: $model = $this->defaultModel();
129: }
130: if (!isset($this->request->params['paging']) || empty($this->request->params['paging'][$model])) {
131: return array(
132: 'prevPage' => false,
133: 'nextPage' => true,
134: 'paramType' => 'named',
135: 'pageCount' => 1,
136: 'options' => array(),
137: 'page' => 1
138: );
139: }
140: return $this->request->params['paging'][$model];
141: }
142:
143: /**
144: * Convenience access to any of the paginator params.
145: *
146: * @param string $key Key of the paginator params array to retrieve.
147: * @param string $model Optional model name. Uses the default if none is specified.
148: * @return mixed Content of the requested param.
149: * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/paginator.html#PaginatorHelper::params
150: */
151: public function param($key, $model = null) {
152: $params = $this->params($model);
153: if (!isset($params[$key])) {
154: return null;
155: }
156: return $params[$key];
157: }
158:
159: /**
160: * Sets default options for all pagination links
161: *
162: * @param array|string $options Default options for pagination links. If a string is supplied - it
163: * is used as the DOM id element to update. See PaginatorHelper::$options for list of keys.
164: * @return void
165: * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/paginator.html#PaginatorHelper::options
166: */
167: public function options($options = array()) {
168: if (is_string($options)) {
169: $options = array('update' => $options);
170: }
171:
172: if (!empty($options['paging'])) {
173: if (!isset($this->request->params['paging'])) {
174: $this->request->params['paging'] = array();
175: }
176: $this->request->params['paging'] = array_merge($this->request->params['paging'], $options['paging']);
177: unset($options['paging']);
178: }
179: $model = $this->defaultModel();
180:
181: if (!empty($options[$model])) {
182: if (!isset($this->request->params['paging'][$model])) {
183: $this->request->params['paging'][$model] = array();
184: }
185: $this->request->params['paging'][$model] = array_merge(
186: $this->request->params['paging'][$model], $options[$model]
187: );
188: unset($options[$model]);
189: }
190: if (!empty($options['convertKeys'])) {
191: $options['convertKeys'] = array_merge($this->options['convertKeys'], $options['convertKeys']);
192: }
193: $this->options = array_filter(array_merge($this->options, $options));
194: if (!empty($this->options['model'])) {
195: $this->defaultModel($this->options['model']);
196: }
197: }
198:
199: /**
200: * Gets the current page of the recordset for the given model
201: *
202: * @param string $model Optional model name. Uses the default if none is specified.
203: * @return string The current page number of the recordset.
204: * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/paginator.html#PaginatorHelper::current
205: */
206: public function current($model = null) {
207: $params = $this->params($model);
208:
209: if (isset($params['page'])) {
210: return $params['page'];
211: }
212: return 1;
213: }
214:
215: /**
216: * Gets the current key by which the recordset is sorted
217: *
218: * @param string $model Optional model name. Uses the default if none is specified.
219: * @param array $options Options for pagination links. See #options for list of keys.
220: * @return string|null The name of the key by which the recordset is being sorted, or
221: * null if the results are not currently sorted.
222: * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/paginator.html#PaginatorHelper::sortKey
223: */
224: public function sortKey($model = null, $options = array()) {
225: if (empty($options)) {
226: $params = $this->params($model);
227: $options = $params['options'];
228: }
229: if (isset($options['sort']) && !empty($options['sort'])) {
230: return $options['sort'];
231: }
232: if (isset($options['order'])) {
233: return is_array($options['order']) ? key($options['order']) : $options['order'];
234: }
235: if (isset($params['order'])) {
236: return is_array($params['order']) ? key($params['order']) : $params['order'];
237: }
238: return null;
239: }
240:
241: /**
242: * Gets the current direction the recordset is sorted
243: *
244: * @param string $model Optional model name. Uses the default if none is specified.
245: * @param array $options Options for pagination links. See #options for list of keys.
246: * @return string The direction by which the recordset is being sorted, or
247: * null if the results are not currently sorted.
248: * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/paginator.html#PaginatorHelper::sortDir
249: */
250: public function sortDir($model = null, $options = array()) {
251: $dir = null;
252:
253: if (empty($options)) {
254: $params = $this->params($model);
255: $options = $params['options'];
256: }
257:
258: if (isset($options['direction'])) {
259: $dir = strtolower($options['direction']);
260: } elseif (isset($options['order']) && is_array($options['order'])) {
261: $dir = strtolower(current($options['order']));
262: } elseif (isset($params['order']) && is_array($params['order'])) {
263: $dir = strtolower(current($params['order']));
264: }
265:
266: if ($dir === 'desc') {
267: return 'desc';
268: }
269: return 'asc';
270: }
271:
272: /**
273: * Generates a "previous" link for a set of paged records
274: *
275: * ### Options:
276: *
277: * - `url` Allows sending routing parameters such as controllers, actions or passed arguments.
278: * - `tag` The tag wrapping tag you want to use, defaults to 'span'. Set this to false to disable this option
279: * - `escape` Whether you want the contents html entity encoded, defaults to true
280: * - `model` The model to use, defaults to PaginatorHelper::defaultModel()
281: * - `disabledTag` Tag to use instead of A tag when there is no previous page
282: *
283: * @param string $title Title for the link. Defaults to '<< Previous'.
284: * @param array $options Options for pagination link. See #options for list of keys.
285: * @param string $disabledTitle Title when the link is disabled.
286: * @param array $disabledOptions Options for the disabled pagination link. See #options for list of keys.
287: * @return string A "previous" link or $disabledTitle text if the link is disabled.
288: * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/paginator.html#PaginatorHelper::prev
289: */
290: public function prev($title = '<< Previous', $options = array(), $disabledTitle = null, $disabledOptions = array()) {
291: $defaults = array(
292: 'rel' => 'prev'
293: );
294: $options = (array)$options + $defaults;
295: return $this->_pagingLink('Prev', $title, $options, $disabledTitle, $disabledOptions);
296: }
297:
298: /**
299: * Generates a "next" link for a set of paged records
300: *
301: * ### Options:
302: *
303: * - `url` Allows sending routing parameters such as controllers, actions or passed arguments.
304: * - `tag` The tag wrapping tag you want to use, defaults to 'span'. Set this to false to disable this option
305: * - `escape` Whether you want the contents html entity encoded, defaults to true
306: * - `model` The model to use, defaults to PaginatorHelper::defaultModel()
307: * - `disabledTag` Tag to use instead of A tag when there is no next page
308: *
309: * @param string $title Title for the link. Defaults to 'Next >>'.
310: * @param array $options Options for pagination link. See above for list of keys.
311: * @param string $disabledTitle Title when the link is disabled.
312: * @param array $disabledOptions Options for the disabled pagination link. See above for list of keys.
313: * @return string A "next" link or $disabledTitle text if the link is disabled.
314: * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/paginator.html#PaginatorHelper::next
315: */
316: public function next($title = 'Next >>', $options = array(), $disabledTitle = null, $disabledOptions = array()) {
317: $defaults = array(
318: 'rel' => 'next'
319: );
320: $options = (array)$options + $defaults;
321: return $this->_pagingLink('Next', $title, $options, $disabledTitle, $disabledOptions);
322: }
323:
324: /**
325: * Generates a sorting link. Sets named parameters for the sort and direction. Handles
326: * direction switching automatically.
327: *
328: * ### Options:
329: *
330: * - `escape` Whether you want the contents html entity encoded, defaults to true.
331: * - `model` The model to use, defaults to PaginatorHelper::defaultModel().
332: * - `direction` The default direction to use when this link isn't active.
333: * - `lock` Lock direction. Will only use the default direction then, defaults to false.
334: *
335: * @param string $key The name of the key that the recordset should be sorted.
336: * @param string $title Title for the link. If $title is null $key will be used
337: * for the title and will be generated by inflection.
338: * @param array $options Options for sorting link. See above for list of keys.
339: * @return string A link sorting default by 'asc'. If the resultset is sorted 'asc' by the specified
340: * key the returned link will sort by 'desc'.
341: * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/paginator.html#PaginatorHelper::sort
342: */
343: public function sort($key, $title = null, $options = array()) {
344: $options += array('url' => array(), 'model' => null);
345: $url = $options['url'];
346: unset($options['url']);
347:
348: if (empty($title)) {
349: $title = $key;
350:
351: if (strpos($title, '.') !== false) {
352: $title = str_replace('.', ' ', $title);
353: }
354:
355: $title = __(Inflector::humanize(preg_replace('/_id$/', '', $title)));
356: }
357: $defaultDir = isset($options['direction']) ? strtolower($options['direction']) : 'asc';
358: unset($options['direction']);
359:
360: $locked = isset($options['lock']) ? $options['lock'] : false;
361: unset($options['lock']);
362:
363: $sortKey = $this->sortKey($options['model']);
364: $defaultModel = $this->defaultModel();
365: $model = $options['model'] ?: $defaultModel;
366: list($table, $field) = explode('.', $key . '.');
367: if (!$field) {
368: $field = $table;
369: $table = $model;
370: }
371: $isSorted = (
372: $sortKey === $table . '.' . $field ||
373: $sortKey === $defaultModel . '.' . $key ||
374: $table . '.' . $field === $defaultModel . '.' . $sortKey
375: );
376:
377: $dir = $defaultDir;
378: if ($isSorted) {
379: $dir = $this->sortDir($options['model']) === 'asc' ? 'desc' : 'asc';
380: $class = $dir === 'asc' ? 'desc' : 'asc';
381: if (!empty($options['class'])) {
382: $options['class'] .= ' ' . $class;
383: } else {
384: $options['class'] = $class;
385: }
386: if ($locked) {
387: $dir = $defaultDir;
388: $options['class'] .= ' locked';
389: }
390: }
391: if (is_array($title) && array_key_exists($dir, $title)) {
392: $title = $title[$dir];
393: }
394:
395: $url = array_merge(array('sort' => $key, 'direction' => $dir), $url, array('order' => null));
396: return $this->link($title, $url, $options);
397: }
398:
399: /**
400: * Generates a plain or Ajax link with pagination parameters
401: *
402: * ### Options
403: *
404: * - `update` The Id of the DOM element you wish to update. Creates Ajax enabled links
405: * with the AjaxHelper.
406: * - `escape` Whether you want the contents html entity encoded, defaults to true
407: * - `model` The model to use, defaults to PaginatorHelper::defaultModel()
408: *
409: * @param string $title Title for the link.
410: * @param string|array $url URL for the action. See Router::url()
411: * @param array $options Options for the link. See #options for list of keys.
412: * @return string A link with pagination parameters.
413: * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/paginator.html#PaginatorHelper::link
414: */
415: public function link($title, $url = array(), $options = array()) {
416: $options += array('model' => null, 'escape' => true);
417: $model = $options['model'];
418: unset($options['model']);
419:
420: if (!empty($this->options)) {
421: $options += $this->options;
422: }
423: if (isset($options['url'])) {
424: $url = array_merge((array)$options['url'], (array)$url);
425: unset($options['url']);
426: }
427: unset($options['convertKeys']);
428:
429: $url = $this->url($url, true, $model);
430:
431: $obj = isset($options['update']) ? $this->_ajaxHelperClass : 'Html';
432: return $this->{$obj}->link($title, $url, $options);
433: }
434:
435: /**
436: * Merges passed URL options with current pagination state to generate a pagination URL.
437: *
438: * @param array $options Pagination/URL options array
439: * @param bool $asArray Return the URL as an array, or a URI string
440: * @param string $model Which model to paginate on
441: * @return mixed By default, returns a full pagination URL string for use in non-standard contexts (i.e. JavaScript)
442: * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/paginator.html#PaginatorHelper::url
443: */
444: public function url($options = array(), $asArray = false, $model = null) {
445: $paging = $this->params($model);
446: $url = array_merge(array_filter($paging['options']), $options);
447:
448: if (isset($url['order'])) {
449: $sort = $direction = null;
450: if (is_array($url['order'])) {
451: list($sort, $direction) = array($this->sortKey($model, $url), current($url['order']));
452: }
453: unset($url['order']);
454: $url = array_merge($url, compact('sort', 'direction'));
455: }
456: $url = $this->_convertUrlKeys($url, $paging['paramType']);
457: if (!empty($url['page']) && $url['page'] == 1) {
458: $url['page'] = null;
459: }
460: if (!empty($url['?']['page']) && $url['?']['page'] == 1) {
461: unset($url['?']['page']);
462: }
463: if (!empty($paging['queryScope'])) {
464: $url = array($paging['queryScope'] => $url);
465: if (empty($url[$paging['queryScope']]['page'])) {
466: unset($url[$paging['queryScope']]['page']);
467: }
468: }
469:
470: if ($asArray) {
471: return $url;
472: }
473: return parent::url($url);
474: }
475:
476: /**
477: * Converts the keys being used into the format set by options.paramType
478: *
479: * @param array $url Array of URL params to convert
480: * @param string $type Keys type.
481: * @return array converted URL params.
482: */
483: protected function _convertUrlKeys($url, $type) {
484: if ($type === 'named') {
485: return $url;
486: }
487: if (!isset($url['?'])) {
488: $url['?'] = array();
489: }
490: foreach ($this->options['convertKeys'] as $key) {
491: if (isset($url[$key])) {
492: $url['?'][$key] = $url[$key];
493: unset($url[$key]);
494: }
495: }
496: return $url;
497: }
498:
499: /**
500: * Protected method for generating prev/next links
501: *
502: * @param string $which Link type: 'Prev', 'Next'.
503: * @param string $title Link title.
504: * @param array $options Options list.
505: * @param string $disabledTitle Disabled link title.
506: * @param array $disabledOptions Disabled link options.
507: * @return string
508: */
509: protected function _pagingLink($which, $title = null, $options = array(), $disabledTitle = null, $disabledOptions = array()) {
510: $check = 'has' . $which;
511: $_defaults = array(
512: 'url' => array(), 'step' => 1, 'escape' => true, 'model' => null,
513: 'tag' => 'span', 'class' => strtolower($which), 'disabledTag' => null
514: );
515: $options = (array)$options + $_defaults;
516: $paging = $this->params($options['model']);
517: if (empty($disabledOptions)) {
518: $disabledOptions = $options;
519: }
520:
521: if (!$this->{$check}($options['model']) && (!empty($disabledTitle) || !empty($disabledOptions))) {
522: if (!empty($disabledTitle) && $disabledTitle !== true) {
523: $title = $disabledTitle;
524: }
525: $options = (array)$disabledOptions + array_intersect_key($options, $_defaults) + $_defaults;
526: } elseif (!$this->{$check}($options['model'])) {
527: return '';
528: }
529:
530: foreach (array_keys($_defaults) as $key) {
531: ${$key} = $options[$key];
532: unset($options[$key]);
533: }
534:
535: if ($this->{$check}($model)) {
536: $url = array_merge(
537: array('page' => $paging['page'] + ($which === 'Prev' ? $step * -1 : $step)),
538: $url
539: );
540: if ($tag === false) {
541: return $this->link(
542: $title,
543: $url,
544: compact('escape', 'model', 'class') + $options
545: );
546: }
547: $link = $this->link($title, $url, compact('escape', 'model') + $options);
548: return $this->Html->tag($tag, $link, compact('class'));
549: }
550: unset($options['rel']);
551: if (!$tag) {
552: if ($disabledTag) {
553: $tag = $disabledTag;
554: $disabledTag = null;
555: } else {
556: $tag = $_defaults['tag'];
557: }
558: }
559: if ($disabledTag) {
560: $title = $this->Html->tag($disabledTag, $title, compact('escape') + $options);
561: return $this->Html->tag($tag, $title, compact('class'));
562: }
563: return $this->Html->tag($tag, $title, compact('escape', 'class') + $options);
564: }
565:
566: /**
567: * Returns true if the given result set is not at the first page
568: *
569: * @param string $model Optional model name. Uses the default if none is specified.
570: * @return bool True if the result set is not at the first page.
571: * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/paginator.html#PaginatorHelper::hasPrev
572: */
573: public function hasPrev($model = null) {
574: return $this->_hasPage($model, 'prev');
575: }
576:
577: /**
578: * Returns true if the given result set is not at the last page
579: *
580: * @param string $model Optional model name. Uses the default if none is specified.
581: * @return bool True if the result set is not at the last page.
582: * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/paginator.html#PaginatorHelper::hasNext
583: */
584: public function hasNext($model = null) {
585: return $this->_hasPage($model, 'next');
586: }
587:
588: /**
589: * Returns true if the given result set has the page number given by $page
590: *
591: * @param string $model Optional model name. Uses the default if none is specified.
592: * @param int $page The page number - if not set defaults to 1.
593: * @return bool True if the given result set has the specified page number.
594: * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/paginator.html#PaginatorHelper::hasPage
595: */
596: public function hasPage($model = null, $page = 1) {
597: if (is_numeric($model)) {
598: $page = $model;
599: $model = null;
600: }
601: $paging = $this->params($model);
602: return $page <= $paging['pageCount'];
603: }
604:
605: /**
606: * Does $model have $page in its range?
607: *
608: * @param string $model Model name to get parameters for.
609: * @param int $page Page number you are checking.
610: * @return bool Whether model has $page
611: */
612: protected function _hasPage($model, $page) {
613: $params = $this->params($model);
614: return !empty($params) && $params[$page . 'Page'];
615: }
616:
617: /**
618: * Gets or sets the default model of the paged sets
619: *
620: * @param string|null $model Model name to set
621: * @return string|null Model name or null if the pagination isn't initialized.
622: * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/paginator.html#PaginatorHelper::defaultModel
623: */
624: public function defaultModel($model = null) {
625: if ($model !== null) {
626: $this->_defaultModel = $model;
627: }
628: if ($this->_defaultModel) {
629: return $this->_defaultModel;
630: }
631: if (empty($this->request->params['paging'])) {
632: return null;
633: }
634: list($this->_defaultModel) = array_keys($this->request->params['paging']);
635: return $this->_defaultModel;
636: }
637:
638: /**
639: * Returns a counter string for the paged result set
640: *
641: * ### Options
642: *
643: * - `model` The model to use, defaults to PaginatorHelper::defaultModel();
644: * - `format` The format string you want to use, defaults to 'pages' Which generates output like '1 of 5'
645: * set to 'range' to generate output like '1 - 3 of 13'. Can also be set to a custom string, containing
646: * the following placeholders `{:page}`, `{:pages}`, `{:current}`, `{:count}`, `{:model}`, `{:start}`, `{:end}` and any
647: * custom content you would like.
648: * - `separator` The separator string to use, default to ' of '
649: *
650: * The `%page%` style placeholders also work, but are deprecated and will be removed in a future version.
651: *
652: * @param array $options Options for the counter string. See #options for list of keys.
653: * @return string Counter string.
654: * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/paginator.html#PaginatorHelper::counter
655: */
656: public function counter($options = array()) {
657: if (is_string($options)) {
658: $options = array('format' => $options);
659: }
660:
661: $options += array(
662: 'model' => $this->defaultModel(),
663: 'format' => 'pages',
664: 'separator' => __d('cake', ' of ')
665: );
666:
667: $paging = $this->params($options['model']);
668: if (!$paging['pageCount']) {
669: $paging['pageCount'] = 1;
670: }
671: $start = 0;
672: if ($paging['count'] >= 1) {
673: $start = (($paging['page'] - 1) * $paging['limit']) + 1;
674: }
675: $end = $start + $paging['limit'] - 1;
676: if ($paging['count'] < $end) {
677: $end = $paging['count'];
678: }
679:
680: switch ($options['format']) {
681: case 'range':
682: if (!is_array($options['separator'])) {
683: $options['separator'] = array(' - ', $options['separator']);
684: }
685: $out = $start . $options['separator'][0] . $end . $options['separator'][1];
686: $out .= $paging['count'];
687: break;
688: case 'pages':
689: $out = $paging['page'] . $options['separator'] . $paging['pageCount'];
690: break;
691: default:
692: $map = array(
693: '%page%' => $paging['page'],
694: '%pages%' => $paging['pageCount'],
695: '%current%' => $paging['current'],
696: '%count%' => $paging['count'],
697: '%start%' => $start,
698: '%end%' => $end,
699: '%model%' => strtolower(Inflector::humanize(Inflector::tableize($options['model'])))
700: );
701: $out = str_replace(array_keys($map), array_values($map), $options['format']);
702:
703: $newKeys = array(
704: '{:page}', '{:pages}', '{:current}', '{:count}', '{:start}', '{:end}', '{:model}'
705: );
706: $out = str_replace($newKeys, array_values($map), $out);
707: }
708: return $out;
709: }
710:
711: /**
712: * Returns a set of numbers for the paged result set
713: * uses a modulus to decide how many numbers to show on each side of the current page (default: 8).
714: *
715: * `$this->Paginator->numbers(array('first' => 2, 'last' => 2));`
716: *
717: * Using the first and last options you can create links to the beginning and end of the page set.
718: *
719: * ### Options
720: *
721: * - `before` Content to be inserted before the numbers
722: * - `after` Content to be inserted after the numbers
723: * - `model` Model to create numbers for, defaults to PaginatorHelper::defaultModel()
724: * - `modulus` how many numbers to include on either side of the current page, defaults to 8.
725: * - `separator` Separator content defaults to ' | '
726: * - `tag` The tag to wrap links in, defaults to 'span'
727: * - `first` Whether you want first links generated, set to an integer to define the number of 'first'
728: * links to generate. If a string is set a link to the first page will be generated with the value
729: * as the title.
730: * - `last` Whether you want last links generated, set to an integer to define the number of 'last'
731: * links to generate. If a string is set a link to the last page will be generated with the value
732: * as the title.
733: * - `ellipsis` Ellipsis content, defaults to '...'
734: * - `class` Class for wrapper tag
735: * - `currentClass` Class for wrapper tag on current active page, defaults to 'current'
736: * - `currentTag` Tag to use for current page number, defaults to null
737: *
738: * @param array|bool $options Options for the numbers, (before, after, model, modulus, separator)
739: * @return string Numbers string.
740: * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/paginator.html#PaginatorHelper::numbers
741: */
742: public function numbers($options = array()) {
743: if ($options === true) {
744: $options = array(
745: 'before' => ' | ', 'after' => ' | ', 'first' => 'first', 'last' => 'last'
746: );
747: }
748:
749: $defaults = array(
750: 'tag' => 'span', 'before' => null, 'after' => null, 'model' => $this->defaultModel(), 'class' => null,
751: 'modulus' => '8', 'separator' => ' | ', 'first' => null, 'last' => null, 'ellipsis' => '...',
752: 'currentClass' => 'current', 'currentTag' => null
753: );
754: $options += $defaults;
755:
756: $params = (array)$this->params($options['model']) + array('page' => 1);
757: unset($options['model']);
758:
759: if (empty($params['pageCount']) || $params['pageCount'] <= 1) {
760: return '';
761: }
762:
763: extract($options);
764: unset($options['tag'], $options['before'], $options['after'], $options['model'],
765: $options['modulus'], $options['separator'], $options['first'], $options['last'],
766: $options['ellipsis'], $options['class'], $options['currentClass'], $options['currentTag']
767: );
768: $out = '';
769:
770: if ($modulus && $params['pageCount'] > $modulus) {
771: $half = (int)($modulus / 2);
772: $end = $params['page'] + $half;
773:
774: if ($end > $params['pageCount']) {
775: $end = $params['pageCount'];
776: }
777: $start = $params['page'] - ($modulus - ($end - $params['page']));
778: if ($start <= 1) {
779: $start = 1;
780: $end = $params['page'] + ($modulus - $params['page']) + 1;
781: }
782:
783: $firstPage = is_int($first) ? $first : 0;
784: if ($first && $start > 1) {
785: $offset = ($start <= $firstPage) ? $start - 1 : $first;
786: if ($firstPage < $start - 1) {
787: $out .= $this->first($offset, compact('tag', 'separator', 'ellipsis', 'class'));
788: } else {
789: $out .= $this->first($offset, compact('tag', 'separator', 'class', 'ellipsis') + array('after' => $separator));
790: }
791: }
792:
793: $out .= $before;
794:
795: for ($i = $start; $i < $params['page']; $i++) {
796: $out .= $this->Html->tag($tag, $this->link($i, array('page' => $i), $options), compact('class')) . $separator;
797: }
798:
799: if ($class) {
800: $currentClass .= ' ' . $class;
801: }
802: if ($currentTag) {
803: $out .= $this->Html->tag($tag, $this->Html->tag($currentTag, $params['page']), array('class' => $currentClass));
804: } else {
805: $out .= $this->Html->tag($tag, $params['page'], array('class' => $currentClass));
806: }
807: if ($i != $params['pageCount']) {
808: $out .= $separator;
809: }
810:
811: $start = $params['page'] + 1;
812: for ($i = $start; $i < $end; $i++) {
813: $out .= $this->Html->tag($tag, $this->link($i, array('page' => $i), $options), compact('class')) . $separator;
814: }
815:
816: if ($end != $params['page']) {
817: $out .= $this->Html->tag($tag, $this->link($i, array('page' => $end), $options), compact('class'));
818: }
819:
820: $out .= $after;
821:
822: if ($last && $end < $params['pageCount']) {
823: $lastPage = is_int($last) ? $last : 0;
824: $offset = ($params['pageCount'] < $end + $lastPage) ? $params['pageCount'] - $end : $last;
825: if ($offset <= $lastPage && $params['pageCount'] - $end > $lastPage) {
826: $out .= $this->last($offset, compact('tag', 'separator', 'ellipsis', 'class'));
827: } else {
828: $out .= $this->last($offset, compact('tag', 'separator', 'class', 'ellipsis') + array('before' => $separator));
829: }
830: }
831:
832: } else {
833: $out .= $before;
834:
835: for ($i = 1; $i <= $params['pageCount']; $i++) {
836: if ($i == $params['page']) {
837: if ($class) {
838: $currentClass .= ' ' . $class;
839: }
840: if ($currentTag) {
841: $out .= $this->Html->tag($tag, $this->Html->tag($currentTag, $i), array('class' => $currentClass));
842: } else {
843: $out .= $this->Html->tag($tag, $i, array('class' => $currentClass));
844: }
845: } else {
846: $out .= $this->Html->tag($tag, $this->link($i, array('page' => $i), $options), compact('class'));
847: }
848: if ($i != $params['pageCount']) {
849: $out .= $separator;
850: }
851: }
852:
853: $out .= $after;
854: }
855:
856: return $out;
857: }
858:
859: /**
860: * Returns a first or set of numbers for the first pages.
861: *
862: * `echo $this->Paginator->first('< first');`
863: *
864: * Creates a single link for the first page. Will output nothing if you are on the first page.
865: *
866: * `echo $this->Paginator->first(3);`
867: *
868: * Will create links for the first 3 pages, once you get to the third or greater page. Prior to that
869: * nothing will be output.
870: *
871: * ### Options:
872: *
873: * - `tag` The tag wrapping tag you want to use, defaults to 'span'
874: * - `after` Content to insert after the link/tag
875: * - `model` The model to use defaults to PaginatorHelper::defaultModel()
876: * - `separator` Content between the generated links, defaults to ' | '
877: * - `ellipsis` Content for ellipsis, defaults to '...'
878: *
879: * @param string|int $first if string use as label for the link. If numeric, the number of page links
880: * you want at the beginning of the range.
881: * @param array $options An array of options.
882: * @return string Numbers string.
883: * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/paginator.html#PaginatorHelper::first
884: */
885: public function first($first = '<< first', $options = array()) {
886: $options = (array)$options + array(
887: 'tag' => 'span',
888: 'after' => null,
889: 'model' => $this->defaultModel(),
890: 'separator' => ' | ',
891: 'ellipsis' => '...',
892: 'class' => null
893: );
894:
895: $params = array_merge(array('page' => 1), (array)$this->params($options['model']));
896: unset($options['model']);
897:
898: if ($params['pageCount'] <= 1) {
899: return '';
900: }
901: extract($options);
902: unset($options['tag'], $options['after'], $options['model'], $options['separator'], $options['ellipsis'], $options['class']);
903:
904: $out = '';
905:
906: if ((is_int($first) || ctype_digit($first)) && $params['page'] >= $first) {
907: if ($after === null) {
908: $after = $ellipsis;
909: }
910: for ($i = 1; $i <= $first; $i++) {
911: $out .= $this->Html->tag($tag, $this->link($i, array('page' => $i), $options), compact('class'));
912: if ($i != $first) {
913: $out .= $separator;
914: }
915: }
916: $out .= $after;
917: } elseif ($params['page'] > 1 && is_string($first)) {
918: $options += array('rel' => 'first');
919: $out = $this->Html->tag($tag, $this->link($first, array('page' => 1), $options), compact('class')) . $after;
920: }
921: return $out;
922: }
923:
924: /**
925: * Returns a last or set of numbers for the last pages.
926: *
927: * `echo $this->Paginator->last('last >');`
928: *
929: * Creates a single link for the last page. Will output nothing if you are on the last page.
930: *
931: * `echo $this->Paginator->last(3);`
932: *
933: * Will create links for the last 3 pages. Once you enter the page range, no output will be created.
934: *
935: * ### Options:
936: *
937: * - `tag` The tag wrapping tag you want to use, defaults to 'span'
938: * - `before` Content to insert before the link/tag
939: * - `model` The model to use defaults to PaginatorHelper::defaultModel()
940: * - `separator` Content between the generated links, defaults to ' | '
941: * - `ellipsis` Content for ellipsis, defaults to '...'
942: *
943: * @param string|int $last if string use as label for the link, if numeric print page numbers
944: * @param array $options Array of options
945: * @return string Numbers string.
946: * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/paginator.html#PaginatorHelper::last
947: */
948: public function last($last = 'last >>', $options = array()) {
949: $options = (array)$options + array(
950: 'tag' => 'span',
951: 'before' => null,
952: 'model' => $this->defaultModel(),
953: 'separator' => ' | ',
954: 'ellipsis' => '...',
955: 'class' => null
956: );
957:
958: $params = array_merge(array('page' => 1), (array)$this->params($options['model']));
959: unset($options['model']);
960:
961: if ($params['pageCount'] <= 1) {
962: return '';
963: }
964:
965: extract($options);
966: unset($options['tag'], $options['before'], $options['model'], $options['separator'], $options['ellipsis'], $options['class']);
967:
968: $out = '';
969: $lower = $params['pageCount'] - (int)$last + 1;
970:
971: if ((is_int($last) || ctype_digit($last)) && $params['page'] <= $lower) {
972: if ($before === null) {
973: $before = $ellipsis;
974: }
975: for ($i = $lower; $i <= $params['pageCount']; $i++) {
976: $out .= $this->Html->tag($tag, $this->link($i, array('page' => $i), $options), compact('class'));
977: if ($i != $params['pageCount']) {
978: $out .= $separator;
979: }
980: }
981: $out = $before . $out;
982: } elseif ($params['page'] < $params['pageCount'] && is_string($last)) {
983: $options += array('rel' => 'last');
984: $out = $before . $this->Html->tag(
985: $tag, $this->link($last, array('page' => $params['pageCount']), $options), compact('class')
986: );
987: }
988: return $out;
989: }
990:
991: /**
992: * Returns the meta-links for a paginated result set.
993: *
994: * `echo $this->Paginator->meta();`
995: *
996: * Echos the links directly, will output nothing if there is neither a previous nor next page.
997: *
998: * `$this->Paginator->meta(array('block' => true));`
999: *
1000: * Will append the output of the meta function to the named block - if true is passed the "meta"
1001: * block is used.
1002: *
1003: * ### Options:
1004: *
1005: * - `model` The model to use defaults to PaginatorHelper::defaultModel()
1006: * - `block` The block name to append the output to, or false/absent to return as a string
1007: *
1008: * @param array $options Array of options.
1009: * @return string|null Meta links.
1010: */
1011: public function meta($options = array()) {
1012: $model = isset($options['model']) ? $options['model'] : null;
1013: $params = $this->params($model);
1014: $urlOptions = isset($this->options['url']) ? $this->options['url'] : array();
1015: $links = array();
1016: if ($this->hasPrev()) {
1017: $links[] = $this->Html->meta(array(
1018: 'rel' => 'prev',
1019: 'link' => $this->url(array_merge($urlOptions, array('page' => $params['page'] - 1)), true)
1020: ));
1021: }
1022: if ($this->hasNext()) {
1023: $links[] = $this->Html->meta(array(
1024: 'rel' => 'next',
1025: 'link' => $this->url(array_merge($urlOptions, array('page' => $params['page'] + 1)), true)
1026: ));
1027: }
1028: $out = implode($links);
1029: if (empty($options['block'])) {
1030: return $out;
1031: }
1032: if ($options['block'] === true) {
1033: $options['block'] = __FUNCTION__;
1034: }
1035: $this->_View->append($options['block'], $out);
1036: }
1037:
1038: }
1039: