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