paginator.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. <?php namespace Laravel;
  2. class Paginator {
  3. /**
  4. * The results for the current page.
  5. *
  6. * @var array
  7. */
  8. public $results;
  9. /**
  10. * The current page.
  11. *
  12. * @var int
  13. */
  14. public $page;
  15. /**
  16. * The last page available for the result set.
  17. *
  18. * @var int
  19. */
  20. public $last;
  21. /**
  22. * The total number of results.
  23. *
  24. * @var int
  25. */
  26. public $total;
  27. /**
  28. * The number of items per page.
  29. *
  30. * @var int
  31. */
  32. public $per_page;
  33. /**
  34. * The values that should be appended to the end of the link query strings.
  35. *
  36. * @var array
  37. */
  38. protected $appends;
  39. /**
  40. * The compiled appendage that will be appended to the links.
  41. *
  42. * This consists of a sprintf format with a page place-holder and query string.
  43. *
  44. * @var string
  45. */
  46. protected $appendage;
  47. /**
  48. * The language that should be used when creating the pagination links.
  49. *
  50. * @var string
  51. */
  52. protected $language;
  53. /**
  54. * The "dots" element used in the pagination slider.
  55. *
  56. * @var string
  57. */
  58. protected $dots = '<span class="dots">...</span>';
  59. /**
  60. * Create a new Paginator instance.
  61. *
  62. * @param array $results
  63. * @param int $page
  64. * @param int $total
  65. * @param int $per_page
  66. * @param int $last
  67. * @return void
  68. */
  69. protected function __construct($results, $page, $total, $per_page, $last)
  70. {
  71. $this->page = $page;
  72. $this->last = $last;
  73. $this->total = $total;
  74. $this->results = $results;
  75. $this->per_page = $per_page;
  76. }
  77. /**
  78. * Create a new Paginator instance.
  79. *
  80. * @param array $results
  81. * @param int $total
  82. * @param int $per_page
  83. * @return Paginator
  84. */
  85. public static function make($results, $total, $per_page)
  86. {
  87. $page = static::page($total, $per_page);
  88. $last = ceil($total / $per_page);
  89. return new static($results, $page, $total, $per_page, $last);
  90. }
  91. /**
  92. * Get the current page from the request query string.
  93. *
  94. * @param int $total
  95. * @param int $per_page
  96. * @return int
  97. */
  98. public static function page($total, $per_page)
  99. {
  100. $page = Input::get('page', 1);
  101. // The page will be validated and adjusted if it is less than one or greater
  102. // than the last page. For example, if the current page is not an integer or
  103. // less than one, one will be returned. If the current page is greater than
  104. // the last page, the last page will be returned.
  105. if (is_numeric($page) and $page > $last = ceil($total / $per_page))
  106. {
  107. return ($last > 0) ? $last : 1;
  108. }
  109. return (static::valid($page)) ? $page : 1;
  110. }
  111. /**
  112. * Determine if a given page number is a valid page.
  113. *
  114. * A valid page must be greater than or equal to one and a valid integer.
  115. *
  116. * @param int $page
  117. * @return bool
  118. */
  119. protected static function valid($page)
  120. {
  121. return $page >= 1 and filter_var($page, FILTER_VALIDATE_INT) !== false;
  122. }
  123. /**
  124. * Create the HTML pagination links.
  125. *
  126. * Typically, an intelligent, "sliding" window of links will be rendered based
  127. * on the total number of pages, the current page, and the number of adjacent
  128. * pages that should rendered. This creates a beautiful paginator similar to
  129. * that of Google's.
  130. *
  131. * Example: 1 2 ... 23 24 25 [26] 27 28 29 ... 51 52
  132. *
  133. * If you wish to render only certain elements of the pagination control,
  134. * explore some of the other public methods available on the instance.
  135. *
  136. * <code>
  137. * // Render the pagination links
  138. * echo $paginator->links();
  139. *
  140. * // Render the pagination links using a given window size
  141. * echo $paginator->links(5);
  142. * </code>
  143. *
  144. * @param int $adjacent
  145. * @return string
  146. */
  147. public function links($adjacent = 3)
  148. {
  149. if ($this->last <= 1) return '';
  150. // The hard-coded seven is to account for all of the constant elements in a
  151. // sliding range, such as the current page, the two ellipses, and the two
  152. // beginning and ending pages.
  153. //
  154. // If there are not enough pages to make the creation of a slider possible
  155. // based on the adjacent pages, we will simply display all of the pages.
  156. // Otherwise, we will create a "truncating" sliding window.
  157. if ($this->last < 7 + ($adjacent * 2))
  158. {
  159. $links = $this->range(1, $this->last);
  160. }
  161. else
  162. {
  163. $links = $this->slider($adjacent);
  164. }
  165. $content = $this->previous().' '.$links.' '.$this->next();
  166. return '<div class="pagination">'.$content.'</div>';
  167. }
  168. /**
  169. * Build sliding list of HTML numeric page links.
  170. *
  171. * This method is very similar to the "links" method, only it does not
  172. * render the "first" and "last" pagination links, but only the pages.
  173. *
  174. * <code>
  175. * // Render the pagination slider
  176. * echo $paginator->slider();
  177. *
  178. * // Render the pagination slider using a given window size
  179. * echo $paginator->slider(5);
  180. * </code>
  181. *
  182. * @param int $adjacent
  183. * @return string
  184. */
  185. public function slider($adjacent = 3)
  186. {
  187. $window = $adjacent * 2;
  188. // If the current page is so close to the beginning that we do not have
  189. // room to create a full sliding window, we will only show the first
  190. // several pages, followed by the ending of the slider.
  191. //
  192. // Likewise, if the page is very close to the end, we will create the
  193. // beginning of the slider, but just show the last several pages at
  194. // the end of the slider. Otherwise, we'll build the range.
  195. //
  196. // Example: 1 [2] 3 4 5 6 ... 23 24
  197. if ($this->page <= $window)
  198. {
  199. return $this->range(1, $window + 2).' '.$this->ending();
  200. }
  201. // Example: 1 2 ... 32 33 34 35 [36] 37
  202. elseif ($this->page >= $this->last - $window)
  203. {
  204. return $this->beginning().' '.$this->range($this->last - $window - 2, $this->last);
  205. }
  206. // Example: 1 2 ... 23 24 25 [26] 27 28 29 ... 51 52
  207. $content = $this->range($this->page - $adjacent, $this->page + $adjacent);
  208. return $this->beginning().' '.$content.' '.$this->ending();
  209. }
  210. /**
  211. * Generate the "previous" HTML link.
  212. *
  213. * <code>
  214. * // Create the "previous" pagination element
  215. * echo $paginator->previous();
  216. *
  217. * // Create the "previous" pagination element with custom text
  218. * echo $paginator->previous('Go Back');
  219. * </code>
  220. *
  221. * @param string $text
  222. * @return string
  223. */
  224. public function previous($text = null)
  225. {
  226. $disabled = function($page) { return $page <= 1; };
  227. return $this->element(__FUNCTION__, $this->page - 1, $text, $disabled);
  228. }
  229. /**
  230. * Generate the "next" HTML link.
  231. *
  232. * <code>
  233. * // Create the "next" pagination element
  234. * echo $paginator->next();
  235. *
  236. * // Create the "next" pagination element with custom text
  237. * echo $paginator->next('Skip Forwards');
  238. * </code>
  239. *
  240. * @param string $text
  241. * @return string
  242. */
  243. public function next($text = null)
  244. {
  245. $disabled = function($page, $last) { return $page >= $last; };
  246. return $this->element(__FUNCTION__, $this->page + 1, $text, $disabled);
  247. }
  248. /**
  249. * Create a chronological pagination element, such as a "previous" or "next" link.
  250. *
  251. * @param string $element
  252. * @param int $page
  253. * @param string $text
  254. * @param Closure $disabled
  255. * @return string
  256. */
  257. protected function element($element, $page, $text, $disabled)
  258. {
  259. $class = "{$element}_page";
  260. if (is_null($text))
  261. {
  262. $text = Lang::line("pagination.{$element}")->get($this->language);
  263. }
  264. // Each consumer of this method provides a "disabled" Closure which can
  265. // be used to determine if the element should be a span element or an
  266. // actual link. For example, if the current page is the first page,
  267. // the "first" element should be a span instead of a link.
  268. if ($disabled($this->page, $this->last))
  269. {
  270. return HTML::span($text, array('class' => "{$class} disabled"));
  271. }
  272. else
  273. {
  274. return $this->link($page, $text, $class);
  275. }
  276. }
  277. /**
  278. * Build the first two page links for a sliding page range.
  279. *
  280. * @return string
  281. */
  282. protected function beginning()
  283. {
  284. return $this->range(1, 2).' '.$this->dots;
  285. }
  286. /**
  287. * Build the last two page links for a sliding page range.
  288. *
  289. * @return string
  290. */
  291. protected function ending()
  292. {
  293. return $this->dots.' '.$this->range($this->last - 1, $this->last);
  294. }
  295. /**
  296. * Build a range of numeric pagination links.
  297. *
  298. * For the current page, an HTML span element will be generated instead of a link.
  299. *
  300. * @param int $start
  301. * @param int $end
  302. * @return string
  303. */
  304. protected function range($start, $end)
  305. {
  306. $pages = array();
  307. // To generate the range of page links, we will iterate through each page
  308. // and, if the current page matches the page, we will generate a span,
  309. // otherwise we will generate a link for the page. The span elements
  310. // will be assigned the "current" CSS class for convenient styling.
  311. for ($page = $start; $page <= $end; $page++)
  312. {
  313. if ($this->page == $page)
  314. {
  315. $pages[] = HTML::span($page, array('class' => 'current'));
  316. }
  317. else
  318. {
  319. $pages[] = $this->link($page, $page, null);
  320. }
  321. }
  322. return implode(' ', $pages);
  323. }
  324. /**
  325. * Create a HTML page link.
  326. *
  327. * @param int $page
  328. * @param string $text
  329. * @param string $class
  330. * @return string
  331. */
  332. protected function link($page, $text, $class)
  333. {
  334. $query = '?page='.$page.$this->appendage($this->appends);
  335. return HTML::link(URI::current().$query, $text, compact('class'), Request::secure());
  336. }
  337. /**
  338. * Create the "appendage" to be attached to every pagination link.
  339. *
  340. * @param array $appends
  341. * @return string
  342. */
  343. protected function appendage($appends)
  344. {
  345. // The developer may assign an array of values that will be converted to a
  346. // query string and attached to every pagination link. This allows simple
  347. // implementation of sorting or other things the developer may need.
  348. if ( ! is_null($this->appendage)) return $this->appendage;
  349. if (count($appends) <= 0)
  350. {
  351. return $this->appendage = '';
  352. }
  353. return $this->appendage = '&'.http_build_query($appends);
  354. }
  355. /**
  356. * Set the items that should be appended to the link query strings.
  357. *
  358. * @param array $values
  359. * @return Paginator
  360. */
  361. public function appends($values)
  362. {
  363. $this->appends = $values;
  364. return $this;
  365. }
  366. /**
  367. * Set the language that should be used when creating the pagination links.
  368. *
  369. * @param string $language
  370. * @return Paginator
  371. */
  372. public function speaks($language)
  373. {
  374. $this->language = $language;
  375. return $this;
  376. }
  377. }