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