1<?php
2
3/**
4 * This file is part of the Nette Framework (https://nette.org)
5 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6 */
7
8declare(strict_types=1);
9
10namespace Nette\Utils;
11
12use Nette;
13
14
15/**
16 * Paginating math.
17 *
18 * @property   int $page
19 * @property-read int $firstPage
20 * @property-read int|null $lastPage
21 * @property-read int<0,max> $firstItemOnPage
22 * @property-read int<0,max> $lastItemOnPage
23 * @property   int $base
24 * @property-read bool $first
25 * @property-read bool $last
26 * @property-read int<0,max>|null $pageCount
27 * @property   positive-int $itemsPerPage
28 * @property   int<0,max>|null $itemCount
29 * @property-read int<0,max> $offset
30 * @property-read int<0,max>|null $countdownOffset
31 * @property-read int<0,max> $length
32 */
33class Paginator
34{
35	use Nette\SmartObject;
36
37	private int $base = 1;
38
39	/** @var positive-int */
40	private int $itemsPerPage = 1;
41
42	private int $page = 1;
43
44	/** @var int<0, max>|null */
45	private ?int $itemCount = null;
46
47
48	/**
49	 * Sets current page number.
50	 */
51	public function setPage(int $page): static
52	{
53		$this->page = $page;
54		return $this;
55	}
56
57
58	/**
59	 * Returns current page number.
60	 */
61	public function getPage(): int
62	{
63		return $this->base + $this->getPageIndex();
64	}
65
66
67	/**
68	 * Returns first page number.
69	 */
70	public function getFirstPage(): int
71	{
72		return $this->base;
73	}
74
75
76	/**
77	 * Returns last page number.
78	 */
79	public function getLastPage(): ?int
80	{
81		return $this->itemCount === null
82			? null
83			: $this->base + max(0, $this->getPageCount() - 1);
84	}
85
86
87	/**
88	 * Returns the sequence number of the first element on the page
89	 * @return int<0, max>
90	 */
91	public function getFirstItemOnPage(): int
92	{
93		return $this->itemCount !== 0
94			? $this->offset + 1
95			: 0;
96	}
97
98
99	/**
100	 * Returns the sequence number of the last element on the page
101	 * @return int<0, max>
102	 */
103	public function getLastItemOnPage(): int
104	{
105		return $this->offset + $this->length;
106	}
107
108
109	/**
110	 * Sets first page (base) number.
111	 */
112	public function setBase(int $base): static
113	{
114		$this->base = $base;
115		return $this;
116	}
117
118
119	/**
120	 * Returns first page (base) number.
121	 */
122	public function getBase(): int
123	{
124		return $this->base;
125	}
126
127
128	/**
129	 * Returns zero-based page number.
130	 * @return int<0, max>
131	 */
132	protected function getPageIndex(): int
133	{
134		$index = max(0, $this->page - $this->base);
135		return $this->itemCount === null
136			? $index
137			: min($index, max(0, $this->getPageCount() - 1));
138	}
139
140
141	/**
142	 * Is the current page the first one?
143	 */
144	public function isFirst(): bool
145	{
146		return $this->getPageIndex() === 0;
147	}
148
149
150	/**
151	 * Is the current page the last one?
152	 */
153	public function isLast(): bool
154	{
155		return $this->itemCount === null
156			? false
157			: $this->getPageIndex() >= $this->getPageCount() - 1;
158	}
159
160
161	/**
162	 * Returns the total number of pages.
163	 * @return int<0, max>|null
164	 */
165	public function getPageCount(): ?int
166	{
167		return $this->itemCount === null
168			? null
169			: (int) ceil($this->itemCount / $this->itemsPerPage);
170	}
171
172
173	/**
174	 * Sets the number of items to display on a single page.
175	 */
176	public function setItemsPerPage(int $itemsPerPage): static
177	{
178		$this->itemsPerPage = max(1, $itemsPerPage);
179		return $this;
180	}
181
182
183	/**
184	 * Returns the number of items to display on a single page.
185	 * @return positive-int
186	 */
187	public function getItemsPerPage(): int
188	{
189		return $this->itemsPerPage;
190	}
191
192
193	/**
194	 * Sets the total number of items.
195	 */
196	public function setItemCount(?int $itemCount = null): static
197	{
198		$this->itemCount = $itemCount === null ? null : max(0, $itemCount);
199		return $this;
200	}
201
202
203	/**
204	 * Returns the total number of items.
205	 * @return int<0, max>|null
206	 */
207	public function getItemCount(): ?int
208	{
209		return $this->itemCount;
210	}
211
212
213	/**
214	 * Returns the absolute index of the first item on current page.
215	 * @return int<0, max>
216	 */
217	public function getOffset(): int
218	{
219		return $this->getPageIndex() * $this->itemsPerPage;
220	}
221
222
223	/**
224	 * Returns the absolute index of the first item on current page in countdown paging.
225	 * @return int<0, max>|null
226	 */
227	public function getCountdownOffset(): ?int
228	{
229		return $this->itemCount === null
230			? null
231			: max(0, $this->itemCount - ($this->getPageIndex() + 1) * $this->itemsPerPage);
232	}
233
234
235	/**
236	 * Returns the number of items on current page.
237	 * @return int<0, max>
238	 */
239	public function getLength(): int
240	{
241		return $this->itemCount === null
242			? $this->itemsPerPage
243			: min($this->itemsPerPage, $this->itemCount - $this->getPageIndex() * $this->itemsPerPage);
244	}
245}
246