• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

aplus-framework / pagination / 8127679472

16 Jan 2024 08:39PM UTC coverage: 100.0%. Remained the same
8127679472

push

github

natanfelles
Merge branch 'new' into development

589 of 589 relevant lines covered (100.0%)

6.3 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/src/Pager.php
1
<?php declare(strict_types=1);
2
/*
3
 * This file is part of Aplus Framework Pagination Library.
4
 *
5
 * (c) Natan Felles <natanfelles@gmail.com>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace Framework\Pagination;
11

12
use Framework\Helpers\Isolation;
13
use Framework\HTTP\URL;
14
use Framework\Language\Language;
15
use InvalidArgumentException;
16
use JetBrains\PhpStorm\ArrayShape;
17
use JetBrains\PhpStorm\Deprecated;
18
use JetBrains\PhpStorm\Pure;
19
use JsonSerializable;
20
use LogicException;
21
use Stringable;
22

23
/**
24
 * Class Pager.
25
 *
26
 * @package pagination
27
 */
28
class Pager implements JsonSerializable, Stringable
29
{
30
    protected int $currentPage;
31
    protected ?int $previousPage = null;
32
    protected ?int $nextPage = null;
33
    protected int $lastPage;
34
    protected int $itemsPerPage;
35
    protected int $totalItems;
36
    protected int $surround = 2;
37
    /**
38
     * @var array<string,string>
39
     */
40
    protected array $views = [
41
        // HTML Head
42
        'head' => __DIR__ . '/Views/head.php',
43
        // HTTP Header
44
        'header' => __DIR__ . '/Views/header.php',
45
        // HTML Previous and Next
46
        'pager' => __DIR__ . '/Views/pagination-short.php',
47
        // HTML Full
48
        'pagination' => __DIR__ . '/Views/pagination.php',
49
        'pagination-short' => __DIR__ . '/Views/pagination-short.php',
50
        // Bootstrap 5
51
        'bootstrap' => __DIR__ . '/Views/bootstrap.php',
52
        'bootstrap-short' => __DIR__ . '/Views/bootstrap-short.php',
53
        'bootstrap5' => __DIR__ . '/Views/bootstrap.php',
54
        'bootstrap5-short' => __DIR__ . '/Views/bootstrap-short.php',
55
        // Bulma 0
56
        'bulma' => __DIR__ . '/Views/bulma.php',
57
        'bulma-short' => __DIR__ . '/Views/bulma-short.php',
58
        // Foundation 6
59
        'foundation' => __DIR__ . '/Views/foundation.php',
60
        'foundation-short' => __DIR__ . '/Views/foundation-short.php',
61
        'foundation6' => __DIR__ . '/Views/foundation.php',
62
        'foundation6-short' => __DIR__ . '/Views/foundation-short.php',
63
        // Materialize 1
64
        'materialize' => __DIR__ . '/Views/materialize.php',
65
        'materialize-short' => __DIR__ . '/Views/materialize-short.php',
66
        'materialize1' => __DIR__ . '/Views/materialize.php',
67
        'materialize1-short' => __DIR__ . '/Views/materialize-short.php',
68
        // Primer 20
69
        'primer' => __DIR__ . '/Views/primer.php',
70
        'primer-short' => __DIR__ . '/Views/primer-short.php',
71
        'primer20' => __DIR__ . '/Views/primer.php',
72
        'primer20-short' => __DIR__ . '/Views/primer-short.php',
73
        // Semantic UI 2
74
        'semantic-ui' => __DIR__ . '/Views/semantic-ui.php',
75
        'semantic-ui-short' => __DIR__ . '/Views/semantic-ui-short.php',
76
        'semantic-ui2' => __DIR__ . '/Views/semantic-ui.php',
77
        'semantic-ui2-short' => __DIR__ . '/Views/semantic-ui-short.php',
78
        // Tailwind CSS 3
79
        'tailwind' => __DIR__ . '/Views/tailwind.php',
80
        'tailwind-short' => __DIR__ . '/Views/tailwind-short.php',
81
        'tailwind3' => __DIR__ . '/Views/tailwind.php',
82
        'tailwind3-short' => __DIR__ . '/Views/tailwind-short.php',
83
        // W3.CSS 4
84
        'w3' => __DIR__ . '/Views/w3.php',
85
        'w3-short' => __DIR__ . '/Views/w3-short.php',
86
        'w34' => __DIR__ . '/Views/w3.php',
87
        'w34-short' => __DIR__ . '/Views/w3-short.php',
88
    ];
89
    protected string $defaultView = 'pagination';
90
    protected URL $url;
91
    protected string $oldUrl;
92
    protected string $query = 'page';
93
    protected Language $language;
94

95
    /**
96
     * Pager constructor.
97
     *
98
     * @param int|string $currentPage
99
     * @param int|string $itemsPerPage
100
     * @param int $totalItems
101
     * @param Language|null $language Language instance
102
     * @param string|null $url
103
     */
104
    public function __construct(
105
        int | string $currentPage,
106
        int | string $itemsPerPage,
107
        int $totalItems,
108
        Language $language = null,
109
        string $url = null
110
    ) {
111
        if ($language) {
44✔
112
            $this->setLanguage($language);
1✔
113
        }
114
        $this->setCurrentPage(static::sanitize($currentPage))
44✔
115
            ->setItemsPerPage(static::sanitize($itemsPerPage))
44✔
116
            ->setTotalItems($totalItems);
44✔
117
        $lastPage = (int) \ceil($this->getTotalItems() / $this->getItemsPerPage());
44✔
118
        $this->setLastPage(static::sanitize($lastPage));
44✔
119
        if ($this->getCurrentPage() > 1) {
44✔
120
            if ($this->getCurrentPage() - 1 <= $this->getLastPage()) {
26✔
121
                $this->setPreviousPage($this->getCurrentPage() - 1);
24✔
122
            } elseif ($this->getLastPage() > 1) {
2✔
123
                $this->setPreviousPage($this->getLastPage());
2✔
124
            }
125
        }
126
        if ($this->getCurrentPage() < $this->getLastPage()) {
44✔
127
            $this->setNextPage($this->getCurrentPage() + 1);
44✔
128
        }
129
        isset($url) ? $this->setUrl($url) : $this->prepareUrl();
44✔
130
    }
131

132
    public function __toString() : string
133
    {
134
        return $this->render();
1✔
135
    }
136

137
    /**
138
     * @since 3.5
139
     *
140
     * @return int
141
     */
142
    public function getItemsPerPage() : int
143
    {
144
        return $this->itemsPerPage;
44✔
145
    }
146

147
    /**
148
     * @since 3.5
149
     *
150
     * @param int $itemsPerPage
151
     *
152
     * @return static
153
     */
154
    protected function setItemsPerPage(int $itemsPerPage) : static
155
    {
156
        $this->itemsPerPage = $itemsPerPage;
44✔
157
        return $this;
44✔
158
    }
159

160
    /**
161
     * @since 3.5
162
     *
163
     * @return int
164
     */
165
    public function getTotalItems() : int
166
    {
167
        return $this->totalItems;
44✔
168
    }
169

170
    /**
171
     * @since 3.5
172
     *
173
     * @param int $totalItems
174
     *
175
     * @return static
176
     */
177
    protected function setTotalItems(int $totalItems) : static
178
    {
179
        $this->totalItems = $totalItems;
44✔
180
        return $this;
44✔
181
    }
182

183
    /**
184
     * @param Language|null $language
185
     *
186
     * @return static
187
     */
188
    public function setLanguage(Language $language = null) : static
189
    {
190
        $this->language = $language ?? new Language();
27✔
191
        $this->language->addDirectory(__DIR__ . '/Languages');
27✔
192
        return $this;
27✔
193
    }
194

195
    /**
196
     * @return Language
197
     */
198
    public function getLanguage() : Language
199
    {
200
        if (!isset($this->language)) {
27✔
201
            $this->setLanguage();
27✔
202
        }
203
        return $this->language;
27✔
204
    }
205

206
    /**
207
     * @param string $name
208
     * @param string $filepath
209
     *
210
     * @return static
211
     */
212
    public function setView(string $name, string $filepath) : static
213
    {
214
        if (!\is_file($filepath)) {
2✔
215
            throw new InvalidArgumentException('Invalid Pager view filepath: ' . $filepath);
1✔
216
        }
217
        $this->views[$name] = $filepath;
1✔
218
        return $this;
1✔
219
    }
220

221
    /**
222
     * Get a view filepath.
223
     *
224
     * @param string $name The view name. Default names are: head, header, pager and pagination
225
     *
226
     * @return string
227
     */
228
    public function getView(string $name) : string
229
    {
230
        if (empty($this->views[$name])) {
28✔
231
            throw new InvalidArgumentException('Pager view not found: ' . $name);
1✔
232
        }
233
        return $this->views[$name];
27✔
234
    }
235

236
    /**
237
     * @return array<string,string>
238
     */
239
    #[Pure]
240
    public function getViews() : array
241
    {
242
        return $this->views;
1✔
243
    }
244

245
    /**
246
     * @return int
247
     */
248
    #[Pure]
249
    public function getSurround() : int
250
    {
251
        return $this->surround;
12✔
252
    }
253

254
    /**
255
     * @param int $surround
256
     *
257
     * @return static
258
     */
259
    public function setSurround(int $surround) : static
260
    {
261
        $this->surround = $surround < 0 ? 0 : $surround;
2✔
262
        return $this;
2✔
263
    }
264

265
    /**
266
     * @param string $query
267
     *
268
     * @return static
269
     */
270
    public function setQuery(string $query = 'page') : static
271
    {
272
        $this->query = $query;
1✔
273
        return $this;
1✔
274
    }
275

276
    #[Pure]
277
    public function getQuery() : string
278
    {
279
        return $this->query;
35✔
280
    }
281

282
    /**
283
     * @param array<string>|null $allowed List of queries, an empty array
284
     *  to allow only the default or null to allow all
285
     *
286
     * @return static
287
     */
288
    public function setAllowedQueries(array | null $allowed) : static
289
    {
290
        $this->setUrl($this->oldUrl, $allowed);
1✔
291
        return $this;
1✔
292
    }
293

294
    protected function prepareUrl() : static
295
    {
296
        $scheme = ((isset($_SERVER['REQUEST_SCHEME']) && $_SERVER['REQUEST_SCHEME'] === 'https')
44✔
297
            || (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on'))
44✔
298
            ? 'https'
1✔
299
            : 'http';
44✔
300
        $this->setUrl($scheme . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
44✔
301
        return $this;
44✔
302
    }
303

304
    /**
305
     * @param string|URL $currentPageUrl
306
     * @param array<string>|null $allowedQueries List of queries, an empty array
307
     * to allow only the default or null to allow all
308
     *
309
     * @return static
310
     */
311
    public function setUrl(string | URL $currentPageUrl, array $allowedQueries = null) : static
312
    {
313
        $currentPageUrl = $currentPageUrl instanceof URL
44✔
314
            ? clone $currentPageUrl
1✔
315
            : new URL($currentPageUrl);
44✔
316
        $this->oldUrl = $currentPageUrl->toString();
44✔
317
        if ($allowedQueries !== null) {
44✔
318
            $allowedQueries[] = $this->getQuery();
1✔
319
        }
320
        $currentPageUrl->setQuery(
44✔
321
            $currentPageUrl->getQuery() ?? '',
44✔
322
            $allowedQueries ?? []
44✔
323
        );
44✔
324
        $this->url = $currentPageUrl;
44✔
325
        return $this;
44✔
326
    }
327

328
    public function getUrl() : URL
329
    {
330
        return $this->url;
2✔
331
    }
332

333
    public function getPageUrl(?int $page) : ?string
334
    {
335
        if ($page === null || $page === 0) {
33✔
336
            return null;
5✔
337
        }
338
        return $this->url->addQuery($this->getQuery(), $page)->toString();
33✔
339
    }
340

341
    public function getCurrentPage() : int
342
    {
343
        return $this->currentPage;
44✔
344
    }
345

346
    /**
347
     * @since 3.5
348
     *
349
     * @param int $currentPage
350
     *
351
     * @return static
352
     */
353
    protected function setCurrentPage(int $currentPage) : static
354
    {
355
        $this->currentPage = $currentPage;
44✔
356
        return $this;
44✔
357
    }
358

359
    public function getCurrentPageUrl() : string
360
    {
361
        return $this->getPageUrl($this->currentPage);
16✔
362
    }
363

364
    #[Pure]
365
    public function getFirstPage() : int
366
    {
367
        return 1;
18✔
368
    }
369

370
    public function getFirstPageUrl() : string
371
    {
372
        return $this->getPageUrl($this->getFirstPage());
17✔
373
    }
374

375
    #[Pure]
376
    public function getLastPage() : int
377
    {
378
        return $this->lastPage;
44✔
379
    }
380

381
    /**
382
     * @since 3.5
383
     *
384
     * @param int $lastPage
385
     *
386
     * @return static
387
     */
388
    protected function setLastPage(int $lastPage) : static
389
    {
390
        $this->lastPage = $lastPage;
44✔
391
        return $this;
44✔
392
    }
393

394
    public function getLastPageUrl() : string
395
    {
396
        return $this->getPageUrl($this->getLastPage());
19✔
397
    }
398

399
    #[Pure]
400
    public function getPreviousPage() : ?int
401
    {
402
        return $this->previousPage;
32✔
403
    }
404

405
    /**
406
     * @since 3.5
407
     *
408
     * @param int $previousPage
409
     *
410
     * @return static
411
     */
412
    protected function setPreviousPage(int $previousPage) : static
413
    {
414
        $this->previousPage = $previousPage;
26✔
415
        return $this;
26✔
416
    }
417

418
    public function getPreviousPageUrl() : ?string
419
    {
420
        return $this->getPageUrl($this->getPreviousPage());
27✔
421
    }
422

423
    #[Pure]
424
    public function getNextPage() : ?int
425
    {
426
        return $this->nextPage;
32✔
427
    }
428

429
    /**
430
     * @since 3.5
431
     *
432
     * @param int $nextPage
433
     *
434
     * @return static
435
     */
436
    protected function setNextPage(int $nextPage) : static
437
    {
438
        $this->nextPage = $nextPage;
44✔
439
        return $this;
44✔
440
    }
441

442
    public function getNextPageUrl() : ?string
443
    {
444
        return $this->getPageUrl($this->getNextPage());
29✔
445
    }
446

447
    /**
448
     * @return array<int,string>
449
     */
450
    public function getPreviousPagesUrls() : array
451
    {
452
        $urls = [];
16✔
453
        if ($this->currentPage > 1 && $this->currentPage <= $this->lastPage) {
16✔
454
            $range = \range($this->currentPage - $this->surround, $this->currentPage - 1);
13✔
455
            foreach ($range as $page) {
13✔
456
                if ($page < 1) {
13✔
457
                    continue;
1✔
458
                }
459
                $urls[$page] = $this->getPageUrl($page);
13✔
460
            }
461
        }
462
        return $urls;
16✔
463
    }
464

465
    /**
466
     * @return array<int,string>
467
     */
468
    public function getNextPagesUrls() : array
469
    {
470
        $urls = [];
15✔
471
        if ($this->currentPage < $this->lastPage) {
15✔
472
            $range = \range($this->currentPage + 1, $this->currentPage + $this->surround);
14✔
473
            foreach ($range as $page) {
14✔
474
                if ($page > $this->lastPage) {
14✔
475
                    break;
1✔
476
                }
477
                $urls[$page] = $this->getPageUrl($page);
14✔
478
            }
479
        }
480
        return $urls;
15✔
481
    }
482

483
    /**
484
     * @return array<string,int|null>
485
     */
486
    #[ArrayShape([
487
        'self' => 'int',
488
        'first' => 'int',
489
        'prev' => 'int|null',
490
        'next' => 'int|null',
491
        'last' => 'int',
492
    ])]
493
    #[Pure]
494
    public function get() : array
495
    {
496
        return [
1✔
497
            'self' => $this->getCurrentPage(),
1✔
498
            'first' => $this->getFirstPage(),
1✔
499
            'prev' => $this->getPreviousPage(),
1✔
500
            'next' => $this->getNextPage(),
1✔
501
            'last' => $this->getLastPage(),
1✔
502
        ];
1✔
503
    }
504

505
    /**
506
     * @return array<string,string|null>
507
     */
508
    #[ArrayShape([
509
        'self' => 'string',
510
        'first' => 'string',
511
        'prev' => 'string|null',
512
        'next' => 'string|null',
513
        'last' => 'string',
514
    ])]
