xref: /template/strap/ComboStrap/Web/Url.php (revision f2b2e30e50d059a9e88af4c6f2a429af9b5d0f9a)
104fd306cSNickeau<?php
204fd306cSNickeau
304fd306cSNickeau
404fd306cSNickeaunamespace ComboStrap\Web;
504fd306cSNickeau
604fd306cSNickeauuse ComboStrap\ArrayCaseInsensitive;
704fd306cSNickeauuse ComboStrap\DataType;
8e346390aSgerardnicouse ComboStrap\DokuwikiId;
904fd306cSNickeauuse ComboStrap\ExceptionBadArgument;
1004fd306cSNickeauuse ComboStrap\ExceptionBadSyntax;
1104fd306cSNickeauuse ComboStrap\ExceptionCompile;
1204fd306cSNickeauuse ComboStrap\ExceptionNotEquals;
1304fd306cSNickeauuse ComboStrap\ExceptionNotFound;
1404fd306cSNickeauuse ComboStrap\ExceptionRuntimeInternal;
1504fd306cSNickeauuse ComboStrap\FetcherRawLocalPath;
1604fd306cSNickeauuse ComboStrap\FetcherSystem;
1704fd306cSNickeauuse ComboStrap\LocalFileSystem;
1804fd306cSNickeauuse ComboStrap\LogUtility;
1970bbd7f1Sgerardnicouse ComboStrap\MediaMarkup;
2004fd306cSNickeauuse ComboStrap\Path;
2104fd306cSNickeauuse ComboStrap\PathAbs;
22ea72c629Sgerardnicouse ComboStrap\PluginUtility;
2304fd306cSNickeauuse ComboStrap\Site;
2404fd306cSNickeauuse ComboStrap\WikiPath;
255b0932efSgerardnicouse dokuwiki\Input\Input;
2604fd306cSNickeau
2704fd306cSNickeau/**
2804fd306cSNickeau * Class Url
2904fd306cSNickeau * @package ComboStrap
3004fd306cSNickeau * There is no URL class in php
3104fd306cSNickeau * Only function
3204fd306cSNickeau * https://www.php.net/manual/en/ref.url.php
3304fd306cSNickeau */
3404fd306cSNickeauclass Url extends PathAbs
3504fd306cSNickeau{
3604fd306cSNickeau
3704fd306cSNickeau
3804fd306cSNickeau    public const PATH_SEP = "/";
3904fd306cSNickeau    /**
4004fd306cSNickeau     * In HTML (not in css)
4104fd306cSNickeau     *
4204fd306cSNickeau     * Because ampersands are used to denote HTML entities,
4304fd306cSNickeau     * if you want to use them as literal characters, you must escape them as entities,
4404fd306cSNickeau     * e.g.  &amp;.
4504fd306cSNickeau     *
4604fd306cSNickeau     * In HTML, Browser will do the translation for you if you give an URL
4704fd306cSNickeau     * not encoded but testing library may not and refuse them
4804fd306cSNickeau     *
4904fd306cSNickeau     * This URL encoding is mandatory for the {@link ml} function
5004fd306cSNickeau     * when there is a width and use them not otherwise
5104fd306cSNickeau     *
5204fd306cSNickeau     * Thus, if you want to link to:
5304fd306cSNickeau     * http://images.google.com/images?num=30&q=larry+bird
5404fd306cSNickeau     * you need to encode (ie pass this parameter to the {@link ml} function:
5504fd306cSNickeau     * http://images.google.com/images?num=30&amp;q=larry+bird
5604fd306cSNickeau     *
5704fd306cSNickeau     * https://daringfireball.net/projects/markdown/syntax#autoescape
5804fd306cSNickeau     *
5904fd306cSNickeau     */
6004fd306cSNickeau    public const AMPERSAND_URL_ENCODED_FOR_HTML = '&amp;';
6104fd306cSNickeau    /**
6204fd306cSNickeau     * Used in dokuwiki syntax & in CSS attribute
6304fd306cSNickeau     * (Css attribute value are then HTML encoded as value of the attribute)
6404fd306cSNickeau     */
6504fd306cSNickeau    public const AMPERSAND_CHARACTER = "&";
6604fd306cSNickeau
6704fd306cSNickeau    const CANONICAL = "url";
6804fd306cSNickeau    /**
6904fd306cSNickeau     * The schemes that are relative (normallu only URL ? ie http, https)
7004fd306cSNickeau     * This class is much more an URI
7104fd306cSNickeau     */
7204fd306cSNickeau    const RELATIVE_URL_SCHEMES = ["http", "https"];
7304fd306cSNickeau
7404fd306cSNickeau
7504fd306cSNickeau    private ArrayCaseInsensitive $query;
7604fd306cSNickeau    private ?string $path = null;
7704fd306cSNickeau    private ?string $scheme = null;
7804fd306cSNickeau    private ?string $host = null;
7904fd306cSNickeau    private ?string $fragment = null;
8004fd306cSNickeau    /**
8104fd306cSNickeau     * @var string - original url string
8204fd306cSNickeau     */
8304fd306cSNickeau    private $url;
8404fd306cSNickeau    private ?int $port = null;
8504fd306cSNickeau    /**
8604fd306cSNickeau     * @var bool - does the URL rewrite occurs
8704fd306cSNickeau     */
88e8a4711bSNico    private bool $withRewrite = true;
8904fd306cSNickeau
9004fd306cSNickeau
9104fd306cSNickeau    /**
9204fd306cSNickeau     * UrlUtility constructor.
9304fd306cSNickeau     * @throws ExceptionBadSyntax
9404fd306cSNickeau     * @throws ExceptionBadArgument
9504fd306cSNickeau     */
9604fd306cSNickeau    public function __construct(string $url = null)
9704fd306cSNickeau    {
9804fd306cSNickeau
9904fd306cSNickeau        $this->url = $url;
10004fd306cSNickeau        $this->query = new ArrayCaseInsensitive();
10104fd306cSNickeau        if ($this->url !== null) {
10204fd306cSNickeau            /**
10304fd306cSNickeau             *
10404fd306cSNickeau             * @var false
10504fd306cSNickeau             *
10604fd306cSNickeau             * Note: Url validation is hard with regexp
10704fd306cSNickeau             * for instance:
10804fd306cSNickeau             *  - http://example.lan/utility/a-combostrap-component-to-render-web-code-in-a-web-page-javascript-html-...-u8fe6ahw
10904fd306cSNickeau             *  - does not pass return preg_match('|^http(s)?://[a-z0-9-]+(.[a-z0-9-]+)*(:[0-9]+)?(/.*)?$|i', $url);
11004fd306cSNickeau             * of preg_match('/^https?:\/\//',$url) ? from redirect plugin
11104fd306cSNickeau             *
11204fd306cSNickeau             * We try to create the object, the object use the {@link parse_url()}
11304fd306cSNickeau             * method to validate or send an exception if it can be parsed
11404fd306cSNickeau             */
11504fd306cSNickeau            $urlComponents = parse_url($url);
11604fd306cSNickeau            if ($urlComponents === false) {
11704fd306cSNickeau                throw new ExceptionBadSyntax("The url ($url) is not valid");
11804fd306cSNickeau            }
11994bd6462Sgerardnico            $queryKeys = [];
12094bd6462Sgerardnico            $queryString = $urlComponents['query'] ?? null;
12194bd6462Sgerardnico            if ($queryString !== null) {
12294bd6462Sgerardnico                parse_str($queryString, $queryKeys);
12394bd6462Sgerardnico            }
12404fd306cSNickeau            $this->query = new ArrayCaseInsensitive($queryKeys);
125be61a7dfSgerardnico            $this->scheme = $urlComponents["scheme"] ?? null;
126be61a7dfSgerardnico            $this->host = $urlComponents["host"] ?? null;
127be61a7dfSgerardnico            $port = $urlComponents["port"] ?? null;
12804fd306cSNickeau            try {
12904fd306cSNickeau                if ($port !== null) {
13004fd306cSNickeau                    $this->port = DataType::toInteger($port);
13104fd306cSNickeau                }
13204fd306cSNickeau            } catch (ExceptionBadArgument $e) {
13304fd306cSNickeau                throw new ExceptionBadArgument("The port ($port) in ($url) is not an integer. Error: {$e->getMessage()}");
13404fd306cSNickeau            }
135be61a7dfSgerardnico            $pathUrlComponent = $urlComponents["path"] ?? null;
13604fd306cSNickeau            if ($pathUrlComponent !== null) {
13704fd306cSNickeau                $this->setPath($pathUrlComponent);
13804fd306cSNickeau            }
139be61a7dfSgerardnico            $this->fragment = $urlComponents["fragment"] ?? null;
1405ef50f6fSNico
1415ef50f6fSNico            /**
142e8a4711bSNico             * Rewrite occurs only on Dokuwiki Request
143e8a4711bSNico             * Not on CDN
144e8a4711bSNico             * We use a negation because otherwise the router redirect for now if the value is false by default
145e8a4711bSNico             *
146e8a4711bSNico             * Rewrite is only allowed on
147e8a4711bSNico             *   * relative url
148e8a4711bSNico             *   * first party url
1495ef50f6fSNico             */
150e8a4711bSNico            $requestHost = $_SERVER['HTTP_HOST'] ?? null;
151e8a4711bSNico            if(!(
152e8a4711bSNico                // relative url
153e8a4711bSNico                $this->host == null
154e8a4711bSNico                ||
155e8a4711bSNico                // first party url
156e8a4711bSNico                ($requestHost != null && $this->host == $requestHost))
157e8a4711bSNico            ){
158e8a4711bSNico                $this->withRewrite = false;
1595ef50f6fSNico            }
1605ef50f6fSNico
16104fd306cSNickeau        }
16204fd306cSNickeau    }
16304fd306cSNickeau
16404fd306cSNickeau
16504fd306cSNickeau    const RESERVED_WORDS = [':', '!', '#', '$', '&', '\'', '(', ')', '*', '+', ',', '/', ';', '=', '?', '@', '[', ']'];
16604fd306cSNickeau
16704fd306cSNickeau    /**
16804fd306cSNickeau     * A text to an encoded url
16904fd306cSNickeau     * @param $string -  a string
17004fd306cSNickeau     * @param string $separator - the path separator in the string
17104fd306cSNickeau     */
17204fd306cSNickeau    public static function encodeToUrlPath($string, string $separator = WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT): string
17304fd306cSNickeau    {
17404fd306cSNickeau        $parts = explode($separator, $string);
17504fd306cSNickeau        $encodedParts = array_map(function ($e) {
17604fd306cSNickeau            return urlencode($e);
17704fd306cSNickeau        }, $parts);
17804fd306cSNickeau        return implode("/", $encodedParts);
17904fd306cSNickeau    }
18004fd306cSNickeau
18104fd306cSNickeau    public static function createEmpty(): Url
18204fd306cSNickeau    {
18304fd306cSNickeau        return new Url();
18404fd306cSNickeau    }
18504fd306cSNickeau
18604fd306cSNickeau    /**
18704fd306cSNickeau     *
18804fd306cSNickeau     */
18904fd306cSNickeau    public static function createFromGetOrPostGlobalVariable(): Url
19004fd306cSNickeau    {
19104fd306cSNickeau        /**
19245a874f4SNico         * May be Just ???
19345a874f4SNico         * Url::createFromString($_SERVER['REQUEST_URI']);
19445a874f4SNico         */
19545a874f4SNico        /**
19645a874f4SNico         * $_REQUEST is a merge between:
19745a874f4SNico         *   * $_GET: the URL parameters (aka. query string)
19845a874f4SNico         *   * $_POST: the array of variables when using a POST application/x-www-form-urlencoded or multipart/form-data
19904fd306cSNickeau         * Shared check between post and get HTTP method
20045a874f4SNico         * managed and encapsulated by {@link Input}.
2015b0932efSgerardnico         * They add users and other
20204fd306cSNickeau         * {@link \TestRequest} is using it
20304fd306cSNickeau         */
20404fd306cSNickeau        $url = Url::createEmpty();
20504fd306cSNickeau        foreach ($_REQUEST as $key => $value) {
20604fd306cSNickeau            if (is_array($value)) {
20704fd306cSNickeau                foreach ($value as $subkey => $subval) {
20804fd306cSNickeau                    if (is_array($subval)) {
20904fd306cSNickeau                        if ($key !== "config") {
21004fd306cSNickeau                            // dokuwiki things
21104fd306cSNickeau                            LogUtility::warning("The key ($key) is an array of an array and was not taken into account in the request url.");
21204fd306cSNickeau                        }
21304fd306cSNickeau                        continue;
21404fd306cSNickeau                    }
2155b0932efSgerardnico
21604fd306cSNickeau                    if ($key == "do") {
21704fd306cSNickeau                        // for whatever reason, dokuwiki puts the value in the key
21804fd306cSNickeau                        $url->addQueryParameter($key, $subkey);
21904fd306cSNickeau                        continue;
22004fd306cSNickeau                    }
22104fd306cSNickeau                    $url->addQueryParameter($key, $subval);
22204fd306cSNickeau
22304fd306cSNickeau                }
22404fd306cSNickeau            } else {
22504fd306cSNickeau                /**
22604fd306cSNickeau                 * Bad URL format test
22704fd306cSNickeau                 * In the `src` attribute of `script`, the url should not be encoded
22804fd306cSNickeau                 * with {@link Url::AMPERSAND_URL_ENCODED_FOR_HTML}
22904fd306cSNickeau                 * otherwise we get `amp;` as prefix
23004fd306cSNickeau                 * in Chrome
23104fd306cSNickeau                 */
23204fd306cSNickeau                if (strpos($key, "amp;") === 0) {
23304fd306cSNickeau                    /**
23404fd306cSNickeau                     * We don't advertise this error, it should not happen
23504fd306cSNickeau                     * and there is nothing to do to get back on its feet
23604fd306cSNickeau                     */
237ea72c629Sgerardnico                    $message = "The url in src has a bad encoding (the attribute ($key) has a amp; prefix. Infinite cache will not work. Request: " . DataType::toString($_REQUEST);
238ea72c629Sgerardnico                    if (PluginUtility::isDevOrTest()) {
23904fd306cSNickeau                        throw new ExceptionRuntimeInternal($message);
240ea72c629Sgerardnico                    } else {
241ea72c629Sgerardnico                        LogUtility::warning($message, "url");
242ea72c629Sgerardnico                    }
24304fd306cSNickeau                }
2445b0932efSgerardnico                /**
2455b0932efSgerardnico                 * Added in {@link auth_setup}
2465b0932efSgerardnico                 * Used by dokuwiki
2475b0932efSgerardnico                 */
2485b0932efSgerardnico                if (in_array($key, ['u', 'p', 'http_credentials', 'r'])) {
2495b0932efSgerardnico                    continue;
2505b0932efSgerardnico                }
25104fd306cSNickeau                $url->addQueryParameter($key, $value);
25204fd306cSNickeau            }
25304fd306cSNickeau        }
25404fd306cSNickeau        return $url;
25504fd306cSNickeau    }
25604fd306cSNickeau
25704fd306cSNickeau    /**
25804fd306cSNickeau     * Utility class to transform windows separator to url path separator
25904fd306cSNickeau     * @param string $pathString
26004fd306cSNickeau     * @return array|string|string[]
26104fd306cSNickeau     */
26204fd306cSNickeau    public static function toUrlSeparator(string $pathString)
26304fd306cSNickeau    {
26404fd306cSNickeau        return str_replace('\\', '/', $pathString);
26504fd306cSNickeau    }
26604fd306cSNickeau
26704fd306cSNickeau
26804fd306cSNickeau    function getQueryProperties(): array
26904fd306cSNickeau    {
27004fd306cSNickeau        return $this->query->getOriginalArray();
27104fd306cSNickeau    }
27204fd306cSNickeau
27304fd306cSNickeau    /**
27404fd306cSNickeau     * @throws ExceptionNotFound
27504fd306cSNickeau     */
27604fd306cSNickeau    function getQueryPropertyValue($key)
27704fd306cSNickeau    {
27804fd306cSNickeau        $value = $this->query[$key];
27904fd306cSNickeau        if ($value === null) {
28004fd306cSNickeau            throw new ExceptionNotFound("The key ($key) was not found");
28104fd306cSNickeau        }
28204fd306cSNickeau        return $value;
28304fd306cSNickeau    }
28404fd306cSNickeau
28504fd306cSNickeau    /**
28604fd306cSNickeau     * Extract the value of a property
28704fd306cSNickeau     * @param $propertyName
28804fd306cSNickeau     * @return string - the value of the property
28904fd306cSNickeau     * @throws ExceptionNotFound
29004fd306cSNickeau     */
29104fd306cSNickeau    public function getPropertyValue($propertyName): string
29204fd306cSNickeau    {
29304fd306cSNickeau        if (!isset($this->query[$propertyName])) {
29404fd306cSNickeau            throw new ExceptionNotFound("The property ($propertyName) was not found", self::CANONICAL);
29504fd306cSNickeau        }
29604fd306cSNickeau        return $this->query[$propertyName];
29704fd306cSNickeau    }
29804fd306cSNickeau
29904fd306cSNickeau
30004fd306cSNickeau    /**
30104fd306cSNickeau     * @throws ExceptionBadSyntax|ExceptionBadArgument
30204fd306cSNickeau     */
30304fd306cSNickeau    public static function createFromString(string $url): Url
30404fd306cSNickeau    {
30504fd306cSNickeau        return new Url($url);
30604fd306cSNickeau    }
30704fd306cSNickeau
308*f2b2e30eSNicolas GERARD    /**
309*f2b2e30eSNicolas GERARD     * @throws ExceptionNotFound
310*f2b2e30eSNicolas GERARD     */
31104fd306cSNickeau    public function getScheme(): string
31204fd306cSNickeau    {
31304fd306cSNickeau        if ($this->scheme === null) {
314*f2b2e30eSNicolas GERARD            throw new ExceptionNotFound("The scheme was not found");
31504fd306cSNickeau        }
31604fd306cSNickeau        return $this->scheme;
31704fd306cSNickeau    }
31804fd306cSNickeau
31904fd306cSNickeau    /**
32004fd306cSNickeau     * @param string $path
32104fd306cSNickeau     * @return $this
32204fd306cSNickeau     * in a https scheme: Not the path has a leading `/` that makes the path absolute
32304fd306cSNickeau     * in a email scheme: the path is the email (without /) then
32404fd306cSNickeau     */
32504fd306cSNickeau    public function setPath(string $path): Url
32604fd306cSNickeau    {
32704fd306cSNickeau
32804fd306cSNickeau        /**
32904fd306cSNickeau         * Normalization hack
33004fd306cSNickeau         */
33104fd306cSNickeau        if (strpos($path, "/./") === 0) {
33204fd306cSNickeau            $path = substr($path, 2);
33304fd306cSNickeau        }
33404fd306cSNickeau        $this->path = $path;
33504fd306cSNickeau        return $this;
33604fd306cSNickeau    }
33704fd306cSNickeau
33804fd306cSNickeau    /**
33904fd306cSNickeau     * @return bool - true if http, https scheme
34004fd306cSNickeau     */
34104fd306cSNickeau    public function isHttpUrl(): bool
34204fd306cSNickeau    {
34304fd306cSNickeau        try {
34404fd306cSNickeau            return in_array($this->getScheme(), ["http", "https"]);
34504fd306cSNickeau        } catch (ExceptionNotFound $e) {
34604fd306cSNickeau            return false;
34704fd306cSNickeau        }
34804fd306cSNickeau    }
34904fd306cSNickeau
35004fd306cSNickeau    /**
35104fd306cSNickeau     * Multiple parameter can be set to form an array
35204fd306cSNickeau     *
35304fd306cSNickeau     * Example: s=word1&s=word2
35404fd306cSNickeau     *
35504fd306cSNickeau     * https://stackoverflow.com/questions/24059773/correct-way-to-pass-multiple-values-for-same-parameter-name-in-get-request
35604fd306cSNickeau     */
35704fd306cSNickeau    public function addQueryParameter(string $key, ?string $value = null): Url
35804fd306cSNickeau    {
35904fd306cSNickeau        /**
36004fd306cSNickeau         * Php Array syntax
36104fd306cSNickeau         */
36204fd306cSNickeau        if (substr($key, -2) === "[]") {
36304fd306cSNickeau            $key = substr($key, 0, -2);
36404fd306cSNickeau            $actualValue = $this->query[$key];
36504fd306cSNickeau            if ($actualValue === null || is_array($actualValue)) {
36604fd306cSNickeau                $this->query[$key] = [$value];
36704fd306cSNickeau            } else {
36804fd306cSNickeau                $actualValue[] = $value;
36904fd306cSNickeau                $this->query[$key] = $actualValue;
37004fd306cSNickeau            }
37104fd306cSNickeau            return $this;
37204fd306cSNickeau        }
37304fd306cSNickeau        if (isset($this->query[$key])) {
37404fd306cSNickeau            $actualValue = $this->query[$key];
37504fd306cSNickeau            if (is_array($actualValue)) {
37604fd306cSNickeau                $this->query[$key][] = $value;
37704fd306cSNickeau            } else {
37804fd306cSNickeau                $this->query[$key] = [$actualValue, $value];
37904fd306cSNickeau            }
38004fd306cSNickeau        } else {
38104fd306cSNickeau            $this->query[$key] = $value;
38204fd306cSNickeau        }
38304fd306cSNickeau        return $this;
38404fd306cSNickeau    }
38504fd306cSNickeau
38604fd306cSNickeau
38704fd306cSNickeau    public function hasProperty(string $key): bool
38804fd306cSNickeau    {
38904fd306cSNickeau        if (isset($this->query[$key])) {
39004fd306cSNickeau            return true;
39104fd306cSNickeau        }
39204fd306cSNickeau        return false;
39304fd306cSNickeau    }
39404fd306cSNickeau
39504fd306cSNickeau    /**
39604fd306cSNickeau     * @return Url - add the scheme and the host based on the request if not present
39704fd306cSNickeau     */
39804fd306cSNickeau    public function toAbsoluteUrl(): Url
39904fd306cSNickeau    {
400e346390aSgerardnico        /**
401e346390aSgerardnico         * Do we have a path information
40270bbd7f1Sgerardnico         * If not, this is a local url (ie #id)
40370bbd7f1Sgerardnico         * We don't make it absolute
404e346390aSgerardnico         */
405e346390aSgerardnico        if ($this->isLocal()) {
406e346390aSgerardnico            return $this;
407e346390aSgerardnico        }
408*f2b2e30eSNicolas GERARD        try {
409*f2b2e30eSNicolas GERARD            $this->getScheme();
410*f2b2e30eSNicolas GERARD        } catch (ExceptionNotFound $e) {
41104fd306cSNickeau            /**
41204fd306cSNickeau             * See {@link getBaseURL()}
41304fd306cSNickeau             */
414f9161b9dSNico            if (!is_ssl()) {
41504fd306cSNickeau                $this->setScheme("http");
41604fd306cSNickeau            } else {
41704fd306cSNickeau                $this->setScheme("https");
41804fd306cSNickeau            }
41904fd306cSNickeau        }
42004fd306cSNickeau        try {
42104fd306cSNickeau            $this->getHost();
42204fd306cSNickeau        } catch (ExceptionNotFound $e) {
42304fd306cSNickeau            $remoteHost = Site::getServerHost();
42404fd306cSNickeau            $this->setHost($remoteHost);
42504fd306cSNickeau
42604fd306cSNickeau        }
42704fd306cSNickeau        return $this;
42804fd306cSNickeau    }
42904fd306cSNickeau
43004fd306cSNickeau    /**
43104fd306cSNickeau     * @return string - utility function that call {@link Url::toAbsoluteUrl()} absolute and {@link Url::toString()}
43204fd306cSNickeau     */
43304fd306cSNickeau    public function toAbsoluteUrlString(): string
43404fd306cSNickeau    {
43504fd306cSNickeau        $this->toAbsoluteUrl();
43604fd306cSNickeau        return $this->toString();
43704fd306cSNickeau    }
43804fd306cSNickeau
43904fd306cSNickeau    /**
44004fd306cSNickeau     * @throws ExceptionNotFound
44104fd306cSNickeau     */
44204fd306cSNickeau    public function getHost(): string
44304fd306cSNickeau    {
44404fd306cSNickeau        if ($this->host === null) {
44504fd306cSNickeau            throw new ExceptionNotFound("No host");
44604fd306cSNickeau        }
44704fd306cSNickeau        return $this->host;
44804fd306cSNickeau    }
44904fd306cSNickeau
45004fd306cSNickeau    /**
45104fd306cSNickeau     * @throws ExceptionNotFound
45204fd306cSNickeau     */
45304fd306cSNickeau    public function getPath(): string
45404fd306cSNickeau    {
45565e00826Sgerardnico        if ($this->path === null || $this->path === '/') {
45604fd306cSNickeau            throw new ExceptionNotFound("The path was not found");
45704fd306cSNickeau        }
45804fd306cSNickeau        return $this->path;
45904fd306cSNickeau    }
46004fd306cSNickeau
46104fd306cSNickeau    /**
46204fd306cSNickeau     * @throws ExceptionNotFound
46304fd306cSNickeau     */
46404fd306cSNickeau    public function getFragment(): string
46504fd306cSNickeau    {
46604fd306cSNickeau        if ($this->fragment === null) {
46704fd306cSNickeau            throw new ExceptionNotFound("The fragment was not set");
46804fd306cSNickeau        }
46904fd306cSNickeau        return $this->fragment;
47004fd306cSNickeau    }
47104fd306cSNickeau
47204fd306cSNickeau
47304fd306cSNickeau    public function __toString()
47404fd306cSNickeau    {
47504fd306cSNickeau        return $this->toString();
47604fd306cSNickeau    }
47704fd306cSNickeau
47804fd306cSNickeau    public function getQueryPropertyValueOrDefault(string $key, string $defaultIfNull)
47904fd306cSNickeau    {
48004fd306cSNickeau        try {
48104fd306cSNickeau            return $this->getQueryPropertyValue($key);
48204fd306cSNickeau        } catch (ExceptionNotFound $e) {
48304fd306cSNickeau            return $defaultIfNull;
48404fd306cSNickeau        }
48504fd306cSNickeau    }
48604fd306cSNickeau
48704fd306cSNickeau    /**
48804fd306cSNickeau     * Actual vs expected
48904fd306cSNickeau     *
49004fd306cSNickeau     * We use this vocabulary (actual/expected) and not (internal/external or left/right) because this function
49104fd306cSNickeau     * is mostly used in a test framework.
49204fd306cSNickeau     *
49304fd306cSNickeau     * @throws ExceptionNotEquals
49404fd306cSNickeau     */
49504fd306cSNickeau    public function equals(Url $expectedUrl)
49604fd306cSNickeau    {
49704fd306cSNickeau        /**
49804fd306cSNickeau         * Scheme
49904fd306cSNickeau         */
50004fd306cSNickeau        try {
50104fd306cSNickeau            $actualScheme = $this->getScheme();
50204fd306cSNickeau        } catch (ExceptionNotFound $e) {
50304fd306cSNickeau            $actualScheme = "";
50404fd306cSNickeau        }
50504fd306cSNickeau        try {
50604fd306cSNickeau            $expectedScheme = $expectedUrl->getScheme();
50704fd306cSNickeau        } catch (ExceptionNotFound $e) {
50804fd306cSNickeau            $expectedScheme = "";
50904fd306cSNickeau        }
51004fd306cSNickeau        if ($actualScheme !== $expectedScheme) {
51104fd306cSNickeau            throw new ExceptionNotEquals("The scheme are not equals ($actualScheme vs $expectedScheme)");
51204fd306cSNickeau        }
51304fd306cSNickeau        /**
51404fd306cSNickeau         * Host
51504fd306cSNickeau         */
51604fd306cSNickeau        try {
51704fd306cSNickeau            $actualHost = $this->getHost();
51804fd306cSNickeau        } catch (ExceptionNotFound $e) {
51904fd306cSNickeau            $actualHost = "";
52004fd306cSNickeau        }
52104fd306cSNickeau        try {
52204fd306cSNickeau            $expectedHost = $expectedUrl->getHost();
52304fd306cSNickeau        } catch (ExceptionNotFound $e) {
52404fd306cSNickeau            $expectedHost = "";
52504fd306cSNickeau        }
52604fd306cSNickeau        if ($actualHost !== $expectedHost) {
52704fd306cSNickeau            throw new ExceptionNotEquals("The host are not equals ($actualHost vs $expectedHost)");
52804fd306cSNickeau        }
52904fd306cSNickeau        /**
53004fd306cSNickeau         * Query
53104fd306cSNickeau         */
53204fd306cSNickeau        $actualQuery = $this->getQueryProperties();
53304fd306cSNickeau        $expectedQuery = $expectedUrl->getQueryProperties();
53404fd306cSNickeau        foreach ($actualQuery as $key => $value) {
53504fd306cSNickeau            $expectedValue = $expectedQuery[$key];
53604fd306cSNickeau            if ($expectedValue === null) {
53704fd306cSNickeau                throw new ExceptionNotEquals("The expected url does not have the $key property");
53804fd306cSNickeau            }
53904fd306cSNickeau            if ($expectedValue !== $value) {
54004fd306cSNickeau                throw new ExceptionNotEquals("The $key property does not have the same value ($value vs $expectedValue)");
54104fd306cSNickeau            }
54204fd306cSNickeau            unset($expectedQuery[$key]);
54304fd306cSNickeau        }
54404fd306cSNickeau        foreach ($expectedQuery as $key => $value) {
54504fd306cSNickeau            throw new ExceptionNotEquals("The expected URL has an extra property ($key=$value)");
54604fd306cSNickeau        }
54704fd306cSNickeau
54804fd306cSNickeau        /**
54904fd306cSNickeau         * Fragment
55004fd306cSNickeau         */
55104fd306cSNickeau        try {
55204fd306cSNickeau            $actualFragment = $this->getFragment();
55304fd306cSNickeau        } catch (ExceptionNotFound $e) {
55404fd306cSNickeau            $actualFragment = "";
55504fd306cSNickeau        }
55604fd306cSNickeau        try {
55704fd306cSNickeau            $expectedFragment = $expectedUrl->getFragment();
55804fd306cSNickeau        } catch (ExceptionNotFound $e) {
55904fd306cSNickeau            $expectedFragment = "";
56004fd306cSNickeau        }
56104fd306cSNickeau        if ($actualFragment !== $expectedFragment) {
56204fd306cSNickeau            throw new ExceptionNotEquals("The fragment are not equals ($actualFragment vs $expectedFragment)");
56304fd306cSNickeau        }
56404fd306cSNickeau
56504fd306cSNickeau    }
56604fd306cSNickeau
56704fd306cSNickeau    public function setScheme(string $scheme): Url
56804fd306cSNickeau    {
56904fd306cSNickeau        $this->scheme = $scheme;
57004fd306cSNickeau        return $this;
57104fd306cSNickeau    }
57204fd306cSNickeau
57304fd306cSNickeau    public function setHost($host): Url
57404fd306cSNickeau    {
57504fd306cSNickeau        $this->host = $host;
57604fd306cSNickeau        return $this;
57704fd306cSNickeau    }
57804fd306cSNickeau
57962e890e0Sgerardnico    /**
58062e890e0Sgerardnico     * @param string $fragment
58162e890e0Sgerardnico     * @return $this
58262e890e0Sgerardnico     * Example `#step:11:24728`, this fragment is valid!
58362e890e0Sgerardnico     */
58404fd306cSNickeau    public function setFragment(string $fragment): Url
58504fd306cSNickeau    {
58604fd306cSNickeau        $this->fragment = $fragment;
58704fd306cSNickeau        return $this;
58804fd306cSNickeau    }
58904fd306cSNickeau
59004fd306cSNickeau    /**
59104fd306cSNickeau     * @throws ExceptionNotFound
59204fd306cSNickeau     */
59304fd306cSNickeau    public function getQueryString($ampersand = Url::AMPERSAND_CHARACTER): string
59404fd306cSNickeau    {
59504fd306cSNickeau        if (sizeof($this->query) === 0) {
59604fd306cSNickeau            throw new ExceptionNotFound("No Query string");
59704fd306cSNickeau        }
59804fd306cSNickeau        /**
59904fd306cSNickeau         * To be able to diff them
60004fd306cSNickeau         */
60104fd306cSNickeau        $originalArray = $this->query->getOriginalArray();
60204fd306cSNickeau        ksort($originalArray);
60304fd306cSNickeau
60404fd306cSNickeau        /**
60504fd306cSNickeau         * We don't use {@link http_build_query} because:
60604fd306cSNickeau         *   * it does not the follow the array format (ie s[]=searchword1+seachword2)
60704fd306cSNickeau         *   * it output 'key=' instead of `key` when the value is null
60804fd306cSNickeau         */
60904fd306cSNickeau        $queryString = null;
61004fd306cSNickeau        foreach ($originalArray as $key => $value) {
61104fd306cSNickeau            if ($queryString !== null) {
61204fd306cSNickeau                /**
61304fd306cSNickeau                 * HTML encoding (ie {@link self::AMPERSAND_URL_ENCODED_FOR_HTML}
61404fd306cSNickeau                 * happens only when outputing to HTML
61504fd306cSNickeau                 * The url may also be used elsewhere where &amp; is unknown or not wanted such as css ...
61604fd306cSNickeau                 *
61704fd306cSNickeau                 * In test, we may ask the url HTML encoded
61804fd306cSNickeau                 */
61904fd306cSNickeau                $queryString .= $ampersand;
62004fd306cSNickeau            }
62104fd306cSNickeau            if ($value === null) {
62204fd306cSNickeau                $queryString .= urlencode($key);
62304fd306cSNickeau            } else {
62404fd306cSNickeau                if (is_array($value)) {
62504fd306cSNickeau                    for ($i = 0; $i < sizeof($value); $i++) {
62604fd306cSNickeau                        $val = $value[$i];
62704fd306cSNickeau                        if ($i > 0) {
62804fd306cSNickeau                            $queryString .= self::AMPERSAND_CHARACTER;
62904fd306cSNickeau                        }
63004fd306cSNickeau                        $queryString .= urlencode($key) . "[]=" . urlencode($val);
63104fd306cSNickeau                    }
63204fd306cSNickeau                } else {
63304fd306cSNickeau                    $queryString .= urlencode($key) . "=" . urlencode($value);
63404fd306cSNickeau                }
63504fd306cSNickeau            }
63604fd306cSNickeau        }
63704fd306cSNickeau        return $queryString;
63804fd306cSNickeau
63904fd306cSNickeau
64004fd306cSNickeau    }
64104fd306cSNickeau
64204fd306cSNickeau    /**
64304fd306cSNickeau     * @throws ExceptionNotFound
64404fd306cSNickeau     */
64504fd306cSNickeau    public function getQueryPropertyValueAndRemoveIfPresent(string $key)
64604fd306cSNickeau    {
64704fd306cSNickeau        $value = $this->getQueryPropertyValue($key);
64804fd306cSNickeau        unset($this->query[$key]);
64904fd306cSNickeau        return $value;
65004fd306cSNickeau    }
65104fd306cSNickeau
65204fd306cSNickeau
65304fd306cSNickeau    /**
65404fd306cSNickeau     * @throws ExceptionNotFound
65504fd306cSNickeau     */
65604fd306cSNickeau    function getLastName(): string
65704fd306cSNickeau    {
65804fd306cSNickeau        $names = $this->getNames();
65904fd306cSNickeau        $namesCount = count($names);
66004fd306cSNickeau        if ($namesCount === 0) {
66104fd306cSNickeau            throw new ExceptionNotFound("No last name");
66204fd306cSNickeau        }
66304fd306cSNickeau        return $names[$namesCount - 1];
66404fd306cSNickeau
66504fd306cSNickeau    }
66604fd306cSNickeau
66704fd306cSNickeau    /**
66804fd306cSNickeau     * @return string
66904fd306cSNickeau     * @throws ExceptionNotFound
67004fd306cSNickeau     */
67104fd306cSNickeau    public function getExtension(): string
67204fd306cSNickeau    {
67370bbd7f1Sgerardnico        if ($this->hasProperty(MediaMarkup::$MEDIA_QUERY_PARAMETER)) {
67404fd306cSNickeau
67504fd306cSNickeau            try {
67604fd306cSNickeau                return FetcherSystem::createPathFetcherFromUrl($this)->getMime()->getExtension();
67704fd306cSNickeau            } catch (ExceptionCompile $e) {
67804fd306cSNickeau                LogUtility::internalError("Build error from a Media Fetch URL. We were unable to get the mime. Error: {$e->getMessage()}");
67904fd306cSNickeau            }
68004fd306cSNickeau
68104fd306cSNickeau        }
68204fd306cSNickeau        return parent::getExtension();
68304fd306cSNickeau    }
68404fd306cSNickeau
68504fd306cSNickeau
68604fd306cSNickeau    function getNames()
68704fd306cSNickeau    {
68804fd306cSNickeau
68904fd306cSNickeau        try {
69004fd306cSNickeau            $names = explode(self::PATH_SEP, $this->getPath());
69104fd306cSNickeau            return array_slice($names, 1);
69204fd306cSNickeau        } catch (ExceptionNotFound $e) {
69304fd306cSNickeau            return [];
69404fd306cSNickeau        }
69504fd306cSNickeau
69604fd306cSNickeau    }
69704fd306cSNickeau
69804fd306cSNickeau    /**
69904fd306cSNickeau     * @throws ExceptionNotFound
70004fd306cSNickeau     */
70104fd306cSNickeau    function getParent(): Url
70204fd306cSNickeau    {
70304fd306cSNickeau        $names = $this->getNames();
70404fd306cSNickeau        $count = count($names);
70504fd306cSNickeau        if ($count === 0) {
70604fd306cSNickeau            throw new ExceptionNotFound("No Parent");
70704fd306cSNickeau        }
70804fd306cSNickeau        $parentPath = implode(self::PATH_SEP, array_splice($names, 0, $count - 1));
70904fd306cSNickeau        return $this->setPath($parentPath);
71004fd306cSNickeau    }
71104fd306cSNickeau
71204fd306cSNickeau    function toAbsoluteId(): string
71304fd306cSNickeau    {
71404fd306cSNickeau        try {
71504fd306cSNickeau            return $this->getPath();
71604fd306cSNickeau        } catch (ExceptionNotFound $e) {
71704fd306cSNickeau            return "";
71804fd306cSNickeau        }
71904fd306cSNickeau    }
72004fd306cSNickeau
72104fd306cSNickeau    function toAbsolutePath(): Url
72204fd306cSNickeau    {
72304fd306cSNickeau        return $this->toAbsoluteUrl();
72404fd306cSNickeau    }
72504fd306cSNickeau
72604fd306cSNickeau    function resolve(string $name): Url
72704fd306cSNickeau    {
72804fd306cSNickeau        try {
72904fd306cSNickeau            $path = $this->getPath();
73004fd306cSNickeau            if ($this->path[strlen($path) - 1] === URL::PATH_SEP) {
73104fd306cSNickeau                $this->path .= $name;
73204fd306cSNickeau            } else {
73304fd306cSNickeau                $this->path .= URL::PATH_SEP . $name;
73404fd306cSNickeau            }
73504fd306cSNickeau            return $this;
73604fd306cSNickeau        } catch (ExceptionNotFound $e) {
73704fd306cSNickeau            $this->setPath($name);
73804fd306cSNickeau            return $this;
73904fd306cSNickeau        }
74004fd306cSNickeau
74104fd306cSNickeau    }
74204fd306cSNickeau
74304fd306cSNickeau    /**
74404fd306cSNickeau     * @param string $ampersand
74504fd306cSNickeau     * @return string
74604fd306cSNickeau     */
74704fd306cSNickeau    public function toString(string $ampersand = Url::AMPERSAND_CHARACTER): string
74804fd306cSNickeau    {
74904fd306cSNickeau
75004fd306cSNickeau        try {
75104fd306cSNickeau            $scheme = $this->getScheme();
75204fd306cSNickeau        } catch (ExceptionNotFound $e) {
75304fd306cSNickeau            $scheme = null;
75404fd306cSNickeau        }
75504fd306cSNickeau
75604fd306cSNickeau
75704fd306cSNickeau        switch ($scheme) {
75804fd306cSNickeau            case LocalFileSystem::SCHEME:
75904fd306cSNickeau                /**
76004fd306cSNickeau                 * file://host/path
76104fd306cSNickeau                 */
76204fd306cSNickeau                $base = "$scheme://";
76304fd306cSNickeau                try {
76404fd306cSNickeau                    $base = "$base{$this->getHost()}";
76504fd306cSNickeau                } catch (ExceptionNotFound $e) {
76604fd306cSNickeau                    // no host
76704fd306cSNickeau                }
76804fd306cSNickeau                try {
76904fd306cSNickeau                    $path = $this->getAbsolutePath();
77004fd306cSNickeau                    // linux, network share (file://host/path)
77104fd306cSNickeau                    $base = "$base{$path}";
77204fd306cSNickeau                } catch (ExceptionNotFound $e) {
77304fd306cSNickeau                    // no path
77404fd306cSNickeau                }
77504fd306cSNickeau                return $base;
77604fd306cSNickeau            case "mailto":
77704fd306cSNickeau            case "whatsapp":
77804fd306cSNickeau            case "skype":
77904fd306cSNickeau                /**
78004fd306cSNickeau                 * Skype. Example: skype:echo123?call
78104fd306cSNickeau                 * https://docs.microsoft.com/en-us/skype-sdk/skypeuris/skypeuris
78204fd306cSNickeau                 * Mailto: Example: mailto:java-net@java.sun.com?subject=yolo
78304fd306cSNickeau                 * https://datacadamia.com/marketing/email/mailto
78404fd306cSNickeau                 */
78504fd306cSNickeau                $base = "$scheme:";
78604fd306cSNickeau                try {
78704fd306cSNickeau                    $base = "$base{$this->getPath()}";
78804fd306cSNickeau                } catch (ExceptionNotFound $e) {
78904fd306cSNickeau                    // no path
79004fd306cSNickeau                }
79104fd306cSNickeau                try {
79204fd306cSNickeau                    $base = "$base?{$this->getQueryString()}";
79304fd306cSNickeau                } catch (ExceptionNotFound $e) {
79404fd306cSNickeau                    // no query string
79504fd306cSNickeau                }
79604fd306cSNickeau                try {
79704fd306cSNickeau                    $base = "$base#{$this->getFragment()}";
79804fd306cSNickeau                } catch (ExceptionNotFound $e) {
79904fd306cSNickeau                    // no fragment
80004fd306cSNickeau                }
80104fd306cSNickeau                return $base;
80204fd306cSNickeau            case "http":
80304fd306cSNickeau            case "https":
80404fd306cSNickeau            case "ftp":
80504fd306cSNickeau            default:
80604fd306cSNickeau                /**
80704fd306cSNickeau                 * Url Rewrite
80804fd306cSNickeau                 * Absolute vs Relative, __media, ...
80904fd306cSNickeau                 */
81004fd306cSNickeau                if ($this->withRewrite) {
81104fd306cSNickeau                    UrlRewrite::rewrite($this);
81204fd306cSNickeau                }
81304fd306cSNickeau                /**
81404fd306cSNickeau                 * Rewrite may have set a default scheme
81504fd306cSNickeau                 * We read it again
81604fd306cSNickeau                 */
81704fd306cSNickeau                try {
81804fd306cSNickeau                    $scheme = $this->getScheme();
81904fd306cSNickeau                } catch (ExceptionNotFound $e) {
82004fd306cSNickeau                    $scheme = null;
82104fd306cSNickeau                }
82204fd306cSNickeau                try {
82304fd306cSNickeau                    $host = $this->getHost();
82404fd306cSNickeau                } catch (ExceptionNotFound $e) {
82504fd306cSNickeau                    $host = null;
82604fd306cSNickeau                }
82704fd306cSNickeau                /**
82804fd306cSNickeau                 * Absolute/Relative Uri
82904fd306cSNickeau                 */
83004fd306cSNickeau                $base = "";
83104fd306cSNickeau                if ($host !== null) {
83204fd306cSNickeau                    if ($scheme !== null) {
83304fd306cSNickeau                        $base = "{$scheme}://";
83404fd306cSNickeau                    }
83504fd306cSNickeau                    $base = "$base{$host}";
83604fd306cSNickeau                    try {
83704fd306cSNickeau                        $base = "$base:{$this->getPort()}";
83804fd306cSNickeau                    } catch (ExceptionNotFound $e) {
83904fd306cSNickeau                        // no port
84004fd306cSNickeau                    }
84104fd306cSNickeau                } else {
84204fd306cSNickeau                    if (!in_array($scheme, self::RELATIVE_URL_SCHEMES) && $scheme !== null) {
84304fd306cSNickeau                        $base = "{$scheme}:";
84404fd306cSNickeau                    }
84504fd306cSNickeau                }
84604fd306cSNickeau
84704fd306cSNickeau                try {
84804fd306cSNickeau                    $base = "$base{$this->getAbsolutePath()}";
84904fd306cSNickeau                } catch (ExceptionNotFound $e) {
85004fd306cSNickeau                    // ok
85104fd306cSNickeau                }
85204fd306cSNickeau
85304fd306cSNickeau                try {
85404fd306cSNickeau                    $base = "$base?{$this->getQueryString($ampersand)}";
85504fd306cSNickeau                } catch (ExceptionNotFound $e) {
85604fd306cSNickeau                    // ok
85704fd306cSNickeau                }
85804fd306cSNickeau
85904fd306cSNickeau                try {
86004fd306cSNickeau                    $base = "$base#{$this->getFragment()}";
86104fd306cSNickeau                } catch (ExceptionNotFound $e) {
86204fd306cSNickeau                    // ok
86304fd306cSNickeau                }
86404fd306cSNickeau                return $base;
86504fd306cSNickeau        }
86604fd306cSNickeau
86704fd306cSNickeau
86804fd306cSNickeau    }
86904fd306cSNickeau
87004fd306cSNickeau    /**
87104fd306cSNickeau     * Query parameter can have several values
87204fd306cSNickeau     * This function makes sure that there is only one value for one key
87304fd306cSNickeau     * if the value are different, the value will be added
87404fd306cSNickeau     * @param string $key
87504fd306cSNickeau     * @param string $value
87604fd306cSNickeau     * @return Url
87704fd306cSNickeau     */
87804fd306cSNickeau    public function addQueryParameterIfNotActualSameValue(string $key, string $value): Url
87904fd306cSNickeau    {
88004fd306cSNickeau        try {
88104fd306cSNickeau            $actualValue = $this->getQueryPropertyValue($key);
88204fd306cSNickeau            if ($actualValue !== $value) {
88304fd306cSNickeau                $this->addQueryParameter($key, $value);
88404fd306cSNickeau            }
88504fd306cSNickeau        } catch (ExceptionNotFound $e) {
88604fd306cSNickeau            $this->addQueryParameter($key, $value);
88704fd306cSNickeau        }
88804fd306cSNickeau
88904fd306cSNickeau        return $this;
89004fd306cSNickeau
89104fd306cSNickeau    }
89204fd306cSNickeau
89304fd306cSNickeau    function getUrl(): Url
89404fd306cSNickeau    {
89504fd306cSNickeau        return $this;
89604fd306cSNickeau    }
89704fd306cSNickeau
89804fd306cSNickeau    public function toHtmlString(): string
89904fd306cSNickeau    {
90004fd306cSNickeau        return $this->toString(Url::AMPERSAND_URL_ENCODED_FOR_HTML);
90104fd306cSNickeau    }
90204fd306cSNickeau
90304fd306cSNickeau    /**
90404fd306cSNickeau     * @throws ExceptionNotFound
90504fd306cSNickeau     */
90604fd306cSNickeau    private function getPort(): int
90704fd306cSNickeau    {
90804fd306cSNickeau        if ($this->port === null) {
90904fd306cSNickeau            throw new ExceptionNotFound("No port specified");
91004fd306cSNickeau        }
91104fd306cSNickeau        return $this->port;
91204fd306cSNickeau    }
91304fd306cSNickeau
91404fd306cSNickeau    public function addQueryParameterIfNotPresent(string $key, string $value)
91504fd306cSNickeau    {
91604fd306cSNickeau        if (!$this->hasProperty($key)) {
91704fd306cSNickeau            $this->addQueryParameterIfNotActualSameValue($key, $value);
91804fd306cSNickeau        }
91904fd306cSNickeau    }
92004fd306cSNickeau
92104fd306cSNickeau    /**
92204fd306cSNickeau     * Set/replace a query parameter with the new value
92304fd306cSNickeau     * @param string $key
92404fd306cSNickeau     * @param string $value
92504fd306cSNickeau     * @return Url
92604fd306cSNickeau     */
92704fd306cSNickeau    public function setQueryParameter(string $key, string $value): Url
92804fd306cSNickeau    {
92904fd306cSNickeau        $this->deleteQueryParameter($key);
93004fd306cSNickeau        $this->addQueryParameter($key, $value);
93104fd306cSNickeau        return $this;
93204fd306cSNickeau    }
93304fd306cSNickeau
93404fd306cSNickeau    public function deleteQueryParameter(string $key)
93504fd306cSNickeau    {
93604fd306cSNickeau        unset($this->query[$key]);
93704fd306cSNickeau    }
93804fd306cSNickeau
93904fd306cSNickeau    /**
94004fd306cSNickeau     * @return string - An url in the DOM use the ampersand character
94104fd306cSNickeau     * If you want to check the value of a DOM attribute, you need to check it with this value
94204fd306cSNickeau     */
94304fd306cSNickeau    public function toDomString(): string
94404fd306cSNickeau    {
94504fd306cSNickeau        // ampersand for dom string
94604fd306cSNickeau        return $this->toString();
94704fd306cSNickeau    }
94804fd306cSNickeau
94904fd306cSNickeau    public function toCssString(): string
95004fd306cSNickeau    {
95104fd306cSNickeau        // ampersand for css
95204fd306cSNickeau        return $this->toString();
95304fd306cSNickeau    }
95404fd306cSNickeau
95504fd306cSNickeau    /**
95604fd306cSNickeau     * @return bool - if the url points to the same website than the host
95704fd306cSNickeau     */
95804fd306cSNickeau    public function isExternal(): bool
95904fd306cSNickeau    {
96004fd306cSNickeau        try {
96170bbd7f1Sgerardnico            // We set the path, otherwise it's seen as a local url
96270bbd7f1Sgerardnico            $localHost = Url::createEmpty()->setPath("/")->toAbsoluteUrl()->getHost();
96304fd306cSNickeau            return $localHost !== $this->getHost();
96404fd306cSNickeau        } catch (ExceptionNotFound $e) {
96504fd306cSNickeau            // no host meaning that the url is relative and then local
96604fd306cSNickeau            return false;
96704fd306cSNickeau        }
96804fd306cSNickeau    }
96904fd306cSNickeau
97004fd306cSNickeau    /**
97104fd306cSNickeau     * In a url, in a case, the path should be absolute
97204fd306cSNickeau     * This function makes it absolute if not.
97304fd306cSNickeau     * In case of messaging scheme (mailto, whatsapp, ...), this is not the case
97404fd306cSNickeau     * @throws ExceptionNotFound
97504fd306cSNickeau     */
97604fd306cSNickeau    private function getAbsolutePath(): string
97704fd306cSNickeau    {
97804fd306cSNickeau        $pathString = $this->getPath();
97904fd306cSNickeau        if ($pathString[0] !== "/") {
98004fd306cSNickeau            return "/{$pathString}";
98104fd306cSNickeau        }
98204fd306cSNickeau        return $pathString;
98304fd306cSNickeau    }
98404fd306cSNickeau
98504fd306cSNickeau
98604fd306cSNickeau    /**
98704fd306cSNickeau     * @throws ExceptionBadSyntax
98804fd306cSNickeau     * @throws ExceptionBadArgument
98904fd306cSNickeau     */
99004fd306cSNickeau    public static function createFromUri(string $uri): Path
99104fd306cSNickeau    {
99204fd306cSNickeau        return new Url($uri);
99304fd306cSNickeau    }
99404fd306cSNickeau
99504fd306cSNickeau    public function deleteQueryProperties(): Url
99604fd306cSNickeau    {
99704fd306cSNickeau        $this->query = new ArrayCaseInsensitive();;
99804fd306cSNickeau        return $this;
99904fd306cSNickeau    }
100004fd306cSNickeau
100104fd306cSNickeau    public function withoutRewrite(): Url
100204fd306cSNickeau    {
100304fd306cSNickeau        $this->withRewrite = false;
100404fd306cSNickeau        return $this;
100504fd306cSNickeau    }
100604fd306cSNickeau
1007e346390aSgerardnico    /**
1008e346390aSgerardnico     * Dokuwiki utility to check if the URL is local
100970bbd7f1Sgerardnico     * (ie has not path, only a fragment such as #id)
1010e346390aSgerardnico     * @return bool
1011e346390aSgerardnico     */
1012e346390aSgerardnico    public function isLocal(): bool
1013e346390aSgerardnico    {
1014e346390aSgerardnico        if ($this->path !== null) {
1015e346390aSgerardnico            return false;
1016e346390aSgerardnico        }
1017e346390aSgerardnico        /**
1018e346390aSgerardnico         * The path paramater of Dokuwiki
1019e346390aSgerardnico         */
1020e346390aSgerardnico        if ($this->hasProperty(DokuwikiId::DOKUWIKI_ID_ATTRIBUTE)) {
1021e346390aSgerardnico            return false;
1022e346390aSgerardnico        }
102370bbd7f1Sgerardnico        if ($this->hasProperty(MediaMarkup::$MEDIA_QUERY_PARAMETER)) {
1024e346390aSgerardnico            return false;
1025e346390aSgerardnico        }
1026e346390aSgerardnico        if ($this->hasProperty(FetcherRawLocalPath::SRC_QUERY_PARAMETER)) {
1027e346390aSgerardnico            return false;
1028e346390aSgerardnico        }
1029e346390aSgerardnico        return true;
1030e346390aSgerardnico    }
1031e346390aSgerardnico
1032e346390aSgerardnico
103304fd306cSNickeau}
1034