411 lines
8.3 KiB
PHP
Executable File
411 lines
8.3 KiB
PHP
Executable File
<?php
|
|
|
|
namespace Kirby\Toolkit;
|
|
|
|
use Exception;
|
|
|
|
/**
|
|
* Basic pagination handling
|
|
*/
|
|
class Pagination
|
|
{
|
|
|
|
/**
|
|
* The current page
|
|
*
|
|
* @var integer
|
|
*/
|
|
protected $page;
|
|
|
|
/**
|
|
* Total number of items
|
|
*
|
|
* @var integer
|
|
*/
|
|
protected $total;
|
|
|
|
/**
|
|
* The number of items per page
|
|
*
|
|
* @var integer
|
|
*/
|
|
protected $limit;
|
|
|
|
/**
|
|
* Creates a new pagination object
|
|
* with the given parameters
|
|
*
|
|
* @param array $params
|
|
*/
|
|
public function __construct(array $params = [])
|
|
{
|
|
$this->page($params['page'] ?? 1);
|
|
$this->limit($params['limit'] ?? 20);
|
|
$this->total($params['total'] ?? 0);
|
|
}
|
|
|
|
/**
|
|
* Creates a pagination instance for the given
|
|
* collection with a flexible argument api
|
|
*
|
|
* @param Collection $collection
|
|
* @param ...mixed $arguments
|
|
* @return self
|
|
*/
|
|
public static function for(Collection $collection, ...$arguments)
|
|
{
|
|
$a = $arguments[0] ?? null;
|
|
$b = $arguments[1] ?? null;
|
|
|
|
$params = [];
|
|
|
|
if (is_array($a) === true) {
|
|
|
|
/**
|
|
* First argument is an option array
|
|
*
|
|
* $collection->paginate([...])
|
|
*/
|
|
$params = $a;
|
|
} elseif (is_int($a) === true && $b === null) {
|
|
|
|
/**
|
|
* First argument is the limit
|
|
*
|
|
* $collection->paginate(10)
|
|
*/
|
|
$params['limit'] = $a;
|
|
} elseif (is_int($a) === true && is_int($b) === true) {
|
|
|
|
/**
|
|
* First argument is the limit,
|
|
* second argument is the page
|
|
*
|
|
* $collection->paginate(10, 2)
|
|
*/
|
|
$params['limit'] = $a;
|
|
$params['page'] = $b;
|
|
} elseif (is_int($a) === true && is_array($b) === true) {
|
|
|
|
/**
|
|
* First argument is the limit,
|
|
* second argument are options
|
|
*
|
|
* $collection->paginate(10, [...])
|
|
*/
|
|
$params = $b;
|
|
$params['limit'] = $a;
|
|
}
|
|
|
|
// add the total count from the collection
|
|
$params['total'] = $collection->count();
|
|
|
|
// remove null values to make later merges work properly
|
|
$params = array_filter($params);
|
|
|
|
// create the pagination instance
|
|
return new static($params);
|
|
}
|
|
|
|
/**
|
|
* Getter and setter for the current page
|
|
*
|
|
* @param int|null $page
|
|
* @return int|Pagination
|
|
*/
|
|
public function page(int $page = null)
|
|
{
|
|
if ($page === null) {
|
|
if ($this->page > $this->pages()) {
|
|
$this->page = $this->lastPage();
|
|
}
|
|
|
|
if ($this->page < 1) {
|
|
$this->page = $this->firstPage();
|
|
}
|
|
|
|
return $this->page;
|
|
}
|
|
|
|
$this->page = $page;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Getter and setter for the total number of items
|
|
*
|
|
* @param int|null $total
|
|
* @return int|Pagination
|
|
*/
|
|
public function total(int $total = null)
|
|
{
|
|
if ($total === null) {
|
|
return $this->total;
|
|
}
|
|
|
|
if ($total < 0) {
|
|
throw new Exception('Invalid total number of items: ' . $total);
|
|
}
|
|
|
|
$this->total = $total;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Getter and setter for the number of items per page
|
|
*
|
|
* @param int|null $limit
|
|
* @return int|Pagination
|
|
*/
|
|
public function limit(int $limit = null)
|
|
{
|
|
if ($limit === null) {
|
|
return $this->limit;
|
|
}
|
|
|
|
if ($limit < 1) {
|
|
throw new Exception('Invalid pagination limit: ' . $limit);
|
|
}
|
|
|
|
$this->limit = $limit;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Returns the index of the first item on the page
|
|
*
|
|
* @return int
|
|
*/
|
|
public function start(): int
|
|
{
|
|
$index = $this->page() - 1;
|
|
|
|
if ($index < 0) {
|
|
$index = 0;
|
|
}
|
|
|
|
return $index * $this->limit() + 1;
|
|
}
|
|
|
|
/**
|
|
* Returns the index of the last item on the page
|
|
*
|
|
* @return int
|
|
*/
|
|
public function end(): int
|
|
{
|
|
$value = ($this->start() - 1) + $this->limit();
|
|
|
|
if ($value <= $this->total()) {
|
|
return $value;
|
|
}
|
|
|
|
return $this->total();
|
|
}
|
|
|
|
/**
|
|
* Returns the total number of pages
|
|
*
|
|
* @return int
|
|
*/
|
|
public function pages(): int
|
|
{
|
|
if ($this->total() === 0) {
|
|
return 0;
|
|
}
|
|
|
|
return ceil($this->total() / $this->limit());
|
|
}
|
|
|
|
/**
|
|
* Returns the first page
|
|
*
|
|
* @return int
|
|
*/
|
|
public function firstPage(): int
|
|
{
|
|
return $this->total() === 0 ? 0 : 1;
|
|
}
|
|
|
|
/**
|
|
* Returns the last page
|
|
*
|
|
* @return int
|
|
*/
|
|
public function lastPage(): int
|
|
{
|
|
return $this->pages();
|
|
}
|
|
|
|
/**
|
|
* Returns the offset (i.e. for db queries)
|
|
*
|
|
* @return int
|
|
*/
|
|
public function offset(): int
|
|
{
|
|
return $this->start() - 1;
|
|
}
|
|
|
|
/**
|
|
* Checks if the given page exists
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function hasPage(int $page): bool
|
|
{
|
|
if ($page <= 0) {
|
|
return false;
|
|
}
|
|
|
|
if ($page > $this->pages()) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Checks if there are any pages at all
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function hasPages(): bool
|
|
{
|
|
return $this->total() > $this->limit();
|
|
}
|
|
|
|
/**
|
|
* Checks if there's a previous page
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function hasPrevPage(): bool
|
|
{
|
|
return $this->page() > 1;
|
|
}
|
|
|
|
/**
|
|
* Returns the previous page
|
|
*
|
|
* @return int|null
|
|
*/
|
|
public function prevPage()
|
|
{
|
|
return $this->hasPrevPage() ? $this->page() - 1 : null;
|
|
}
|
|
|
|
/**
|
|
* Checks if there's a next page
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function hasNextPage(): bool
|
|
{
|
|
return $this->end() < $this->total();
|
|
}
|
|
|
|
/**
|
|
* Returns the next page
|
|
*
|
|
* @return int|null
|
|
*/
|
|
public function nextPage()
|
|
{
|
|
return $this->hasNextPage() ? $this->page() + 1 : null;
|
|
}
|
|
|
|
/**
|
|
* Checks if the current page is the first page
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function isFirstPage(): bool
|
|
{
|
|
return $this->page() === $this->firstPage();
|
|
}
|
|
|
|
/**
|
|
* Checks if the current page is the last page
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function isLastPage(): bool
|
|
{
|
|
return $this->page() === $this->lastPage();
|
|
}
|
|
|
|
/**
|
|
* Creates a range of page numbers for Google-like pagination
|
|
*
|
|
* @return array
|
|
*/
|
|
public function range(int $range = 5): array
|
|
{
|
|
$page = $this->page();
|
|
$pages = $this->pages();
|
|
$start = 1;
|
|
$end = $pages;
|
|
|
|
if ($pages <= $range) {
|
|
return range($start, $end);
|
|
}
|
|
|
|
$start = $page - (int)floor($range/2);
|
|
$end = $page + (int)floor($range/2);
|
|
|
|
if ($start <= 0) {
|
|
$end += abs($start);
|
|
$start = 1;
|
|
}
|
|
|
|
if ($end > $pages) {
|
|
$start -= $end - $pages;
|
|
$end = $pages;
|
|
}
|
|
|
|
return range($start, $end);
|
|
}
|
|
|
|
/**
|
|
* Returns the first page of the created range
|
|
*
|
|
* @return int
|
|
*/
|
|
public function rangeStart(int $range = 5): int
|
|
{
|
|
return $this->range($range)[0];
|
|
}
|
|
|
|
/**
|
|
* Returns the last page of the created range
|
|
*
|
|
* @return int
|
|
*/
|
|
public function rangeEnd(int $range = 5): int
|
|
{
|
|
$range = $this->range($range);
|
|
return array_pop($range);
|
|
}
|
|
|
|
/**
|
|
* Returns an array with all properties
|
|
*
|
|
* @return array
|
|
*/
|
|
public function toArray(): array
|
|
{
|
|
return [
|
|
'page' => $this->page(),
|
|
'firstPage' => $this->firstPage(),
|
|
'lastPage' => $this->lastPage(),
|
|
'pages' => $this->pages(),
|
|
'offset' => $this->offset(),
|
|
'limit' => $this->limit(),
|
|
'total' => $this->total(),
|
|
'start' => $this->start(),
|
|
'end' => $this->end(),
|
|
];
|
|
}
|
|
}
|