515
    public function getWithUrl() : array
516
    {
517
        return [
2✔
518
            'self' => $this->getCurrentPageUrl(),
2✔
519
            'first' => $this->getFirstPageUrl(),
2✔
520
            'prev' => $this->getPreviousPageUrl(),
2✔
521
            'next' => $this->getNextPageUrl(),
2✔
522
            'last' => $this->getLastPageUrl(),
2✔
523
        ];
2✔
524
    }
525

526
    /**
527
     * @param string|null $view
528
     *
529
     * @return string
530
     */
531
    public function render(string $view = null) : string
532
    {
533
        $filename = $this->getView($view ?? $this->getDefaultView());
26✔
534
        \ob_start();
26✔
535
        Isolation::require($filename, ['pager' => $this]);
26✔
536
        return (string) \ob_get_clean();
26✔
537
    }
538

539
    public function renderShort() : string
540
    {
541
        $view = $this->getDefaultView();
1✔
542
        if (!\str_ends_with($view, '-short')) {
1✔
543
            $view .= '-short';
1✔
544
        }
545
        return $this->render($view);
1✔
546
    }
547

548
    public function setDefaultView(string $defaultView) : static
549
    {
550
        if (!\array_key_exists($defaultView, $this->views)) {
2✔
551
            throw new LogicException(
1✔
552
                'Default view "' . $defaultView . '" is not a valid value'
1✔
553
            );
1✔
554
        }
555
        $this->defaultView = $defaultView;
2✔
556
        return $this;
2✔
557
    }
