Completed
Push — master ( 0849ba...a2fecd )
by Freek
51:06 queued 11:43
created

PaginateRoute::getRightPoint()   A

Complexity

Conditions 5
Paths 12

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 13
rs 9.5222
c 0
b 0
f 0
cc 5
nc 12
nop 1
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
     * Get all urls in an array.
172
     *
173
     * @param \Illuminate\Contracts\Pagination\LengthAwarePaginator $paginator
174
     * @param bool                                                  $full      Return the full version of the URL in for the first page
175
     *                                                                         Ex. /users/page/1 instead of /users
176
     *
177
     * @return array
178
     */
179
    public function allUrls(LengthAwarePaginator $paginator, $full = false)
180
    {
181
        if (! $paginator->hasPages()) {
182
            return [];
183
        }
184
185
        $urls = [];
186
        $left = $this->getLeftPoint($paginator);
187
        $right = $this->getRightPoint($paginator);
188
        for ($page = $left; $page <= $right; $page++) {
189
            $urls[$page] = $this->pageUrl($page, $full);
190
        }
191
192
        return $urls;
193
    }
194
195
    /**
196
     * Get the left most point in the pagination element
197
     * 
198
     * @param LengthAwarePaginator $paginator
199
     * @return int
200
     */
201
    public function getLeftPoint(LengthAwarePaginator $paginator)
202
    {
203
        $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...
204
        $current = $paginator->currentPage();
205
        $last = $paginator->lastPage();
206
207
        if (!empty($side)) {
208
            $x = $current + $side;
209
            $offset = $x >= $last ? $x - $last : 0;
210
            $left = $current - $side - $offset;
211
        }
212
        
213
        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...
214
    }
215
216
    /**
217
     * Get the right or last point of the pagination element
218
     * 
219
     * @param LengthAwarePaginator $paginator
220
     * @return int
221
     */
222
    public function getRightPoint(LengthAwarePaginator $paginator)
223
    {
224
        $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...
225
        $current = $paginator->currentPage();
226
        $last = $paginator->lastPage();
227
228
        if (!empty($side)) {
229
            $offset = $current <= $side ? $side - $current + 1 : 0;
230
            $right = $current + $side + $offset;
231
        }
232
233
        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...
234
    }
235
    
236
    /**
237
     * Render a plain html list with previous, next and all urls. The current page gets a current class on the list item.
238
     *
239
     * @param \Illuminate\Contracts\Pagination\LengthAwarePaginator $paginator
240
     * @param bool                                                  $full              Return the full version of the URL in for the first page
241
     *                                                                                 Ex. /users/page/1 instead of /users
242
     * @param string                                                $class             Include class on pagination list
243
     *                                                                                 Ex. <ul class="pagination">
244
     * @param bool                                                  $additionalLinks   Include prev and next links on pagination list
245
     *
246
     * @return string
247
     */
248
    public function renderPageList(LengthAwarePaginator $paginator, $full = false, $class = null, $additionalLinks = false)
249
    {
250
        $urls = $this->allUrls($paginator, $full);
251
252
        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...
253
            $class = " class=\"$class\"";
254
        }
255
256
        $listItems = "<ul{$class}>";
257
258
        if ($this->hasPreviousPage() && $additionalLinks) {
259
            $listItems .= "<li><a href=\"{$this->previousPageUrl()}\">&laquo;</a></li>";
260
        }
261
262
        foreach ($urls as $i => $url) {
263
            $pageNum = $i;
264
            $css = '';
265
266
            $link = "<a href=\"{$url}\">{$pageNum}</a>";
267
            if ($pageNum == $this->currentPage()) {
268
                $css = ' class="active"';
269
                $link = $pageNum;
270
            }
271
272
            $listItems .= "<li{$css}>$link</li>";
273
        }
274
275
        if ($this->hasNextPage($paginator) && $additionalLinks) {
276
            $listItems .= "<li><a href=\"{$this->nextPageUrl($paginator)}\">&raquo;</a></li>";
277
        }
278
279
        $listItems .= '</ul>';
