Passed
Push — main ( 02442c...4ee80d )
by Dimitri
13:27
created

UrlGenerator::getRequest()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
/**
4
 * This file is part of Blitz PHP framework.
5
 *
6
 * (c) 2022 Dimitri Sitchet Tomkeu <[email protected]>
7
 *
8
 * For the full copyright and license information, please view
9
 * the LICENSE file that was distributed with this source code.
10
 */
11
12
namespace BlitzPHP\Http;
13
14
use BlitzPHP\Container\Services;
15
use BlitzPHP\Contracts\Router\RouteCollectionInterface;
16
use BlitzPHP\Exceptions\HttpException;
17
use BlitzPHP\Session\Store;
0 ignored issues
show
Bug introduced by
The type BlitzPHP\Session\Store 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...
18
use BlitzPHP\Traits\Macroable;
19
use BlitzPHP\Utilities\Iterable\Arr;
20
use BlitzPHP\Utilities\String\Text;
21
use Closure;
22
23
/**
24
 * @credit <a href="http://laravel.com">Laravel - \Illuminate\Routing\UrlGenerator</a>
25
 */
26
class UrlGenerator 
27
{
28
	use Macroable;
0 ignored issues
show
Bug introduced by
The trait BlitzPHP\Traits\Macroable requires the property $name which is not provided by BlitzPHP\Http\UrlGenerator.
Loading history...
29
30
    /**
31
     * The forced URL root.
32
     */
33
    protected string $forcedRoot = '';
34
35
    /**
36
     * The forced scheme for URLs.
37
     */
38
    protected string $forceScheme = '';
39
40
    /**
41
     * A cached copy of the URL root for the current request.
42
     */
43
    protected ?string $cachedRoot = null;
44
45
    /**
46
     * A cached copy of the URL scheme for the current request.
47
     */
48
    protected ?string $cachedScheme = null;
49
50
    /**
51
     * The root namespace being applied to controller actions.
52
     */
53
    protected string $rootNamespace = '';
54
55
    /**
56
     * The session resolver callable.
57
     *
58
     * @var callable
59
     */
60
    protected $sessionResolver;
61
62
    /**
63
     * The encryption key resolver callable.
64
     *
65
     * @var callable
66
     */
67
    protected $keyResolver;
68
69
    /**
70
     * The callback to use to format hosts.
71
     *
72
     * @var \Closure
73
     */
74
    protected $formatHostUsing;
75
76
    /**
77
     * The callback to use to format paths.
78
     *
79
     * @var \Closure
80
     */
81
    protected $formatPathUsing;
82
83
    /**
84
     * Create a new URL Generator instance.
85
     *
86
     * @param  RouteCollectionInterface  $routes The route collection.
87
     * @param  Request  $request  The request instance.
88
     * @param  string|null  $assetRoot The asset root URL.
89
     * @return void
90
     */
91
    public function __construct(protected RouteCollectionInterface $routes, protected Request $request, protected ?string $assetRoot = null)
92
    {
93
		$this->setRequest($request);
94
    }
95
96
    /**
97
     * Get the full URL for the current request.
98
     */
99
    public function full(): string
100
    {
101
        return $this->request->fullUrl();
102
    }
103
104
    /**
105
     * Get the current URL for the request.
106
     */
107
    public function current(): string
108
    {
109
        return $this->to($this->request->getUri()->getPath());
110
    }
111
112
    /**
113
     * Get the URL for the previous request.
114
     *
115
     * @param  mixed  $fallback
116
     */
117
    public function previous($fallback = false): string
118
    {
119
        $referrer = $this->request->getHeaderLine('Referer');
120
121
        $url = $referrer ? $this->to($referrer) : $this->getPreviousUrlFromSession();
122
123
        if ($url) {
124
            return $url;
125
        } elseif ($fallback) {
126
            return $this->to($fallback);
127
        }
128
129
        return $this->to('/');
130
    }
131
132
    /**
133
     * Get the previous URL from the session if possible.
134
     */
135
    protected function getPreviousUrlFromSession(): ?string
136
    {
137
        $session = $this->getSession();
138
139
        return $session ? $session->previousUrl() : null;
0 ignored issues
show
introduced by
$session is of type BlitzPHP\Session\Store, thus it always evaluated to true.
Loading history...
140
    }
141
142
    /**
143
     * Generate an absolute URL to the given path.
144
     */
145
    public function to(string $path, mixed $extra = [], ?bool $secure = null): string
146
    {
147
        // First we will check if the URL is already a valid URL. If it is we will not
148
        // try to generate a new one but will simply return the URL as is, which is
149
        // convenient since developers do not always have to check if it's valid.
150
        if ($this->isValidUrl($path)) {
151
            return $path;
152
        }
153
154
        $tail = implode('/', array_map(
155
            'rawurlencode', (array) $this->formatParameters($extra))
156
        );
157
158
        // Once we have the scheme we will compile the "tail" by collapsing the values
159
        // into a single string delimited by slashes. This just makes it convenient
160
        // for passing the array of parameters to this URL as a list of segments.
161
        $root = $this->formatRoot($this->formatScheme($secure));
162
163
        [$path, $query] = $this->extractQueryString($path);
164
165
        return $this->format(
166
            $root, '/'.trim($path.'/'.$tail, '/')
167
        ).$query;
168
    }
169
170
    /**
171
     * Generate a secure, absolute URL to the given path.
172
     */
173
    public function secure(string $path, array $parameters = []): string
174
    {
175
        return $this->to($path, $parameters, true);
176
    }
177
178
    /**
179
     * Generate the URL to an application asset.
180
     */
181
    public function asset(string $path, ?bool $secure = null): string
182
    {
183
        if ($this->isValidUrl($path)) {
184
            return $path;
185
        }
186
187
        // Once we get the root URL, we will check to see if it contains an index.php
188
        // file in the paths. If it does, we will remove it since it is not needed
189
        // for asset paths, but only for routes to endpoints in the application.
190
        $root = $this->assetRoot ?: $this->formatRoot($this->formatScheme($secure));
191
192
        return $this->removeIndex($root).'/'.trim($path, '/');
193
    }
194
195
    /**
196
     * Generate the URL to a secure asset.
197
     */
198
    public function secureAsset(string $path): string
199
    {
200
        return $this->asset($path, true);
201
    }
202
203
    /**
204
     * Generate the URL to an asset from a custom root domain such as CDN, etc.
205
     */
206
    public function assetFrom(string $root, string $path, ?bool $secure = null): string
207
    {
208
        // Once we get the root URL, we will check to see if it contains an index.php
209
        // file in the paths. If it does, we will remove it since it is not needed
210
        // for asset paths, but only for routes to endpoints in the application.
211
        $root = $this->formatRoot($this->formatScheme($secure), $root);
212
213
        return $this->removeIndex($root).'/'.trim($path, '/');
214
    }
215
216
    /**
217
     * Remove the index.php file from a path.
218
     */
219
    protected function removeIndex(string $root): string
220
    {
221
        $i = 'index.php';
222
223
        return Text::contains($root, $i) ? str_replace('/'.$i, '', $root) : $root;
0 ignored issues
show
Bug introduced by
$i of type string is incompatible with the type iterable expected by parameter $needles of BlitzPHP\Utilities\String\Text::contains(). ( Ignorable by Annotation )

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

223
        return Text::contains($root, /** @scrutinizer ignore-type */ $i) ? str_replace('/'.$i, '', $root) : $root;
Loading history...
224
    }
225
226
    /**
227
     * Get the default scheme for a raw URL.
228
     */
229
    public function formatScheme(?bool $secure = null): string
230
    {
231
        if (null !== $secure) {
232
            return $secure ? 'https://' : 'http://';
233
        }
234
235
        if (null === $this->cachedScheme) {
236
            $this->cachedScheme = $this->forceScheme ?: $this->request->getScheme().'://';
237
        }
238
239
        return $this->cachedScheme;
240
    }
241
242
    /**
243
     * Get the URL to a named route.
244
	 * 
245
     * @return string|false
246
     */
247
    public function route(string $name, array $parameters = [], bool $absolute = true)
248
    {
249
		$route = $this->routes->reverseRoute($name, ...$parameters);
250
251
        if (! $route) {
252
            throw HttpException::invalidRedirectRoute($route);
0 ignored issues
show
Bug introduced by
It seems like $route can also be of type false; however, parameter $route of BlitzPHP\Exceptions\Http...:invalidRedirectRoute() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

252
            throw HttpException::invalidRedirectRoute(/** @scrutinizer ignore-type */ $route);
Loading history...
253
        }
254
255
		return $absolute ? site_url($route) : $route;
256
    }
257
	
258
259
    /**
260
     * Format the array of URL parameters.
261
     */
262
    public function formatParameters(mixed $parameters): array
263
    {
264
        $parameters = Arr::wrap($parameters);
265
266
        return $parameters;
267
    }
268
269
    /**
270
     * Extract the query string from the given path.
271
     */
272
    protected function extractQueryString(string $path): array
273
    {
274
        if (($queryPosition = strpos($path, '?')) !== false) {
275
            return [
276
                substr($path, 0, $queryPosition),
277
                substr($path, $queryPosition),
278
            ];
279
        }
280
281
        return [$path, ''];
282
    }
283
284
    /**
285
     * Get the base URL for the request.
286
     */
287
    public function formatRoot(string $scheme, ?string $root = null): string
288
    {
289
        if (is_null($root)) {
290
            if (is_null($this->cachedRoot)) {
291
                $this->cachedRoot = $this->forcedRoot ?: $this->request->root();
292
            }
293
294
            $root = $this->cachedRoot;
295
        }
296
297
        $start = Text::startsWith($root, 'http://') ? 'http://' : 'https://';
0 ignored issues
show
Bug introduced by
It seems like $root can also be of type null; however, parameter $haystack of BlitzPHP\Utilities\String\Text::startsWith() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

297
        $start = Text::startsWith(/** @scrutinizer ignore-type */ $root, 'http://') ? 'http://' : 'https://';
Loading history...
Bug introduced by
'http://' of type string is incompatible with the type iterable expected by parameter $needles of BlitzPHP\Utilities\String\Text::startsWith(). ( Ignorable by Annotation )

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

297
        $start = Text::startsWith($root, /** @scrutinizer ignore-type */ 'http://') ? 'http://' : 'https://';
Loading history...
298
299
        return preg_replace('~'.$start.'~', $scheme, $root, 1);
300
    }
301
302
    /**
303
     * Format the given URL segments into a single URL.
304
     *
305
     * @param  string  $root
306
     * @param  string  $path
307
     * @param  \Illuminate\Routing\Route|null  $route
0 ignored issues
show
Bug introduced by
The type Illuminate\Routing\Route 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...
308
     * @return string
309
     */
310
    public function format($root, $path, $route = null)
311
    {
312
        $path = '/'.trim($path, '/');
313
314
        if ($this->formatHostUsing) {
315
            $root = call_user_func($this->formatHostUsing, $root, $route);
316
        }
317
318
        if ($this->formatPathUsing) {
319
            $path = call_user_func($this->formatPathUsing, $path, $route);
320
        }
321
322
        return trim($root.$path, '/');
323
    }
324
325
    /**
326
     * Determine if the given path is a valid URL.
327
     */
328
    public function isValidUrl(string $path): bool
329
    {
330
        if (! preg_match('~^(#|//|https?://|(mailto|tel|sms):)~', $path)) {
331
            return filter_var($path, FILTER_VALIDATE_URL) !== false;
332
        }
333
334
        return true;
335
    }
336
337
    /**
338
     * Force the scheme for URLs.
339
     */
340
    public function forceScheme(?string $scheme): void
341
    {
342
        $this->cachedScheme = null;
343
344
        $this->forceScheme = $scheme ? $scheme.'://' : null;
345
    }
346
347
    /**
348
     * Set the forced root URL.
349
     */
350
    public function forceRootUrl(?string $root): void
351
    {
352
        $this->forcedRoot = $root ? rtrim($root, '/') : null;
353
354
        $this->cachedRoot = null;
355
    }
356
357
    /**
358
     * Set a callback to be used to format the host of generated URLs.
359
     *
360
     * @param  \Closure  $callback
361
     * @return $this
362
     */
363
    public function formatHostUsing(Closure $callback)
364
    {
365
        $this->formatHostUsing = $callback;
366
367
        return $this;
368
    }
369
370
    /**
371
     * Set a callback to be used to format the path of generated URLs.
372
     *
373
     * @param  \Closure  $callback
374
     * @return $this
375
     */
376
    public function formatPathUsing(Closure $callback)
377
    {
378
        $this->formatPathUsing = $callback;
379
380
        return $this;
381
    }
382
383
    /**
384
     * Get the path formatter being used by the URL generator.
385
     *
386
     * @return \Closure
387
     */
388
    public function pathFormatter()
389
    {
390
        return $this->formatPathUsing ?: function ($path) {
391
            return $path;
392
        };
393
    }
394
395
    /**
396
     * Get the request instance.
397
     */
398
    public function getRequest(): Request
399
    {
400
        return $this->request;
401
    }
402
403
    /**
404
     * Set the current request instance.
405
     */
406
    public function setRequest(Request $request): self
407
    {
408
        $this->request = $request;
409
410
        $this->cachedRoot = null;
411
        $this->cachedScheme = null;
412
413
		return $this;
414
    }
415
416
    /**
417
     * Set the route collection.
418
     */
419
    public function setRoutes(RouteCollectionInterface $routes): self
420
    {
421
        $this->routes = $routes;
422
423
        return $this;
424
    }
425
426
    /**
427
     * Get the session implementation from the resolver.
428
     */
429
    protected function getSession(): ?Store
430
    {
431
        if ($this->sessionResolver) {
432
            return call_user_func($this->sessionResolver);
433
        }
434
435
		return Services::session();
436
    }
437
438
    /**
439
     * Set the session resolver for the generator.
440
     *
441
     * @param  callable  $sessionResolver
442
     * @return $this
443
     */
444
    public function setSessionResolver(callable $sessionResolver)
445
    {
446
        $this->sessionResolver = $sessionResolver;
447
448
        return $this;
449
    }
450
451
    /**
452
     * Set the encryption key resolver.
453
     *
454
     * @param  callable  $keyResolver
455
     * @return $this
456
     */
457
    public function setKeyResolver(callable $keyResolver)
458
    {
459
        $this->keyResolver = $keyResolver;
460
461
        return $this;
462
    }
463
464
    /**
465
     * Set the root controller namespace.
466
     *
467
     * @param  string  $rootNamespace
468
     * @return $this
469
     */
470
    public function setRootControllerNamespace($rootNamespace)
471
    {
472
        $this->rootNamespace = $rootNamespace;
473
474
        return $this;
475
    }
476
}
477