PaginatorHelper.php 28 KB

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