xref: /template/strap/ComboStrap/Web/Url.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
1*04fd306cSNickeau<?php
2*04fd306cSNickeau
3*04fd306cSNickeau
4*04fd306cSNickeaunamespace ComboStrap\Web;
5*04fd306cSNickeau
6*04fd306cSNickeauuse ComboStrap\ArrayCaseInsensitive;
7*04fd306cSNickeauuse ComboStrap\DataType;
8*04fd306cSNickeauuse ComboStrap\ExceptionBadArgument;
9*04fd306cSNickeauuse ComboStrap\ExceptionBadSyntax;
10*04fd306cSNickeauuse ComboStrap\ExceptionCompile;
11*04fd306cSNickeauuse ComboStrap\ExceptionNotEquals;
12*04fd306cSNickeauuse ComboStrap\ExceptionNotExists;
13*04fd306cSNickeauuse ComboStrap\ExceptionNotFound;
14*04fd306cSNickeauuse ComboStrap\ExceptionRuntimeInternal;
15*04fd306cSNickeauuse ComboStrap\FetcherRawLocalPath;
16*04fd306cSNickeauuse ComboStrap\FetcherSystem;
17*04fd306cSNickeauuse ComboStrap\LocalFileSystem;
18*04fd306cSNickeauuse ComboStrap\LogUtility;
19*04fd306cSNickeauuse ComboStrap\Path;
20*04fd306cSNickeauuse ComboStrap\PathAbs;
21*04fd306cSNickeauuse ComboStrap\Site;
22*04fd306cSNickeauuse ComboStrap\Web\UrlRewrite;
23*04fd306cSNickeauuse ComboStrap\WikiPath;
24*04fd306cSNickeau
25*04fd306cSNickeau/**
26*04fd306cSNickeau * Class Url
27*04fd306cSNickeau * @package ComboStrap
28*04fd306cSNickeau * There is no URL class in php
29*04fd306cSNickeau * Only function
30*04fd306cSNickeau * https://www.php.net/manual/en/ref.url.php
31*04fd306cSNickeau */
32*04fd306cSNickeauclass Url extends PathAbs
33*04fd306cSNickeau{
34*04fd306cSNickeau
35*04fd306cSNickeau
36*04fd306cSNickeau    public const PATH_SEP = "/";
37*04fd306cSNickeau    /**
38*04fd306cSNickeau     * In HTML (not in css)
39*04fd306cSNickeau     *
40*04fd306cSNickeau     * Because ampersands are used to denote HTML entities,
41*04fd306cSNickeau     * if you want to use them as literal characters, you must escape them as entities,
42*04fd306cSNickeau     * e.g.  &amp;.
43*04fd306cSNickeau     *
44*04fd306cSNickeau     * In HTML, Browser will do the translation for you if you give an URL
45*04fd306cSNickeau     * not encoded but testing library may not and refuse them
46*04fd306cSNickeau     *
47*04fd306cSNickeau     * This URL encoding is mandatory for the {@link ml} function
48*04fd306cSNickeau     * when there is a width and use them not otherwise
49*04fd306cSNickeau     *
50*04fd306cSNickeau     * Thus, if you want to link to:
51*04fd306cSNickeau     * http://images.google.com/images?num=30&q=larry+bird
52*04fd306cSNickeau     * you need to encode (ie pass this parameter to the {@link ml} function:
53*04fd306cSNickeau     * http://images.google.com/images?num=30&amp;q=larry+bird
54*04fd306cSNickeau     *
55*04fd306cSNickeau     * https://daringfireball.net/projects/markdown/syntax#autoescape
56*04fd306cSNickeau     *
57*04fd306cSNickeau     */
58*04fd306cSNickeau    public const AMPERSAND_URL_ENCODED_FOR_HTML = '&amp;';
59*04fd306cSNickeau    /**
60*04fd306cSNickeau     * Used in dokuwiki syntax & in CSS attribute
61*04fd306cSNickeau     * (Css attribute value are then HTML encoded as value of the attribute)
62*04fd306cSNickeau     */
63*04fd306cSNickeau    public const AMPERSAND_CHARACTER = "&";
64*04fd306cSNickeau
65*04fd306cSNickeau    const CANONICAL = "url";
66*04fd306cSNickeau    /**
67*04fd306cSNickeau     * The schemes that are relative (normallu only URL ? ie http, https)
68*04fd306cSNickeau     * This class is much more an URI
69*04fd306cSNickeau     */
70*04fd306cSNickeau    const RELATIVE_URL_SCHEMES = ["http", "https"];
71*04fd306cSNickeau
72*04fd306cSNickeau
73*04fd306cSNickeau    private ArrayCaseInsensitive $query;
74*04fd306cSNickeau    private ?string $path = null;
75*04fd306cSNickeau    private ?string $scheme = null;
76*04fd306cSNickeau    private ?string $host = null;
77*04fd306cSNickeau    private ?string $fragment = null;
78*04fd306cSNickeau    /**
79*04fd306cSNickeau     * @var string - original url string
80*04fd306cSNickeau     */
81*04fd306cSNickeau    private $url;
82*04fd306cSNickeau    private ?int $port = null;
83*04fd306cSNickeau    /**
84*04fd306cSNickeau     * @var bool - does the URL rewrite occurs
85*04fd306cSNickeau     */
86*04fd306cSNickeau    private bool $withRewrite = true;
87*04fd306cSNickeau
88*04fd306cSNickeau
89*04fd306cSNickeau    /**
90*04fd306cSNickeau     * UrlUtility constructor.
91*04fd306cSNickeau     * @throws ExceptionBadSyntax
92*04fd306cSNickeau     * @throws ExceptionBadArgument
93*04fd306cSNickeau     */
94*04fd306cSNickeau    public function __construct(string $url = null)
95*04fd306cSNickeau    {
96*04fd306cSNickeau
97*04fd306cSNickeau        $this->url = $url;
98*04fd306cSNickeau        $this->query = new ArrayCaseInsensitive();
99*04fd306cSNickeau        if ($this->url !== null) {
100*04fd306cSNickeau            /**
101*04fd306cSNickeau             *
102*04fd306cSNickeau             * @var false
103*04fd306cSNickeau             *
104*04fd306cSNickeau             * Note: Url validation is hard with regexp
105*04fd306cSNickeau             * for instance:
106*04fd306cSNickeau             *  - http://example.lan/utility/a-combostrap-component-to-render-web-code-in-a-web-page-javascript-html-...-u8fe6ahw
107*04fd306cSNickeau             *  - does not pass return preg_match('|^http(s)?://[a-z0-9-]+(.[a-z0-9-]+)*(:[0-9]+)?(/.*)?$|i', $url);
108*04fd306cSNickeau             * of preg_match('/^https?:\/\//',$url) ? from redirect plugin
109*04fd306cSNickeau             *
110*04fd306cSNickeau             * We try to create the object, the object use the {@link parse_url()}
111*04fd306cSNickeau             * method to validate or send an exception if it can be parsed
112*04fd306cSNickeau             */
113*04fd306cSNickeau            $urlComponents = parse_url($url);
114*04fd306cSNickeau            if ($urlComponents === false) {
115*04fd306cSNickeau                throw new ExceptionBadSyntax("The url ($url) is not valid");
116*04fd306cSNickeau            }
117*04fd306cSNickeau            parse_str($urlComponents['query'], $queryKeys);
118*04fd306cSNickeau            $this->query = new ArrayCaseInsensitive($queryKeys);
119*04fd306cSNickeau            $this->scheme = $urlComponents["scheme"];
120*04fd306cSNickeau            $this->host = $urlComponents["host"];
121*04fd306cSNickeau            $port = $urlComponents["port"];
122*04fd306cSNickeau            try {
123*04fd306cSNickeau                if ($port !== null) {
124*04fd306cSNickeau                    $this->port = DataType::toInteger($port);
125*04fd306cSNickeau                }
126*04fd306cSNickeau            } catch (ExceptionBadArgument $e) {
127*04fd306cSNickeau                throw new ExceptionBadArgument("The port ($port) in ($url) is not an integer. Error: {$e->getMessage()}");
128*04fd306cSNickeau            }
129*04fd306cSNickeau            $pathUrlComponent = $urlComponents["path"];
130*04fd306cSNickeau            if ($pathUrlComponent !== null) {
131*04fd306cSNickeau                $this->setPath($pathUrlComponent);
132*04fd306cSNickeau            }
133*04fd306cSNickeau            $this->fragment = $urlComponents["fragment"];
134*04fd306cSNickeau        }
135*04fd306cSNickeau    }
136*04fd306cSNickeau
137*04fd306cSNickeau
138*04fd306cSNickeau    const RESERVED_WORDS = [':', '!', '#', '$', '&', '\'', '(', ')', '*', '+', ',', '/', ';', '=', '?', '@', '[', ']'];
139*04fd306cSNickeau
140*04fd306cSNickeau    /**
141*04fd306cSNickeau     * A text to an encoded url
142*04fd306cSNickeau     * @param $string -  a string
143*04fd306cSNickeau     * @param string $separator - the path separator in the string
144*04fd306cSNickeau     */
145*04fd306cSNickeau    public static function encodeToUrlPath($string, string $separator = WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT): string
146*04fd306cSNickeau    {
147*04fd306cSNickeau        $parts = explode($separator, $string);
148*04fd306cSNickeau        $encodedParts = array_map(function ($e) {
149*04fd306cSNickeau            return urlencode($e);
150*04fd306cSNickeau        }, $parts);
151*04fd306cSNickeau        return implode("/", $encodedParts);
152*04fd306cSNickeau    }
153*04fd306cSNickeau
154*04fd306cSNickeau    public static function createEmpty(): Url
155*04fd306cSNickeau    {
156*04fd306cSNickeau        return new Url();
157*04fd306cSNickeau    }
158*04fd306cSNickeau
159*04fd306cSNickeau    /**
160*04fd306cSNickeau     *
161*04fd306cSNickeau     */
162*04fd306cSNickeau    public static function createFromGetOrPostGlobalVariable(): Url
163*04fd306cSNickeau    {
164*04fd306cSNickeau        /**
165*04fd306cSNickeau         * $_REQUEST is a merge between get and post property
166*04fd306cSNickeau         * Shared check between post and get HTTP method
167*04fd306cSNickeau         * {@link \TestRequest} is using it
168*04fd306cSNickeau         */
169*04fd306cSNickeau        $url = Url::createEmpty();
170*04fd306cSNickeau        foreach ($_REQUEST as $key => $value) {
171*04fd306cSNickeau            if (is_array($value)) {
172*04fd306cSNickeau                foreach ($value as $subkey => $subval) {
173*04fd306cSNickeau                    if (is_array($subval)) {
174*04fd306cSNickeau                        if ($key !== "config") {
175*04fd306cSNickeau                            // dokuwiki things
176*04fd306cSNickeau                            LogUtility::warning("The key ($key) is an array of an array and was not taken into account in the request url.");
177*04fd306cSNickeau                        }
178*04fd306cSNickeau                        continue;
179*04fd306cSNickeau                    }
180*04fd306cSNickeau                    if ($key == "do") {
181*04fd306cSNickeau                        // for whatever reason, dokuwiki puts the value in the key
182*04fd306cSNickeau                        $url->addQueryParameter($key, $subkey);
183*04fd306cSNickeau                        continue;
184*04fd306cSNickeau                    }
185*04fd306cSNickeau                    $url->addQueryParameter($key, $subval);
186*04fd306cSNickeau
187*04fd306cSNickeau                }
188*04fd306cSNickeau            } else {
189*04fd306cSNickeau                /**
190*04fd306cSNickeau                 * Bad URL format test
191*04fd306cSNickeau                 * In the `src` attribute of `script`, the url should not be encoded
192*04fd306cSNickeau                 * with {@link Url::AMPERSAND_URL_ENCODED_FOR_HTML}
193*04fd306cSNickeau                 * otherwise we get `amp;` as prefix
194*04fd306cSNickeau                 * in Chrome
195*04fd306cSNickeau                 */
196*04fd306cSNickeau                if (strpos($key, "amp;") === 0) {
197*04fd306cSNickeau                    /**
198*04fd306cSNickeau                     * We don't advertise this error, it should not happen
199*04fd306cSNickeau                     * and there is nothing to do to get back on its feet
200*04fd306cSNickeau                     */
201*04fd306cSNickeau                    $message = "The url in src has a bad encoding (the attribute have a amp; prefix. Infinite cache will not work.";
202*04fd306cSNickeau                    throw new ExceptionRuntimeInternal($message);
203*04fd306cSNickeau                }
204*04fd306cSNickeau                $url->addQueryParameter($key, $value);
205*04fd306cSNickeau            }
206*04fd306cSNickeau        }
207*04fd306cSNickeau        return $url;
208*04fd306cSNickeau    }
209*04fd306cSNickeau
210*04fd306cSNickeau    /**
211*04fd306cSNickeau     * Utility class to transform windows separator to url path separator
212*04fd306cSNickeau     * @param string $pathString
213*04fd306cSNickeau     * @return array|string|string[]
214*04fd306cSNickeau     */
215*04fd306cSNickeau    public static function toUrlSeparator(string $pathString)
216*04fd306cSNickeau    {
217*04fd306cSNickeau        return str_replace('\\', '/', $pathString);
218*04fd306cSNickeau    }
219*04fd306cSNickeau
220*04fd306cSNickeau
221*04fd306cSNickeau    function getQueryProperties(): array
222*04fd306cSNickeau    {
223*04fd306cSNickeau        return $this->query->getOriginalArray();
224*04fd306cSNickeau    }
225*04fd306cSNickeau
226*04fd306cSNickeau    /**
227*04fd306cSNickeau     * @throws ExceptionNotFound
228*04fd306cSNickeau     */
229*04fd306cSNickeau    function getQueryPropertyValue($key)
230*04fd306cSNickeau    {
231*04fd306cSNickeau        $value = $this->query[$key];
232*04fd306cSNickeau        if ($value === null) {
233*04fd306cSNickeau            throw new ExceptionNotFound("The key ($key) was not found");
234*04fd306cSNickeau        }
235*04fd306cSNickeau        return $value;
236*04fd306cSNickeau    }
237*04fd306cSNickeau
238*04fd306cSNickeau    /**
239*04fd306cSNickeau     * Extract the value of a property
240*04fd306cSNickeau     * @param $propertyName
241*04fd306cSNickeau     * @return string - the value of the property
242*04fd306cSNickeau     * @throws ExceptionNotFound
243*04fd306cSNickeau     */
244*04fd306cSNickeau    public function getPropertyValue($propertyName): string
245*04fd306cSNickeau    {
246*04fd306cSNickeau        if (!isset($this->query[$propertyName])) {
247*04fd306cSNickeau            throw new ExceptionNotFound("The property ($propertyName) was not found", self::CANONICAL);
248*04fd306cSNickeau        }
249*04fd306cSNickeau        return $this->query[$propertyName];
250*04fd306cSNickeau    }
251*04fd306cSNickeau
252*04fd306cSNickeau
253*04fd306cSNickeau    /**
254*04fd306cSNickeau     * @throws ExceptionBadSyntax|ExceptionBadArgument
255*04fd306cSNickeau     */
256*04fd306cSNickeau    public static function createFromString(string $url): Url
257*04fd306cSNickeau    {
258*04fd306cSNickeau        return new Url($url);
259*04fd306cSNickeau    }
260*04fd306cSNickeau
261*04fd306cSNickeau    /**
262*04fd306cSNickeau     * @throws ExceptionNotFound
263*04fd306cSNickeau     */
264*04fd306cSNickeau    public function getScheme(): string
265*04fd306cSNickeau    {
266*04fd306cSNickeau        if ($this->scheme === null) {
267*04fd306cSNickeau            throw new ExceptionNotFound("The scheme was not found");
268*04fd306cSNickeau        }
269*04fd306cSNickeau        return $this->scheme;
270*04fd306cSNickeau    }
271*04fd306cSNickeau
272*04fd306cSNickeau    /**
273*04fd306cSNickeau     * @param string $path
274*04fd306cSNickeau     * @return $this
275*04fd306cSNickeau     * in a https scheme: Not the path has a leading `/` that makes the path absolute
276*04fd306cSNickeau     * in a email scheme: the path is the email (without /) then
277*04fd306cSNickeau     */
278*04fd306cSNickeau    public function setPath(string $path): Url
279*04fd306cSNickeau    {
280*04fd306cSNickeau
281*04fd306cSNickeau        /**
282*04fd306cSNickeau         * Normalization hack
283*04fd306cSNickeau         */
284*04fd306cSNickeau        if (strpos($path, "/./") === 0) {
285*04fd306cSNickeau            $path = substr($path, 2);
286*04fd306cSNickeau        }
287*04fd306cSNickeau        $this->path = $path;
288*04fd306cSNickeau        return $this;
289*04fd306cSNickeau    }
290*04fd306cSNickeau
291*04fd306cSNickeau    /**
292*04fd306cSNickeau     * @return bool - true if http, https scheme
293*04fd306cSNickeau     */
294*04fd306cSNickeau    public function isHttpUrl(): bool
295*04fd306cSNickeau    {
296*04fd306cSNickeau        try {
297*04fd306cSNickeau            return in_array($this->getScheme(), ["http", "https"]);
298*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
299*04fd306cSNickeau            return false;
300*04fd306cSNickeau        }
301*04fd306cSNickeau    }
302*04fd306cSNickeau
303*04fd306cSNickeau    /**
304*04fd306cSNickeau     * Multiple parameter can be set to form an array
305*04fd306cSNickeau     *
306*04fd306cSNickeau     * Example: s=word1&s=word2
307*04fd306cSNickeau     *
308*04fd306cSNickeau     * https://stackoverflow.com/questions/24059773/correct-way-to-pass-multiple-values-for-same-parameter-name-in-get-request
309*04fd306cSNickeau     */
310*04fd306cSNickeau    public function addQueryParameter(string $key, ?string $value = null): Url
311*04fd306cSNickeau    {
312*04fd306cSNickeau        /**
313*04fd306cSNickeau         * Php Array syntax
314*04fd306cSNickeau         */
315*04fd306cSNickeau        if (substr($key, -2) === "[]") {
316*04fd306cSNickeau            $key = substr($key, 0, -2);
317*04fd306cSNickeau            $actualValue = $this->query[$key];
318*04fd306cSNickeau            if ($actualValue === null || is_array($actualValue)) {
319*04fd306cSNickeau                $this->query[$key] = [$value];
320*04fd306cSNickeau            } else {
321*04fd306cSNickeau                $actualValue[] = $value;
322*04fd306cSNickeau                $this->query[$key] = $actualValue;
323*04fd306cSNickeau            }
324*04fd306cSNickeau            return $this;
325*04fd306cSNickeau        }
326*04fd306cSNickeau        if (isset($this->query[$key])) {
327*04fd306cSNickeau            $actualValue = $this->query[$key];
328*04fd306cSNickeau            if (is_array($actualValue)) {
329*04fd306cSNickeau                $this->query[$key][] = $value;
330*04fd306cSNickeau            } else {
331*04fd306cSNickeau                $this->query[$key] = [$actualValue, $value];
332*04fd306cSNickeau            }
333*04fd306cSNickeau        } else {
334*04fd306cSNickeau            $this->query[$key] = $value;
335*04fd306cSNickeau        }
336*04fd306cSNickeau        return $this;
337*04fd306cSNickeau    }
338*04fd306cSNickeau
339*04fd306cSNickeau
340*04fd306cSNickeau    public function hasProperty(string $key): bool
341*04fd306cSNickeau    {
342*04fd306cSNickeau        if (isset($this->query[$key])) {
343*04fd306cSNickeau            return true;
344*04fd306cSNickeau        }
345*04fd306cSNickeau        return false;
346*04fd306cSNickeau    }
347*04fd306cSNickeau
348*04fd306cSNickeau    /**
349*04fd306cSNickeau     * @return Url - add the scheme and the host based on the request if not present
350*04fd306cSNickeau     */
351*04fd306cSNickeau    public function toAbsoluteUrl(): Url
352*04fd306cSNickeau    {
353*04fd306cSNickeau        try {
354*04fd306cSNickeau            $this->getScheme();
355*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
356*04fd306cSNickeau            /**
357*04fd306cSNickeau             * See {@link getBaseURL()}
358*04fd306cSNickeau             */
359*04fd306cSNickeau            $https = $_SERVER['HTTPS'];
360*04fd306cSNickeau            if (empty($https)) {
361*04fd306cSNickeau                $this->setScheme("http");
362*04fd306cSNickeau            } else {
363*04fd306cSNickeau                $this->setScheme("https");
364*04fd306cSNickeau            }
365*04fd306cSNickeau        }
366*04fd306cSNickeau        try {
367*04fd306cSNickeau            $this->getHost();
368*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
369*04fd306cSNickeau            $remoteHost = Site::getServerHost();
370*04fd306cSNickeau            $this->setHost($remoteHost);
371*04fd306cSNickeau
372*04fd306cSNickeau        }
373*04fd306cSNickeau        return $this;
374*04fd306cSNickeau    }
375*04fd306cSNickeau
376*04fd306cSNickeau    /**
377*04fd306cSNickeau     * @return string - utility function that call {@link Url::toAbsoluteUrl()} absolute and {@link Url::toString()}
378*04fd306cSNickeau     */
379*04fd306cSNickeau    public function toAbsoluteUrlString(): string
380*04fd306cSNickeau    {
381*04fd306cSNickeau        $this->toAbsoluteUrl();
382*04fd306cSNickeau        return $this->toString();
383*04fd306cSNickeau    }
384*04fd306cSNickeau
385*04fd306cSNickeau    /**
386*04fd306cSNickeau     * @throws ExceptionNotFound
387*04fd306cSNickeau     */
388*04fd306cSNickeau    public function getHost(): string
389*04fd306cSNickeau    {
390*04fd306cSNickeau        if ($this->host === null) {
391*04fd306cSNickeau            throw new ExceptionNotFound("No host");
392*04fd306cSNickeau        }
393*04fd306cSNickeau        return $this->host;
394*04fd306cSNickeau    }
395*04fd306cSNickeau
396*04fd306cSNickeau    /**
397*04fd306cSNickeau     * @throws ExceptionNotFound
398*04fd306cSNickeau     */
399*04fd306cSNickeau    public function getPath(): string
400*04fd306cSNickeau    {
401*04fd306cSNickeau        if ($this->path === null) {
402*04fd306cSNickeau            throw new ExceptionNotFound("The path was not found");
403*04fd306cSNickeau        }
404*04fd306cSNickeau        return $this->path;
405*04fd306cSNickeau    }
406*04fd306cSNickeau
407*04fd306cSNickeau    /**
408*04fd306cSNickeau     * @throws ExceptionNotFound
409*04fd306cSNickeau     */
410*04fd306cSNickeau    public function getFragment(): string
411*04fd306cSNickeau    {
412*04fd306cSNickeau        if ($this->fragment === null) {
413*04fd306cSNickeau            throw new ExceptionNotFound("The fragment was not set");
414*04fd306cSNickeau        }
415*04fd306cSNickeau        return $this->fragment;
416*04fd306cSNickeau    }
417*04fd306cSNickeau
418*04fd306cSNickeau
419*04fd306cSNickeau    public function __toString()
420*04fd306cSNickeau    {
421*04fd306cSNickeau        return $this->toString();
422*04fd306cSNickeau    }
423*04fd306cSNickeau
424*04fd306cSNickeau    public function getQueryPropertyValueOrDefault(string $key, string $defaultIfNull)
425*04fd306cSNickeau    {
426*04fd306cSNickeau        try {
427*04fd306cSNickeau            return $this->getQueryPropertyValue($key);
428*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
429*04fd306cSNickeau            return $defaultIfNull;
430*04fd306cSNickeau        }
431*04fd306cSNickeau    }
432*04fd306cSNickeau
433*04fd306cSNickeau    /**
434*04fd306cSNickeau     * Actual vs expected
435*04fd306cSNickeau     *
436*04fd306cSNickeau     * We use this vocabulary (actual/expected) and not (internal/external or left/right) because this function
437*04fd306cSNickeau     * is mostly used in a test framework.
438*04fd306cSNickeau     *
439*04fd306cSNickeau     * @throws ExceptionNotEquals
440*04fd306cSNickeau     */
441*04fd306cSNickeau    public function equals(Url $expectedUrl)
442*04fd306cSNickeau    {
443*04fd306cSNickeau        /**
444*04fd306cSNickeau         * Scheme
445*04fd306cSNickeau         */
446*04fd306cSNickeau        try {
447*04fd306cSNickeau            $actualScheme = $this->getScheme();
448*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
449*04fd306cSNickeau            $actualScheme = "";
450*04fd306cSNickeau        }
451*04fd306cSNickeau        try {
452*04fd306cSNickeau            $expectedScheme = $expectedUrl->getScheme();
453*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
454*04fd306cSNickeau            $expectedScheme = "";
455*04fd306cSNickeau        }
456*04fd306cSNickeau        if ($actualScheme !== $expectedScheme) {
457*04fd306cSNickeau            throw new ExceptionNotEquals("The scheme are not equals ($actualScheme vs $expectedScheme)");
458*04fd306cSNickeau        }
459*04fd306cSNickeau        /**
460*04fd306cSNickeau         * Host
461*04fd306cSNickeau         */
462*04fd306cSNickeau        try {
463*04fd306cSNickeau            $actualHost = $this->getHost();
464*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
465*04fd306cSNickeau            $actualHost = "";
466*04fd306cSNickeau        }
467*04fd306cSNickeau        try {
468*04fd306cSNickeau            $expectedHost = $expectedUrl->getHost();
469*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
470*04fd306cSNickeau            $expectedHost = "";
471*04fd306cSNickeau        }
472*04fd306cSNickeau        if ($actualHost !== $expectedHost) {
473*04fd306cSNickeau            throw new ExceptionNotEquals("The host are not equals ($actualHost vs $expectedHost)");
474*04fd306cSNickeau        }
475*04fd306cSNickeau        /**
476*04fd306cSNickeau         * Query
477*04fd306cSNickeau         */
478*04fd306cSNickeau        $actualQuery = $this->getQueryProperties();
479*04fd306cSNickeau        $expectedQuery = $expectedUrl->getQueryProperties();
480*04fd306cSNickeau        foreach ($actualQuery as $key => $value) {
481*04fd306cSNickeau            $expectedValue = $expectedQuery[$key];
482*04fd306cSNickeau            if ($expectedValue === null) {
483*04fd306cSNickeau                throw new ExceptionNotEquals("The expected url does not have the $key property");
484*04fd306cSNickeau            }
485*04fd306cSNickeau            if ($expectedValue !== $value) {
486*04fd306cSNickeau                throw new ExceptionNotEquals("The $key property does not have the same value ($value vs $expectedValue)");
487*04fd306cSNickeau            }
488*04fd306cSNickeau            unset($expectedQuery[$key]);
489*04fd306cSNickeau        }
490*04fd306cSNickeau        foreach ($expectedQuery as $key => $value) {
491*04fd306cSNickeau            throw new ExceptionNotEquals("The expected URL has an extra property ($key=$value)");
492*04fd306cSNickeau        }
493*04fd306cSNickeau
494*04fd306cSNickeau        /**
495*04fd306cSNickeau         * Fragment
496*04fd306cSNickeau         */
497*04fd306cSNickeau        try {
498*04fd306cSNickeau            $actualFragment = $this->getFragment();
499*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
500*04fd306cSNickeau            $actualFragment = "";
501*04fd306cSNickeau        }
502*04fd306cSNickeau        try {
503*04fd306cSNickeau            $expectedFragment = $expectedUrl->getFragment();
504*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
505*04fd306cSNickeau            $expectedFragment = "";
506*04fd306cSNickeau        }
507*04fd306cSNickeau        if ($actualFragment !== $expectedFragment) {
508*04fd306cSNickeau            throw new ExceptionNotEquals("The fragment are not equals ($actualFragment vs $expectedFragment)");
509*04fd306cSNickeau        }
510*04fd306cSNickeau
511*04fd306cSNickeau    }
512*04fd306cSNickeau
513*04fd306cSNickeau    public function setScheme(string $scheme): Url
514*04fd306cSNickeau    {
515*04fd306cSNickeau        $this->scheme = $scheme;
516*04fd306cSNickeau        return $this;
517*04fd306cSNickeau    }
518*04fd306cSNickeau
519*04fd306cSNickeau    public function setHost($host): Url
520*04fd306cSNickeau    {
521*04fd306cSNickeau        $this->host = $host;
522*04fd306cSNickeau        return $this;
523*04fd306cSNickeau    }
524*04fd306cSNickeau
525*04fd306cSNickeau    public function setFragment(string $fragment): Url
526*04fd306cSNickeau    {
527*04fd306cSNickeau        $this->fragment = $fragment;
528*04fd306cSNickeau        return $this;
529*04fd306cSNickeau    }
530*04fd306cSNickeau
531*04fd306cSNickeau    /**
532*04fd306cSNickeau     * @throws ExceptionNotFound
533*04fd306cSNickeau     */
534*04fd306cSNickeau    public function getQueryString($ampersand = Url::AMPERSAND_CHARACTER): string
535*04fd306cSNickeau    {
536*04fd306cSNickeau        if (sizeof($this->query) === 0) {
537*04fd306cSNickeau            throw new ExceptionNotFound("No Query string");
538*04fd306cSNickeau        }
539*04fd306cSNickeau        /**
540*04fd306cSNickeau         * To be able to diff them
541*04fd306cSNickeau         */
542*04fd306cSNickeau        $originalArray = $this->query->getOriginalArray();
543*04fd306cSNickeau        ksort($originalArray);
544*04fd306cSNickeau
545*04fd306cSNickeau        /**
546*04fd306cSNickeau         * We don't use {@link http_build_query} because:
547*04fd306cSNickeau         *   * it does not the follow the array format (ie s[]=searchword1+seachword2)
548*04fd306cSNickeau         *   * it output 'key=' instead of `key` when the value is null
549*04fd306cSNickeau         */
550*04fd306cSNickeau        $queryString = null;
551*04fd306cSNickeau        foreach ($originalArray as $key => $value) {
552*04fd306cSNickeau            if ($queryString !== null) {
553*04fd306cSNickeau                /**
554*04fd306cSNickeau                 * HTML encoding (ie {@link self::AMPERSAND_URL_ENCODED_FOR_HTML}
555*04fd306cSNickeau                 * happens only when outputing to HTML
556*04fd306cSNickeau                 * The url may also be used elsewhere where &amp; is unknown or not wanted such as css ...
557*04fd306cSNickeau                 *
558*04fd306cSNickeau                 * In test, we may ask the url HTML encoded
559*04fd306cSNickeau                 */
560*04fd306cSNickeau                $queryString .= $ampersand;
561*04fd306cSNickeau            }
562*04fd306cSNickeau            if ($value === null) {
563*04fd306cSNickeau                $queryString .= urlencode($key);
564*04fd306cSNickeau            } else {
565*04fd306cSNickeau                if (is_array($value)) {
566*04fd306cSNickeau                    for ($i = 0; $i < sizeof($value); $i++) {
567*04fd306cSNickeau                        $val = $value[$i];
568*04fd306cSNickeau                        if ($i > 0) {
569*04fd306cSNickeau                            $queryString .= self::AMPERSAND_CHARACTER;
570*04fd306cSNickeau                        }
571*04fd306cSNickeau                        $queryString .= urlencode($key) . "[]=" . urlencode($val);
572*04fd306cSNickeau                    }
573*04fd306cSNickeau                } else {
574*04fd306cSNickeau                    $queryString .= urlencode($key) . "=" . urlencode($value);
575*04fd306cSNickeau                }
576*04fd306cSNickeau            }
577*04fd306cSNickeau        }
578*04fd306cSNickeau        return $queryString;
579*04fd306cSNickeau
580*04fd306cSNickeau
581*04fd306cSNickeau    }
582*04fd306cSNickeau
583*04fd306cSNickeau    /**
584*04fd306cSNickeau     * @throws ExceptionNotFound
585*04fd306cSNickeau     */
586*04fd306cSNickeau    public function getQueryPropertyValueAndRemoveIfPresent(string $key)
587*04fd306cSNickeau    {
588*04fd306cSNickeau        $value = $this->getQueryPropertyValue($key);
589*04fd306cSNickeau        unset($this->query[$key]);
590*04fd306cSNickeau        return $value;
591*04fd306cSNickeau    }
592*04fd306cSNickeau
593*04fd306cSNickeau
594*04fd306cSNickeau    /**
595*04fd306cSNickeau     * @throws ExceptionNotFound
596*04fd306cSNickeau     */
597*04fd306cSNickeau    function getLastName(): string
598*04fd306cSNickeau    {
599*04fd306cSNickeau        $names = $this->getNames();
600*04fd306cSNickeau        $namesCount = count($names);
601*04fd306cSNickeau        if ($namesCount === 0) {
602*04fd306cSNickeau            throw new ExceptionNotFound("No last name");
603*04fd306cSNickeau        }
604*04fd306cSNickeau        return $names[$namesCount - 1];
605*04fd306cSNickeau
606*04fd306cSNickeau    }
607*04fd306cSNickeau
608*04fd306cSNickeau    /**
609*04fd306cSNickeau     * @return string
610*04fd306cSNickeau     * @throws ExceptionNotFound
611*04fd306cSNickeau     */
612*04fd306cSNickeau    public function getExtension(): string
613*04fd306cSNickeau    {
614*04fd306cSNickeau        if ($this->hasProperty(FetcherRawLocalPath::$MEDIA_QUERY_PARAMETER)) {
615*04fd306cSNickeau
616*04fd306cSNickeau            try {
617*04fd306cSNickeau                return FetcherSystem::createPathFetcherFromUrl($this)->getMime()->getExtension();
618*04fd306cSNickeau            } catch (ExceptionCompile $e) {
619*04fd306cSNickeau                LogUtility::internalError("Build error from a Media Fetch URL. We were unable to get the mime. Error: {$e->getMessage()}");
620*04fd306cSNickeau            }
621*04fd306cSNickeau
622*04fd306cSNickeau        }
623*04fd306cSNickeau        return parent::getExtension();
624*04fd306cSNickeau    }
625*04fd306cSNickeau
626*04fd306cSNickeau
627*04fd306cSNickeau    function getNames()
628*04fd306cSNickeau    {
629*04fd306cSNickeau
630*04fd306cSNickeau        try {
631*04fd306cSNickeau            $names = explode(self::PATH_SEP, $this->getPath());
632*04fd306cSNickeau            return array_slice($names, 1);
633*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
634*04fd306cSNickeau            return [];
635*04fd306cSNickeau        }
636*04fd306cSNickeau
637*04fd306cSNickeau    }
638*04fd306cSNickeau
639*04fd306cSNickeau    /**
640*04fd306cSNickeau     * @throws ExceptionNotFound
641*04fd306cSNickeau     */
642*04fd306cSNickeau    function getParent(): Url
643*04fd306cSNickeau    {
644*04fd306cSNickeau        $names = $this->getNames();
645*04fd306cSNickeau        $count = count($names);
646*04fd306cSNickeau        if ($count === 0) {
647*04fd306cSNickeau            throw new ExceptionNotFound("No Parent");
648*04fd306cSNickeau        }
649*04fd306cSNickeau        $parentPath = implode(self::PATH_SEP, array_splice($names, 0, $count - 1));
650*04fd306cSNickeau        return $this->setPath($parentPath);
651*04fd306cSNickeau    }
652*04fd306cSNickeau
653*04fd306cSNickeau    function toAbsoluteId(): string
654*04fd306cSNickeau    {
655*04fd306cSNickeau        try {
656*04fd306cSNickeau            return $this->getPath();
657*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
658*04fd306cSNickeau            return "";
659*04fd306cSNickeau        }
660*04fd306cSNickeau    }
661*04fd306cSNickeau
662*04fd306cSNickeau    function toAbsolutePath(): Url
663*04fd306cSNickeau    {
664*04fd306cSNickeau        return $this->toAbsoluteUrl();
665*04fd306cSNickeau    }
666*04fd306cSNickeau
667*04fd306cSNickeau    function resolve(string $name): Url
668*04fd306cSNickeau    {
669*04fd306cSNickeau        try {
670*04fd306cSNickeau            $path = $this->getPath();
671*04fd306cSNickeau            if ($this->path[strlen($path) - 1] === URL::PATH_SEP) {
672*04fd306cSNickeau                $this->path .= $name;
673*04fd306cSNickeau            } else {
674*04fd306cSNickeau                $this->path .= URL::PATH_SEP . $name;
675*04fd306cSNickeau            }
676*04fd306cSNickeau            return $this;
677*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
678*04fd306cSNickeau            $this->setPath($name);
679*04fd306cSNickeau            return $this;
680*04fd306cSNickeau        }
681*04fd306cSNickeau
682*04fd306cSNickeau    }
683*04fd306cSNickeau
684*04fd306cSNickeau    /**
685*04fd306cSNickeau     * @param string $ampersand
686*04fd306cSNickeau     * @return string
687*04fd306cSNickeau     */
688*04fd306cSNickeau    public function toString(string $ampersand = Url::AMPERSAND_CHARACTER): string
689*04fd306cSNickeau    {
690*04fd306cSNickeau
691*04fd306cSNickeau        try {
692*04fd306cSNickeau            $scheme = $this->getScheme();
693*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
694*04fd306cSNickeau            $scheme = null;
695*04fd306cSNickeau        }
696*04fd306cSNickeau
697*04fd306cSNickeau
698*04fd306cSNickeau        switch ($scheme) {
699*04fd306cSNickeau            case LocalFileSystem::SCHEME:
700*04fd306cSNickeau                /**
701*04fd306cSNickeau                 * file://host/path
702*04fd306cSNickeau                 */
703*04fd306cSNickeau                $base = "$scheme://";
704*04fd306cSNickeau                try {
705*04fd306cSNickeau                    $base = "$base{$this->getHost()}";
706*04fd306cSNickeau                } catch (ExceptionNotFound $e) {
707*04fd306cSNickeau                    // no host
708*04fd306cSNickeau                }
709*04fd306cSNickeau                try {
710*04fd306cSNickeau                    $path = $this->getAbsolutePath();
711*04fd306cSNickeau                    // linux, network share (file://host/path)
712*04fd306cSNickeau                    $base = "$base{$path}";
713*04fd306cSNickeau                } catch (ExceptionNotFound $e) {
714*04fd306cSNickeau                    // no path
715*04fd306cSNickeau                }
716*04fd306cSNickeau                return $base;
717*04fd306cSNickeau            case "mailto":
718*04fd306cSNickeau            case "whatsapp":
719*04fd306cSNickeau            case "skype":
720*04fd306cSNickeau                /**
721*04fd306cSNickeau                 * Skype. Example: skype:echo123?call
722*04fd306cSNickeau                 * https://docs.microsoft.com/en-us/skype-sdk/skypeuris/skypeuris
723*04fd306cSNickeau                 * Mailto: Example: mailto:java-net@java.sun.com?subject=yolo
724*04fd306cSNickeau                 * https://datacadamia.com/marketing/email/mailto
725*04fd306cSNickeau                 */
726*04fd306cSNickeau                $base = "$scheme:";
727*04fd306cSNickeau                try {
728*04fd306cSNickeau                    $base = "$base{$this->getPath()}";
729*04fd306cSNickeau                } catch (ExceptionNotFound $e) {
730*04fd306cSNickeau                    // no path
731*04fd306cSNickeau                }
732*04fd306cSNickeau                try {
733*04fd306cSNickeau                    $base = "$base?{$this->getQueryString()}";
734*04fd306cSNickeau                } catch (ExceptionNotFound $e) {
735*04fd306cSNickeau                    // no query string
736*04fd306cSNickeau                }
737*04fd306cSNickeau                try {
738*04fd306cSNickeau                    $base = "$base#{$this->getFragment()}";
739*04fd306cSNickeau                } catch (ExceptionNotFound $e) {
740*04fd306cSNickeau                    // no fragment
741*04fd306cSNickeau                }
742*04fd306cSNickeau                return $base;
743*04fd306cSNickeau            case "http":
744*04fd306cSNickeau            case "https":
745*04fd306cSNickeau            case "ftp":
746*04fd306cSNickeau            default:
747*04fd306cSNickeau                /**
748*04fd306cSNickeau                 * Url Rewrite
749*04fd306cSNickeau                 * Absolute vs Relative, __media, ...
750*04fd306cSNickeau                 */
751*04fd306cSNickeau                if ($this->withRewrite) {
752*04fd306cSNickeau                    UrlRewrite::rewrite($this);
753*04fd306cSNickeau                }
754*04fd306cSNickeau                /**
755*04fd306cSNickeau                 * Rewrite may have set a default scheme
756*04fd306cSNickeau                 * We read it again
757*04fd306cSNickeau                 */
758*04fd306cSNickeau                try {
759*04fd306cSNickeau                    $scheme = $this->getScheme();
760*04fd306cSNickeau                } catch (ExceptionNotFound $e) {
761*04fd306cSNickeau                    $scheme = null;
762*04fd306cSNickeau                }
763*04fd306cSNickeau                try {
764*04fd306cSNickeau                    $host = $this->getHost();
765*04fd306cSNickeau                } catch (ExceptionNotFound $e) {
766*04fd306cSNickeau                    $host = null;
767*04fd306cSNickeau                }
768*04fd306cSNickeau                /**
769*04fd306cSNickeau                 * Absolute/Relative Uri
770*04fd306cSNickeau                 */
771*04fd306cSNickeau                $base = "";
772*04fd306cSNickeau                if ($host !== null) {
773*04fd306cSNickeau                    if ($scheme !== null) {
774*04fd306cSNickeau                        $base = "{$scheme}://";
775*04fd306cSNickeau                    }
776*04fd306cSNickeau                    $base = "$base{$host}";
777*04fd306cSNickeau                    try {
778*04fd306cSNickeau                        $base = "$base:{$this->getPort()}";
779*04fd306cSNickeau                    } catch (ExceptionNotFound $e) {
780*04fd306cSNickeau                        // no port
781*04fd306cSNickeau                    }
782*04fd306cSNickeau                } else {
783*04fd306cSNickeau                    if (!in_array($scheme, self::RELATIVE_URL_SCHEMES) && $scheme !== null) {
784*04fd306cSNickeau                        $base = "{$scheme}:";
785*04fd306cSNickeau                    }
786*04fd306cSNickeau                }
787*04fd306cSNickeau
788*04fd306cSNickeau                try {
789*04fd306cSNickeau                    $base = "$base{$this->getAbsolutePath()}";
790*04fd306cSNickeau                } catch (ExceptionNotFound $e) {
791*04fd306cSNickeau                    // ok
792*04fd306cSNickeau                }
793*04fd306cSNickeau
794*04fd306cSNickeau                try {
795*04fd306cSNickeau                    $base = "$base?{$this->getQueryString($ampersand)}";
796*04fd306cSNickeau                } catch (ExceptionNotFound $e) {
797*04fd306cSNickeau                    // ok
798*04fd306cSNickeau                }
799*04fd306cSNickeau
800*04fd306cSNickeau                try {
801*04fd306cSNickeau                    $base = "$base#{$this->getFragment()}";
802*04fd306cSNickeau                } catch (ExceptionNotFound $e) {
803*04fd306cSNickeau                    // ok
804*04fd306cSNickeau                }
805*04fd306cSNickeau                return $base;
806*04fd306cSNickeau        }
807*04fd306cSNickeau
808*04fd306cSNickeau
809*04fd306cSNickeau    }
810*04fd306cSNickeau
811*04fd306cSNickeau    /**
812*04fd306cSNickeau     * Query parameter can have several values
813*04fd306cSNickeau     * This function makes sure that there is only one value for one key
814*04fd306cSNickeau     * if the value are different, the value will be added
815*04fd306cSNickeau     * @param string $key
816*04fd306cSNickeau     * @param string $value
817*04fd306cSNickeau     * @return Url
818*04fd306cSNickeau     */
819*04fd306cSNickeau    public function addQueryParameterIfNotActualSameValue(string $key, string $value): Url
820*04fd306cSNickeau    {
821*04fd306cSNickeau        try {
822*04fd306cSNickeau            $actualValue = $this->getQueryPropertyValue($key);
823*04fd306cSNickeau            if ($actualValue !== $value) {
824*04fd306cSNickeau                $this->addQueryParameter($key, $value);
825*04fd306cSNickeau            }
826*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
827*04fd306cSNickeau            $this->addQueryParameter($key, $value);
828*04fd306cSNickeau        }
829*04fd306cSNickeau
830*04fd306cSNickeau        return $this;
831*04fd306cSNickeau
832*04fd306cSNickeau    }
833*04fd306cSNickeau
834*04fd306cSNickeau    function getUrl(): Url
835*04fd306cSNickeau    {
836*04fd306cSNickeau        return $this;
837*04fd306cSNickeau    }
838*04fd306cSNickeau
839*04fd306cSNickeau    public function toHtmlString(): string
840*04fd306cSNickeau    {
841*04fd306cSNickeau        return $this->toString(Url::AMPERSAND_URL_ENCODED_FOR_HTML);
842*04fd306cSNickeau    }
843*04fd306cSNickeau
844*04fd306cSNickeau    /**
845*04fd306cSNickeau     * @throws ExceptionNotFound
846*04fd306cSNickeau     */
847*04fd306cSNickeau    private function getPort(): int
848*04fd306cSNickeau    {
849*04fd306cSNickeau        if ($this->port === null) {
850*04fd306cSNickeau            throw new ExceptionNotFound("No port specified");
851*04fd306cSNickeau        }
852*04fd306cSNickeau        return $this->port;
853*04fd306cSNickeau    }
854*04fd306cSNickeau
855*04fd306cSNickeau    public function addQueryParameterIfNotPresent(string $key, string $value)
856*04fd306cSNickeau    {
857*04fd306cSNickeau        if (!$this->hasProperty($key)) {
858*04fd306cSNickeau            $this->addQueryParameterIfNotActualSameValue($key, $value);
859*04fd306cSNickeau        }
860*04fd306cSNickeau    }
861*04fd306cSNickeau
862*04fd306cSNickeau    /**
863*04fd306cSNickeau     * Set/replace a query parameter with the new value
864*04fd306cSNickeau     * @param string $key
865*04fd306cSNickeau     * @param string $value
866*04fd306cSNickeau     * @return Url
867*04fd306cSNickeau     */
868*04fd306cSNickeau    public function setQueryParameter(string $key, string $value): Url
869*04fd306cSNickeau    {
870*04fd306cSNickeau        $this->deleteQueryParameter($key);
871*04fd306cSNickeau        $this->addQueryParameter($key, $value);
872*04fd306cSNickeau        return $this;
873*04fd306cSNickeau    }
874*04fd306cSNickeau
875*04fd306cSNickeau    public function deleteQueryParameter(string $key)
876*04fd306cSNickeau    {
877*04fd306cSNickeau        unset($this->query[$key]);
878*04fd306cSNickeau    }
879*04fd306cSNickeau
880*04fd306cSNickeau    /**
881*04fd306cSNickeau     * @return string - An url in the DOM use the ampersand character
882*04fd306cSNickeau     * If you want to check the value of a DOM attribute, you need to check it with this value
883*04fd306cSNickeau     */
884*04fd306cSNickeau    public function toDomString(): string
885*04fd306cSNickeau    {
886*04fd306cSNickeau        // ampersand for dom string
887*04fd306cSNickeau        return $this->toString();
888*04fd306cSNickeau    }
889*04fd306cSNickeau
890*04fd306cSNickeau    public function toCssString(): string
891*04fd306cSNickeau    {
892*04fd306cSNickeau        // ampersand for css
893*04fd306cSNickeau        return $this->toString();
894*04fd306cSNickeau    }
895*04fd306cSNickeau
896*04fd306cSNickeau    /**
897*04fd306cSNickeau     * @return bool - if the url points to the same website than the host
898*04fd306cSNickeau     */
899*04fd306cSNickeau    public function isExternal(): bool
900*04fd306cSNickeau    {
901*04fd306cSNickeau        try {
902*04fd306cSNickeau            $localHost = Url::createEmpty()->toAbsoluteUrl()->getHost();
903*04fd306cSNickeau            return $localHost !== $this->getHost();
904*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
905*04fd306cSNickeau            // no host meaning that the url is relative and then local
906*04fd306cSNickeau            return false;
907*04fd306cSNickeau        }
908*04fd306cSNickeau    }
909*04fd306cSNickeau
910*04fd306cSNickeau    /**
911*04fd306cSNickeau     * In a url, in a case, the path should be absolute
912*04fd306cSNickeau     * This function makes it absolute if not.
913*04fd306cSNickeau     * In case of messaging scheme (mailto, whatsapp, ...), this is not the case
914*04fd306cSNickeau     * @throws ExceptionNotFound
915*04fd306cSNickeau     */
916*04fd306cSNickeau    private function getAbsolutePath(): string
917*04fd306cSNickeau    {
918*04fd306cSNickeau        $pathString = $this->getPath();
919*04fd306cSNickeau        if ($pathString[0] !== "/") {
920*04fd306cSNickeau            return "/{$pathString}";
921*04fd306cSNickeau        }
922*04fd306cSNickeau        return $pathString;
923*04fd306cSNickeau    }
924*04fd306cSNickeau
925*04fd306cSNickeau
926*04fd306cSNickeau    /**
927*04fd306cSNickeau     * @throws ExceptionBadSyntax
928*04fd306cSNickeau     * @throws ExceptionBadArgument
929*04fd306cSNickeau     */
930*04fd306cSNickeau    public static function createFromUri(string $uri): Path
931*04fd306cSNickeau    {
932*04fd306cSNickeau        return new Url($uri);
933*04fd306cSNickeau    }
934*04fd306cSNickeau
935*04fd306cSNickeau    public function deleteQueryProperties(): Url
936*04fd306cSNickeau    {
937*04fd306cSNickeau        $this->query = new ArrayCaseInsensitive();;
938*04fd306cSNickeau        return $this;
939*04fd306cSNickeau    }
940*04fd306cSNickeau
941*04fd306cSNickeau    public function withoutRewrite(): Url
942*04fd306cSNickeau    {
943*04fd306cSNickeau        $this->withRewrite = false;
944*04fd306cSNickeau        return $this;
945*04fd306cSNickeau    }
946*04fd306cSNickeau
947*04fd306cSNickeau}
948