Paginator::getPageNumbers()   B
last analyzed

Complexity

Conditions 8
Paths 34

Size

Total Lines 50
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 25
nc 34
nop 0
dl 0
loc 50
rs 8.4444
c 0
b 0
f 0
1
<?php
2
3
/*
4
The MIT License (MIT)
5
6
Copyright (c) 2014 Jason Grimes
7
8
Permission is hereby granted, free of charge, to any person obtaining a copy
9
of this software and associated documentation files (the "Software"), to deal
10
in the Software without restriction, including without limitation the rights
11
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
copies of the Software, and to permit persons to whom the Software is
13
furnished to do so, subject to the following conditions:
14
15
The above copyright notice and this permission notice shall be included in all
16
copies or substantial portions of the Software.
17
18
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
SOFTWARE.
25
*/
26
27
/**
28
 * Paginator.php - Jaxon Paginator
29
 *
30
 * Create pagination links from a Jaxon request and a data array.
31
 *
32
 * @package jaxon-core
33
 * @author Jason Grimes
34
 * @author Thierry Feuzeu
35
 * @copyright 2014 Jason Grimes
36
 * @copyright 2016 Thierry Feuzeu
37
 * @license https://opensource.org/licenses/MIT MIT License
38
 * @link https://github.com/jaxon-php/jaxon-core
39
 */
