PaginateRoute::currentPage()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 12
rs 9.8666
cc 3
nc 3
nop 0
1
<?php
2
3
namespace Spatie\PaginateRoute;
4
5
use Illuminate\Routing\Router;
6
use Illuminate\Translation\Translator;
7
use Illuminate\Support\Facades\Request;
8
use Illuminate\Routing\RouteParameterBinder;
9
use Illuminate\Contracts\Pagination\Paginator;
10
use Illuminate\Contracts\Routing\UrlGenerator;
11
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
12
13
class PaginateRoute
14
{
15
    /**
16
     * @var \Illuminate\Translation\Translator
17
     */
18
    protected $translator;
19
20
    /**
21
     * @var \Illuminate\Routing\Router
22
     */
23
    protected $router;
24
25
    /**
26
     * @var \Illuminate\Contracts\Routing\UrlGenerator
27
     */
28
    protected $urlGenerator;
29
30
    /**
31
     * @var string
32
     */
33
    protected $pageKeyword;
34
35
    /**
36
     * @param \Illuminate\Translation\Translator         $translator
37
     * @param \Illuminate\Routing\Router                 $router
38
     * @param \Illuminate\Contracts\Routing\UrlGenerator $urlGenerator
39
     */
40
    public function __construct(Translator $translator, Router $router, UrlGenerator $urlGenerator)
41
    {
42
        $this->translator = $translator;
43
        $this->router = $router;
44
        $this->urlGenerator = $urlGenerator;
45
46
        // Unfortunately we can't do this in the service provider since routes are booted first
47
        $this->translator->addNamespace('paginateroute', __DIR__.'/../resources/lang');
48
49
        $this->pageKeyword = $this->translator->get('paginateroute::paginateroute.page');
50
    }
51
52
    /**
53
     * Return the current page.
54
     *
55
     * @return int
56
     */
57
    public function currentPage()
58
    {
59
        $currentRoute = $this->router->getCurrentRoute();
60
61
        if (! $currentRoute) {
62
            return 1;
63
        }
64
65
        $query = $currentRoute->parameter('pageQuery');
66
67
        return (int) str_replace($this->pageKeyword.'/', '', $query) ?: 1;
68
    }
69
70
    /**
71
     * Check if the given page is the current page.
72
     *
73
     * @param int $page
74
     *
75
     * @return bool
76
     */
77
    public function isCurrentPage($page)
78
    {
79
        return $this->currentPage() === $page;
80
    }
81
82
    /**
83
     * Get the next page number.
84
     *
85
     * @param \Illuminate\Contracts\Pagination\Paginator $paginator
86
     *
87
     * @return string|null
88
     */
89
    public function nextPage(Paginator $paginator)
90
    {
91
        if (! $paginator->hasMorePages()) {
92
            return;
93
        }
94
95
        return $this->currentPage() + 1;
96
    }
97
98
    /**
99
     * Determine wether there is a next page.
100
     *
101
     * @param \Illuminate\Contracts\Pagination\Paginator $paginator
102
     *
103
     * @return bool
104
     */
105
    public function hasNextPage(Paginator $paginator)
106
    {
107
        return $this->nextPage($paginator) !== null;
108
    }
109
110
    /**
111
     * Get the next page URL.
112
     *
113
     * @param \Illuminate\Contracts\Pagination\Paginator $paginator
114
     *
115
     * @return string|null
116
     */
117
    public function nextPageUrl(Paginator $paginator)
118
    {
119
        $nextPage = $this->nextPage($paginator);
120
121
        if ($nextPage === null) {
122
            return;
123
        }
124
125
        return $this->pageUrl($nextPage);
126
    }
127
128
    /**
129
     * Get the previous page number.
130
     *
131
     * @return string|null
132
     */
133
    public function previousPage()
134
    {
135
        if ($this->currentPage() <= 1) {
136
            return;
137
        }
138
139
        return $this->currentPage() - 1;
140
    }
141
142
    /**
143
     * Determine wether there is a previous page.
144
     *
145
     * @return bool
146
     */
147
    public function hasPreviousPage()
148
    {
149
        return $this->previousPage() !== null;
150
    }
151
152
    /**
153
     * Get the previous page URL.
154
     *
155
     * @param bool $full Return the full version of the URL in for the first page
156
     *                   Ex. /users/page/1 instead of /users
157
     *
158
     * @return string|null
159
     */
160
    public function previousPageUrl($full = false)
161
    {
162
        $previousPage = $this->previousPage();
163
164
        if ($previousPage === null) {
165
            return;
166
        }
167
168
        return $this->pageUrl($previousPage, $full);
169
    }
170
171
    /**
172
     * Get all urls in an array.
173
     *
174
     * @param \Illuminate\Contracts\Pagination\LengthAwarePaginator $paginator
175
     * @param bool                                                  $full      Return the full version of the URL in for the first page
176
     *                                                                         Ex. /users/page/1 instead of /users
177
     *
178
     * @return array
179
     */
180
    public function allUrls(LengthAwarePaginator $paginator, $full = false)
181
    {
182
        if (! $paginator->hasPages()) {
183
            return [];
184
        }
185
186
        $urls = [];
187
        $left = $this->getLeftPoint($paginator);
188
        $right = $this->getRightPoint($paginator);
189
        for ($page = $left; $page <= $right; $page++) {
190
            $urls[$page] = $this->pageUrl($page, $full);
191
        }
192
193
        return $urls;
194
    }
195
196
    /**
197
     * Get the left most point in the pagination element.
198
     *
199
     * @param LengthAwarePaginator $paginator
200
     * @return int
201
     */
202
    public function getLeftPoint(LengthAwarePaginator $paginator)
203
    {
204
        $side = $paginator->onEachSide;
0 ignored issues
show
Bug introduced by
Accessing onEachSide on the interface Illuminate\Contracts\Pag...on\LengthAwarePaginator suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
205
        $current = $paginator->currentPage();
206
        $last = $paginator->lastPage();
207
208
        if (! empty($side)) {
209
            $x = $current + $side;
210
            $offset = $x >= $last ? $x - $last : 0;
211
            $left = $current - $side - $offset;
212
        }
213
214
        return ! isset($left) || $left < 1 ? 1 : $left;
0 ignored issues
show
Bug introduced by
The variable $left does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
215
    }
216
217
    /**
218
     * Get the right or last point of the pagination element.
219
     *
220
     * @param LengthAwarePaginator $paginator
221
     * @return int
222
     */
223
    public function getRightPoint(LengthAwarePaginator $paginator)
224
    {
225
        $side = $paginator->onEachSide;
0 ignored issues
show
Bug introduced by
Accessing onEachSide on the interface Illuminate\Contracts\Pag...on\LengthAwarePaginator suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
226
        $current = $paginator->currentPage();
227
        $last = $paginator->lastPage();
228
229
        if (! empty($side)) {
230
            $offset = $current <= $side ? $side - $current + 1 : 0;
231
            $right = $current + $side + $offset;
232
        }
233
234
        return ! isset($right) || $right > $last ? $last : $right;
0 ignored issues
show
Bug introduced by
The variable $right does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
235
    }
236
237
    /**
238
     * Render a plain html list with previous, next and all urls. The current page gets a current class on the list item.
239
     *
240
     * @param \Illuminate\Contracts\Pagination\LengthAwarePaginator $paginator
241
     * @param bool                                                  $full              Return the full version of the URL in for the first page
242
     *                                                                                 Ex. /users/page/1 instead of /users
243
     * @param string                                                $class             Include class on pagination list
244
     *                                                                                 Ex. <ul class="pagination">
245
     * @param bool                                                  $additionalLinks   Include prev and next links on pagination list
246
     *
247
     * @return string
248
     */
249
    public function renderPageList(LengthAwarePaginator $paginator, $full = false, $class = null, $additionalLinks = false)
250
    {
251
        $urls = $this->allUrls($paginator, $full);
252
253
        if ($class) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $class of type string|null is loosely compared to true; this is ambiguous if the string can be empty. 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 string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
254
            $class = " class=\"$class\"";
255
        }
256
257
        $listItems = "<ul{$class}>";
258
259
        if ($this->hasPreviousPage() && $additionalLinks) {
260
            $listItems .= "<li><a href=\"{$this->previousPageUrl()}\">&laquo;</a></li>";
261
        }
262
263
        foreach ($urls as $i => $url) {
264
            $pageNum = $i;
265
            $css = '';
266
267
            $link = "<a href=\"{$url}\">{$pageNum}</a>";
268
            if ($pageNum == $this->currentPage()) {
269
                $css = ' class="active"';
270
                $link = $pageNum;
271
            }
272
273
            $listItems .= "<li{$css}>$link</li>";
274
        }
275
276
        if ($this->hasNextPage($paginator) && $additionalLinks) {
277
            $listItems .= "<li><a href=\"{$this->nextPageUrl($paginator)}\">&raquo;</a></li>";
278
        }
279
280
        $listItems .= '</ul>';
281
282
        return $listItems;
283
    }
