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