Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Illuminate\Routing;
4
 
5
use BackedEnum;
6
use Closure;
7
use Illuminate\Contracts\Routing\UrlGenerator as UrlGeneratorContract;
8
use Illuminate\Contracts\Routing\UrlRoutable;
9
use Illuminate\Http\Request;
10
use Illuminate\Support\Arr;
11
use Illuminate\Support\Carbon;
12
use Illuminate\Support\InteractsWithTime;
13
use Illuminate\Support\Str;
14
use Illuminate\Support\Traits\Macroable;
15
use InvalidArgumentException;
16
use Symfony\Component\Routing\Exception\RouteNotFoundException;
17
 
18
class UrlGenerator implements UrlGeneratorContract
19
{
20
    use InteractsWithTime, Macroable;
21
 
22
    /**
23
     * The route collection.
24
     *
25
     * @var \Illuminate\Routing\RouteCollectionInterface
26
     */
27
    protected $routes;
28
 
29
    /**
30
     * The request instance.
31
     *
32
     * @var \Illuminate\Http\Request
33
     */
34
    protected $request;
35
 
36
    /**
37
     * The asset root URL.
38
     *
39
     * @var string
40
     */
41
    protected $assetRoot;
42
 
43
    /**
44
     * The forced URL root.
45
     *
46
     * @var string
47
     */
48
    protected $forcedRoot;
49
 
50
    /**
51
     * The forced scheme for URLs.
52
     *
53
     * @var string
54
     */
55
    protected $forceScheme;
56
 
57
    /**
58
     * A cached copy of the URL root for the current request.
59
     *
60
     * @var string|null
61
     */
62
    protected $cachedRoot;
63
 
64
    /**
65
     * A cached copy of the URL scheme for the current request.
66
     *
67
     * @var string|null
68
     */
69
    protected $cachedScheme;
70
 
71
    /**
72
     * The root namespace being applied to controller actions.
73
     *
74
     * @var string
75
     */
76
    protected $rootNamespace;
77
 
78
    /**
79
     * The session resolver callable.
80
     *
81
     * @var callable
82
     */
83
    protected $sessionResolver;
84
 
85
    /**
86
     * The encryption key resolver callable.
87
     *
88
     * @var callable
89
     */
90
    protected $keyResolver;
91
 
92
    /**
93
     * The callback to use to format hosts.
94
     *
95
     * @var \Closure
96
     */
97
    protected $formatHostUsing;
98
 
99
    /**
100
     * The callback to use to format paths.
101
     *
102
     * @var \Closure
103
     */
104
    protected $formatPathUsing;
105
 
106
    /**
107
     * The route URL generator instance.
108
     *
109
     * @var \Illuminate\Routing\RouteUrlGenerator|null
110
     */
111
    protected $routeGenerator;
112
 
113
    /**
114
     * Create a new URL Generator instance.
115
     *
116
     * @param  \Illuminate\Routing\RouteCollectionInterface  $routes
117
     * @param  \Illuminate\Http\Request  $request
118
     * @param  string|null  $assetRoot
119
     * @return void
120
     */
121
    public function __construct(RouteCollectionInterface $routes, Request $request, $assetRoot = null)
122
    {
123
        $this->routes = $routes;
124
        $this->assetRoot = $assetRoot;
125
 
126
        $this->setRequest($request);
127
    }
128
 
129
    /**
130
     * Get the full URL for the current request.
131
     *
132
     * @return string
133
     */
134
    public function full()
135
    {
136
        return $this->request->fullUrl();
137
    }
138
 
139
    /**
140
     * Get the current URL for the request.
141
     *
142
     * @return string
143
     */
144
    public function current()
145
    {
146
        return $this->to($this->request->getPathInfo());
147
    }
148
 
149
    /**
150
     * Get the URL for the previous request.
151
     *
152
     * @param  mixed  $fallback
153
     * @return string
154
     */
155
    public function previous($fallback = false)
156
    {
157
        $referrer = $this->request->headers->get('referer');
158
 
159
        $url = $referrer ? $this->to($referrer) : $this->getPreviousUrlFromSession();
160
 
161
        if ($url) {
162
            return $url;
163
        } elseif ($fallback) {
164
            return $this->to($fallback);
165
        }
166
 
167
        return $this->to('/');
168
    }
169
 
170
    /**
171
     * Get the previous path info for the request.
172
     *
173
     * @param  mixed  $fallback
174
     * @return string
175
     */
176
    public function previousPath($fallback = false)
177
    {
178
        $previousPath = str_replace($this->to('/'), '', rtrim(preg_replace('/\?.*/', '', $this->previous($fallback)), '/'));
179
 
180
        return $previousPath === '' ? '/' : $previousPath;
181
    }
182
 
183
    /**
184
     * Get the previous URL from the session if possible.
185
     *
186
     * @return string|null
187
     */
188
    protected function getPreviousUrlFromSession()
189
    {
190
        $session = $this->getSession();
191
 
192
        return $session ? $session->previousUrl() : null;
193
    }
194
 
195
    /**
196
     * Generate an absolute URL to the given path.
197
     *
198
     * @param  string  $path
199
     * @param  mixed  $extra
200
     * @param  bool|null  $secure
201
     * @return string
202
     */
203
    public function to($path, $extra = [], $secure = null)
204
    {
205
        // First we will check if the URL is already a valid URL. If it is we will not
206
        // try to generate a new one but will simply return the URL as is, which is
207
        // convenient since developers do not always have to check if it's valid.
208
        if ($this->isValidUrl($path)) {
209
            return $path;
210
        }
211
 
212
        $tail = implode('/', array_map(
213
            'rawurlencode', (array) $this->formatParameters($extra))
214
        );
215
 
216
        // Once we have the scheme we will compile the "tail" by collapsing the values
217
        // into a single string delimited by slashes. This just makes it convenient
218
        // for passing the array of parameters to this URL as a list of segments.
219
        $root = $this->formatRoot($this->formatScheme($secure));
220
 
221
        [$path, $query] = $this->extractQueryString($path);
222
 
223
        return $this->format(
224
            $root, '/'.trim($path.'/'.$tail, '/')
225
        ).$query;
226
    }
227
 
228
    /**
229
     * Generate a secure, absolute URL to the given path.
230
     *
231
     * @param  string  $path
232
     * @param  array  $parameters
233
     * @return string
234
     */
235
    public function secure($path, $parameters = [])
236
    {
237
        return $this->to($path, $parameters, true);
238
    }
239
 
240
    /**
241
     * Generate the URL to an application asset.
242
     *
243
     * @param  string  $path
244
     * @param  bool|null  $secure
245
     * @return string
246
     */
247
    public function asset($path, $secure = null)
248
    {
249
        if ($this->isValidUrl($path)) {
250
            return $path;
251
        }
252
 
253
        // Once we get the root URL, we will check to see if it contains an index.php
254
        // file in the paths. If it does, we will remove it since it is not needed
255
        // for asset paths, but only for routes to endpoints in the application.
256
        $root = $this->assetRoot ?: $this->formatRoot($this->formatScheme($secure));
257
 
258
        return $this->removeIndex($root).'/'.trim($path, '/');
259
    }
260
 
261
    /**
262
     * Generate the URL to a secure asset.
263
     *
264
     * @param  string  $path
265
     * @return string
266
     */
267
    public function secureAsset($path)
268
    {
269
        return $this->asset($path, true);
270
    }
271
 
272
    /**
273
     * Generate the URL to an asset from a custom root domain such as CDN, etc.
274
     *
275
     * @param  string  $root
276
     * @param  string  $path
277
     * @param  bool|null  $secure
278
     * @return string
279
     */
280
    public function assetFrom($root, $path, $secure = null)
281
    {
282
        // Once we get the root URL, we will check to see if it contains an index.php
283
        // file in the paths. If it does, we will remove it since it is not needed
284
        // for asset paths, but only for routes to endpoints in the application.
285
        $root = $this->formatRoot($this->formatScheme($secure), $root);
286
 
287
        return $this->removeIndex($root).'/'.trim($path, '/');
288
    }
289
 
290
    /**
291
     * Remove the index.php file from a path.
292
     *
293
     * @param  string  $root
294
     * @return string
295
     */
296
    protected function removeIndex($root)
297
    {
298
        $i = 'index.php';
299
 
300
        return str_contains($root, $i) ? str_replace('/'.$i, '', $root) : $root;
301
    }
302
 
303
    /**
304
     * Get the default scheme for a raw URL.
305
     *
306
     * @param  bool|null  $secure
307
     * @return string
308
     */
309
    public function formatScheme($secure = null)
310
    {
311
        if (! is_null($secure)) {
312
            return $secure ? 'https://' : 'http://';
313
        }
314
 
315
        if (is_null($this->cachedScheme)) {
316
            $this->cachedScheme = $this->forceScheme ?: $this->request->getScheme().'://';
317
        }
318
 
319
        return $this->cachedScheme;
320
    }
321
 
322
    /**
323
     * Create a signed route URL for a named route.
324
     *
325
     * @param  string  $name
326
     * @param  mixed  $parameters
327
     * @param  \DateTimeInterface|\DateInterval|int|null  $expiration
328
     * @param  bool  $absolute
329
     * @return string
330
     *
331
     * @throws \InvalidArgumentException
332
     */
333
    public function signedRoute($name, $parameters = [], $expiration = null, $absolute = true)
334
    {
335
        $this->ensureSignedRouteParametersAreNotReserved(
336
            $parameters = Arr::wrap($parameters)
337
        );
338
 
339
        if ($expiration) {
340
            $parameters = $parameters + ['expires' => $this->availableAt($expiration)];
341
        }
342
 
343
        ksort($parameters);
344
 
345
        $key = call_user_func($this->keyResolver);
346
 
347
        return $this->route($name, $parameters + [
348
            'signature' => hash_hmac('sha256', $this->route($name, $parameters, $absolute), $key),
349
        ], $absolute);
350
    }
351
 
352
    /**
353
     * Ensure the given signed route parameters are not reserved.
354
     *
355
     * @param  mixed  $parameters
356
     * @return void
357
     */
358
    protected function ensureSignedRouteParametersAreNotReserved($parameters)
359
    {
360
        if (array_key_exists('signature', $parameters)) {
361
            throw new InvalidArgumentException(
362
                '"Signature" is a reserved parameter when generating signed routes. Please rename your route parameter.'
363
            );
364
        }
365
 
366
        if (array_key_exists('expires', $parameters)) {
367
            throw new InvalidArgumentException(
368
                '"Expires" is a reserved parameter when generating signed routes. Please rename your route parameter.'
369
            );
370
        }
371
    }
372
 
373
    /**
374
     * Create a temporary signed route URL for a named route.
375
     *
376
     * @param  string  $name
377
     * @param  \DateTimeInterface|\DateInterval|int  $expiration
378
     * @param  array  $parameters
379
     * @param  bool  $absolute
380
     * @return string
381
     */
382
    public function temporarySignedRoute($name, $expiration, $parameters = [], $absolute = true)
383
    {
384
        return $this->signedRoute($name, $parameters, $expiration, $absolute);
385
    }
386
 
387
    /**
388
     * Determine if the given request has a valid signature.
389
     *
390
     * @param  \Illuminate\Http\Request  $request
391
     * @param  bool  $absolute
392
     * @param  array  $ignoreQuery
393
     * @return bool
394
     */
395
    public function hasValidSignature(Request $request, $absolute = true, array $ignoreQuery = [])
396
    {
397
        return $this->hasCorrectSignature($request, $absolute, $ignoreQuery)
398
            && $this->signatureHasNotExpired($request);
399
    }
400
 
401
    /**
402
     * Determine if the given request has a valid signature for a relative URL.
403
     *
404
     * @param  \Illuminate\Http\Request  $request
405
     * @param  array  $ignoreQuery
406
     * @return bool
407
     */
408
    public function hasValidRelativeSignature(Request $request, array $ignoreQuery = [])
409
    {
410
        return $this->hasValidSignature($request, false, $ignoreQuery);
411
    }
412
 
413
    /**
414
     * Determine if the signature from the given request matches the URL.
415
     *
416
     * @param  \Illuminate\Http\Request  $request
417
     * @param  bool  $absolute
418
     * @param  array  $ignoreQuery
419
     * @return bool
420
     */
421
    public function hasCorrectSignature(Request $request, $absolute = true, array $ignoreQuery = [])
422
    {
423
        $ignoreQuery[] = 'signature';
424
 
425
        $url = $absolute ? $request->url() : '/'.$request->path();
426
 
427
        $queryString = collect(explode('&', (string) $request->server->get('QUERY_STRING')))
428
            ->reject(fn ($parameter) => in_array(Str::before($parameter, '='), $ignoreQuery))
429
            ->join('&');
430
 
431
        $original = rtrim($url.'?'.$queryString, '?');
432
 
433
        $signature = hash_hmac('sha256', $original, call_user_func($this->keyResolver));
434
 
435
        return hash_equals($signature, (string) $request->query('signature', ''));
436
    }
437
 
438
    /**
439
     * Determine if the expires timestamp from the given request is not from the past.
440
     *
441
     * @param  \Illuminate\Http\Request  $request
442
     * @return bool
443
     */
444
    public function signatureHasNotExpired(Request $request)
445
    {
446
        $expires = $request->query('expires');
447
 
448
        return ! ($expires && Carbon::now()->getTimestamp() > $expires);
449
    }
450
 
451
    /**
452
     * Get the URL to a named route.
453
     *
454
     * @param  string  $name
455
     * @param  mixed  $parameters
456
     * @param  bool  $absolute
457
     * @return string
458
     *
459
     * @throws \Symfony\Component\Routing\Exception\RouteNotFoundException
460
     */
461
    public function route($name, $parameters = [], $absolute = true)
462
    {
463
        if (! is_null($route = $this->routes->getByName($name))) {
464
            return $this->toRoute($route, $parameters, $absolute);
465
        }
466
 
467
        throw new RouteNotFoundException("Route [{$name}] not defined.");
468
    }
469
 
470
    /**
471
     * Get the URL for a given route instance.
472
     *
473
     * @param  \Illuminate\Routing\Route  $route
474
     * @param  mixed  $parameters
475
     * @param  bool  $absolute
476
     * @return string
477
     *
478
     * @throws \Illuminate\Routing\Exceptions\UrlGenerationException
479
     */
480
    public function toRoute($route, $parameters, $absolute)
481
    {
482
        $parameters = collect(Arr::wrap($parameters))->map(function ($value, $key) use ($route) {
483
            $value = $value instanceof UrlRoutable && $route->bindingFieldFor($key)
484
                    ? $value->{$route->bindingFieldFor($key)}
485
                    : $value;
486
 
487
            return function_exists('enum_exists') && $value instanceof BackedEnum
488
                ? $value->value
489
                : $value;
490
        })->all();
491
 
492
        return $this->routeUrl()->to(
493
            $route, $this->formatParameters($parameters), $absolute
494
        );
495
    }
496
 
497
    /**
498
     * Get the URL to a controller action.
499
     *
500
     * @param  string|array  $action
501
     * @param  mixed  $parameters
502
     * @param  bool  $absolute
503
     * @return string
504
     *
505
     * @throws \InvalidArgumentException
506
     */
507
    public function action($action, $parameters = [], $absolute = true)
508
    {
509
        if (is_null($route = $this->routes->getByAction($action = $this->formatAction($action)))) {
510
            throw new InvalidArgumentException("Action {$action} not defined.");
511
        }
512
 
513
        return $this->toRoute($route, $parameters, $absolute);
514
    }
515
 
516
    /**
517
     * Format the given controller action.
518
     *
519
     * @param  string|array  $action
520
     * @return string
521
     */
522
    protected function formatAction($action)
523
    {
524
        if (is_array($action)) {
525
            $action = '\\'.implode('@', $action);
526
        }
527
 
528
        if ($this->rootNamespace && ! str_starts_with($action, '\\')) {
529
            return $this->rootNamespace.'\\'.$action;
530
        } else {
531
            return trim($action, '\\');
532
        }
533
    }
534
 
535
    /**
536
     * Format the array of URL parameters.
537
     *
538
     * @param  mixed|array  $parameters
539
     * @return array
540
     */
541
    public function formatParameters($parameters)
542
    {
543
        $parameters = Arr::wrap($parameters);
544
 
545
        foreach ($parameters as $key => $parameter) {
546
            if ($parameter instanceof UrlRoutable) {
547
                $parameters[$key] = $parameter->getRouteKey();
548
            }
549
        }
550
 
551
        return $parameters;
552
    }
553
 
554
    /**
555
     * Extract the query string from the given path.
556
     *
557
     * @param  string  $path
558
     * @return array
559
     */
560
    protected function extractQueryString($path)
561
    {
562
        if (($queryPosition = strpos($path, '?')) !== false) {
563
            return [
564
                substr($path, 0, $queryPosition),
565
                substr($path, $queryPosition),
566
            ];
567
        }
568
 
569
        return [$path, ''];
570
    }
571
 
572
    /**
573
     * Get the base URL for the request.
574
     *
575
     * @param  string  $scheme
576
     * @param  string|null  $root
577
     * @return string
578
     */
579
    public function formatRoot($scheme, $root = null)
580
    {
581
        if (is_null($root)) {
582
            if (is_null($this->cachedRoot)) {
583
                $this->cachedRoot = $this->forcedRoot ?: $this->request->root();
584
            }
585
 
586
            $root = $this->cachedRoot;
587
        }
588
 
589
        $start = str_starts_with($root, 'http://') ? 'http://' : 'https://';
590
 
591
        return preg_replace('~'.$start.'~', $scheme, $root, 1);
592
    }
593
 
594
    /**
595
     * Format the given URL segments into a single URL.
596
     *
597
     * @param  string  $root
598
     * @param  string  $path
599
     * @param  \Illuminate\Routing\Route|null  $route
600
     * @return string
601
     */
602
    public function format($root, $path, $route = null)
603
    {
604
        $path = '/'.trim($path, '/');
605
 
606
        if ($this->formatHostUsing) {
607
            $root = call_user_func($this->formatHostUsing, $root, $route);
608
        }
609
 
610
        if ($this->formatPathUsing) {
611
            $path = call_user_func($this->formatPathUsing, $path, $route);
612
        }
613
 
614
        return trim($root.$path, '/');
615
    }
616
 
617
    /**
618
     * Determine if the given path is a valid URL.
619
     *
620
     * @param  string  $path
621
     * @return bool
622
     */
623
    public function isValidUrl($path)
624
    {
625
        if (! preg_match('~^(#|//|https?://|(mailto|tel|sms):)~', $path)) {
626
            return filter_var($path, FILTER_VALIDATE_URL) !== false;
627
        }
628
 
629
        return true;
630
    }
631
 
632
    /**
633
     * Get the Route URL generator instance.
634
     *
635
     * @return \Illuminate\Routing\RouteUrlGenerator
636
     */
637
    protected function routeUrl()
638
    {
639
        if (! $this->routeGenerator) {
640
            $this->routeGenerator = new RouteUrlGenerator($this, $this->request);
641
        }
642
 
643
        return $this->routeGenerator;
644
    }
645
 
646
    /**
647
     * Set the default named parameters used by the URL generator.
648
     *
649
     * @param  array  $defaults
650
     * @return void
651
     */
652
    public function defaults(array $defaults)
653
    {
654
        $this->routeUrl()->defaults($defaults);
655
    }
656
 
657
    /**
658
     * Get the default named parameters used by the URL generator.
659
     *
660
     * @return array
661
     */
662
    public function getDefaultParameters()
663
    {
664
        return $this->routeUrl()->defaultParameters;
665
    }
666
 
667
    /**
668
     * Force the scheme for URLs.
669
     *
670
     * @param  string|null  $scheme
671
     * @return void
672
     */
673
    public function forceScheme($scheme)
674
    {
675
        $this->cachedScheme = null;
676
 
677
        $this->forceScheme = $scheme ? $scheme.'://' : null;
678
    }
679
 
680
    /**
681
     * Set the forced root URL.
682
     *
683
     * @param  string|null  $root
684
     * @return void
685
     */
686
    public function forceRootUrl($root)
687
    {
688
        $this->forcedRoot = $root ? rtrim($root, '/') : null;
689
 
690
        $this->cachedRoot = null;
691
    }
692
 
693
    /**
694
     * Set a callback to be used to format the host of generated URLs.
695
     *
696
     * @param  \Closure  $callback
697
     * @return $this
698
     */
699
    public function formatHostUsing(Closure $callback)
700
    {
701
        $this->formatHostUsing = $callback;
702
 
703
        return $this;
704
    }
705
 
706
    /**
707
     * Set a callback to be used to format the path of generated URLs.
708
     *
709
     * @param  \Closure  $callback
710
     * @return $this
711
     */
712
    public function formatPathUsing(Closure $callback)
713
    {
714
        $this->formatPathUsing = $callback;
715
 
716
        return $this;
717
    }
718
 
719
    /**
720
     * Get the path formatter being used by the URL generator.
721
     *
722
     * @return \Closure
723
     */
724
    public function pathFormatter()
725
    {
726
        return $this->formatPathUsing ?: function ($path) {
727
            return $path;
728
        };
729
    }
730
 
731
    /**
732
     * Get the request instance.
733
     *
734
     * @return \Illuminate\Http\Request
735
     */
736
    public function getRequest()
737
    {
738
        return $this->request;
739
    }
740
 
741
    /**
742
     * Set the current request instance.
743
     *
744
     * @param  \Illuminate\Http\Request  $request
745
     * @return void
746
     */
747
    public function setRequest(Request $request)
748
    {
749
        $this->request = $request;
750
 
751
        $this->cachedRoot = null;
752
        $this->cachedScheme = null;
753
 
754
        tap(optional($this->routeGenerator)->defaultParameters ?: [], function ($defaults) {
755
            $this->routeGenerator = null;
756
 
757
            if (! empty($defaults)) {
758
                $this->defaults($defaults);
759
            }
760
        });
761
    }
762
 
763
    /**
764
     * Set the route collection.
765
     *
766
     * @param  \Illuminate\Routing\RouteCollectionInterface  $routes
767
     * @return $this
768
     */
769
    public function setRoutes(RouteCollectionInterface $routes)
770
    {
771
        $this->routes = $routes;
772
 
773
        return $this;
774
    }
775
 
776
    /**
777
     * Get the session implementation from the resolver.
778
     *
779
     * @return \Illuminate\Session\Store|null
780
     */
781
    protected function getSession()
782
    {
783
        if ($this->sessionResolver) {
784
            return call_user_func($this->sessionResolver);
785
        }
786
    }
787
 
788
    /**
789
     * Set the session resolver for the generator.
790
     *
791
     * @param  callable  $sessionResolver
792
     * @return $this
793
     */
794
    public function setSessionResolver(callable $sessionResolver)
795
    {
796
        $this->sessionResolver = $sessionResolver;
797
 
798
        return $this;
799
    }
800
 
801
    /**
802
     * Set the encryption key resolver.
803
     *
804
     * @param  callable  $keyResolver
805
     * @return $this
806
     */
807
    public function setKeyResolver(callable $keyResolver)
808
    {
809
        $this->keyResolver = $keyResolver;
810
 
811
        return $this;
812
    }
813
 
814
    /**
815
     * Clone a new instance of the URL generator with a different encryption key resolver.
816
     *
817
     * @param  callable  $keyResolver
818
     * @return \Illuminate\Routing\UrlGenerator
819
     */
820
    public function withKeyResolver(callable $keyResolver)
821
    {
822
        return (clone $this)->setKeyResolver($keyResolver);
823
    }
824
 
825
    /**
826
     * Get the root controller namespace.
827
     *
828
     * @return string
829
     */
830
    public function getRootControllerNamespace()
831
    {
832
        return $this->rootNamespace;
833
    }
834
 
835
    /**
836
     * Set the root controller namespace.
837
     *
838
     * @param  string  $rootNamespace
839
     * @return $this
840
     */
841
    public function setRootControllerNamespace($rootNamespace)
842
    {
843
        $this->rootNamespace = $rootNamespace;
844
 
845
        return $this;
846
    }
847
}