1<?php 2/** 3 * Copyright (c) 2020. ComboStrap, Inc. and its affiliates. All Rights Reserved. 4 * 5 * This source code is licensed under the GPL license found in the 6 * COPYING file in the root directory of this source tree. 7 * 8 * @license GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html) 9 * @author ComboStrap <support@combostrap.com> 10 * 11 */ 12 13namespace ComboStrap; 14 15 16use Doku_Renderer_metadata; 17use Doku_Renderer_xhtml; 18use dokuwiki\Extension\PluginTrait; 19use dokuwiki\Utf8\Conversion; 20use syntax_plugin_combo_tooltip; 21 22require_once(__DIR__ . '/PluginUtility.php'); 23 24/** 25 * 26 * @package ComboStrap 27 * 28 * Parse the ref found in a markup link 29 * and return an XHTML compliant array 30 * with href, style, ... attributes 31 */ 32class MarkupRef 33{ 34 35 36 /** 37 * Type of link 38 */ 39 const INTERWIKI_URI = 'interwiki'; 40 const WINDOWS_SHARE_URI = 'windowsShare'; 41 const WEB_URI = 'external'; 42 43 const EMAIL_URI = 'email'; 44 const LOCAL_URI = 'local'; 45 const WIKI_URI = 'internal'; 46 const VARIABLE_URI = 'internal_template'; 47 48 49 /** 50 * Class added to the type of link 51 * Class have styling rule conflict, they are by default not set 52 * but this configuration permits to turn it back 53 */ 54 const CONF_USE_DOKUWIKI_CLASS_NAME = "useDokuwikiLinkClassName"; 55 /** 56 * This configuration will set for all internal link 57 * the {@link MarkupRef::PREVIEW_ATTRIBUTE} preview attribute 58 */ 59 const CONF_PREVIEW_LINK = "previewLink"; 60 const CONF_PREVIEW_LINK_DEFAULT = 0; 61 62 63 const TEXT_ERROR_CLASS = "text-danger"; 64 65 /** 66 * The known parameters for an email url 67 */ 68 const EMAIL_VALID_PARAMETERS = ["subject"]; 69 70 /** 71 * If set, it will show a page preview 72 */ 73 const PREVIEW_ATTRIBUTE = "preview"; 74 const PREVIEW_TOOLTIP = "preview"; 75 76 /** 77 * Highlight Key 78 * Adding this property to the internal query will highlight the words 79 * 80 * See {@link html_hilight} 81 */ 82 const SEARCH_HIGHLIGHT_QUERY_PROPERTY = "s"; 83 84 85 /** 86 * @var mixed 87 */ 88 private $uriType; 89 /** 90 * @var mixed 91 */ 92 private $ref; 93 94 /** 95 * @var Page the internal linked page if the link is an internal one 96 */ 97 private $linkedPage; 98 99 /** 100 * @var string The value of the title attribute of an anchor 101 */ 102 private $title; 103 104 105 /** 106 * The name of the wiki for an inter wiki link 107 * @var string 108 */ 109 private $wiki; 110 111 112 /** 113 * 114 * @var false|string 115 */ 116 private $schemeUri; 117 118 /** 119 * The uri scheme that can be used inside a page 120 * @var array 121 */ 122 private $authorizedSchemes; 123 124 125 /** 126 * @var DokuwikiUrl 127 */ 128 private $dokuwikiUrl; 129 /** 130 * @var array|string|null 131 */ 132 private $type; 133 /** 134 * @var array 135 */ 136 private $interwiki; 137 138 /** 139 * Link constructor. 140 * @param $ref 141 */ 142 public function __construct($ref) 143 { 144 145 146 /** 147 * Windows share link 148 */ 149 if ($this->uriType == null) { 150 if (preg_match('/^\\\\\\\\[^\\\\]+?\\\\/u', $ref)) { 151 $this->uriType = self::WINDOWS_SHARE_URI; 152 $this->ref = $ref; 153 return; 154 } 155 } 156 157 /** 158 * URI like links section with query and fragment 159 */ 160 161 /** 162 * Local 163 */ 164 if ($this->uriType == null) { 165 if (preg_match('!^#.+!', $ref)) { 166 $this->uriType = self::LOCAL_URI; 167 $this->ref = $ref; 168 } 169 } 170 171 /** 172 * Email validation pattern 173 * E-Mail (pattern below is defined in inc/mail.php) 174 * 175 * Example: 176 * [[support@combostrap.com?subject=hallo]] 177 * [[support@combostrap.com]] 178 */ 179 if ($this->uriType == null) { 180 $emailRfc2822 = "0-9a-zA-Z!#$%&'*+/=?^_`{|}~-"; 181 $emailPattern = '[' . $emailRfc2822 . ']+(?:\.[' . $emailRfc2822 . ']+)*@(?i:[0-9a-z][0-9a-z-]*\.)+(?i:[a-z]{2,63})'; 182 if (preg_match('<' . $emailPattern . '>', $ref)) { 183 $this->uriType = self::EMAIL_URI; 184 $this->ref = $ref; 185 // we don't return. The query part is parsed afterwards 186 } 187 } 188 189 190 /** 191 * External (ie only https) 192 */ 193 if ($this->uriType == null) { 194 /** 195 * Example: `https://` 196 * 197 * Other scheme are not yet recognized 198 * because it can also be a wiki id 199 * For instance, `mailto:` is also a valid page 200 */ 201 if (preg_match('#^([a-z0-9\-\.+]+?)://#i', $ref)) { 202 $this->uriType = self::WEB_URI; 203 $this->schemeUri = strtolower(substr($ref, 0, strpos($ref, ":"))); 204 $this->ref = $ref; 205 } 206 } 207 208 /** 209 * Interwiki ? 210 */ 211 $refProcessing = $ref; 212 if ($this->uriType == null) { 213 $interwikiPosition = strpos($refProcessing, ">"); 214 if ($interwikiPosition !== false) { 215 $this->wiki = strtolower(substr($refProcessing, 0, $interwikiPosition)); 216 $refProcessing = substr($refProcessing, $interwikiPosition + 1); 217 $this->ref = $ref; 218 $this->uriType = self::INTERWIKI_URI; 219 } 220 } 221 222 /** 223 * Internal then 224 */ 225 if ($this->uriType == null) { 226 /** 227 * It can be a link with a ref template 228 */ 229 if (TemplateUtility::isVariable($ref)) { 230 $this->uriType = self::VARIABLE_URI; 231 } else { 232 $this->uriType = self::WIKI_URI; 233 } 234 $this->ref = $ref; 235 } 236 237 238 /** 239 * Url (called ref by dokuwiki) 240 */ 241 $this->dokuwikiUrl = DokuwikiUrl::createFromUrl($refProcessing); 242 243 244 } 245 246 public static function createFromPageId($id): MarkupRef 247 { 248 return new MarkupRef(":$id"); 249 } 250 251 public static function createFromRef(string $ref): MarkupRef 252 { 253 return new MarkupRef($ref); 254 } 255 256 257 /** 258 * @param $uriType 259 * @return $this 260 */ 261 public function setUriType($uriType): MarkupRef 262 { 263 $this->uriType = $uriType; 264 return $this; 265 } 266 267 268 /** 269 * 270 * 271 * 272 * @throws ExceptionCombo 273 */ 274 public function toAttributes($logicalTag = \syntax_plugin_combo_link::TAG): TagAttributes 275 { 276 277 $outputAttributes = TagAttributes::createEmpty($logicalTag); 278 279 $type = $this->getUriType(); 280 281 282 /** 283 * Add the attribute from the URL 284 * if this is not a `do` 285 */ 286 287 switch ($type) { 288 case self::WIKI_URI: 289 if (!$this->dokuwikiUrl->hasQueryParameter("do")) { 290 foreach ($this->getDokuwikiUrl()->getQueryParameters() as $key => $value) { 291 if ($key !== self::SEARCH_HIGHLIGHT_QUERY_PROPERTY) { 292 $outputAttributes->addComponentAttributeValue($key, $value); 293 } 294 } 295 } 296 break; 297 case 298 self::EMAIL_URI: 299 foreach ($this->getDokuwikiUrl()->getQueryParameters() as $key => $value) { 300 if (!in_array($key, self::EMAIL_VALID_PARAMETERS)) { 301 $outputAttributes->addComponentAttributeValue($key, $value); 302 } 303 } 304 break; 305 } 306 307 308 global $conf; 309 310 /** 311 * Get the url 312 */ 313 $url = $this->getUrl(); 314 if (!empty($url)) { 315 $outputAttributes->addOutputAttributeValue("href", $url); 316 } 317 318 319 /** 320 * Processing by type 321 */ 322 switch ($this->getUriType()) { 323 case self::INTERWIKI_URI: 324 325 // normal link for the `this` wiki 326 if ($this->getWiki() !== "this") { 327 PluginUtility::getSnippetManager()->attachCssInternalStyleSheetForSlot(self::INTERWIKI_URI); 328 } 329 /** 330 * Target 331 */ 332 $interWikiConf = $conf['target']['interwiki']; 333 if (!empty($interWikiConf)) { 334 $outputAttributes->addOutputAttributeValue('target', $interWikiConf); 335 $outputAttributes->addOutputAttributeValue('rel', 'noopener'); 336 } 337 $outputAttributes->addClassName(self::getHtmlClassInterWikiLink()); 338 $wikiClass = "iw_" . preg_replace('/[^_\-a-z0-9]+/i', '_', $this->getWiki()); 339 $outputAttributes->addClassName($wikiClass); 340 if (!$this->wikiExists()) { 341 $outputAttributes->addClassName(self::getHtmlClassNotExist()); 342 $outputAttributes->addOutputAttributeValue("rel", 'nofollow'); 343 } 344 345 break; 346 case self::WIKI_URI: 347 /** 348 * Derived from {@link Doku_Renderer_xhtml::internallink()} 349 */ 350 // https://www.dokuwiki.org/config:target 351 $target = $conf['target']['wiki']; 352 if (!empty($target)) { 353 $outputAttributes->addOutputAttributeValue('target', $target); 354 } 355 /** 356 * Internal Page 357 */ 358 $linkedPage = $this->getInternalPage(); 359 $outputAttributes->addOutputAttributeValue("data-wiki-id", $linkedPage->getDokuwikiId()); 360 361 362 if (!$linkedPage->exists()) { 363 364 /** 365 * Red color 366 */ 367 $outputAttributes->addClassName(self::getHtmlClassNotExist()); 368 $outputAttributes->addOutputAttributeValue("rel", 'nofollow'); 369 370 } else { 371 372 /** 373 * Internal Link Class 374 */ 375 $outputAttributes->addClassName(self::getHtmlClassInternalLink()); 376 377 /** 378 * Link Creation 379 * Do we need to set the title or the tooltip 380 * Processing variables 381 */ 382 $acronym = ""; 383 384 /** 385 * Preview tooltip 386 */ 387 $previewConfig = PluginUtility::getConfValue(self::CONF_PREVIEW_LINK, self::CONF_PREVIEW_LINK_DEFAULT); 388 $preview = $outputAttributes->getBooleanValueAndRemoveIfPresent(self::PREVIEW_ATTRIBUTE, $previewConfig); 389 if ($preview) { 390 Tooltip::addToolTipSnippetIfNeeded(); 391 $tooltipHtml = <<<EOF 392<h3>{$linkedPage->getNameOrDefault()}</h3> 393<p>{$linkedPage->getDescriptionOrElseDokuWiki()}</p> 394EOF; 395 $dataAttributeNamespace = Bootstrap::getDataNamespace(); 396 $outputAttributes->addOutputAttributeValue("data{$dataAttributeNamespace}-toggle", "tooltip"); 397 $outputAttributes->addOutputAttributeValue("data{$dataAttributeNamespace}-placement", "top"); 398 $outputAttributes->addOutputAttributeValue("data{$dataAttributeNamespace}-html", "true"); 399 $outputAttributes->addOutputAttributeValue("title", $tooltipHtml); 400 } 401 402 /** 403 * Low quality Page 404 * (It has a higher priority than preview and 405 * the code comes then after) 406 */ 407 $pageProtectionAcronym = strtolower(PageProtection::ACRONYM); 408 if ($linkedPage->isLowQualityPage()) { 409 410 /** 411 * Add a class to style it differently 412 * (the acronym is added to the description, later) 413 */ 414 $acronym = LowQualityPage::LOW_QUALITY_PROTECTION_ACRONYM; 415 $lowerCaseLowQualityAcronym = strtolower(LowQualityPage::LOW_QUALITY_PROTECTION_ACRONYM); 416 $outputAttributes->addClassName(LowQualityPage::CLASS_NAME . "-combo"); 417 $snippetLowQualityPageId = $lowerCaseLowQualityAcronym; 418 PluginUtility::getSnippetManager()->attachCssInternalStyleSheetForSlot($snippetLowQualityPageId); 419 /** 420 * Note The protection does occur on Javascript level, not on the HTML 421 * because the created page is valid for a anonymous or logged-in user 422 * Javascript is controlling 423 */ 424 if (LowQualityPage::isProtectionEnabled()) { 425 426 $linkType = LowQualityPage::getLowQualityLinkType(); 427 $outputAttributes->addOutputAttributeValue("data-$pageProtectionAcronym-link", $linkType); 428 $outputAttributes->addOutputAttributeValue("data-$pageProtectionAcronym-source", $lowerCaseLowQualityAcronym); 429 430 /** 431 * Low Quality Page protection javascript is only for warning or login link 432 */ 433 if (in_array($linkType, [PageProtection::PAGE_PROTECTION_LINK_WARNING, PageProtection::PAGE_PROTECTION_LINK_LOGIN])) { 434 PageProtection::addPageProtectionSnippet(); 435 } 436 437 } 438 } 439 440 /** 441 * Late publication has a higher priority than 442 * the late publication and the is therefore after 443 * (In case this a low quality page late published) 444 */ 445 if ($linkedPage->isLatePublication()) { 446 /** 447 * Add a class to style it differently if needed 448 */ 449 $outputAttributes->addClassName(PagePublicationDate::LATE_PUBLICATION_CLASS_NAME . "-combo"); 450 if (PagePublicationDate::isLatePublicationProtectionEnabled()) { 451 $acronym = PagePublicationDate::LATE_PUBLICATION_PROTECTION_ACRONYM; 452 $lowerCaseLatePublicationAcronym = strtolower(PagePublicationDate::LATE_PUBLICATION_PROTECTION_ACRONYM); 453 $outputAttributes->addOutputAttributeValue("data-$pageProtectionAcronym-link", PageProtection::PAGE_PROTECTION_LINK_LOGIN); 454 $outputAttributes->addOutputAttributeValue("data-$pageProtectionAcronym-source", $lowerCaseLatePublicationAcronym); 455 PageProtection::addPageProtectionSnippet(); 456 } 457 458 } 459 460 /** 461 * Title (ie tooltip vs title html attribute) 462 */ 463 if (!$outputAttributes->hasAttribute("title")) { 464 465 /** 466 * If this is not a link into the same page 467 */ 468 if (!empty($this->getDokuwikiUrl()->getPath())) { 469 $description = $linkedPage->getDescriptionOrElseDokuWiki(); 470 if (empty($description)) { 471 // Rare case 472 $description = $linkedPage->getH1OrDefault(); 473 } 474 if (!empty($acronym)) { 475 $description = $description . " ($acronym)"; 476 } 477 $outputAttributes->addOutputAttributeValue("title", $description); 478 } 479 480 } 481 482 } 483 484 break; 485 486 case self::WINDOWS_SHARE_URI: 487 // https://www.dokuwiki.org/config:target 488 $windowsTarget = $conf['target']['windows']; 489 if (!empty($windowsTarget)) { 490 $outputAttributes->addOutputAttributeValue('target', $windowsTarget); 491 } 492 $outputAttributes->addClassName("windows"); 493 break; 494 case self::LOCAL_URI: 495 break; 496 case self::EMAIL_URI: 497 $outputAttributes->addClassName(self::getHtmlClassEmailLink()); 498 break; 499 case self::WEB_URI: 500 if ($conf['relnofollow']) { 501 $outputAttributes->addOutputAttributeValue("rel", 'nofollow ugc'); 502 } 503 // https://www.dokuwiki.org/config:target 504 $externTarget = $conf['target']['extern']; 505 if (!empty($externTarget)) { 506 $outputAttributes->addOutputAttributeValue('target', $externTarget); 507 $outputAttributes->addOutputAttributeValue("rel", 'noopener'); 508 } 509 if ($this->type === null) { 510 /** 511 * Default class for default external link 512 * To not interfere with other external link style 513 * For instance, {@link \syntax_plugin_combo_share} 514 */ 515 $outputAttributes->addClassName(self::getHtmlClassExternalLink()); 516 } 517 break; 518 default: 519 /** 520 * May be any external link 521 * such as {@link \syntax_plugin_combo_share} 522 */ 523 break; 524 525 } 526 527 /** 528 * An email URL and title 529 * may be already encoded because of the vanguard configuration 530 * 531 * The url is not treated as an attribute 532 * because the transformation function encodes the value 533 * to mitigate XSS 534 * 535 */ 536 if ($this->getUriType() == self::EMAIL_URI) { 537 $emailAddress = $this->obfuscateEmail($this->dokuwikiUrl->getPath()); 538 $outputAttributes->addOutputAttributeValue("title", $emailAddress); 539 } 540 541 /** 542 * Return 543 */ 544 return $outputAttributes; 545 546 547 } 548 549 550 /** 551 * Return the type of link from an ID 552 * 553 * @return string a `TYPE_xxx` constant 554 */ 555 public 556 function getUriType(): string 557 { 558 return $this->uriType; 559 } 560 561 562 /** 563 * @return Page - the internal page or an error if the link is not an internal one 564 */ 565 public 566 function getInternalPage(): Page 567 { 568 if ($this->linkedPage == null) { 569 if ($this->getUriType() == self::WIKI_URI) { 570 // if there is no path, this is the actual page 571 $pathOrId = $this->dokuwikiUrl->getPath(); 572 573 $this->linkedPage = Page::createPageFromNonQualifiedPath($pathOrId); 574 575 } else { 576 throw new \RuntimeException("You can't ask the internal page id from a link that is not an internal one"); 577 } 578 } 579 return $this->linkedPage; 580 } 581 582 public 583 function getRef() 584 { 585 return $this->ref; 586 } 587 588 /** 589 * The label inside the anchor tag if there is none 590 * @param false $navigation 591 * @return string|null 592 */ 593 public function getLabel(bool $navigation = false): ?string 594 { 595 596 switch ($this->getUriType()) { 597 case self::WIKI_URI: 598 if ($navigation) { 599 return $this->getInternalPage()->getNameOrDefault(); 600 } else { 601 return $this->getInternalPage()->getTitleOrDefault(); 602 } 603 604 case self::EMAIL_URI: 605 606 global $conf; 607 $email = $this->dokuwikiUrl->getPath(); 608 switch ($conf['mailguard']) { 609 case 'none' : 610 return $email; 611 case 'visible' : 612 default : 613 $obfuscate = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] '); 614 return strtr($email, $obfuscate); 615 } 616 case self::INTERWIKI_URI: 617 return $this->dokuwikiUrl->getPath(); 618 case self::LOCAL_URI: 619 return $this->dokuwikiUrl->getFragment(); 620 default: 621 return $this->getRef(); 622 } 623 } 624 625 /** 626 * @param $title - the value of the title attribute of the anchor 627 */ 628 public 629 function setTitle($title) 630 { 631 $this->title = $title; 632 } 633 634 635 /** 636 * @throws ExceptionCombo 637 * @var string $targetEnvironmentAmpersand 638 * By default, all data are encoded 639 * at {@link TagAttributes::encodeToHtmlValue()} 640 * therefore the default is non-encoded 641 * 642 */ 643 public function getUrl() 644 { 645 646 switch ($this->getUriType()) { 647 case self::WIKI_URI: 648 $page = $this->getInternalPage(); 649 650 /** 651 * Styling attribute 652 * may be passed via parameters 653 * for internal link 654 * We don't want the styling attribute 655 * in the URL 656 * 657 * We will not overwrite the parameters if this is an dokuwiki 658 * action link (with the `do` property) 659 */ 660 if ($this->dokuwikiUrl->hasQueryParameter("do")) { 661 662 $absoluteUrl = Site::shouldUrlBeAbsolute(); 663 $url = wl( 664 $page->getDokuwikiId(), 665 $this->dokuwikiUrl->getQueryParameters(), 666 $absoluteUrl, 667 '&' 668 ); 669 670 } else { 671 672 /** 673 * No parameters by default known 674 */ 675 $url = $page->getCanonicalUrl( 676 [], 677 false 678 ); 679 680 /** 681 * The search term 682 * Code adapted found at {@link Doku_Renderer_xhtml::internallink()} 683 * We can't use the previous {@link wl function} 684 * because it encode too much 685 */ 686 $searchTerms = $this->dokuwikiUrl->getQueryParameter(self::SEARCH_HIGHLIGHT_QUERY_PROPERTY); 687 if ($searchTerms !== null) { 688 $url .= DokuwikiUrl::AMPERSAND_CHARACTER; 689 PluginUtility::getSnippetManager()->attachCssInternalStyleSheetForSlot("search-hit"); 690 if (is_array($searchTerms)) { 691 /** 692 * To verify, do we really need the [] 693 * to get an array in php ? 694 */ 695 $searchTermsQuery = []; 696 foreach ($searchTerms as $searchTerm) { 697 $searchTermsQuery[] = "s[]=$searchTerm"; 698 } 699 $url .= implode(DokuwikiUrl::AMPERSAND_CHARACTER, $searchTermsQuery); 700 } else { 701 $url .= "s=$searchTerms"; 702 } 703 } 704 705 706 } 707 if ($this->dokuwikiUrl->getFragment() != null) { 708 /** 709 * pageutils (transform a fragment in section id) 710 */ 711 $check = false; 712 $url .= '#' . sectionID($this->dokuwikiUrl->getFragment(), $check); 713 } 714 break; 715 case self::INTERWIKI_URI: 716 $wiki = $this->wiki; 717 $extendedPath = $this->dokuwikiUrl->getPath(); 718 if ($this->dokuwikiUrl->getFragment() !== null) { 719 $extendedPath .= "#{$this->dokuwikiUrl->getFragment()}"; 720 } 721 $url = $this->interWikiRefToUrl($wiki, $extendedPath); 722 break; 723 case self::WINDOWS_SHARE_URI: 724 $url = str_replace('\\', '/', $this->getRef()); 725 $url = 'file:///' . $url; 726 break; 727 case self::EMAIL_URI: 728 /** 729 * An email link is `<email>` 730 * {@link Emaillink::connectTo()} 731 * or 732 * {@link PluginTrait::email() 733 */ 734 // common.php#obfsucate implements the $conf['mailguard'] 735 $uri = $this->getDokuwikiUrl()->getPath(); 736 $uri = $this->obfuscateEmail($uri); 737 $uri = urlencode($uri); 738 $queryParameters = $this->getDokuwikiUrl()->getQueryParameters(); 739 if (sizeof($queryParameters) > 0) { 740 $uri .= "?"; 741 foreach ($queryParameters as $key => $value) { 742 $value = urlencode($value); 743 $key = urlencode($key); 744 if (in_array($key, self::EMAIL_VALID_PARAMETERS)) { 745 $uri .= "$key=$value"; 746 } 747 } 748 } 749 $url = 'mailto:' . $uri; 750 break; 751 case self::LOCAL_URI: 752 $check = false; 753 $url = '#' . sectionID($this->ref, $check); 754 break; 755 case self::WEB_URI: 756 /** 757 * Default is external 758 * For instance, {@link \syntax_plugin_combo_share} link 759 */ 760 /** 761 * Authorized scheme only 762 * to not inject code 763 */ 764 if (is_null($this->authorizedSchemes)) { 765 // https://www.dokuwiki.org/urlschemes 766 $this->authorizedSchemes = getSchemes(); 767 $this->authorizedSchemes[] = "whatsapp"; 768 $this->authorizedSchemes[] = "mailto"; 769 } 770 if (!in_array($this->schemeUri, $this->authorizedSchemes)) { 771 throw new ExceptionCombo("The scheme ($this->schemeUri) is not authorized as uri"); 772 } else { 773 $url = $this->ref; 774 } 775 break; 776 case self::VARIABLE_URI: 777 throw new ExceptionCombo("A template variable uri ($this->ref) can not give back an url, it should be first be replaced"); 778 default: 779 throw new ExceptionCombo("The structure of the reference ($this->ref) is unknown"); 780 } 781 782 783 return $url; 784 } 785 786 public function getWiki(): ?string 787 { 788 return $this->wiki; 789 } 790 791 792 public 793 function getScheme() 794 { 795 return $this->schemeUri; 796 } 797 798 799 private 800 function wikiExists(): bool 801 { 802 $wikis = getInterwiki(); 803 return key_exists($this->wiki, $wikis); 804 } 805 806 private 807 function obfuscateEmail($email, $inAttribute = true): string 808 { 809 /** 810 * adapted from {@link obfuscate()} in common.php 811 */ 812 global $conf; 813 814 $mailGuard = $conf['mailguard']; 815 if ($mailGuard === "hex" && $inAttribute) { 816 $mailGuard = "visible"; 817 } 818 switch ($mailGuard) { 819 case 'visible' : 820 $obfuscate = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] '); 821 return strtr($email, $obfuscate); 822 823 case 'hex' : 824 return Conversion::toHtml($email, true); 825 826 case 'none' : 827 default : 828 return $email; 829 } 830 } 831 832 833 public 834 function isRelative(): bool 835 { 836 return strpos($this->path, ':') !== 0; 837 } 838 839 public 840 function getDokuwikiUrl(): DokuwikiUrl 841 { 842 return $this->dokuwikiUrl; 843 } 844 845 846 public 847 static function getHtmlClassInternalLink(): string 848 { 849 $oldClassName = PluginUtility::getConfValue(self::CONF_USE_DOKUWIKI_CLASS_NAME); 850 if ($oldClassName) { 851 return "wikilink1"; 852 } else { 853 return "link-internal"; 854 } 855 } 856 857 public 858 static function getHtmlClassEmailLink(): string 859 { 860 $oldClassName = PluginUtility::getConfValue(self::CONF_USE_DOKUWIKI_CLASS_NAME); 861 if ($oldClassName) { 862 return "mail"; 863 } else { 864 return "link-mail"; 865 } 866 } 867 868 public 869 static function getHtmlClassInterWikiLink(): string 870 { 871 $oldClassName = PluginUtility::getConfValue(self::CONF_USE_DOKUWIKI_CLASS_NAME); 872 if ($oldClassName) { 873 return "interwiki"; 874 } else { 875 return "link-interwiki"; 876 } 877 } 878 879 public 880 static function getHtmlClassExternalLink(): string 881 { 882 $oldClassName = PluginUtility::getConfValue(self::CONF_USE_DOKUWIKI_CLASS_NAME); 883 if ($oldClassName) { 884 return "urlextern"; 885 } else { 886 return "link-external"; 887 } 888 } 889 890//FYI: exist in dokuwiki is "wikilink1 but we let the control to the user 891 public 892 static function getHtmlClassNotExist(): string 893 { 894 $oldClassName = PluginUtility::getConfValue(self::CONF_USE_DOKUWIKI_CLASS_NAME); 895 if ($oldClassName) { 896 return "wikilink2"; 897 } else { 898 return self::TEXT_ERROR_CLASS; 899 } 900 } 901 902 public 903 function __toString() 904 { 905 return $this->ref; 906 } 907 908 private 909 function getEmailObfuscationConfiguration() 910 { 911 global $conf; 912 return $conf['mailguard']; 913 } 914 915 /** 916 * @param string $shortcut 917 * @param string $reference 918 * @return mixed|string 919 * Adapted from {@link Doku_Renderer_xhtml::_resolveInterWiki()} 920 * @noinspection DuplicatedCode 921 */ 922 private function interWikiRefToUrl(string &$shortcut, string $reference) 923 { 924 925 if ($this->interwiki === null) { 926 $this->interwiki = getInterwiki(); 927 } 928 929 // Get interwiki URL 930 if (isset($this->interwiki[$shortcut])) { 931 $url = $this->interwiki[$shortcut]; 932 } elseif (isset($this->interwiki['default'])) { 933 $shortcut = 'default'; 934 $url = $this->interwiki[$shortcut]; 935 } else { 936 // not parsable interwiki outputs '' to make sure string manipulation works 937 $shortcut = ''; 938 $url = ''; 939 } 940 941 //split into hash and url part 942 $hash = strrchr($reference, '#'); 943 if ($hash) { 944 $reference = substr($reference, 0, -strlen($hash)); 945 $hash = substr($hash, 1); 946 } 947 948 //replace placeholder 949 if (preg_match('#\{(URL|NAME|SCHEME|HOST|PORT|PATH|QUERY)\}#', $url)) { 950 //use placeholders 951 $url = str_replace('{URL}', rawurlencode($reference), $url); 952 //wiki names will be cleaned next, otherwise urlencode unsafe chars 953 $url = str_replace('{NAME}', ($url[0] === ':') ? $reference : 954 preg_replace_callback('/[[\\\\\]^`{|}#%]/', function ($match) { 955 return rawurlencode($match[0]); 956 }, $reference), $url); 957 $parsed = parse_url($reference); 958 if (empty($parsed['scheme'])) $parsed['scheme'] = ''; 959 if (empty($parsed['host'])) $parsed['host'] = ''; 960 if (empty($parsed['port'])) $parsed['port'] = 80; 961 if (empty($parsed['path'])) $parsed['path'] = ''; 962 if (empty($parsed['query'])) $parsed['query'] = ''; 963 $url = strtr($url, [ 964 '{SCHEME}' => $parsed['scheme'], 965 '{HOST}' => $parsed['host'], 966 '{PORT}' => $parsed['port'], 967 '{PATH}' => $parsed['path'], 968 '{QUERY}' => $parsed['query'], 969 ]); 970 } else if ($url != '') { 971 // make sure when no url is defined, we keep it null 972 // default 973 $url = $url . rawurlencode($reference); 974 } 975 //handle as wiki links 976 if ($url[0] === ':') { 977 $urlParam = null; 978 $id = $url; 979 if (strpos($url, '?') !== false) { 980 list($id, $urlParam) = explode('?', $url, 2); 981 } 982 $url = wl(cleanID($id), $urlParam); 983 $exists = page_exists($id); 984 } 985 if ($hash) $url .= '#' . rawurlencode($hash); 986 987 return $url; 988 } 989 990 991} 992