paginator.php 10 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
<?php namespace Laravel;

class Paginator {

	/**
	 * The results for the current page.
	 *
	 * @var array
	 */
	public $results;

	/**
13
	 * The current page.
14 15 16
	 *
	 * @var int
	 */
17
	public $page;
18 19

	/**
20
	 * The last page available for the result set.
21 22 23
	 *
	 * @var int
	 */
24
	public $last;
25 26

	/**
27
	 * The total number of results.
28 29 30
	 *
	 * @var int
	 */
31
	public $total;
32 33

	/**
34
	 * The number of items per page.
35 36 37
	 *
	 * @var int
	 */
38
	public $per_page;
39 40 41 42 43 44 45

	/**
	 * The values that should be appended to the end of the link query strings.
	 *
	 * @var array
	 */
	protected $appends;
46 47

	/**
48 49 50
	 * The compiled appendage that will be appended to the links.
	 *
	 * This consists of a sprintf format  with a page place-holder and query string.
51 52 53
	 *
	 * @var string
	 */
54
	protected $appendage;
55 56

	/**
57 58 59 60 61 62 63
	 * The language that should be used when creating the pagination links.
	 *
	 * @var string
	 */
	protected $language;

	/**
64
	 * The "dots" element used in the pagination slider.
65
	 *
66
	 * @var string
67
	 */
68
	protected $dots = '<span class="dots">...</span>';
69 70

	/**
71 72 73 74 75 76
	 * Create a new Paginator instance.
	 *
	 * @param  array  $results
	 * @param  int    $page
	 * @param  int    $total
	 * @param  int    $per_page
Phill Sparks committed
77
	 * @param  int    $last
78 79
	 * @return void
	 */
80
	protected function __construct($results, $page, $total, $per_page, $last)
81 82
	{
		$this->page = $page;
83
		$this->last = $last;
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
		$this->total = $total;
		$this->results = $results;
		$this->per_page = $per_page;
	}

	/**
	 * Create a new Paginator instance.
	 *
	 * @param  array      $results
	 * @param  int        $total
	 * @param  int        $per_page
	 * @return Paginator
	 */
	public static function make($results, $total, $per_page)
	{
99 100
		$page = static::page($total, $per_page);

101
		$last = ceil($total / $per_page);
102

103
		return new static($results, $page, $total, $per_page, $last);
104 105 106 107 108 109 110 111 112 113 114
	}

	/**
	 * Get the current page from the request query string.
	 *
	 * @param  int  $total
	 * @param  int  $per_page
	 * @return int
	 */
	public static function page($total, $per_page)
	{
115
		$page = Input::get('page', 1);
116

117 118 119 120 121
		// The page will be validated and adjusted if it is less than one or greater
		// than the last page. For example, if the current page is not an integer or
		// less than one, one will be returned. If the current page is greater than
		// the last page, the last page will be returned.
		if (is_numeric($page) and $page > $last = ceil($total / $per_page))
122
		{
123
			return ($last > 0) ? $last : 1;
124 125
		}

126 127 128 129 130 131 132 133 134 135 136 137 138 139
		return (static::valid($page)) ? $page : 1;
	}

	/**
	 * Determine if a given page number is a valid page.
	 *
	 * A valid page must be greater than or equal to one and a valid integer.
	 *
	 * @param  int   $page
	 * @return bool
	 */
	protected static function valid($page)
	{
		return $page >= 1 and filter_var($page, FILTER_VALIDATE_INT) !== false;
140 141 142 143 144
	}

