PaginatorHelper.php 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://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. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 1.2.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\View\Helper;
  16. use Cake\Utility\Inflector;
  17. use Cake\View\Helper;
  18. use Cake\View\StringTemplateTrait;
  19. use Cake\View\View;
  20. /**
  21. * Pagination Helper class for easy generation of pagination links.
  22. *
  23. * PaginationHelper encloses all methods needed when working with pagination.
  24. *
  25. * @property UrlHelper $Url
  26. * @property NumberHelper $Number
  27. * @property HtmlHelper $Html
  28. * @link http://book.cakephp.org/3.0/en/views/helpers/paginator.html
  29. */
  30. class PaginatorHelper extends Helper
  31. {
  32. use StringTemplateTrait;
  33. /**
  34. * List of helpers used by this helper
  35. *
  36. * @var array
  37. */
  38. public $helpers = ['Url', 'Number', 'Html'];
  39. /**
  40. * Default config for this class
  41. *
  42. * Options: Holds the default options for pagination links
  43. *
  44. * The values that may be specified are:
  45. *
  46. * - `url` Url of the action. See Router::url()
  47. * - `url['sort']` the key that the recordset is sorted.
  48. * - `url['direction']` Direction of the sorting (default: 'asc').
  49. * - `url['page']` Page number to use in links.
  50. * - `model` The name of the model.
  51. * - `escape` Defines if the title field for the link should be escaped (default: true).
  52. *
  53. * Templates: the templates used by this class
  54. *
  55. * @var array
  56. */
  57. protected $_defaultConfig = [
  58. 'options' => [],
  59. 'templates' => [
  60. 'nextActive' => '<li class="next"><a rel="next" href="{{url}}">{{text}}</a></li>',
  61. 'nextDisabled' => '<li class="next disabled"><a href="" onclick="return false;">{{text}}</a></li>',
  62. 'prevActive' => '<li class="prev"><a rel="prev" href="{{url}}">{{text}}</a></li>',
  63. 'prevDisabled' => '<li class="prev disabled"><a href="" onclick="return false;">{{text}}</a></li>',
  64. 'counterRange' => '{{start}} - {{end}} of {{count}}',
  65. 'counterPages' => '{{page}} of {{pages}}',
  66. 'first' => '<li class="first"><a href="{{url}}">{{text}}</a></li>',
  67. 'last' => '<li class="last"><a href="{{url}}">{{text}}</a></li>',
  68. 'number' => '<li><a href="{{url}}">{{text}}</a></li>',
  69. 'current' => '<li class="active"><a href="">{{text}}</a></li>',
  70. 'ellipsis' => '<li class="ellipsis">...</li>',
  71. 'sort' => '<a href="{{url}}">{{text}}</a>',
  72. 'sortAsc' => '<a class="asc" href="{{url}}">{{text}}</a>',
  73. 'sortDesc' => '<a class="desc" href="{{url}}">{{text}}</a>',
  74. 'sortAscLocked' => '<a class="asc locked" href="{{url}}">{{text}}</a>',
  75. 'sortDescLocked' => '<a class="desc locked" href="{{url}}">{{text}}</a>',
  76. ]
  77. ];
  78. /**
  79. * Constructor. Overridden to merge passed args with URL options.
  80. *
  81. * @param \Cake\View\View $View The View this helper is being attached to.
  82. * @param array $config Configuration settings for the helper.
  83. */
  84. public function __construct(View $View, array $config = [])
  85. {
  86. parent::__construct($View, $config);
  87. $this->config(
  88. 'options.url',
  89. array_merge($this->request->params['pass'], $this->request->query)
  90. );
  91. }
  92. /**
  93. * Gets the current paging parameters from the resultset for the given model
  94. *
  95. * @param string|null $model Optional model name. Uses the default if none is specified.
  96. * @return array The array of paging parameters for the paginated resultset.
  97. */
  98. public function params($model = null)
  99. {
  100. if (empty($model)) {
  101. $model = $this->defaultModel();
  102. }
  103. if (!isset($this->request->params['paging']) || empty($this->request->params['paging'][$model])) {
  104. return [];
  105. }
  106. return $this->request->params['paging'][$model];
  107. }
  108. /**
  109. * Convenience access to any of the paginator params.
  110. *
  111. * @param string $key Key of the paginator params array to retrieve.
  112. * @param string|null $model Optional model name. Uses the default if none is specified.
  113. * @return mixed Content of the requested param.
  114. */
  115. public function param($key, $model = null)
  116. {
  117. $params = $this->params($model);
  118. if (!isset($params[$key])) {
  119. return null;
  120. }
  121. return $params[$key];
  122. }
  123. /**
  124. * Sets default options for all pagination links
  125. *
  126. * @param array $options Default options for pagination links.
  127. * See PaginatorHelper::$options for list of keys.
  128. * @return void
  129. */
  130. public function options(array $options = [])
  131. {
  132. if (!empty($options['paging'])) {
  133. if (!isset($this->request->params['paging'])) {
  134. $this->request->params['paging'] = [];
  135. }
  136. $this->request->params['paging'] = $options['paging'] + $this->request->params['paging'];
  137. unset($options['paging']);
  138. }
  139. $model = $this->defaultModel();
  140. if (!empty($options[$model])) {
  141. if (!isset($this->request->params['paging'][$model])) {
  142. $this->request->params['paging'][$model] = [];
  143. }
  144. $this->request->params['paging'][$model] = $options[$model] + $this->request->params['paging'][$model];
  145. unset($options[$model]);
  146. }
  147. $this->_config['options'] = array_filter($options + $this->_config['options']);
  148. }
  149. /**
  150. * Gets the current page of the recordset for the given model
  151. *
  152. * @param string|null $model Optional model name. Uses the default if none is specified.
  153. * @return int The current page number of the recordset.
  154. * @link http://book.cakephp.org/3.0/en/views/helpers/paginator.html#checking-the-pagination-state
  155. */
  156. public function current($model = null)
  157. {
  158. $params = $this->params($model);
  159. if (isset($params['page'])) {
  160. return $params['page'];
  161. }
  162. return 1;
  163. }
  164. /**
  165. * Gets the current key by which the recordset is sorted
  166. *
  167. * @param string|null $model Optional model name. Uses the default if none is specified.
  168. * @param array $options Options for pagination links. See #options for list of keys.
  169. * @return string|null The name of the key by which the recordset is being sorted, or
  170. * null if the results are not currently sorted.
  171. * @link http://book.cakephp.org/3.0/en/views/helpers/paginator.html#creating-sort-links
  172. */
  173. public function sortKey($model = null, array $options = [])
  174. {
  175. if (empty($options)) {
  176. $options = $this->params($model);
  177. }
  178. if (!empty($options['sort'])) {
  179. return $options['sort'];
  180. }
  181. return null;
  182. }
  183. /**
  184. * Gets the current direction the recordset is sorted
  185. *
  186. * @param string|null $model Optional model name. Uses the default if none is specified.
  187. * @param array $options Options for pagination links. See #options for list of keys.
  188. * @return string The direction by which the recordset is being sorted, or
  189. * null if the results are not currently sorted.
  190. * @link http://book.cakephp.org/3.0/en/views/helpers/paginator.html#creating-sort-links
  191. */
  192. public function sortDir($model = null, array $options = [])
  193. {
  194. $dir = null;
  195. if (empty($options)) {
  196. $options = $this->params($model);
  197. }
  198. if (isset($options['direction'])) {
  199. $dir = strtolower($options['direction']);
  200. }
  201. if ($dir === 'desc') {
  202. return 'desc';
  203. }
  204. return 'asc';
  205. }
  206. /**
  207. * Generate an active/inactive link for next/prev methods.
  208. *
  209. * @param string|bool $text The enabled text for the link.
  210. * @param bool $enabled Whether or not the enabled/disabled version should be created.
  211. * @param array $options An array of options from the calling method.
  212. * @param array $templates An array of templates with the 'active' and 'disabled' keys.
  213. * @return string Generated HTML
  214. */
  215. protected function _toggledLink($text, $enabled, $options, $templates)
  216. {
  217. $template = $templates['active'];
  218. if (!$enabled) {
  219. $text = $options['disabledTitle'];
  220. $template = $templates['disabled'];
  221. }
  222. if (!$enabled && $text === false) {
  223. return '';
  224. }
  225. $text = $options['escape'] ? h($text) : $text;
  226. if (!$enabled) {
  227. return $this->templater()->format($template, [
  228. 'text' => $text,
  229. ]);
  230. }
  231. $paging = $this->params($options['model']);
  232. $url = array_merge(
  233. $options['url'],
  234. ['page' => $paging['page'] + $options['step']]
  235. );
  236. $url = $this->generateUrl($url, $options['model']);
  237. return $this->templater()->format($template, [
  238. 'url' => $url,
  239. 'text' => $text,
  240. ]);
  241. }
  242. /**
  243. * Generates a "previous" link for a set of paged records
  244. *
  245. * ### Options:
  246. *
  247. * - `disabledTitle` The text to used when the link is disabled. This
  248. * defaults to the same text at the active link. Setting to false will cause
  249. * this method to return ''.
  250. * - `escape` Whether you want the contents html entity encoded, defaults to true
  251. * - `model` The model to use, defaults to PaginatorHelper::defaultModel()
  252. * - `url` An array of additional URL options to use for link generation.
  253. *
  254. * @param string $title Title for the link. Defaults to '<< Previous'.
  255. * @param array $options Options for pagination link. See above for list of keys.
  256. * @return string A "previous" link or a disabled link.
  257. * @link http://book.cakephp.org/3.0/en/views/helpers/paginator.html#creating-jump-links
  258. */
  259. public function prev($title = '<< Previous', array $options = [])
  260. {
  261. $defaults = [
  262. 'url' => [],
  263. 'model' => $this->defaultModel(),
  264. 'disabledTitle' => $title,
  265. 'escape' => true,
  266. ];
  267. $options += $defaults;
  268. $options['step'] = -1;
  269. $enabled = $this->hasPrev($options['model']);
  270. $templates = [
  271. 'active' => 'prevActive',
  272. 'disabled' => 'prevDisabled'
  273. ];
  274. return $this->_toggledLink($title, $enabled, $options, $templates);
  275. }
  276. /**
  277. * Generates a "next" link for a set of paged records
  278. *
  279. * ### Options:
  280. *
  281. * - `disabledTitle` The text to used when the link is disabled. This
  282. * defaults to the same text at the active link. Setting to false will cause
  283. * this method to return ''.
  284. * - `escape` Whether you want the contents html entity encoded, defaults to true
  285. * - `model` The model to use, defaults to PaginatorHelper::defaultModel()
  286. * - `url` An array of additional URL options to use for link generation.
  287. *
  288. * @param string $title Title for the link. Defaults to 'Next >>'.
  289. * @param array $options Options for pagination link. See above for list of keys.
  290. * @return string A "next" link or $disabledTitle text if the link is disabled.
  291. * @link http://book.cakephp.org/3.0/en/views/helpers/paginator.html#creating-jump-links
  292. */
  293. public function next($title = 'Next >>', array $options = [])
  294. {
  295. $defaults = [
  296. 'url' => [],
  297. 'model' => $this->defaultModel(),
  298. 'disabledTitle' => $title,
  299. 'escape' => true,
  300. ];
  301. $options += $defaults;
  302. $options['step'] = 1;
  303. $enabled = $this->hasNext($options['model']);
  304. $templates = [
  305. 'active' => 'nextActive',
  306. 'disabled' => 'nextDisabled'
  307. ];
  308. return $this->_toggledLink($title, $enabled, $options, $templates);
  309. }
  310. /**
  311. * Generates a sorting link. Sets named parameters for the sort and direction. Handles
  312. * direction switching automatically.
  313. *
  314. * ### Options:
  315. *
  316. * - `escape` Whether you want the contents html entity encoded, defaults to true.
  317. * - `model` The model to use, defaults to PaginatorHelper::defaultModel().
  318. * - `direction` The default direction to use when this link isn't active.
  319. * - `lock` Lock direction. Will only use the default direction then, defaults to false.
  320. *
  321. * @param string $key The name of the key that the recordset should be sorted.
  322. * @param string|null $title Title for the link. If $title is null $key will be used
  323. * for the title and will be generated by inflection.
  324. * @param array $options Options for sorting link. See above for list of keys.
  325. * @return string A link sorting default by 'asc'. If the resultset is sorted 'asc' by the specified
  326. * key the returned link will sort by 'desc'.
  327. * @link http://book.cakephp.org/3.0/en/views/helpers/paginator.html#creating-sort-links
  328. */
  329. public function sort($key, $title = null, array $options = [])
  330. {
  331. $options += ['url' => [], 'model' => null, 'escape' => true];
  332. $url = $options['url'];
  333. unset($options['url']);
  334. if (empty($title)) {
  335. $title = $key;
  336. if (strpos($title, '.') !== false) {
  337. $title = str_replace('.', ' ', $title);
  338. }
  339. $title = __(Inflector::humanize(preg_replace('/_id$/', '', $title)));
  340. }
  341. $defaultDir = isset($options['direction']) ? $options['direction'] : 'asc';
  342. unset($options['direction']);
  343. $locked = isset($options['lock']) ? $options['lock'] : false;
  344. unset($options['lock']);
  345. $sortKey = $this->sortKey($options['model']);
  346. $defaultModel = $this->defaultModel();
  347. $isSorted = (
  348. $sortKey === $key ||
  349. $sortKey === $defaultModel . '.' . $key ||
  350. $key === $defaultModel . '.' . $sortKey
  351. );
  352. $template = 'sort';
  353. $dir = $defaultDir;
  354. if ($isSorted) {
  355. if ($locked) {
  356. $template = $dir === 'asc' ? 'sortDescLocked' : 'sortAscLocked';
  357. } else {
  358. $dir = $this->sortDir($options['model']) === 'asc' ? 'desc' : 'asc';
  359. $template = $dir === 'asc' ? 'sortDesc' : 'sortAsc';
  360. }
  361. }
  362. if (is_array($title) && array_key_exists($dir, $title)) {
  363. $title = $title[$dir];
  364. }
  365. $url = array_merge(
  366. ['sort' => $key, 'direction' => $dir],
  367. $url,
  368. ['order' => null]
  369. );
  370. $vars = [
  371. 'text' => $options['escape'] ? h($title) : $title,
  372. 'url' => $this->generateUrl($url, $options['model']),
  373. ];
  374. return $this->templater()->format($template, $vars);
  375. }
  376. /**
  377. * Merges passed URL options with current pagination state to generate a pagination URL.
  378. *
  379. * @param array $options Pagination/URL options array
  380. * @param string|null $model Which model to paginate on
  381. * @param bool $full If true, the full base URL will be prepended to the result
  382. * @return mixed By default, returns a full pagination URL string for use in non-standard contexts (i.e. JavaScript)
  383. * @link http://book.cakephp.org/3.0/en/views/helpers/paginator.html#generating-pagination-urls
  384. */
  385. public function generateUrl(array $options = [], $model = null, $full = false)
  386. {
  387. $paging = $this->params($model);
  388. $paging += ['page' => null, 'sort' => null, 'direction' => null, 'limit' => null];
  389. $url = [
  390. 'page' => $paging['page'],
  391. 'limit' => $paging['limit'],
  392. 'sort' => $paging['sort'],
  393. 'direction' => $paging['direction'],
  394. ];
  395. if (!empty($this->_config['options']['url'])) {
  396. $url = array_merge($url, $this->_config['options']['url']);
  397. }
  398. $url = array_filter($url, function ($value) {
  399. return ($value || is_numeric($value));
  400. });
  401. $url = array_merge($url, $options);
  402. if (!empty($url['page']) && $url['page'] == 1) {
  403. $url['page'] = false;
  404. }
  405. if (isset($paging['sortDefault'], $paging['directionDefault'], $url['sort'], $url['direction']) &&
  406. $url['sort'] === $paging['sortDefault'] &&
  407. $url['direction'] === $paging['directionDefault']
  408. ) {
  409. $url['sort'] = $url['direction'] = null;
  410. }
  411. return $this->Url->build($url, $full);
  412. }
  413. /**
  414. * Returns true if the given result set is not at the first page
  415. *
  416. * @param string|null $model Optional model name. Uses the default if none is specified.
  417. * @return bool True if the result set is not at the first page.
  418. * @link http://book.cakephp.org/3.0/en/views/helpers/paginator.html#checking-the-pagination-state
  419. */
  420. public function hasPrev($model = null)
  421. {
  422. return $this->_hasPage($model, 'prev');
  423. }
  424. /**
  425. * Returns true if the given result set is not at the last page
  426. *
  427. * @param string|null $model Optional model name. Uses the default if none is specified.
  428. * @return bool True if the result set is not at the last page.
  429. * @link http://book.cakephp.org/3.0/en/views/helpers/paginator.html#checking-the-pagination-state
  430. */
  431. public function hasNext($model = null)
  432. {
  433. return $this->_hasPage($model, 'next');
  434. }
  435. /**
  436. * Returns true if the given result set has the page number given by $page
  437. *
  438. * @param string $model Optional model name. Uses the default if none is specified.
  439. * @param int $page The page number - if not set defaults to 1.
  440. * @return bool True if the given result set has the specified page number.
  441. * @link http://book.cakephp.org/3.0/en/views/helpers/paginator.html#checking-the-pagination-state
  442. */
  443. public function hasPage($model = null, $page = 1)
  444. {
  445. if (is_numeric($model)) {
  446. $page = $model;
  447. $model = null;
  448. }
  449. $paging = $this->params($model);
  450. if ($paging === []) {
  451. return false;
  452. }
  453. return $page <= $paging['pageCount'];
  454. }
  455. /**
  456. * Does $model have $page in its range?
  457. *
  458. * @param string $model Model name to get parameters for.
  459. * @param int $page Page number you are checking.
  460. * @return bool Whether model has $page
  461. */
  462. protected function _hasPage($model, $page)
  463. {
  464. $params = $this->params($model);
  465. return !empty($params) && $params[$page . 'Page'];
  466. }
  467. /**
  468. * Gets the default model of the paged sets
  469. *
  470. * @return string|null Model name or null if the pagination isn't initialized.
  471. */
  472. public function defaultModel()
  473. {
  474. if ($this->_defaultModel) {
  475. return $this->_defaultModel;
  476. }
  477. if (empty($this->request->params['paging'])) {
  478. return null;
  479. }
  480. list($this->_defaultModel) = array_keys($this->request->params['paging']);
  481. return $this->_defaultModel;
  482. }
  483. /**
  484. * Returns a counter string for the paged result set
  485. *
  486. * ### Options
  487. *
  488. * - `model` The model to use, defaults to PaginatorHelper::defaultModel();
  489. * - `format` The format string you want to use, defaults to 'pages' Which generates output like '1 of 5'
  490. * set to 'range' to generate output like '1 - 3 of 13'. Can also be set to a custom string, containing
  491. * the following placeholders `{{page}}`, `{{pages}}`, `{{current}}`, `{{count}}`, `{{model}}`, `{{start}}`, `{{end}}` and any
  492. * custom content you would like.
  493. *
  494. * @param string|array $options Options for the counter string. See #options for list of keys.
  495. * If string it will be used as format.
  496. * @return string Counter string.
  497. * @link http://book.cakephp.org/3.0/en/views/helpers/paginator.html#creating-a-page-counter
  498. */
  499. public function counter($options = [])
  500. {
  501. if (is_string($options)) {
  502. $options = ['format' => $options];
  503. }
  504. $options += [
  505. 'model' => $this->defaultModel(),
  506. 'format' => 'pages',
  507. ];
  508. $paging = $this->params($options['model']);
  509. if (!$paging['pageCount']) {
  510. $paging['pageCount'] = 1;
  511. }
  512. $start = 0;
  513. if ($paging['count'] >= 1) {
  514. $start = (($paging['page'] - 1) * $paging['perPage']) + 1;
  515. }
  516. $end = $start + $paging['perPage'] - 1;
  517. if ($paging['count'] < $end) {
  518. $end = $paging['count'];
  519. }
  520. switch ($options['format']) {
  521. case 'range':
  522. case 'pages':
  523. $template = 'counter' . ucfirst($options['format']);
  524. break;
  525. default:
  526. $template = 'counterCustom';
  527. $this->templater()->add([$template => $options['format']]);
  528. }
  529. $map = array_map([$this->Number, 'format'], [
  530. 'page' => $paging['page'],
  531. 'pages' => $paging['pageCount'],
  532. 'current' => $paging['current'],
  533. 'count' => $paging['count'],
  534. 'start' => $start,
  535. 'end' => $end
  536. ]);
  537. $map += [
  538. 'model' => strtolower(Inflector::humanize(Inflector::tableize($options['model'])))
  539. ];
  540. return $this->templater()->format($template, $map);
  541. }
  542. /**
  543. * Returns a set of numbers for the paged result set
  544. * uses a modulus to decide how many numbers to show on each side of the current page (default: 8).
  545. *
  546. * ```
  547. * $this->Paginator->numbers(['first' => 2, 'last' => 2]);
  548. * ```
  549. *
  550. * Using the first and last options you can create links to the beginning and end of the page set.
  551. *
  552. * ### Options
  553. *
  554. * - `before` Content to be inserted before the numbers, but after the first links.
  555. * - `after` Content to be inserted after the numbers, but before the last links.
  556. * - `model` Model to create numbers for, defaults to PaginatorHelper::defaultModel()
  557. * - `modulus` How many numbers to include on either side of the current page, defaults to 8.
  558. * Set to `false` to disable and to show all numbers.
  559. * - `first` Whether you want first links generated, set to an integer to define the number of 'first'
  560. * links to generate.
  561. * - `last` Whether you want last links generated, set to an integer to define the number of 'last'
  562. * links to generate.
  563. * - `templates` An array of templates, or template file name containing the templates you'd like to
  564. * use when generating the numbers. The helper's original templates will be restored once
  565. * numbers() is done.
  566. * - `url` An array of additional URL options to use for link generation.
  567. *
  568. * The generated number links will include the 'ellipsis' template when the `first` and `last` options
  569. * and the number of pages exceed the modulus. For example if you have 25 pages, and use the first/last
  570. * options and a modulus of 8, ellipsis content will be inserted after the first and last link sets.
  571. *
  572. * @param array $options Options for the numbers.
  573. * @return string numbers string.
  574. * @link http://book.cakephp.org/3.0/en/views/helpers/paginator.html#creating-page-number-links
  575. */
  576. public function numbers(array $options = [])
  577. {
  578. $defaults = [
  579. 'before' => null, 'after' => null, 'model' => $this->defaultModel(),
  580. 'modulus' => 8, 'first' => null, 'last' => null, 'url' => []
  581. ];
  582. $options += $defaults;
  583. $params = (array)$this->params($options['model']) + ['page' => 1];
  584. if ($params['pageCount'] <= 1) {
  585. return false;
  586. }
  587. $templater = $this->templater();
  588. if (isset($options['templates'])) {
  589. $templater->push();
  590. $method = is_string($options['templates']) ? 'load' : 'add';
  591. $templater->{$method}($options['templates']);
  592. }
  593. if ($options['modulus'] !== false && $params['pageCount'] > $options['modulus']) {
  594. $out = $this->_modulusNumbers($templater, $params, $options);
  595. } else {
  596. $out = $this->_numbers($templater, $params, $options);
  597. }
  598. if (isset($options['templates'])) {
  599. $templater->pop();
  600. }
  601. return $out;
  602. }
  603. /**
  604. * Calculates the start and end for the pagination numbers.
  605. *
  606. * @param array $params Params from the numbers() method.
  607. * @param array $options Options from the numbers() method.
  608. * @return array An array with the start and end numbers.
  609. */
  610. protected function _getNumbersStartAndEnd($params, $options)
  611. {
  612. $half = (int)($options['modulus'] / 2);
  613. $end = $params['page'] + $half;
  614. if ($end > $params['pageCount']) {
  615. $end = $params['pageCount'];
  616. }
  617. $start = $params['page'] - ($options['modulus'] - ($end - $params['page']));
  618. if ($start <= 1) {
  619. $start = 1;
  620. $end = $params['page'] + ($options['modulus'] - $params['page']) + 1;
  621. }
  622. return [$start, $end];
  623. }
  624. /**
  625. * Formats a number for the paginator number output.
  626. *
  627. * @param \Cake\View\StringTemplate $templater StringTemplate instance.
  628. * @param array $options Options from the numbers() method.
  629. * @return string
  630. */
  631. protected function _formatNumber($templater, $options)
  632. {
  633. $url = array_merge($options['url'], ['page' => $options['page']]);
  634. $vars = [
  635. 'text' => $options['text'],
  636. 'url' => $this->generateUrl($url, $options['model']),
  637. ];
  638. return $templater->format('number', $vars);
  639. }
  640. /**
  641. * Generates the numbers for the paginator numbers() method.
  642. *
  643. * @param \Cake\View\StringTemplate $templater StringTemplate instance.
  644. * @param array $params Params from the numbers() method.
  645. * @param array $options Options from the numbers() method.
  646. * @return string Markup output.
  647. */
  648. protected function _modulusNumbers($templater, $params, $options)
  649. {
  650. $out = '';
  651. $ellipsis = $templater->format('ellipsis', []);
  652. list($start, $end) = $this->_getNumbersStartAndEnd($params, $options);
  653. $out .= $this->_firstNumber($ellipsis, $params, $start, $options);
  654. $out .= $options['before'];
  655. for ($i = $start; $i < $params['page']; $i++) {
  656. $out .= $this->_formatNumber($templater, [
  657. 'text' => $i,
  658. 'page' => $i,
  659. 'model' => $options['model'],
  660. 'url' => $options['url'],
  661. ]);
  662. }
  663. $url = array_merge($options['url'], ['page' => $params['page']]);
  664. $out .= $templater->format('current', [
  665. 'text' => $params['page'],
  666. 'url' => $this->generateUrl($url, $options['model']),
  667. ]);
  668. $start = $params['page'] + 1;
  669. for ($i = $start; $i < $end; $i++) {
  670. $out .= $this->_formatNumber($templater, [
  671. 'text' => $i,
  672. 'page' => $i,
  673. 'model' => $options['model'],
  674. 'url' => $options['url'],
  675. ]);
  676. }
  677. if ($end != $params['page']) {
  678. $out .= $this->_formatNumber($templater, [
  679. 'text' => $i,
  680. 'page' => $end,
  681. 'model' => $options['model'],
  682. 'url' => $options['url'],
  683. ]);
  684. }
  685. $out .= $options['after'];
  686. $out .= $this->_lastNumber($ellipsis, $params, $end, $options);
  687. return $out;
  688. }
  689. /**
  690. * Generates the first number for the paginator numbers() method.
  691. *
  692. * @param \Cake\View\StringTemplate $ellipsis StringTemplate instance.
  693. * @param array $params Params from the numbers() method.
  694. * @param int $start Start number.
  695. * @param array $options Options from the numbers() method.
  696. * @return string Markup output.
  697. */
  698. protected function _firstNumber($ellipsis, $params, $start, $options)
  699. {
  700. $out = '';
  701. if ($options['first'] && $start > 1) {
  702. $offset = ($start <= (int)$options['first']) ? $start - 1 : $options['first'];
  703. $out .= $this->first($offset, $options);
  704. if ($offset < $start - 1) {
  705. $out .= $ellipsis;
  706. }
  707. }
  708. return $out;
  709. }
  710. /**
  711. * Generates the last number for the paginator numbers() method.
  712. *
  713. * @param \Cake\View\StringTemplate $ellipsis StringTemplate instance.
  714. * @param array $params Params from the numbers() method.
  715. * @param int $end End number.
  716. * @param array $options Options from the numbers() method.
  717. * @return string Markup output.
  718. */
  719. protected function _lastNumber($ellipsis, $params, $end, $options)
  720. {
  721. $out = '';
  722. if ($options['last'] && $end < $params['pageCount']) {
  723. $offset = ($params['pageCount'] < $end + (int)$options['last']) ? $params['pageCount'] - $end : $options['last'];
  724. if ($offset <= $options['last'] && $params['pageCount'] - $end > $offset) {
  725. $out .= $ellipsis;
  726. }
  727. $out .= $this->last($offset, $options);
  728. }
  729. return $out;
  730. }
  731. /**
  732. * Generates the numbers for the paginator numbers() method.
  733. *
  734. * @param \Cake\View\StringTemplate $templater StringTemplate instance.
  735. * @param array $params Params from the numbers() method.
  736. * @param array $options Options from the numbers() method.
  737. * @return string Markup output.
  738. */
  739. protected function _numbers($templater, $params, $options)
  740. {
  741. $out = '';
  742. $out .= $options['before'];
  743. for ($i = 1; $i <= $params['pageCount']; $i++) {
  744. $url = array_merge($options['url'], ['page' => $i]);
  745. if ($i == $params['page']) {
  746. $out .= $templater->format('current', [
  747. 'text' => $params['page'],
  748. 'url' => $this->generateUrl($url, $options['model']),
  749. ]);
  750. } else {
  751. $vars = [
  752. 'text' => $i,
  753. 'url' => $this->generateUrl($url, $options['model']),
  754. ];
  755. $out .= $templater->format('number', $vars);
  756. }
  757. }
  758. $out .= $options['after'];
  759. return $out;
  760. }
  761. /**
  762. * Returns a first or set of numbers for the first pages.
  763. *
  764. * ```
  765. * echo $this->Paginator->first('< first');
  766. * ```
  767. *
  768. * Creates a single link for the first page. Will output nothing if you are on the first page.
  769. *
  770. * ```
  771. * echo $this->Paginator->first(3);
  772. * ```
  773. *
  774. * Will create links for the first 3 pages, once you get to the third or greater page. Prior to that
  775. * nothing will be output.
  776. *
  777. * ### Options:
  778. *
  779. * - `model` The model to use defaults to PaginatorHelper::defaultModel()
  780. * - `escape` Whether or not to HTML escape the text.
  781. * - `url` An array of additional URL options to use for link generation.
  782. *
  783. * @param string|int $first if string use as label for the link. If numeric, the number of page links
  784. * you want at the beginning of the range.
  785. * @param array $options An array of options.
  786. * @return string numbers string.
  787. * @link http://book.cakephp.org/3.0/en/views/helpers/paginator.html#creating-jump-links
  788. */
  789. public function first($first = '<< first', array $options = [])
  790. {
  791. $options += [
  792. 'url' => [],
  793. 'model' => $this->defaultModel(),
  794. 'escape' => true
  795. ];
  796. $params = $this->params($options['model']);
  797. if ($params['pageCount'] <= 1) {
  798. return false;
  799. }
  800. $out = '';
  801. if (is_int($first) && $params['page'] >= $first) {
  802. for ($i = 1; $i <= $first; $i++) {
  803. $url = array_merge($options['url'], ['page' => $i]);
  804. $out .= $this->templater()->format('number', [
  805. 'url' => $this->generateUrl($url, $options['model']),
  806. 'text' => $i
  807. ]);
  808. }
  809. } elseif ($params['page'] > 1 && is_string($first)) {
  810. $first = $options['escape'] ? h($first) : $first;
  811. $out .= $this->templater()->format('first', [
  812. 'url' => $this->generateUrl(['page' => 1], $options['model']),
  813. 'text' => $first
  814. ]);
  815. }
  816. return $out;
  817. }
  818. /**
  819. * Returns a last or set of numbers for the last pages.
  820. *
  821. * ```
  822. * echo $this->Paginator->last('last >');
  823. * ```
  824. *
  825. * Creates a single link for the last page. Will output nothing if you are on the last page.
  826. *
  827. * ```
  828. * echo $this->Paginator->last(3);
  829. * ```
  830. *
  831. * Will create links for the last 3 pages. Once you enter the page range, no output will be created.
  832. *
  833. * ### Options:
  834. *
  835. * - `model` The model to use defaults to PaginatorHelper::defaultModel()
  836. * - `escape` Whether or not to HTML escape the text.
  837. * - `url` An array of additional URL options to use for link generation.
  838. *
  839. * @param string|int $last if string use as label for the link, if numeric print page numbers
  840. * @param array $options Array of options
  841. * @return string numbers string.
  842. * @link http://book.cakephp.org/3.0/en/views/helpers/paginator.html#creating-jump-links
  843. */
  844. public function last($last = 'last >>', array $options = [])
  845. {
  846. $options += [
  847. 'model' => $this->defaultModel(),
  848. 'escape' => true,
  849. 'url' => []
  850. ];
  851. $params = $this->params($options['model']);
  852. if ($params['pageCount'] <= 1) {
  853. return false;
  854. }
  855. $out = '';
  856. $lower = $params['pageCount'] - $last + 1;
  857. if (is_int($last) && $params['page'] <= $lower) {
  858. for ($i = $lower; $i <= $params['pageCount']; $i++) {
  859. $url = array_merge($options['url'], ['page' => $i]);
  860. $out .= $this->templater()->format('number', [
  861. 'url' => $this->generateUrl($url, $options['model']),
  862. 'text' => $i
  863. ]);
  864. }
  865. } elseif ($params['page'] < $params['pageCount'] && is_string($last)) {
  866. $last = $options['escape'] ? h($last) : $last;
  867. $out .= $this->templater()->format('last', [
  868. 'url' => $this->generateUrl(['page' => $params['pageCount']], $options['model']),
  869. 'text' => $last
  870. ]);
  871. }
  872. return $out;
  873. }
  874. /**
  875. * Returns the meta-links for a paginated result set.
  876. *
  877. * ```
  878. * echo $this->Paginator->meta();
  879. * ```
  880. *
  881. * Echos the links directly, will output nothing if there is neither a previous nor next page.
  882. *
  883. * ```
  884. * $this->Paginator->meta(['block' => true]);
  885. * ```
  886. *
  887. * Will append the output of the meta function to the named block - if true is passed the "meta"
  888. * block is used.
  889. *
  890. * ### Options:
  891. *
  892. * - `model` The model to use defaults to PaginatorHelper::defaultModel()
  893. * - `block` The block name to append the output to, or false/absenst to return as a string
  894. *
  895. * @param array $options Array of options
  896. * @return string|null Meta links
  897. */
  898. public function meta(array $options = [])
  899. {
  900. $model = isset($options['model']) ? $options['model'] : null;
  901. $params = $this->params($model);
  902. $links = [];
  903. if ($this->hasPrev()) {
  904. $links[] = $this->Html->templater()->format('css', [
  905. 'rel' => 'prev',
  906. 'url' => $this->generateUrl(['page' => $params['page'] - 1], null, true)
  907. ]);
  908. }
  909. if ($this->hasNext()) {
  910. $links[] = $this->Html->templater()->format('css', [
  911. 'rel' => 'next',
  912. 'url' => $this->generateUrl(['page' => $params['page'] + 1], null, true)
  913. ]);
  914. }
  915. $out = implode($links);
  916. if (empty($options['block'])) {
  917. return $out;
  918. }
  919. if ($options['block'] === true) {
  920. $options['block'] = __FUNCTION__;
  921. }
  922. $this->_View->append($options['block'], $out);
  923. }
  924. /**
  925. * Event listeners.
  926. *
  927. * @return array
  928. */
  929. public function implementedEvents()
  930. {
  931. return [];
  932. }
  933. }