]> git.agnieray.net Git - galette.git/blob - galette/lib/Galette/Core/Pagination.php
Drop old (and problematic!) underscore prefix
[galette.git] / galette / lib / Galette / Core / Pagination.php
1 <?php
2
3 /**
4 * Copyright © 2003-2024 The Galette Team
5 *
6 * This file is part of Galette (https://galette.eu).
7 *
8 * Galette is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * Galette is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with Galette. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 namespace Galette\Core;
23
24 use Slim\Routing\RouteParser;
25 use Slim\Slim;
26 use Analog\Analog;
27 use Laminas\Db\Sql\Select;
28 use Slim\Views\Twig;
29
30 /**
31 * Pagination and ordering facilities
32 *
33 * @author Johan Cwiklinski <johan@x-tnd.be>
34 *
35 * @property integer $current_page
36 * @property string $orderby
37 * @property string $ordered
38 * @property integer $show
39 * @property integer $pages
40 * @property integer $counter
41 */
42
43 abstract class Pagination
44 {
45 private int $current_page;
46 private int|string $orderby;
47 private string $ordered;
48 private int $show;
49 private int $pages = 1;
50 private ?int $counter = null;
51 protected ?Twig $view;
52 protected ?RouteParser $routeparser;
53
54 public const ORDER_ASC = 'ASC';
55 public const ORDER_DESC = 'DESC';
56
57 /** @var array<string> */
58 protected array $pagination_fields = array(
59 'current_page',
60 'orderby',
61 'ordered',
62 'show',
63 'pages',
64 'counter'
65 );
66
67 /**
68 * Default constructor
69 */
70 public function __construct()
71 {
72 $this->reinit();
73 }
74
75 /**
76 * Returns the field we want to default set order to
77 *
78 * @return int|string
79 */
80 abstract protected function getDefaultOrder(): int|string;
81
82 /**
83 * Return the default direction for ordering
84 *
85 * @return string ASC or DESC
86 */
87 protected function getDefaultDirection(): string
88 {
89 return self::ORDER_ASC;
90 }
91
92 /**
93 * Reinit default parameters
94 *
95 * @return void
96 */
97 public function reinit(): void
98 {
99 global $preferences;
100
101 $this->current_page = 1;
102 $this->orderby = $this->getDefaultOrder();
103 $this->ordered = $this->getDefaultDirection();
104 $this->show = $preferences->pref_numrows;
105 }
106
107 /**
108 * Invert sort order
109 *
110 * @return void
111 */
112 public function invertorder(): void
113 {
114 $actual = $this->ordered;
115 if ($actual == self::ORDER_ASC) {
116 $this->ordered = self::ORDER_DESC;
117 }
118 if ($actual == self::ORDER_DESC) {
119 $this->ordered = self::ORDER_ASC;
120 }
121 }
122
123 /**
124 * Get current sort direction
125 *
126 * @return self::ORDER_ASC|self::ORDER_DESC
127 */
128 public function getDirection(): string
129 {
130 return $this->ordered;
131 }
132
133 /**
134 * Set sort direction
135 *
136 * @param string $direction self::ORDER_ASC|self::ORDER_DESC
137 *
138 * @return void
139 */
140 public function setDirection(string $direction): void
141 {
142 if ($direction == self::ORDER_ASC || $direction == self::ORDER_DESC) {
143 $this->ordered = $direction;
144 } else {
145 Analog::log(
146 'Trying to set a sort direction that is not know (`' .
147 $direction . '`). Reverting to default value.',
148 Analog::WARNING
149 );
150 $this->ordered == self::ORDER_ASC;
151 }
152 }
153
154 /**
155 * Add limits so we retrieve only relavant rows
156 *
157 * @param Select $select Original select
158 *
159 * @return void
160 */
161 public function setLimits(Select $select): void
162 {
163 if ($this->show !== 0) {
164 $select->limit($this->show);
165 $select->offset(
166 ($this->current_page - 1) * $this->show
167 );
168 }
169 }
170
171 /**
172 * Set counter
173 *
174 * @param int $c Count
175 *
176 * @return void
177 */
178 public function setCounter(int $c): void
179 {
180 $this->counter = $c;
181 $this->countPages();
182 }
183
184 /**
185 * Update or set pages count
186 *
187 * @return void
188 */
189 protected function countPages(): void
190 {
191 if ($this->show !== 0) {
192 if ($this->counter % $this->show == 0) {
193 $this->pages = (int)($this->counter / $this->show);
194 } else {
195 $this->pages = (int)($this->counter / $this->show) + 1;
196 }
197 } else {
198 $this->pages = 0;
199 }
200 if ($this->pages === 0) {
201 $this->pages = 1;
202 }
203 if ($this->current_page > $this->pages) {
204 $this->current_page = $this->pages;
205 }
206 }
207
208 /**
209 * Creates pagination links and assign some useful variables to the template
210 *
211 * @param RouteParser $routeparser Application instance
212 * @param Twig $view View instance
213 * @param boolean $restricted Do not permit to display all
214 *
215 * @return void
216 */
217 public function setViewPagination(RouteParser $routeparser, Twig $view, bool $restricted = true): void
218 {
219 $is_paginated = true;
220 $paginate = null;
221 $this->view = $view;
222 $this->routeparser = $routeparser;
223
224 //Create pagination links
225 if ($this->current_page < 11) {
226 $idepart = 1;
227 } else {
228 $idepart = $this->current_page - 10;
229 }
230 if ($this->current_page + 10 < $this->pages) {
231 $ifin = $this->current_page + 10;
232 } else {
233 $ifin = $this->pages;
234 }
235
236 $next = $this->current_page + 1;
237 $previous = $this->current_page - 1;
238
239 if ($this->current_page != 1) {
240 $paginate .= $this->getLink(
241 '<i class="fast backward small icon" aria-hidden="true"></i>',
242 $this->getHref(1),
243 preg_replace("(%i)", (string)$next, _T("First page"))
244 );
245
246 $paginate .= $this->getLink(
247 '<i class="step backward small icon" aria-hidden="true"></i>',
248 $this->getHref($previous),
249 preg_replace("(%i)", (string)$previous, _T("Previous page (%i)"))
250 );
251 }
252
253 for ($i = $idepart; $i <= $ifin; $i++) {
254 if ($i == $this->current_page) {
255 $paginate .= $this->getLink(
256 "$i",
257 $this->getHref($this->current_page),
258 preg_replace(
259 "(%i)",
260 (string)$this->current_page,
261 _T("Current page (%i)")
262 ),
263 true
264 );
265 } else {
266 $paginate .= $this->getLink(
267 (string)$i,
268 $this->getHref($i),
269 preg_replace("(%i)", (string)$i, _T("Page %i"))
270 );
271 }
272 }
273 if ($this->current_page != $this->pages) {
274 $paginate .= $this->getLink(
275 '<i class="step forward small icon" aria-hidden="true"></i>',
276 $this->getHref($next),
277 preg_replace("(%i)", (string)$next, _T("Next page (%i)"))
278 );
279
280 $paginate .= $this->getLink(
281 '<i class="fast forward small icon" aria-hidden="true"></i>',
282 $this->getHref($this->pages),
283 preg_replace("(%i)", (string)$this->pages, _T("Last page (%i)"))
284 );
285 }
286 if ($this->current_page == 1 && $this->current_page == $this->pages) {
287 $is_paginated = false;
288 }
289
290 $options = array(
291 10 => "10",
292 20 => "20",
293 50 => "50",
294 100 => "100"
295 );
296
297 if ($restricted === false) {
298 $options[0] = _T("All");
299 }
300
301 //Now, we assign common variables to template
302 $view->getEnvironment()->addGlobal('nb_pages', $this->pages);
303 $view->getEnvironment()->addGlobal('page', $this->current_page);
304 $view->getEnvironment()->addGlobal('numrows', $this->show);
305 $view->getEnvironment()->addGlobal('is_paginated', $is_paginated);
306 $view->getEnvironment()->addGlobal('pagination', $paginate);
307 $view->getEnvironment()->addGlobal('nbshow_options', $options);
308
309 //resetting prevents following error:
310 //PHP Fatal error: Uncaught Exception: Serialization of '[...]' is not allowed in [no active file]:0
311 $this->view = null;
312 $this->routeparser = null;
313 }
314
315 /**
316 * Get a pagination link
317 *
318 * @param string $content Links content
319 * @param string $url URL the link to point on
320 * @param string $title Link's title
321 * @param bool $current Is current page
322 *
323 * @return string
324 */
325 private function getLink(string $content, string $url, string $title, bool $current = false): string
326 {
327 if ($current === true) {
328 $active = "active ";
329 } else {
330 $active = "";
331 }
332 $link = "<a href=\"" . $url . "\" " .
333 "title=\"" . $title . "\" class=\"" . $active . "item\">" . $content . "</a>\n";
334 return $link;
335 }
336
337 /**
338 * Build href
339 *
340 * @param int $page Page
341 *
342 * @return string
343 */
344 protected function getHref(int $page): string
345 {
346 $args = [
347 'option' => 'page',
348 'value' => $page
349 ];
350
351 if ($this->view->getEnvironment()->getGlobals()['cur_subroute']) {
352 $args['type'] = $this->view->getEnvironment()->getGlobals()['cur_subroute'];
353 }
354
355 $href = $this->routeparser->urlFor(
356 $this->view->getEnvironment()->getGlobals()['cur_route'],
357 $args
358 );
359 return $href;
360 }
361
362 /**
363 * Global getter method
364 *
365 * @param string $name name of the property we want to retrieve
366 *
367 * @return mixed the called property
368 */
369 public function __get(string $name)
370 {
371 if (in_array($name, $this->pagination_fields)) {
372 return $this->$name;
373 } else {
374 Analog::log(
375 '[' . get_class($this) .
376 '|Pagination] Unable to get property `' . $name . '`',
377 Analog::WARNING
378 );
379 }
380 }
381
382 /**
383 * Global isset method
384 * Required for twig to access properties via __get
385 *
386 * @param string $name name of the property we want to retrieve
387 *
388 * @return bool
389 */
390 public function __isset(string $name): bool
391 {
392 if (in_array($name, $this->pagination_fields)) {
393 return true;
394 }
395 return property_exists($this, $name);
396 }
397
398 /**
399 * Global setter method
400 *
401 * @param string $name name of the property we want to assign a value to
402 * @param mixed $value a relevant value for the property
403 *
404 * @return void
405 */
406 public function __set(string $name, $value): void
407 {
408 switch ($name) {
409 case 'ordered':
410 if ($value == self::ORDER_ASC || $value == self::ORDER_DESC) {
411 $this->$name = $value;
412 } else {
413 Analog::log(
414 '[' . get_class($this) .
415 '|Pagination] Possibles values for field `' .
416 $name . '` are: `' . self::ORDER_ASC . '` or `' .
417 self::ORDER_DESC . '` - `' . $value . '` given',
418 Analog::WARNING
419 );
420 }
421 break;
422 case 'orderby':
423 if ($this->$name == $value) {
424 $this->invertorder();
425 } else {
426 $this->$name = $value;
427 $this->setDirection(self::ORDER_ASC);
428 }
429 break;
430 case 'current_page':
431 case 'counter':
432 case 'pages':
433 if (is_int($value) && $value > 0) {
434 $this->$name = $value;
435 } else {
436 Analog::log(
437 '[' . get_class($this) .
438 '|Pagination] Value for field `' .
439 $name . '` should be a positive integer - (' .
440 gettype($value) . ')' . $value . ' given',
441 Analog::WARNING
442 );
443 }
444 break;
445 case 'show':
446 if (
447 $value == 'all'
448 || preg_match('/[[:digit:]]/', $value)
449 && $value >= 0
450 ) {
451 $this->$name = (int)$value;
452 } else {
453 Analog::log(
454 '[' . get_class($this) . '|Pagination] Value for `' .
455 $name . '` should be a positive integer or \'all\' - (' .
456 gettype($value) . ')' . $value . ' given',
457 Analog::WARNING
458 );
459 }
460 break;
461 default:
462 Analog::log(
463 '[' . get_class($this) .
464 '|Pagination] Unable to set property `' . $name . '`',
465 Analog::WARNING
466 );
467 break;
468 }
469 }
470 }