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. &. 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&q=larry+bird 5604fd306cSNickeau * 5704fd306cSNickeau * https://daringfireball.net/projects/markdown/syntax#autoescape 5804fd306cSNickeau * 5904fd306cSNickeau */ 6004fd306cSNickeau public const AMPERSAND_URL_ENCODED_FOR_HTML = '&'; 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 /** 192*45a874f4SNico * May be Just ??? 193*45a874f4SNico * Url::createFromString($_SERVER['REQUEST_URI']); 194*45a874f4SNico */ 195*45a874f4SNico /** 196*45a874f4SNico * $_REQUEST is a merge between: 197*45a874f4SNico * * $_GET: the URL parameters (aka. query string) 198*45a874f4SNico * * $_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 200*45a874f4SNico * 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 30804fd306cSNickeau /** 30904fd306cSNickeau * @throws ExceptionNotFound 31004fd306cSNickeau */ 31104fd306cSNickeau public function getScheme(): string 31204fd306cSNickeau { 31304fd306cSNickeau if ($this->scheme === null) { 31404fd306cSNickeau 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 } 40804fd306cSNickeau try { 40904fd306cSNickeau $this->getScheme(); 41004fd306cSNickeau } 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 & 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