284
285
    /**
286
     * Render html link tags for SEO indication of previous and next page.
287
     *
288
     * @param \Illuminate\Contracts\Pagination\LengthAwarePaginator $paginator
289
     * @param bool                                                  $full       Return the full version of the URL in for the first page
290
     *                                                                          Ex. /users/page/1 instead of /users
291
     *
292
     * @return string
293
     */
294
    public function renderRelLinks(LengthAwarePaginator $paginator, $full = false)
295
    {
296
        $urls = $this->allUrls($paginator, $full);
297
298
        $linkItems = '';
299
300
        foreach ($urls as $i => $url) {
301
            $pageNum = $i + 1;
302
303
            switch ($pageNum - $this->currentPage()) {
304
                case -1:
305
                    $linkItems .= "<link rel=\"prev\" href=\"{$url}\" />";
306
                    break;
307
                case 1:
308
                    $linkItems .= "<link rel=\"next\" href=\"{$url}\" />";
309
                    break;
310
            }
311
        }
312
313
        return $linkItems;
314
    }
315
316
    /**
317
     * @deprecated in favor of renderPageList.
318
     *
319
     * @param \Illuminate\Contracts\Pagination\LengthAwarePaginator $paginator
320
     * @param bool                                                  $full      Return the full version of the URL in for the first page
321
     *                                                                         Ex. /users/page/1 instead of /users
322
     *
323
     * @return string
324
     */