	/**
	 * Create the HTML pagination links.
	 *
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
	 * Typically, an intelligent, "sliding" window of links will be rendered based
	 * on the total number of pages, the current page, and the number of adjacent
	 * pages that should rendered. This creates a beautiful paginator similar to
	 * that of Google's.
	 *
	 * Example: 1 2 ... 23 24 25 [26] 27 28 29 ... 51 52
	 *
	 * If you wish to render only certain elements of the pagination control,
	 * explore some of the other public methods available on the instance.
	 *
	 * <code>
	 *		// Render the pagination links
	 *		echo $paginator->links();
	 *
	 *		// Render the pagination links using a given window size
	 *		echo $paginator->links(5);
	 * </code>
	 *
	 * @param  int     $adjacent
164 165
	 * @return string
	 */
166
	public function links($adjacent = 3)
167
	{
168
		if ($this->last <= 1) return '';
169

170 171 172
		// The hard-coded seven is to account for all of the constant elements in a
		// sliding range, such as the current page, the two ellipses, and the two
		// beginning and ending pages.
Taylor Otwell committed
173
		//
174 175
		// If there are not enough pages to make the creation of a slider possible
		// based on the adjacent pages, we will simply display all of the pages.
Taylor Otwell committed
176
		// Otherwise, we will create a "truncating" sliding window.
177
		if ($this->last < 7 + ($adjacent * 2))
178
		{
179
			$links = $this->range(1, $this->last);
Taylor Otwell committed
180
		}
181
		else
Taylor Otwell committed
182
		{
183
			$links = $this->slider($adjacent);
184
		}
185

186
		$content = $this->previous().' '.$links.' '.$this->next();
187 188

		return '<div class="pagination">'.$content.'</div>';
189 190 191
	}

	/**
192
	 * Build sliding list of HTML numeric page links.
193
	 *
194 195 196 197 198 199 200 201 202 203 204 205
	 * This method is very similar to the "links" method, only it does not
	 * render the "first" and "last" pagination links, but only the pages.
	 *
	 * <code>
	 *		// Render the pagination slider
	 *		echo $paginator->slider();
	 *
	 *		// Render the pagination slider using a given window size
	 *		echo $paginator->slider(5);
	 * </code>
	 *
	 * @param  int     $adjacent
206 207
	 * @return string
	 */
208
	public function slider($adjacent = 3)
209
	{
210
		$window = $adjacent * 2;
Taylor Otwell committed
211

212 213
		// If the current page is so close to the beginning that we do not have
		// room to create a full sliding window, we will only show the first
Taylor Otwell committed
214
		// several pages, followed by the ending of the slider.
215 216 217
		//
		// Likewise, if the page is very close to the end, we will create the
		// 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 221 222 223 224 225 226 227 228 229 230 231 232 233 234
		// Example: 1 [2] 3 4 5 6 ... 23 24
		if ($this->page <= $window)
		{
			return $this->range(1, $window + 2).' '.$this->ending();
		}
		// Example: 1 2 ... 32 33 34 35 [36] 37
		elseif ($this->page >= $this->last - $window)
		{
			return $this->beginning().' '.$this->range($this->last - $window - 2, $this->last);
		}

		// Example: 1 2 ... 23 24 25 [26] 27 28 29 ... 51 52
		$content = $this->range($this->page - $adjacent, $this->page + $adjacent);

		return $this->beginning().' '.$content.' '.$this->ending();
235 236 237
	}

