AbstractPagination.php 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. <?php
  2. /**
  3. * @package Grav\Framework\Pagination
  4. *
  5. * @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
  6. * @license MIT License; see LICENSE file for details.
  7. */
  8. namespace Grav\Framework\Pagination;
  9. use Grav\Framework\Pagination\Interfaces\PaginationInterface;
  10. use Grav\Framework\Route\Route;
  11. class AbstractPagination implements PaginationInterface
  12. {
  13. /** @var Route Base rouse used for the pagination. */
  14. protected $route;
  15. /** @var int|null Current page. */
  16. protected $page;
  17. /** @var int|null The record number to start displaying from. */
  18. protected $start;
  19. /** @var int Number of records to display per page. */
  20. protected $limit;
  21. /** @var int Total number of records. */
  22. protected $total;
  23. /** @var array Pagination options */
  24. protected $options;
  25. /** @var bool View all flag. */
  26. protected $viewAll;
  27. /** @var int Total number of pages. */
  28. protected $pages;
  29. /** @var int Value pagination object begins at. */
  30. protected $pagesStart;
  31. /** @var int Value pagination object ends at .*/
  32. protected $pagesStop;
  33. /** @var array */
  34. protected $defaultOptions = [
  35. 'type' => 'page',
  36. 'limit' => 10,
  37. 'display' => 5,
  38. 'opening' => 0,
  39. 'ending' => 0,
  40. 'url' => null
  41. ];
  42. /** @var array */
  43. private $items;
  44. public function isEnabled(): bool
  45. {
  46. return $this->count() > 1;
  47. }
  48. public function getOptions(): array
  49. {
  50. return $this->options;
  51. }
  52. public function getRoute(): ?Route
  53. {
  54. return $this->route;
  55. }
  56. public function getTotalPages(): int
  57. {
  58. return $this->pages;
  59. }
  60. public function getPageNumber(): int
  61. {
  62. return $this->page;
  63. }
  64. public function getPrevNumber(int $count = 1): ?int
  65. {
  66. $page = $this->page - $count;
  67. return $page >= 1 ? $page : null;
  68. }
  69. public function getNextNumber(int $count = 1): ?int
  70. {
  71. $page = $this->page + $count;
  72. return $page <= $this->pages ? $page : null;
  73. }
  74. public function getPage(int $page, string $label = null): ?PaginationPage
  75. {
  76. if ($page < 1 || $page > $this->pages) {
  77. return null;
  78. }
  79. $start = ($page - 1) * $this->limit;
  80. if ($this->getOptions()['type'] === 'page') {
  81. $name = 'page';
  82. $offset = $page;
  83. } else {
  84. $name = 'start';
  85. $offset = $start;
  86. }
  87. return new PaginationPage(
  88. [
  89. 'label' => $label ?? (string)$page,
  90. 'number' => $page,
  91. 'offset_start' => $start,
  92. 'offset_end' => min($start + $this->limit, $this->total) - 1,
  93. 'enabled' => $page !== $this->page || $this->viewAll,
  94. 'active' => $page === $this->page,
  95. 'route' => $this->route->withGravParam($name, $offset)
  96. ]
  97. );
  98. }
  99. public function getFirstPage(string $label = null, int $count = 0): ?PaginationPage
  100. {
  101. return $this->getPage(1 + $count, $label ?? $this->getOptions()['label_first'] ?? null);
  102. }
  103. public function getPrevPage(string $label = null, int $count = 1): ?PaginationPage
  104. {
  105. return $this->getPage($this->page - $count, $label ?? $this->getOptions()['label_prev'] ?? null);
  106. }
  107. public function getNextPage(string $label = null, int $count = 1): ?PaginationPage
  108. {
  109. return $this->getPage($this->page + $count, $label ?? $this->getOptions()['label_next'] ?? null);
  110. }
  111. public function getLastPage(string $label = null, int $count = 0): ?PaginationPage
  112. {
  113. return $this->getPage($this->pages - $count, $label ?? $this->getOptions()['label_last'] ?? null);
  114. }
  115. public function getStart(): int
  116. {
  117. return $this->start;
  118. }
  119. public function getLimit(): int
  120. {
  121. return $this->limit;
  122. }
  123. public function getTotal(): int
  124. {
  125. return $this->total;
  126. }
  127. public function count(): int
  128. {
  129. $this->loadItems();
  130. return \count($this->items);
  131. }
  132. public function getIterator()
  133. {
  134. $this->loadItems();
  135. return new \ArrayIterator($this->items);
  136. }
  137. public function getPages(): array
  138. {
  139. $this->loadItems();
  140. return $this->items;
  141. }
  142. protected function loadItems()
  143. {
  144. $this->calculateRange();
  145. // Make list like: 1 ... 4 5 6 ... 10
  146. $range = range($this->pagesStart, $this->pagesStop);
  147. //$range[] = 1;
  148. //$range[] = $this->pages;
  149. natsort($range);
  150. $range = array_unique($range);
  151. $this->items = [];
  152. foreach ($range as $i) {
  153. $this->items[$i] = $this->getPage($i);
  154. }
  155. }
  156. protected function setRoute(Route $route)
  157. {
  158. $this->route = $route;
  159. return $this;
  160. }
  161. protected function setOptions(array $options = null)
  162. {
  163. $this->options = $options ? array_merge($this->defaultOptions, $options) : $this->defaultOptions;
  164. return $this;
  165. }
  166. protected function setPage(int $page = null)
  167. {
  168. $this->page = (int)max($page, 1);
  169. $this->start = null;
  170. return $this;
  171. }
  172. /**
  173. * @param int $start
  174. * @return $this
  175. */
  176. protected function setStart(int $start = null)
  177. {
  178. $this->start = (int)max($start, 0);
  179. $this->page = null;
  180. return $this;
  181. }
  182. /**
  183. * @param int|null $limit
  184. * @return $this
  185. */
  186. protected function setLimit(int $limit = null)
  187. {
  188. $this->limit = (int)max($limit ?? $this->getOptions()['limit'], 0);
  189. // No limit, display all records in a single page.
  190. $this->viewAll = !$limit;
  191. return $this;
  192. }
  193. /**
  194. * @param int $total
  195. * @return $this
  196. */
  197. protected function setTotal(int $total)
  198. {
  199. $this->total = (int)max($total, 0);
  200. return $this;
  201. }
  202. protected function initialize(Route $route, int $total, int $pos = null, int $limit = null, array $options = null)
  203. {
  204. $this->setRoute($route);
  205. $this->setOptions($options);
  206. $this->setTotal($total);
  207. if ($this->getOptions()['type'] === 'start') {
  208. $this->setStart($pos);
  209. } else {
  210. $this->setPage($pos);
  211. }
  212. $this->setLimit($limit);
  213. $this->calculateLimits();
  214. }
  215. protected function calculateLimits()
  216. {
  217. $limit = $this->limit;
  218. $total = $this->total;
  219. if (!$limit || $limit > $total) {
  220. // All records fit into a single page.
  221. $this->start = 0;
  222. $this->page = 1;
  223. $this->pages = 1;
  224. return;
  225. }
  226. if (null === $this->start) {
  227. // If we are using page, convert it to start.
  228. $this->start = (int)(($this->page - 1) * $limit);
  229. }
  230. if ($this->start > $total - $limit) {
  231. // If start is greater than total count (i.e. we are asked to display records that don't exist)
  232. // then set start to display the last natural page of results.
  233. $this->start = (int)max(0, (ceil($total / $limit) - 1) * $limit);
  234. }
  235. // Set the total pages and current page values.
  236. $this->page = (int)ceil(($this->start + 1) / $limit);
  237. $this->pages = (int)ceil($total / $limit);
  238. }
  239. protected function calculateRange()
  240. {
  241. $options = $this->getOptions();
  242. $displayed = $options['display'];
  243. $opening = $options['opening'];
  244. $ending = $options['ending'];
  245. // Set the pagination iteration loop values.
  246. $this->pagesStart = $this->page - (int)($displayed / 2);
  247. if ($this->pagesStart < 1 + $opening) {
  248. $this->pagesStart = 1 + $opening;
  249. }
  250. if ($this->pagesStart + $displayed - $opening > $this->pages) {
  251. $this->pagesStop = $this->pages;
  252. if ($this->pages < $displayed) {
  253. $this->pagesStart = 1 + $opening;
  254. } else {
  255. $this->pagesStart = $this->pages - $displayed + 1 + $opening;
  256. }
  257. } else {
  258. $this->pagesStop = (int)max(1, $this->pagesStart + $displayed - 1 - $ending);
  259. }
  260. }
  261. }