40
41
namespace Jaxon\App\Pagination;
42
43
use Jaxon\App\Pagination\Page;
44
use Jaxon\Response\NodeResponse;
45
use Jaxon\Response\Response;
46
use Jaxon\Plugin\Response\Pagination\PaginatorPlugin;
47
use Jaxon\Script\JsExpr;
48
use Closure;
49
50
use function array_pop;
51
use function array_shift;
52
use function array_walk;
53
use function ceil;
54
use function count;
55
use function floor;
56
use function is_a;
57
use function max;
58
use function trim;
59
60
class Paginator
61
{
62
    /**
63
     * @var integer
64
     */
65
    protected $nItemsCount = 0;
66
67
    /**
68
     * @var integer
69
     */
70
    protected $nPagesCount = 0;
71
72
    /**
73
     * @var integer
74
     */
75
    protected $nItemsPerPage = 0;
76
77
    /**
78
     * @var integer
79
     */
80
    protected $nPageNumber = 0;
81
82
    /**
83
     * @var integer
84
     */
85
    protected $nMaxPages = 10;
86
87
    /**
88
     * @var string
89
     */
90
    protected $sPreviousText = '&laquo;';
91
92
    /**
93
     * @var string
94
     */
95
    protected $sNextText = '&raquo;';
96
97
    /**
98
     * @var string
99
     */
100
    protected $sEllipsysText = '...';
101
102
    /**
103
     * @var PaginatorPlugin
104
     */
105
    private $xPlugin;
106
107
    /**
108
     * The constructor.
109
     *
110
     * @param PaginatorPlugin $xPlugin
111
     * @param int $nPageNumber     The current page number
112
     * @param int $nItemsPerPage    The number of items per page
113
     * @param int $nItemsCount      The total number of items
114
     */
115
    public function __construct(PaginatorPlugin $xPlugin, int $nPageNumber, int $nItemsPerPage, int $nItemsCount)
116
    {
117
        $this->xPlugin = $xPlugin;
118
        $this->nItemsPerPage = $nItemsPerPage > 0 ? $nItemsPerPage : 0;
119
        $this->nItemsCount = $nItemsCount > 0 ? $nItemsCount : 0;
120
        $this->nPageNumber = $nPageNumber < 1 ? 1 : $nPageNumber;
121
        $this->updatePagesCount();
122
    }
123
124
    /**
125
     * Update the number of pages
126
     *
127
     * @return Paginator
128
     */
129
    private function updatePagesCount(): Paginator
130
    {
131
        $this->nPagesCount = ($this->nItemsPerPage === 0 ? 0 :
132
            (int)ceil($this->nItemsCount / $this->nItemsPerPage));
133
        if($this->nPageNumber > $this->nPagesCount)
134
        {
135
            $this->nPageNumber = $this->nPagesCount;
136
        }
137
        return $this;
138
    }
139
140
    /**
141
     * Set the text for the previous page link
142
     *
143
     * @param string $sText    The text for the previous page link
144
     *
145
     * @return Paginator
146
     */
147
    public function setPreviousText(string $sText): Paginator
148
    {
149
        $this->sPreviousText = $sText;
150
        return $this;
151
    }
152
153
    /**
154
     * Set the text for the next page link
155
     *
156
     * @param string $sText    The text for the previous page link
157
     *
158
     * @return Paginator
159
     */
160
    public function setNextText(string $sText): Paginator
161
    {
162
        $this->sNextText = $sText;
163
        return $this;
164
    }
165
166
    /**
167
     * Set the max number of pages to show
168
     *
169
     * @param int $nMaxPages    The max number of pages to show
170
     *
171
     * @return Paginator
172
     */
173
    public function setMaxPages(int $nMaxPages): Paginator
174
    {
175
        $this->nMaxPages = max($nMaxPages, 4);
176
        return $this;
177
    }
178
179
    /**
180
     * Get the previous page data.
181
     *
182
     * @return Page
183
     */
184
    protected function getPrevPage(): Page
185
    {
186
        return $this->nPageNumber <= 1 ?
187
            new Page('disabled', $this->sPreviousText, 0) :
188
            new Page('enabled', $this->sPreviousText, $this->nPageNumber - 1);
189
    }
190
191
    /**
192
     * Get the next page data.
193
     *
194
     * @return Page
195
     */
196
    protected function getNextPage(): Page
197
    {
198
        return $this->nPageNumber >= $this->nPagesCount ?
199
            new Page('disabled', $this->sNextText, 0) :
200
            new Page('enabled', $this->sNextText, $this->nPageNumber + 1);
201
    }
202
203
    /**
204
     * Get a page data.
205
     *
206
     * @param integer $nNumber    The page number
207
     *
208
     * @return Page
209
     */
210
    protected function getPage(int $nNumber): Page
211
    {
212
        if($nNumber < 1)
213
        {
214
            return new Page('disabled', $this->sEllipsysText, 0);
215
        }
216
        $sType = ($nNumber === $this->nPageNumber ? 'current' : 'enabled');
217
        return new Page($sType, "$nNumber", $nNumber);
218
    }
219
220
    /**
221
     * Get the array of page numbers to be printed.
222
     *
223
     * Example: [1, 0, 4, 5, 6, 0, 10]
224
     *
225
     * @return array
226
     */
227
    protected function getPageNumbers(): array
228
    {
229
        $aPageNumbers = [];
230
231
        if($this->nPagesCount <= $this->nMaxPages)
232
        {
233
            for($i = 0; $i < $this->nPagesCount; $i++)
234
            {
235
                $aPageNumbers[] = $i + 1;
236
            }
237
238
            return $aPageNumbers;
239
        }
240
241
        // Determine the sliding range, centered around the current page.
242
        $nNumAdjacents = (int)floor(($this->nMaxPages - 4) / 2);
243
244
        $nSlidingStart = 1;
245
        $nSlidingEndOffset = $nNumAdjacents + 3 - $this->nPageNumber;
246
        if($nSlidingEndOffset < 0)
247
        {
248
            $nSlidingStart = $this->nPageNumber - $nNumAdjacents;
249
            $nSlidingEndOffset = 0;
250
        }
251
252
        $nSlidingEnd = $this->nPagesCount;
253
        $nSlidingStartOffset = $this->nPageNumber + $nNumAdjacents + 2 - $this->nPagesCount;
254
        if($nSlidingStartOffset < 0)
255
        {
256
            $nSlidingEnd = $this->nPageNumber + $nNumAdjacents;
257
            $nSlidingStartOffset = 0;
258
        }
259
260
        // Build the list of page numbers.
261
        if($nSlidingStart > 1)
262
        {
263
            $aPageNumbers[] = 1;
264
            $aPageNumbers[] = 0; // Ellipsys;
265
        }
266
        for($i = $nSlidingStart - $nSlidingStartOffset; $i <= $nSlidingEnd + $nSlidingEndOffset; $i++)
267
        {
268
            $aPageNumbers[] = $i;
269
        }
270
        if($nSlidingEnd < $this->nPagesCount)
271
        {
272
            $aPageNumbers[] = 0; // Ellipsys;
273
            $aPageNumbers[] = $this->nPagesCount;
274
        }
275
276
        return $aPageNumbers;
277
    }
278
279
    /**
280
     * Get the current page number.
281
     *
282
     * @return int
283
     */
284
    public function currentPage(): int
285
    {
286
        return $this->nPageNumber;
287
    }
288
289
    /**
290
     * Get the links (pages raw data).
291
     *
292
     * @return array<Page>
293
     */
294
    public function pages(): array
295
    {
296
        if($this->nPagesCount < 2)
297
        {
298
            return [];
299
        }
300
301
        $aPageNumbers = $this->getPageNumbers();
302
        $aPages = [$this->getPrevPage()];
303
        array_walk($aPageNumbers, function($nNumber) use(&$aPages) {
304
            $aPages[] = $this->getPage($nNumber);
305
        });
306
        $aPages[] = $this->getNextPage();
307
308
        return $aPages;
309
    }
310
311
    /**
312
     * Call a closure that will receive the page number as parameter.
313
     *
314
     * @param Closure $fPageCallback
315
     *
316
     * @return Paginator
317
     */
318
    public function page(Closure $fPageCallback): Paginator
319
    {
320
        $fPageCallback($this->nPageNumber);
321
322
        return $this;
323
    }
324
325
    /**
326
     * Call a closure that will receive the pagination offset as parameter.
327
     *
328
     * @param Closure $fOffsetCallback
329
     *
330
     * @return Paginator
331
     */
332
    public function offset(Closure $fOffsetCallback): Paginator
333
    {
334
        $fOffsetCallback(($this->nPageNumber - 1) * $this->nItemsPerPage);
335
336
        return $this;
337
    }
338
339
    /**
340
     * Show the pagination links
341
     *
342
     * @return string
343
     */
344
    private function renderLinks(): string
345
    {
346
        $aPages = $this->pages();
347
        if(count($aPages) === 0)
348
        {
349
            return '';
350
        }
351
352
        $xPrevPage = array_shift($aPages); // The first entry in the array
353
        $xNextPage = array_pop($aPages); // The last entry in the array
354
        return $this->xPlugin->renderer()->render($aPages, $xPrevPage, $xNextPage);
355
    }
356
357
    /**
358
     * Show the pagination links
359
     *
360
     * @param string $sWrapperId
361
     *
362
     * @return array|null
363
     */
364
    private function showLinks(string $sWrapperId): ?array
365
    {
366
        $sHtml = $this->renderLinks();
367
        // The HTML code must always be displayed, even if it is empty.
368
        if(is_a($this->xPlugin->response(), Response::class))
369
        {
370
            /** @var Response */
371
            $xResponse = $this->xPlugin->response();
372
            $xResponse->html($sWrapperId, $sHtml);
373
            return !$sHtml ? null : ['id' => $sWrapperId];
374
        }
375
376
        // The wrapper id is not needed for the NodeResponse
377
        /** @var NodeResponse */
378
        $xResponse = $this->xPlugin->response();
379
        $xResponse->html($sHtml);
0 ignored issues
show
Bug introduced by
The method html() does not exist on Jaxon\Response\AbstractResponse. It seems like you code against a sub-type of said class. However, the method does not exist in Jaxon\Response\AjaxResponse. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

379
        $xResponse->/** @scrutinizer ignore-call */ 
380
                    html($sHtml);
Loading history...
380
        return !$sHtml ? null : [];
381
    }
382
383
    /**
384
     * @param JsExpr $xCall
385
     * @param string $sWrapperId
386
     *
387
     * @return void
388
     */
389
    public function render(JsExpr $xCall, string $sWrapperId = ''): void
390
    {
391
        if(($xFunc = $xCall->func()) === null)
392
        {
393
            return;
394
        }
395
396
        $aParams = $this->showLinks(trim($sWrapperId));
397
        if($aParams !== null)
398
        {
399
            // Set click handlers on the pagination links
400
            $aParams['func'] = $xFunc->withPage()->jsonSerialize();
401
            $this->xPlugin->addCommand('pg.paginate', $aParams);
402
        }
403
    }
404
}
405