280
281
        return $listItems;
282
    }
283
284
    /**
285
     * Render html link tags for SEO indication of previous and next page.
286
     *
287
     * @param \Illuminate\Contracts\Pagination\LengthAwarePaginator $paginator
288
     * @param bool                                                  $full       Return the full version of the URL in for the first page
289
     *                                                                          Ex. /users/page/1 instead of /users
290
     *
291
     * @return string
292
     */
293
    public function renderRelLinks(LengthAwarePaginator $paginator, $full = false)
294
    {
295
        $urls = $this->allUrls($paginator, $full);
296
297
        $linkItems = '';
298
299
        foreach ($urls as $i => $url) {
300
            $pageNum = $i + 1;
301
302
            switch ($pageNum - $this->currentPage()) {
303
                case -1:
304
                    $linkItems .= "<link rel=\"prev\" href=\"{$url}\" />";
305
                    break;
306
                case 1:
307
                    $linkItems .= "<link rel=\"next\" href=\"{$url}\" />";
308
                    break;
309
            }
310
        }
311
312
        return $linkItems;
313
    }
314
315
    /**
316
     * @deprecated in favor of renderPageList.
317
     *
318
     * @param \Illuminate\Contracts\Pagination\LengthAwarePaginator $paginator
319
     * @param bool                                                  $full      Return the full version of the URL in for the first page
320
     *                                                                         Ex. /users/page/1 instead of /users
321
     *
322
     * @return string
323
     */
324
    public function renderHtml(LengthAwarePaginator $paginator, $full = false)
325
    {
326
        return $this->renderPageList($paginator, $full);
327
    }
328
329
    /**
330
     * Generate a page URL, based on the request's current URL.
331
     *
332
     * @param int  $page
333
     * @param bool $full Return the full version of the URL in for the first page
334
     *                   Ex. /users/page/1 instead of /users
335
     *
336
     * @return string
337
     */
338
    public function pageUrl($page, $full = false)
339
    {
340
        $currentPageUrl = $this->router->getCurrentRoute()->uri();
341
342
        $url = $this->addPageQuery(str_replace('{pageQuery?}', '', $currentPageUrl), $page, $full);
343
344
        foreach ((new RouteParameterBinder($this->router->getCurrentRoute()))->parameters(app('request')) as $parameterName => $parameterValue) {
345
            $url = str_replace(['{'.$parameterName.'}', '{'.$parameterName.'?}'], $parameterValue, $url);
346
        }
347
348
        $query = Request::getQueryString();
349
350
        $query = $query
351
            ? '?'.$query
352
            : '';
353
354
        return $this->urlGenerator->to($url).$query;
355
    }
356
357
    /**
358
     * Append the page query to a URL.
359
     *
360
     * @param string $url
361
     * @param int    $page
362
     * @param bool   $full Return the full version of the URL in for the first page
363
     *                     Ex. /users/page/1 instead of /users
364
     *
365
     * @return string
366
     */
367
    public function addPageQuery($url, $page, $full = false)
368
    {
369
        // If the first page's URL is requested and $full is set to false, there's nothing to be added.
370
        if ($page === 1 && ! $full) {
371
            return $url;
372
        }
373
374
        return trim($url, '/')."/{$this->pageKeyword}/{$page}";
375
    }
376
377
    /**
378
     * Register the Route::paginate macro.
379
     */
380
    public function registerMacros()
381
    {
382
        $pageKeyword = $this->pageKeyword;
383
        $router = $this->router;
384
385
        $router->macro('paginate', function ($uri, $action) use ($pageKeyword, $router) {
386
            $route = null;
387
388
            $router->group(
389
                ['middleware' => 'Spatie\PaginateRoute\SetPageMiddleware'],
390
                function () use ($pageKeyword, $router, $uri, $action, &$route) {
391
                    $route = $router->get($uri.'/{pageQuery?}', $action)->where('pageQuery', $pageKeyword.'/[0-9]+');
392
                });
393
394
            return $route;
395
        });
396
    }
397
}
398