558

559
    #[Pure]
560
    public function getDefaultView() : string
561
    {
562
        return $this->defaultView;
4✔
563
    }
564

565
    /**
566
     * @return array<string,string|null>
567
     */
568
    #[ArrayShape([
569
        'self' => 'string',
570
        'first' => 'string',
571
        'prev' => 'string|null',
572
        'next' => 'string|null',
573
        'last' => 'string',
574
    ])]
575
    public function jsonSerialize() : array
576
    {
577
        return $this->getWithUrl();
1✔
578
    }
579

580
    /**
581
     * @param int|string $number
582
     *
583
     * @return int
584
     *
585
     * @deprecated Use sanitize method
586
     *
587
     * @codeCoverageIgnore
588
     */
589
    #[Deprecated(
590
        reason: 'since version 3.1, use sanitize() instead',
591
        replacement: '%class%::sanitize(%parameter0%)'
592
    )]
593
    public static function sanitizePageNumber(int | string $number) : int
594
    {
595
        \trigger_error(
596
            'Method ' . __METHOD__ . ' is deprecated',
597
            \E_USER_DEPRECATED
598
        );
599
        $number = $number < 1 || !\is_numeric($number) ? 1 : $number;
600
        return $number > \PHP_INT_MAX ? \PHP_INT_MAX : (int) $number;
601
    }
602

603
    /**
604
     * Sanitize a number.
605
     *
606
     * @param mixed $number
607
     *
608
     * @return int
609
     */
610
    #[Pure]
611
    public static function sanitize(mixed $number) : int
612
    {
613
        if (!\is_numeric($number)) {
44✔
614
            return 1;
1✔
615
        }
616
        if ($number < 1) {
44✔
617
            return 1;
44✔
618
        }
619
        return $number >= \PHP_INT_MAX ? \PHP_INT_MAX : (int) $number;
44✔
620
    }
621
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc