Passed
Push — master ( 288a96...964b09 )
by Daniel
10:30
created

PaginatedList::toArray()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\ORM;
4
5
use SilverStripe\Control\HTTP;
6
use SilverStripe\Control\HTTPRequest;
7
use SilverStripe\ORM\Queries\SQLSelect;
8
use SilverStripe\View\ArrayData;
9
use ArrayAccess;
10
use Exception;
11
use IteratorIterator;
12
13
/**
14
 * A decorator that wraps around a data list in order to provide pagination.
15
 */
16
class PaginatedList extends ListDecorator
17
{
18
19
    protected $request;
20
    protected $getVar = 'start';
21
22
    protected $pageLength = 10;
23
    protected $pageStart;
24
    protected $totalItems;
25
    protected $limitItems = true;
26
27
    /**
28
     * Constructs a new paginated list instance around a list.
29
     *
30
     * @param SS_List $list The list to paginate. The getRange method will
31
     *        be used to get the subset of objects to show.
32
     * @param array|ArrayAccess Either a map of request parameters or
0 ignored issues
show
Bug introduced by
The type SilverStripe\ORM\Either was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
33
     *        request object that the pagination offset is read from.
34
     * @throws Exception
35
     */
36
    public function __construct(SS_List $list, $request = array())
37
    {
38
        if (!is_array($request) && !$request instanceof ArrayAccess) {
39
            throw new Exception('The request must be readable as an array.');
40
        }
41
42
        $this->setRequest($request);
43
        parent::__construct($list);
44
    }
45
46
    /**
47
     * Returns the GET var that is used to set the page start. This defaults
48
     * to "start".
49
     *
50
     * If there is more than one paginated list on a page, it is neccesary to
51
     * set a different get var for each using {@link setPaginationGetVar()}.
52
     *
53
     * @return string
54
     */
55
    public function getPaginationGetVar()
56
    {
57
        return $this->getVar;
58
    }
59
60
    /**
61
     * Sets the GET var used to set the page start.
62
     *
63
     * @param string $var
64
     * @return $this
65
     */
66
    public function setPaginationGetVar($var)
67
    {
68
        $this->getVar = $var;
69
        return $this;
70
    }
71
72
    /**
73
     * Returns the number of items displayed per page. This defaults to 10.
74
     *
75
     * @return int.
76
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment int. at position 0 could not be parsed: Unknown type name 'int.' at position 0 in int..
Loading history...
77
    public function getPageLength()
78
    {
79
        return $this->pageLength;
80
    }
81
82
    /**
83
     * Set the number of items displayed per page. Set to zero to disable paging.
84
     *
85
     * @param int $length
86
     * @return $this
87
     */
88
    public function setPageLength($length)
89
    {
90
        $this->pageLength = (int)$length;
91
        return $this;
92
    }
93
94
    /**
95
     * Sets the current page.
96
     *
97
     * @param int $page Page index beginning with 1
98
     * @return $this
99
     */
100
    public function setCurrentPage($page)
101
    {
102
        $this->pageStart = ((int)$page - 1) * $this->getPageLength();
103
        return $this;
104
    }
105
106
    /**
107
     * Returns the offset of the item the current page starts at.
108
     *
109
     * @return int
110
     */
111
    public function getPageStart()
112
    {
113
        $request = $this->getRequest();
114
        if ($this->pageStart === null) {
115
            if ($request
116
                && isset($request[$this->getPaginationGetVar()])
117
                && $request[$this->getPaginationGetVar()] > 0
118
            ) {
119
                $this->pageStart = (int)$request[$this->getPaginationGetVar()];
120
            } else {
121
                $this->pageStart = 0;
122
            }
123
        }
124
125
        return $this->pageStart;
126
    }
127
128
    /**
129
     * Sets the offset of the item that current page starts at. This should be
130
     * a multiple of the page length.
131
     *
132
     * @param int $start
133
     * @return $this
134
     */
135
    public function setPageStart($start)
136
    {
137
        $this->pageStart = (int)$start;
138
        return $this;
139
    }
140
141
    /**
142
     * Returns the total number of items in the unpaginated list.
143
     *
144
     * @return int
145
     */
146
    public function getTotalItems()
147
    {
148
        if ($this->totalItems === null) {
149
            $this->totalItems = count($this->list);
150
        }
151
152
        return $this->totalItems;
153
    }
154
155
    /**
156
     * Sets the total number of items in the list. This is useful when doing
157
     * custom pagination.
158
     *
159
     * @param int $items
160
     * @return $this
161
     */
162
    public function setTotalItems($items)
163
    {
164
        $this->totalItems = (int)$items;
165
        return $this;
166
    }
167
168
    /**
169
     * Sets the page length, page start and total items from a query object's
170
     * limit, offset and unlimited count. The query MUST have a limit clause.
171
     *
172
     * @param SQLSelect $query
173
     * @return $this
174
     */
175
    public function setPaginationFromQuery(SQLSelect $query)
176
    {
177
        if ($limit = $query->getLimit()) {
178
            $this->setPageLength($limit['limit']);
179
            $this->setPageStart($limit['start']);
180
            $this->setTotalItems($query->unlimitedRowCount());
181
        }
182
        return $this;
183
    }
184
185
    /**
186
     * Returns whether or not the underlying list is limited to the current
187
     * pagination range when iterating.
188
     *
189
     * By default the limit method will be called on the underlying list to
190
     * extract the subset for the current page. In some situations, if the list
191
     * is custom generated and already paginated you don't want to additionally
192
     * limit the list. You can use {@link setLimitItems} to control this.
193
     *
194
     * @return bool
195
     */
196
    public function getLimitItems()
197
    {
198
        return $this->limitItems;
199
    }
200
201
    /**
202
     * @param bool $limit
203
     * @return $this
204
     */
205
    public function setLimitItems($limit)
206
    {
207
        $this->limitItems = (bool)$limit;
208
        return $this;
209
    }
210
211
    /**
212
     * @return IteratorIterator
213
     */
214
    public function getIterator()
215
    {
216
        $pageLength = $this->getPageLength();
217
        if ($this->limitItems && $pageLength) {
218
            $tmptList = clone $this->list;
219
            return new IteratorIterator(
220
                $tmptList->limit($pageLength, $this->getPageStart())
0 ignored issues
show
Bug introduced by
The method limit() does not exist on SilverStripe\ORM\SS_List. It seems like you code against a sub-type of said class. However, the method does not exist in SilverStripe\ORM\Sortable or SilverStripe\ORM\Filterable. 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

220
                $tmptList->/** @scrutinizer ignore-call */ 
221
                           limit($pageLength, $this->getPageStart())
Loading history...
221
            );
222
        } else {
223
            return new IteratorIterator($this->list);
224
        }
225
    }
226
227
    /**
228
     * @return array
229
     */
230
    public function toArray()
231
    {
232
        $result = [];
233
234
        // Use getIterator()
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
235
        foreach ($this as $record) {
236
            $result[] = $record;
237
        }
238
239
        return $result;
240
    }
241
242
    /**
243
     * Returns a set of links to all the pages in the list. This is useful for
244
     * basic pagination.
245
     *
246
     * By default it returns links to every page, but if you pass the $max
247
     * parameter the number of pages will be limited to that number, centered
248
     * around the current page.
249
     *
250
     * @param  int $max
251
     * @return SS_List
252
     */
253
    public function Pages($max = null)
254
    {
255
        $result = new ArrayList();
256
257
        if ($max) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $max of type null|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
258
            $start = ($this->CurrentPage() - floor($max / 2)) - 1;
259
            $end = $this->CurrentPage() + floor($max / 2);
260
261
            if ($start < 0) {
262
                $start = 0;
263
                $end = $max;
264
            }
265
266
            if ($end > $this->TotalPages()) {
267
                $end = $this->TotalPages();
268
                $start = max(0, $end - $max);
269
            }
270
        } else {
271
            $start = 0;
272
            $end = $this->TotalPages();
273
        }
274
275
        for ($i = $start; $i < $end; $i++) {
276
            $result->push(new ArrayData(array(
277
                'PageNum' => $i + 1,
278
                'Link' => HTTP::setGetVar(
279
                    $this->getPaginationGetVar(),
280
                    $i * $this->getPageLength(),
281
                    ($this->request instanceof HTTPRequest) ? $this->request->getURL(true) : null
282
                ),
283
                'CurrentBool' => $this->CurrentPage() == ($i + 1)
284
            )));
285
        }
286
287
        return $result;
288
    }
289
290
    /**
291
     * Returns a summarised pagination which limits the number of pages shown
292
     * around the current page for visually balanced.
293
     *
294
     * Example: 25 pages total, currently on page 6, context of 4 pages
295
     * [prev] [1] ... [4] [5] [[6]] [7] [8] ... [25] [next]
296
     *
297
     * Example template usage:
298
     * <code>
299
     *    <% if MyPages.MoreThanOnePage %>
300
     *        <% if MyPages.NotFirstPage %>
301
     *            <a class="prev" href="$MyPages.PrevLink">Prev</a>
302
     *        <% end_if %>
303
     *        <% loop MyPages.PaginationSummary(4) %>
304
     *            <% if CurrentBool %>
305
     *                $PageNum
306
     *            <% else %>
307
     *                <% if Link %>
308
     *                    <a href="$Link">$PageNum</a>
309
     *                <% else %>
310
     *                    ...
311
     *                <% end_if %>
312
     *            <% end_if %>
313
     *        <% end_loop %>
314
     *        <% if MyPages.NotLastPage %>
315
     *            <a class="next" href="$MyPages.NextLink">Next</a>
316
     *        <% end_if %>
317
     *    <% end_if %>
318
     * </code>
319
     *
320
     * @param  int $context The number of pages to display around the current
321
     *         page. The number should be event, as half the number of each pages
322
     *         are displayed on either side of the current one.
323
     * @return SS_List
324
     */
