/core/PaginatedList.php

https://github.com/sminnee/silverstripe-framework · PHP · 420 lines · 189 code · 46 blank · 185 comment · 43 complexity · 9448d3e28ab1d08c8edb4b339af701c1 MD5 · raw file

  1. <?php
  2. /**
  3. * A decorator that wraps around a data list in order to provide pagination.
  4. *
  5. * @package framework
  6. * @subpackage view
  7. */
  8. class PaginatedList extends SS_ListDecorator {
  9. protected $request;
  10. protected $getVar = 'start';
  11. protected $pageLength = 10;
  12. protected $pageStart;
  13. protected $totalItems;
  14. protected $limitItems = true;
  15. /**
  16. * Constructs a new paginated list instance around a list.
  17. *
  18. * @param SS_List $list The list to paginate. The getRange method will
  19. * be used to get the subset of objects to show.
  20. * @param array|ArrayAccess Either a map of request parameters or
  21. * request object that the pagination offset is read from.
  22. */
  23. public function __construct(SS_List $list, $request = array()) {
  24. if (!is_array($request) && !$request instanceof ArrayAccess) {
  25. throw new Exception('The request must be readable as an array.');
  26. }
  27. $this->request = $request;
  28. parent::__construct($list);
  29. }
  30. /**
  31. * Returns the GET var that is used to set the page start. This defaults
  32. * to "start".
  33. *
  34. * If there is more than one paginated list on a page, it is neccesary to
  35. * set a different get var for each using {@link setPaginationGetVar()}.
  36. *
  37. * @return string
  38. */
  39. public function getPaginationGetVar() {
  40. return $this->getVar;
  41. }
  42. /**
  43. * Sets the GET var used to set the page start.
  44. *
  45. * @param string $var
  46. */
  47. public function setPaginationGetVar($var) {
  48. $this->getVar = $var;
  49. return $this;
  50. }
  51. /**
  52. * Returns the number of items displayed per page. This defaults to 10.
  53. *
  54. * @return int.
  55. */
  56. public function getPageLength() {
  57. return $this->pageLength;
  58. }
  59. /**
  60. * Set the number of items displayed per page.
  61. *
  62. * @param int $length
  63. */
  64. public function setPageLength($length) {
  65. $this->pageLength = $length;
  66. return $this;
  67. }
  68. /**
  69. * Sets the current page.
  70. *
  71. * @param int $page
  72. */
  73. public function setCurrentPage($page) {
  74. $this->pageStart = ($page - 1) * $this->pageLength;
  75. return $this;
  76. }
  77. /**
  78. * Returns the offset of the item the current page starts at.
  79. *
  80. * @return int
  81. */
  82. public function getPageStart() {
  83. if ($this->pageStart === null) {
  84. if ($this->request && isset($this->request[$this->getVar])) {
  85. $this->pageStart = (int) $this->request[$this->getVar];
  86. } else {
  87. $this->pageStart = 0;
  88. }
  89. }
  90. return $this->pageStart;
  91. }
  92. /**
  93. * Sets the offset of the item that current page starts at. This should be
  94. * a multiple of the page length.
  95. *
  96. * @param int $start
  97. */
  98. public function setPageStart($start) {
  99. $this->pageStart = $start;
  100. return $this;
  101. }
  102. /**
  103. * Returns the total number of items in the unpaginated list.
  104. *
  105. * @return int
  106. */
  107. public function getTotalItems() {
  108. if ($this->totalItems === null) {
  109. $this->totalItems = count($this->list);
  110. }
  111. return $this->totalItems;
  112. }
  113. /**
  114. * Sets the total number of items in the list. This is useful when doing
  115. * custom pagination.
  116. *
  117. * @param int $items
  118. */
  119. public function setTotalItems($items) {
  120. $this->totalItems = $items;
  121. return $this;
  122. }
  123. /**
  124. * Sets the page length, page start and total items from a query object's
  125. * limit, offset and unlimited count. The query MUST have a limit clause.
  126. *
  127. * @param SQLQuery $query
  128. */
  129. public function setPaginationFromQuery(SQLQuery $query) {
  130. if ($limit = $query->getLimit()) {
  131. $this->setPageLength($limit['limit']);
  132. $this->setPageStart($limit['start']);
  133. $this->setTotalItems($query->unlimitedRowCount());
  134. }
  135. return $this;
  136. }
  137. /**
  138. * Returns whether or not the underlying list is limited to the current
  139. * pagination range when iterating.
  140. *
  141. * By default the limit method will be called on the underlying list to
  142. * extract the subset for the current page. In some situations, if the list
  143. * is custom generated and already paginated you don't want to additionally
  144. * limit the list. You can use {@link setLimitItems} to control this.
  145. *
  146. * @return bool
  147. */
  148. public function getLimitItems() {
  149. return $this->limitItems;
  150. }
  151. /**
  152. * @param bool $limit
  153. */
  154. public function setLimitItems($limit) {
  155. $this->limitItems = (bool) $limit;
  156. return $this;
  157. }
  158. /**
  159. * @return IteratorIterator
  160. */
  161. public function getIterator() {
  162. if($this->limitItems) {
  163. $tmptList = clone $this->list;
  164. return new IteratorIterator(
  165. $tmptList->limit($this->pageLength, $this->getPageStart())
  166. );
  167. } else {
  168. return new IteratorIterator($this->list);
  169. }
  170. }
  171. /**
  172. * Returns a set of links to all the pages in the list. This is useful for
  173. * basic pagination.
  174. *
  175. * By default it returns links to every page, but if you pass the $max
  176. * parameter the number of pages will be limited to that number, centered
  177. * around the current page.
  178. *
  179. * @param int $max
  180. * @return SS_List
  181. */
  182. public function Pages($max = null) {
  183. $result = new ArrayList();
  184. if ($max) {
  185. $start = ($this->CurrentPage() - floor($max / 2)) - 1;
  186. $end = $this->CurrentPage() + floor($max / 2);
  187. if ($start < 0) {
  188. $start = 0;
  189. $end = $max;
  190. }
  191. if ($end > $this->TotalPages()) {
  192. $end = $this->TotalPages();
  193. $start = max(0, $end - $max);
  194. }
  195. } else {
  196. $start = 0;
  197. $end = $this->TotalPages();
  198. }
  199. for ($i = $start; $i < $end; $i++) {
  200. $result->push(new ArrayData(array(
  201. 'PageNum' => $i + 1,
  202. 'Link' => HTTP::setGetVar($this->getVar, $i * $this->pageLength),
  203. 'CurrentBool' => $this->CurrentPage() == ($i + 1)
  204. )));
  205. }
  206. return $result;
  207. }
  208. /**
  209. * Returns a summarised pagination which limits the number of pages shown
  210. * around the current page for visually balanced.
  211. *
  212. * Example: 25 pages total, currently on page 6, context of 4 pages
  213. * [prev] [1] ... [4] [5] [[6]] [7] [8] ... [25] [next]
  214. *
  215. * Example template usage:
  216. * <code>
  217. * <% if MyPages.MoreThanOnePage %>
  218. * <% if MyPages.NotFirstPage %>
  219. * <a class="prev" href="$MyPages.PrevLink">Prev</a>
  220. * <% end_if %>
  221. * <% loop MyPages.PaginationSummary(4) %>
  222. * <% if CurrentBool %>
  223. * $PageNum
  224. * <% else %>
  225. * <% if Link %>
  226. * <a href="$Link">$PageNum</a>
  227. * <% else %>
  228. * ...
  229. * <% end_if %>
  230. * <% end_if %>
  231. * <% end_loop %>
  232. * <% if MyPages.NotLastPage %>
  233. * <a class="next" href="$MyPages.NextLink">Next</a>
  234. * <% end_if %>
  235. * <% end_if %>
  236. * </code>
  237. *
  238. * @param int $context The number of pages to display around the current
  239. * page. The number should be event, as half the number of each pages
  240. * are displayed on either side of the current one.
  241. * @return SS_List
  242. */
  243. public function PaginationSummary($context = 4) {
  244. $result = new ArrayList();
  245. $current = $this->CurrentPage();
  246. $total = $this->TotalPages();
  247. // Make the number even for offset calculations.
  248. if ($context % 2) {
  249. $context--;
  250. }
  251. // If the first or last page is current, then show all context on one
  252. // side of it - otherwise show half on both sides.
  253. if ($current == 1 || $current == $total) {
  254. $offset = $context;
  255. } else {
  256. $offset = floor($context / 2);
  257. }
  258. $left = max($current - $offset, 1);
  259. $range = range($current - $offset, $current + $offset);
  260. if ($left + $context > $total) {
  261. $left = $total - $context;
  262. }
  263. for ($i = 0; $i < $total; $i++) {
  264. $link = HTTP::setGetVar($this->getVar, $i * $this->pageLength);
  265. $num = $i + 1;
  266. $emptyRange = $num != 1 && $num != $total && (
  267. $num == $left - 1 || $num == $left + $context + 1
  268. );
  269. if ($emptyRange) {
  270. $result->push(new ArrayData(array(
  271. 'PageNum' => null,
  272. 'Link' => null,
  273. 'CurrentBool' => false
  274. )));
  275. } elseif ($num == 1 || $num == $total || in_array($num, $range)) {
  276. $result->push(new ArrayData(array(
  277. 'PageNum' => $num,
  278. 'Link' => $link,
  279. 'CurrentBool' => $current == $num
  280. )));
  281. }
  282. }
  283. return $result;
  284. }
  285. /**
  286. * @return int
  287. */
  288. public function CurrentPage() {
  289. return floor($this->getPageStart() / $this->pageLength) + 1;
  290. }
  291. /**
  292. * @return int
  293. */
  294. public function TotalPages() {
  295. return ceil($this->getTotalItems() / $this->pageLength);
  296. }
  297. /**
  298. * @return bool
  299. */
  300. public function MoreThanOnePage() {
  301. return $this->TotalPages() > 1;
  302. }
  303. /**
  304. * @return bool
  305. */
  306. public function NotFirstPage() {
  307. return $this->CurrentPage() != 1;
  308. }
  309. /**
  310. * @return bool
  311. */
  312. public function NotLastPage() {
  313. return $this->CurrentPage() != $this->TotalPages();
  314. }
  315. /**
  316. * Returns the number of the first item being displayed on the current
  317. * page. This is useful for things like "displaying 10-20".
  318. *
  319. * @return int
  320. */
  321. public function FirstItem() {
  322. return ($start = $this->getPageStart()) ? $start + 1 : 1;
  323. }
  324. /**
  325. * Returns the number of the last item being displayed on this page.
  326. *
  327. * @return int
  328. */
  329. public function LastItem() {
  330. if ($start = $this->getPageStart()) {
  331. return min($start + $this->pageLength, $this->getTotalItems());
  332. } else {
  333. return min($this->pageLength, $this->getTotalItems());
  334. }
  335. }
  336. /**
  337. * Returns a link to the first page.
  338. *
  339. * @return string
  340. */
  341. public function FirstLink() {
  342. return HTTP::setGetVar($this->getVar, 0);
  343. }
  344. /**
  345. * Returns a link to the last page.
  346. *
  347. * @return string
  348. */
  349. public function LastLink() {
  350. return HTTP::setGetVar($this->getVar, ($this->TotalPages() - 1) * $this->pageLength);
  351. }
  352. /**
  353. * Returns a link to the next page, if there is another page after the
  354. * current one.
  355. *
  356. * @return string
  357. */
  358. public function NextLink() {
  359. if ($this->NotLastPage()) {
  360. return HTTP::setGetVar($this->getVar, $this->getPageStart() + $this->pageLength);
  361. }
  362. }
  363. /**
  364. * Returns a link to the previous page, if the first page is not currently
  365. * active.
  366. *
  367. * @return string
  368. */
  369. public function PrevLink() {
  370. if ($this->NotFirstPage()) {
  371. return HTTP::setGetVar($this->getVar, $this->getPageStart() - $this->pageLength);
  372. }
  373. }
  374. }