Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
40 / 40
100.00% covered (success)
100.00%
9 / 9
CRAP
100.00% covered (success)
100.00%
1 / 1
Hyperlinks
100.00% covered (success)
100.00%
40 / 40
100.00% covered (success)
100.00%
9 / 9
27
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 formatLink
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
 relativeLink
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
5
 mediaLink
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 asset
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 hasSiteUrl
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 url
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 route
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isRemote
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace Hyde\Foundation\Kernel;
6
7use Hyde\Facades\Config;
8use Hyde\Support\Models\Route;
9use Hyde\Foundation\HydeKernel;
10use JetBrains\PhpStorm\Deprecated;
11use Hyde\Framework\Exceptions\BaseUrlNotSetException;
12use Hyde\Framework\Exceptions\FileNotFoundException;
13use Illuminate\Support\Str;
14
15use function str_ends_with;
16use function str_starts_with;
17use function substr_count;
18use function file_exists;
19use function str_replace;
20use function str_repeat;
21use function substr;
22use function blank;
23use function rtrim;
24use function trim;
25
26/**
27 * Contains helpers and logic for resolving web paths for compiled files.
28 *
29 * It's bound to the HydeKernel instance, and is an integral part of the framework.
30 */
31class Hyperlinks
32{
33    protected HydeKernel $kernel;
34
35    public function __construct(HydeKernel $kernel)
36    {
37        $this->kernel = $kernel;
38    }
39
40    /**
41     * Format a web link to an HTML file, allowing for pretty URLs, if enabled.
42     *
43     * @see \Hyde\Framework\Testing\Unit\Foundation\HyperlinkFormatHtmlPathTest
44     */
45    public function formatLink(string $destination): string
46    {
47        if (Config::getBool('hyde.pretty_urls', false) === true) {
48            if (str_ends_with($destination, '.html')) {
49                if ($destination === 'index.html') {
50                    return '/';
51                }
52
53                if (str_ends_with($destination, 'index.html')) {
54                    return substr($destination, 0, -10);
55                }
56
57                return substr($destination, 0, -5);
58            }
59        }
60
61        return $destination;
62    }
63
64    /**
65     * Inject the proper number of `../` before the links in Blade templates.
66     *
67     * @param  string  $destination  relative to output directory on compiled site
68     *
69     * @see \Hyde\Framework\Testing\Unit\Foundation\HyperlinkFileHelperRelativeLinkTest
70     */
71    public function relativeLink(string $destination): string
72    {
73        if (str_starts_with($destination, '../')) {
74            return $destination;
75        }
76
77        $nestCount = substr_count($this->kernel->currentRouteKey() ?? '', '/');
78        $route = '';
79        if ($nestCount > 0) {
80            $route .= str_repeat('../', $nestCount);
81        }
82        $route .= $this->formatLink($destination);
83
84        if (Config::getBool('hyde.pretty_urls', false) === true && $route === '/') {
85            return './';
86        }
87
88        return str_replace('//', '/', $route);
89    }
90
91    /**
92     * Gets a relative web link to the given file stored in the _site/media folder.
93     *
94     * An exception will be thrown if the file does not exist in the _media directory,
95     * and the second argument is set to true.
96     *
97     * @deprecated This method will be removed in v2.0. Please use `asset()` instead.
98     */
99    #[Deprecated(reason: 'Use `asset` method instead.', replacement: '%class%->asset(%parameter0%)')]
100    public function mediaLink(string $destination, bool $validate = false): string
101    {
102        if ($validate && ! file_exists($sourcePath = "{$this->kernel->getMediaDirectory()}/$destination")) {
103            throw new FileNotFoundException($sourcePath);
104        }
105
106        return $this->relativeLink("{$this->kernel->getMediaOutputDirectory()}/$destination");
107    }
108
109    /**
110     * Gets a relative web link to the given image stored in the _site/media folder.
111     * If the image is remote (starts with http) it will be returned as is.
112     *
113     * If true is passed as the second argument, and a base URL is set,
114     * the image will be returned with a qualified absolute URL.
115     */
116    public function asset(string $name, bool $preferQualifiedUrl = false): string
117    {
118        if (static::isRemote($name)) {
119            return $name;
120        }
121
122        $name = Str::start($name, "{$this->kernel->getMediaOutputDirectory()}/");
123
124        if ($preferQualifiedUrl && $this->hasSiteUrl()) {
125            return $this->url($name);
126        }
127
128        return $this->relativeLink($name);
129    }
130
131    /**
132     * Check if a site base URL has been set in config (or .env).
133     *
134     * The default value is `http://localhost`, which is not considered a valid site URL.
135     */
136    public function hasSiteUrl(): bool
137    {
138        $value = Config::getNullableString('hyde.url');
139
140        return ! blank($value) && $value !== 'http://localhost';
141    }
142
143    /**
144     * Return a qualified URL to the supplied path if a base URL is set.
145     *
146     * @param  string  $path  An optional relative path suffix. Omit to return the base URL.
147     *
148     * @throws BaseUrlNotSetException If no site URL is set and no path is provided.
149     */
150    public function url(string $path = ''): string
151    {
152        $path = $this->formatLink(trim($path, '/'));
153
154        if (static::isRemote($path)) {
155            return $path;
156        }
157
158        if ($this->hasSiteUrl()) {
159            return rtrim(rtrim(Config::getString('hyde.url'), '/')."/$path", '/');
160        }
161
162        // Since v1.7.0, we return the relative path even if the base URL is not set,
163        // as this is more likely to be the desired behavior the user's expecting.
164        if (! blank($path)) {
165            return $path;
166        }
167
168        // User is trying to get the base URL, but it's not set
169        // This exception is deprecated and will be removed in v2.0.0, and we will throw a BadMethodCallException instead.
170        throw new BaseUrlNotSetException();
171    }
172
173    /**
174     * Get a route instance by its key from the kernel's route collection.
175     */
176    public function route(string $key): ?Route
177    {
178        return $this->kernel->routes()->get($key);
179    }
180
181    /**
182     * Determine if the given URL is a remote link.
183     */
184    public static function isRemote(string $url): bool
185    {
186        return str_starts_with($url, 'http') || str_starts_with($url, '//');
187    }
188}