	/**
238 239 240 241 242 243 244 245 246
	 * Generate the "previous" HTML link.
	 *
	 * <code>
	 *		// Create the "previous" pagination element
	 *		echo $paginator->previous();
	 *
	 *		// Create the "previous" pagination element with custom text
	 *		echo $paginator->previous('Go Back');
	 * </code>
247
	 *
Phill Sparks committed
248
	 * @param  string  $text
249 250
	 * @return string
	 */
Taylor Otwell committed
251
	public function previous($text = null)
252
	{
253 254 255
		$disabled = function($page) { return $page <= 1; };

		return $this->element(__FUNCTION__, $this->page - 1, $text, $disabled);
256
	}
257

258
	/**
259 260 261 262 263 264 265 266 267
	 * Generate the "next" HTML link.
	 *
	 * <code>
	 *		// Create the "next" pagination element
	 *		echo $paginator->next();
	 *
	 *		// Create the "next" pagination element with custom text
	 *		echo $paginator->next('Skip Forwards');
	 * </code>
268
	 *
Phill Sparks committed
269
	 * @param  string  $text
270 271
	 * @return string
	 */
Taylor Otwell committed
272
	public function next($text = null)
273
	{
274 275 276
		$disabled = function($page, $last) { return $page >= $last; };

		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 283 284 285
	 * @param  string   $element
	 * @param  int      $page
	 * @param  string   $text
	 * @param  Closure  $disabled
286 287
	 * @return string
	 */
288
	protected function element($element, $page, $text, $disabled)
289
	{
290 291
		$class = "{$element}_page";

292 293 294 295
		if (is_null($text))
		{
			$text = Lang::line("pagination.{$element}")->get($this->language);
		}
296 297 298 299 300 301 302 303 304 305 306 307 308

		// Each consumer of this method provides a "disabled" Closure which can
		// be used to determine if the element should be a span element or an
		// actual link. For example, if the current page is the first page,
		// the "first" element should be a span instead of a link.
		if ($disabled($this->page, $this->last))
		{
			return HTML::span($text, array('class' => "{$class} disabled"));
		}
		else
		{
			return $this->link($page, $text, $class);
		}
309 310 311
	}

	/**
312
	 * Build the first two page links for a sliding page range.
313
	 *
314 315
	 * @return string
	 */
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 325
	 *
	 * @return string
	 */
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 335 336 337
	 * For the current page, an HTML span element will be generated instead of a link.
	 *
	 * @param  int     $start
	 * @param  int     $end
338 339
	 * @return string
	 */
340
	protected function range($start, $end)
341
	{
342
		$pages = array();
Taylor Otwell committed
343

344 345 346 347
		// To generate the range of page links, we will iterate through each page
		// and, if the current page matches the page, we will generate a span,
		// otherwise we will generate a link for the page. The span elements
		// will be assigned the "current" CSS class for convenient styling.
348
		for ($page = $start; $page <= $end; $page++)
349
		{
350 351 352 353 354 355 356 357
			if ($this->page == $page)
			{
				$pages[] = HTML::span($page, array('class' => 'current'));
			}
			else
			{
				$pages[] = $this->link($page, $page, null);
			}
358 359
		}

360
		return implode(' ', $pages);
361 362 363
	}

	/**
364
	 * Create a HTML page link.
365 366
	 *
	 * @param  int     $page
367
	 * @param  string  $text
Phill Sparks committed
368
	 * @param  string  $class
369 370
	 * @return string
	 */
371
	protected function link($page, $text, $class)
372
	{
373
		$query = '?page='.$page.$this->appendage($this->appends);
Taylor Otwell committed
374

375
		return HTML::link(URI::current().$query, $text, compact('class'), Request::secure());
376
	}
377

378
	/**
379
	 * Create the "appendage" to be attached to every pagination link.
380 381 382 383 384 385
	 *
	 * @param  array   $appends
	 * @return string
	 */
	protected function appendage($appends)
	{
386 387 388
	 	// The developer may assign an array of values that will be converted to a
	 	// query string and attached to every pagination link. This allows simple
	 	// implementation of sorting or other things the developer may need.
389 390 391
		if ( ! is_null($this->appendage)) return $this->appendage;

		if (count($appends) <= 0)
392
		{
393
			return $this->appendage = '';
394 395
		}

396
		return $this->appendage = '&'.http_build_query($appends);
397 398 399
	}

	/**
400 401 402
	 * Set the items that should be appended to the link query strings.
	 *
	 * @param  array      $values
403 404
	 * @return Paginator
	 */
405
	public function appends($values)
406
	{
407
		$this->appends = $values;
408 409 410
		return $this;
	}

411 412 413 414 415 416 417 418 419 420 421 422
	/**
	 * Set the language that should be used when creating the pagination links.
	 *
	 * @param  string     $language
	 * @return Paginator
	 */
	public function speaks($language)
	{
		$this->language = $language;
		return $this;
	}

423
}