325
    public function renderHtml(LengthAwarePaginator $paginator, $full = false)
326
    {
327
        return $this->renderPageList($paginator, $full);
328
    }
329
330
    /**
331
     * Generate a page URL, based on the request's current URL.
332
     *
333
     * @param int  $page
334
     * @param bool $full Return the full version of the URL in for the first page
335
     *                   Ex. /users/page/1 instead of /users
336
     *
337
     * @return string
338
     */
339
    public function pageUrl($page, $full = false)
340
    {
341
        $currentPageUrl = $this->router->getCurrentRoute()->uri();
342
343
        $url = $this->addPageQuery(str_replace('{pageQuery?}', '', $currentPageUrl), $page, $full);
344
345
        foreach ((new RouteParameterBinder($this->router->getCurrentRoute()))->parameters(app('request')) as $parameterName => $parameterValue) {
346
            $url = str_replace(['{'.$parameterName.'}', '{'.$parameterName.'?}'], $parameterValue, $url);
347
        }
348
349
        $query = Request::getQueryString();
350
351
        $query = $query
352
            ? '?'.$query
353
            : '';
354
355
        return $this->urlGenerator->to($url).$query;
356
    }
357
358
    /**
359
     * Append the page query to a URL.
360
     *
361
     * @param string $url
362
     * @param int    $page
363
     * @param bool   $full Return the full version of the URL in for the first page
364
     *                     Ex. /users/page/1 instead of /users
365
     *
366
     * @return string
367
     */
368
    public function addPageQuery($url, $page, $full = false)
369
    {
370
        // If the first page's URL is requested and $full is set to false, there's nothing to be added.
371
        if ($page === 1 && ! $full) {
372
            return $url;
373
        }
374
375
        return trim($url, '/')."/{$this->pageKeyword}/{$page}";
376
    }
377
378
    /**
379
     * Register the Route::paginate macro.
380
     */
381
    public function registerMacros()
382
    {
383
        $pageKeyword = $this->pageKeyword;
384
        $router = $this->router;
385
386
        $router->macro('paginate', function ($uri, $action) use ($pageKeyword, $router) {
387
            $route = null;
388
389
            $router->group(
390
                ['middleware' => 'Spatie\PaginateRoute\SetPageMiddleware'],
391
                function () use ($pageKeyword, $router, $uri, $action, &$route) {
392
                    $route = $router->get($uri.'/{pageQuery?}', $action)->where('pageQuery', $pageKeyword.'/[0-9]+');
393
                });
394
395
            return $route;
396
        });
397
    }
398
}
399