325
    public function PaginationSummary($context = 4)
326
    {
327
        $result = new ArrayList();
328
        $current = $this->CurrentPage();
329
        $total = $this->TotalPages();
330
331
        // Make the number even for offset calculations.
332
        if ($context % 2) {
333
            $context--;
334
        }
335
336
        // If the first or last page is current, then show all context on one
337
        // side of it - otherwise show half on both sides.
338
        if ($current == 1 || $current == $total) {
339
            $offset = $context;
340
        } else {
341
            $offset = floor($context / 2);
342
        }
343
344
        $left = max($current - $offset, 1);
345
        $right = min($current + $offset, $total);
346
        $range = range($current - $offset, $current + $offset);
347
348
        if ($left + $context > $total) {
349
            $left = $total - $context;
350
        }
351
352
        for ($i = 0; $i < $total; $i++) {
353
            $link = HTTP::setGetVar(
354
                $this->getPaginationGetVar(),
355
                $i * $this->getPageLength(),
356
                ($this->request instanceof HTTPRequest) ? $this->request->getURL(true) : null
357
            );
358
            $num = $i + 1;
359
360
            $emptyRange = $num != 1 && $num != $total && (
361
                    $num == $left - 1 || $num == $right + 1
362
                );
363
364
            if ($emptyRange) {
365
                $result->push(new ArrayData(array(
366
                    'PageNum' => null,
367
                    'Link' => null,
368
                    'CurrentBool' => false
369
                )));
370
            } elseif ($num == 1 || $num == $total || in_array($num, $range)) {
371
                $result->push(new ArrayData(array(
372
                    'PageNum' => $num,
373
                    'Link' => $link,
374
                    'CurrentBool' => $current == $num
375
                )));
376
            }
377
        }
378
379
        return $result;
380
    }
381
382
    /**
383
     * @return int
384
     */
385
    public function CurrentPage()
386
    {
387
        $pageLength = $this->getPageLength();
388
        return $pageLength
389
            ? floor($this->getPageStart() / $pageLength) + 1
390
            : 1;
391
    }
