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 21App::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 */ 32class 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