Paginator::getPage()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 4
c 1
b 0
f 0
nc 3
nop 1
dl 0
loc 8
rs 10
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\Plugin\Response\Pagination;
42
43
use Closure;
44
use Jaxon\Response\Response;
45
use Jaxon\Response\ComponentResponse;
46
use Jaxon\Script\JsExpr;
47
48
use function array_walk;
49
use function array_map;
50
use function array_pop;
51
use function array_shift;
52
use function ceil;
53
use function count;
54
use function floor;
55
use function is_a;
56
use function max;
57
use function trim;
58
59
class Paginator
60
{
61
    /**
62
     * @var integer
63
     */
64
    protected $nItemsCount = 0;
65
66
    /**
67
     * @var integer
68
     */
69
    protected $nPagesCount = 0;
70
71
    /**
72
     * @var integer
73
     */
74
    protected $nItemsPerPage = 0;
75
76
    /**
77
     * @var integer
78
     */
79
    protected $nPageNumber = 0;
80
81
    /**
82
     * @var integer
83
     */
84
    protected $nMaxPages = 10;
85
86
    /**
87
     * @var string
88
     */
89
    protected $sPreviousText = '&laquo;';
90
91
    /**
92
     * @var string
93
     */
94
    protected $sNextText = '&raquo;';
95
96
    /**
97
     * @var string
98
     */
99
    protected $sEllipsysText = '...';
100
101
    /**
102
     * @var PaginatorPlugin
103
     */
104
    private $xPlugin;
105
106
    /**
107
     * The constructor.
108
     *
109
     * @param PaginatorPlugin $xPlugin
110
     * @param int $nPageNumber     The current page number
111
     * @param int $nItemsPerPage    The number of items per page
112
     * @param int $nItemsCount      The total number of items
113
     */
114
    public function __construct(PaginatorPlugin $xPlugin, int $nPageNumber, int $nItemsPerPage, int $nItemsCount)
115
    {
116
        $this->xPlugin = $xPlugin;
117
        $this->nItemsPerPage = $nItemsPerPage > 0 ? $nItemsPerPage : 0;
118
        $this->nItemsCount = $nItemsCount > 0 ? $nItemsCount : 0;
119
        $this->nPageNumber = $nPageNumber < 1 ? 1 : $nPageNumber;
120
        $this->updatePagesCount();
121
    }
122
123
    /**
124
     * Update the number of pages
125
     *
126
     * @return Paginator
127
     */
128
    private function updatePagesCount(): Paginator
129
    {
130
        $this->nPagesCount = ($this->nItemsPerPage === 0 ? 0 :
131
            (int)ceil($this->nItemsCount / $this->nItemsPerPage));
132
        if($this->nPageNumber > $this->nPagesCount)
133
        {
134
            $this->nPageNumber = $this->nPagesCount;
135
        }
136
        return $this;
137
    }
138
139
    /**
140
     * Set the text for the previous page link
141
     *
142
     * @param string $sText    The text for the previous page link
143
     *
144
     * @return Paginator
145
     */
146
    public function setPreviousText(string $sText): Paginator
147
    {
148
        $this->sPreviousText = $sText;
149
        return $this;
150
    }
151
152
    /**
153
     * Set the text for the next page link
154
     *
155
     * @param string $sText    The text for the previous page link
156
     *
157
     * @return Paginator
158
     */
159
    public function setNextText(string $sText): Paginator
160
    {
161
        $this->sNextText = $sText;
162
        return $this;
163
    }
164
165
    /**
166
     * Set the max number of pages to show
167
     *
168
     * @param int $nMaxPages    The max number of pages to show
169
     *
170
     * @return Paginator
171
     */
172
    public function setMaxPages(int $nMaxPages): Paginator
173
    {
174
        $this->nMaxPages = max($nMaxPages, 4);
175
        return $this;
176
    }
177
178
    /**
179
     * Get the previous page data.
180
     *
181
     * @return Page
182
     */
183
    protected function getPrevPage(): Page
184
    {
185
        return $this->nPageNumber <= 1 ?
186
            new Page('disabled', $this->sPreviousText, 0) :
187
            new Page('enabled', $this->sPreviousText, $this->nPageNumber - 1);
188
    }
189
190
    /**
191
     * Get the next page data.
192
     *
193
     * @return Page
194
     */
195
    protected function getNextPage(): Page
196
    {
197
        return $this->nPageNumber >= $this->nPagesCount ?
198
            new Page('disabled', $this->sNextText, 0) :
199
            new Page('enabled', $this->sNextText, $this->nPageNumber + 1);
200
    }
201
202
    /**
203
     * Get a page data.
204
     *
205
     * @param integer $nNumber    The page number
206
     *
207
     * @return Page
208
     */
209
    protected function getPage(int $nNumber): Page
210
    {
211
        if($nNumber < 1)
212
        {
213
            return new Page('disabled', $this->sEllipsysText, 0);
214
        }
215
        $sType = ($nNumber === $this->nPageNumber ? 'current' : 'enabled');
216
        return new Page($sType, "$nNumber", $nNumber);
217
    }
218
219
    /**
220
     * Get the array of page numbers to be printed.
221
     *
222
     * Example: [1, 0, 4, 5, 6, 0, 10]
223
     *
224
     * @return array
225
     */
226
    protected function getPageNumbers(): array
227
    {
228
        $aPageNumbers = [];
229
230
        if($this->nPagesCount <= $this->nMaxPages)
231
        {
232
            for($i = 0; $i < $this->nPagesCount; $i++)
233
            {
234
                $aPageNumbers[] = $i + 1;
235
            }
236
237
            return $aPageNumbers;
238
        }
239
240
        // Determine the sliding range, centered around the current page.
241
        $nNumAdjacents = (int)floor(($this->nMaxPages - 4) / 2);
242
243
        $nSlidingStart = 1;
244
        $nSlidingEndOffset = $nNumAdjacents + 3 - $this->nPageNumber;
245
        if($nSlidingEndOffset < 0)
246
        {
247
            $nSlidingStart = $this->nPageNumber - $nNumAdjacents;
248
            $nSlidingEndOffset = 0;
249
        }
250
251
        $nSlidingEnd = $this->nPagesCount;
252
        $nSlidingStartOffset = $this->nPageNumber + $nNumAdjacents + 2 - $this->nPagesCount;
253
        if($nSlidingStartOffset < 0)
254
        {
255
            $nSlidingEnd = $this->nPageNumber + $nNumAdjacents;
256
            $nSlidingStartOffset = 0;
257
        }
258
259
        // Build the list of page numbers.
260
        if($nSlidingStart > 1)
261
        {
262
            $aPageNumbers[] = 1;
263
            $aPageNumbers[] = 0; // Ellipsys;
264
        }
265
        for($i = $nSlidingStart - $nSlidingStartOffset; $i <= $nSlidingEnd + $nSlidingEndOffset; $i++)
266
        {
267
            $aPageNumbers[] = $i;
268
        }
269
        if($nSlidingEnd < $this->nPagesCount)
270
        {
271
            $aPageNumbers[] = 0; // Ellipsys;
272
            $aPageNumbers[] = $this->nPagesCount;
273
        }
274
275
        return $aPageNumbers;
276
    }
277
278
    /**
279
     * Get the links (pages raw data).
280
     *
281
     * @return array<Page>
282
     */
283
    public function pages(): array
284
    {
285
        if($this->nPagesCount < 2)
286
        {
287
            return [];
288
        }
289
290
        $aPageNumbers = $this->getPageNumbers();
291
        $aPages = [$this->getPrevPage()];
292
        array_walk($aPageNumbers, function($nNumber) use(&$aPages) {
293
            $aPages[] = $this->getPage($nNumber);
294
        });
295
        $aPages[] = $this->getNextPage();
296
297
        return $aPages;
298
    }
299
300
    /**
301
     * Call a closure that will receive the page number as parameter.
302
     *
303
     * @param Closure $fPageCallback
304
     *
305
     * @return Paginator
306
     */
307
    public function page(Closure $fPageCallback): Paginator
308
    {
309
        $fPageCallback($this->nPageNumber);
310
311
        return $this;
312
    }
313
314
    /**
315
     * Call a closure that will receive the pagination offset as parameter.
316
     *
317
     * @param Closure $fOffsetCallback
318
     *
319
     * @return Paginator
320
     */
321
    public function offset(Closure $fOffsetCallback): Paginator
322
    {
323
        $fOffsetCallback(($this->nPageNumber - 1) * $this->nItemsPerPage);
324
325
        return $this;
326
    }
327
328
    /**
329
     * @param JsExpr $xCall
330
     * @param string $sWrapperId
331
     *
332
     * @return void
333
     */
334
    public function render(JsExpr $xCall, string $sWrapperId = '')
335
    {
336
        if(($xFunc = $xCall->func()) === null)
337
        {
338
            return;
339
        }
340
341
        $sHtml = '';
342
        $aPages = $this->pages();
343
        if(count($aPages) > 0)
344
        {
345
            $xRenderer = $this->xPlugin->renderer();
346
            $aPages = array_map(function($xPage) use($xRenderer) {
347
                return $xRenderer->render('pagination::links/' . $xPage->sType, [
348
                    'page' => $xPage->nNumber,
349
                    'text' => $xPage->sText,
350
                ]);
351
            }, $aPages);
352
            $aPrevPage = array_shift($aPages); // The first entry in the array
353
            $aNextPage = array_pop($aPages); // The last entry in the array
354
            $sHtml = trim($xRenderer->render('pagination::wrapper', [
355
                'links' => $aPages,
356
                'prev' => $aPrevPage,
357
                'next' => $aNextPage,
358
            ])->__toString());
359
        }
360
        // The HTML code must always be displayed, even if it is empty.
361
        $aParams = $this->showLinks($sHtml, trim($sWrapperId));
362
        if($sHtml !== '')
363
        {
364
            // Set click handlers on the pagination links
365
            $aParams['func'] = $xFunc->withPage()->jsonSerialize();
366
            $this->xPlugin->addCommand('pg.paginate', $aParams);
367
        }
368
    }
369
370
    /**
371
     * Show the pagination links
372
     *
373
     * @param string $sHtml
374
     * @param string $sWrapperId
375
     *
376
     * @return array
377
     */
378
    private function showLinks(string $sHtml, string $sWrapperId): array
379
    {
380
        if(is_a($this->xPlugin->response(), Response::class))
381
        {
382
            /** @var Response */
383
            $xResponse = $this->xPlugin->response();
384
            $xResponse->html($sWrapperId, $sHtml);
385
            return ['id' => $sWrapperId];
386
        }
387
388
        // The wrapper id is not needed for the ComponentResponse
389
        /** @var ComponentResponse */
390
        $xResponse = $this->xPlugin->response();
391
        $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

391
        $xResponse->/** @scrutinizer ignore-call */ 
392
                    html($sHtml);
Loading history...
392
        return [];
393
    }
394
}
395