392
393
    /**
394
     * @return int
395
     */
396
    public function TotalPages()
397
    {
398
        $pageLength = $this->getPageLength();
399
        return $pageLength
400
            ? ceil($this->getTotalItems() / $pageLength)
401
            : min($this->getTotalItems(), 1);
402
    }
403
404
    /**
405
     * @return bool
406
     */
407
    public function MoreThanOnePage()
408
    {
409
        return $this->TotalPages() > 1;
410
    }
411
412
    /**
413
     * @return bool
414
     */
415
    public function NotFirstPage()
416
    {
417
        return $this->CurrentPage() != 1;
418
    }
419
420
    /**
421
     * @return bool
422
     */
423
    public function NotLastPage()
424
    {
425
        return $this->CurrentPage() < $this->TotalPages();
426
    }
427
428
    /**
429
     * Returns the number of the first item being displayed on the current
430
     * page. This is useful for things like "displaying 10-20".
431
     *
432
     * @return int
433
     */
434
    public function FirstItem()
435
    {
436
        return ($start = $this->getPageStart()) ? $start + 1 : 1;
437
    }
438
439
    /**
440
     * Returns the number of the last item being displayed on this page.
441
     *
442
     * @return int
443
     */
444
    public function LastItem()
445
    {
446
        $pageLength = $this->getPageLength();
447
        if (!$pageLength) {
448
            return $this->getTotalItems();
449
        } elseif ($start = $this->getPageStart()) {
450
            return min($start + $pageLength, $this->getTotalItems());
451
        } else {
452
            return min($pageLength, $this->getTotalItems());
453
        }
454
    }
455
456
    /**
457
     * Returns a link to the first page.
458
     *
459
     * @return string
460
     */
461
    public function FirstLink()
462
    {
463
        return HTTP::setGetVar(
464
            $this->getPaginationGetVar(),
465
            0,
466
            ($this->request instanceof HTTPRequest) ? $this->request->getURL(true) : null
467
        );
468
    }
469
470
    /**
471
     * Returns a link to the last page.
472
     *
473
     * @return string
474
     */
475
    public function LastLink()
476
    {
477
        return HTTP::setGetVar(
478
            $this->getPaginationGetVar(),
479
            ($this->TotalPages() - 1) * $this->getPageLength(),
480
            ($this->request instanceof HTTPRequest) ? $this->request->getURL(true) : null
481
        );
482
    }
483
484
    /**
485
     * Returns a link to the next page, if there is another page after the
486
     * current one.
487
     *
488
     * @return string
489
     */
490
    public function NextLink()
491
    {
492
        if ($this->NotLastPage()) {
493
            return HTTP::setGetVar(
494
                $this->getPaginationGetVar(),
495
                $this->getPageStart() + $this->getPageLength(),
496
                ($this->request instanceof HTTPRequest) ? $this->request->getURL(true) : null
497
            );
498
        }
499
    }
500
501
    /**
502
     * Returns a link to the previous page, if the first page is not currently
503
     * active.
504
     *
505
     * @return string
506
     */
507
    public function PrevLink()
508
    {
509
        if ($this->NotFirstPage()) {
510
            return HTTP::setGetVar(
511
                $this->getPaginationGetVar(),
512
                $this->getPageStart() - $this->getPageLength(),
513
                ($this->request instanceof HTTPRequest) ? $this->request->getURL(true) : null
514
            );
515
        }
516
    }
517
518
    /**
519
     * Returns the total number of items in the list
520
     */
521
    public function TotalItems()
522
    {
523
        return $this->getTotalItems();
524
    }
525
526
    /**
527
     * Set the request object for this list
528
     *
529
     * @param HTTPRequest|ArrayAccess
530
     */
531
    public function setRequest($request)
532
    {
533
        $this->request = $request;
534
    }
535
536
    /**
537
     * Get the request object for this list
538
     */
539
    public function getRequest()
540
    {
541
        return $this->request;
542
    }
543
}
544