104fd306cSNickeau<?php 204fd306cSNickeau/** 304fd306cSNickeau * Copyright (c) 2020. ComboStrap, Inc. and its affiliates. All Rights Reserved. 404fd306cSNickeau * 504fd306cSNickeau * This source code is licensed under the GPL license found in the 604fd306cSNickeau * COPYING file in the root directory of this source tree. 704fd306cSNickeau * 804fd306cSNickeau * @license GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html) 904fd306cSNickeau * @author ComboStrap <support@combostrap.com> 1004fd306cSNickeau * 1104fd306cSNickeau */ 1204fd306cSNickeau 1304fd306cSNickeaunamespace ComboStrap; 1404fd306cSNickeau 1504fd306cSNickeau 1604fd306cSNickeauuse ComboStrap\Meta\Field\PageTemplateName; 1704fd306cSNickeauuse ComboStrap\TagAttribute\StyleAttribute; 1804fd306cSNickeauuse Doku_Renderer_xhtml; 1904fd306cSNickeauuse dokuwiki\Extension\PluginTrait; 2004fd306cSNickeauuse dokuwiki\Utf8\Conversion; 2104fd306cSNickeauuse syntax_plugin_combo_link; 2204fd306cSNickeau 2304fd306cSNickeau 2404fd306cSNickeau/** 2504fd306cSNickeau * 2604fd306cSNickeau * @package ComboStrap 2704fd306cSNickeau * 2804fd306cSNickeau * Parse the ref found in a markup link 2904fd306cSNickeau * and return an {@link LinkMarkup::toAttributes()} array for an anchor (a) 3004fd306cSNickeau * with href, style, ... attributes 3104fd306cSNickeau * 3204fd306cSNickeau */ 3304fd306cSNickeauclass LinkMarkup 3404fd306cSNickeau{ 3504fd306cSNickeau 3604fd306cSNickeau 3704fd306cSNickeau /** 3804fd306cSNickeau * Class added to the type of link 3904fd306cSNickeau * Class have styling rule conflict, they are by default not set 4004fd306cSNickeau * but this configuration permits to turn it back 4104fd306cSNickeau */ 4204fd306cSNickeau const CONF_USE_DOKUWIKI_CLASS_NAME = "useDokuwikiLinkClassName"; 4304fd306cSNickeau 4404fd306cSNickeau /** 4504fd306cSNickeau * This configuration will set for all internal link 4604fd306cSNickeau * the {@link LinkMarkup::PREVIEW_ATTRIBUTE} preview attribute 4704fd306cSNickeau */ 4804fd306cSNickeau const CONF_PREVIEW_LINK = "previewLink"; 4904fd306cSNickeau const CONF_PREVIEW_LINK_DEFAULT = 0; 5004fd306cSNickeau 5104fd306cSNickeau 5204fd306cSNickeau const TEXT_ERROR_CLASS = "text-danger"; 5304fd306cSNickeau 5404fd306cSNickeau /** 5504fd306cSNickeau * The known parameters for an email url 5604fd306cSNickeau * The other are styling attribute :) 5704fd306cSNickeau */ 5804fd306cSNickeau const EMAIL_VALID_PARAMETERS = ["subject"]; 5904fd306cSNickeau 6004fd306cSNickeau /** 6104fd306cSNickeau * If set, it will show a page preview 6204fd306cSNickeau */ 6304fd306cSNickeau const PREVIEW_ATTRIBUTE = "preview"; 6404fd306cSNickeau 6504fd306cSNickeau 6604fd306cSNickeau /** 6704fd306cSNickeau * Highlight Key 6804fd306cSNickeau * Adding this property to the internal query will highlight the words 6904fd306cSNickeau * 7004fd306cSNickeau * See {@link html_hilight} 7104fd306cSNickeau */ 7204fd306cSNickeau const SEARCH_HIGHLIGHT_QUERY_PROPERTY = "s"; 7304fd306cSNickeau const DATA_WIKI_ID = "data-wiki-id"; 7404fd306cSNickeau 7504fd306cSNickeau /** 7604fd306cSNickeau * For styling on the anchor tag (ie a) 7704fd306cSNickeau */ 7846021406Sgerardnico public const ANCHOR_HTML_SNIPPET_ID = "anchor"; 7904fd306cSNickeau 8004fd306cSNickeau /** 8104fd306cSNickeau * Url properties 8204fd306cSNickeau * that are not seen as styling properties 8304fd306cSNickeau * for a page 8404fd306cSNickeau * We could also build the {@link FetcherPage} 8504fd306cSNickeau * and see which attributes were not taken ? 8604fd306cSNickeau */ 8704fd306cSNickeau const PROTECTED_URL_PROPERTY = [ 8804fd306cSNickeau self::SEARCH_HIGHLIGHT_QUERY_PROPERTY, 8904fd306cSNickeau DokuWikiId::DOKUWIKI_ID_ATTRIBUTE, 9004fd306cSNickeau PageTemplateName::PROPERTY_NAME, 9104fd306cSNickeau FetcherPage::PURGE 9204fd306cSNickeau ]; 9304fd306cSNickeau 9404fd306cSNickeau 9504fd306cSNickeau private MarkupRef $markupRef; 9604fd306cSNickeau 9704fd306cSNickeau 9804fd306cSNickeau private TagAttributes $stylingAttributes; 9904fd306cSNickeau 10004fd306cSNickeau /** 10104fd306cSNickeau * Link constructor. 10204fd306cSNickeau * @param String $ref 10304fd306cSNickeau * @throws ExceptionBadArgument 10404fd306cSNickeau * @throws ExceptionBadSyntax 10504fd306cSNickeau * @throws ExceptionNotFound 10604fd306cSNickeau */ 10704fd306cSNickeau public function __construct(string $ref) 10804fd306cSNickeau { 10904fd306cSNickeau 11004fd306cSNickeau $this->stylingAttributes = TagAttributes::createEmpty(syntax_plugin_combo_link::TAG); 11104fd306cSNickeau 11204fd306cSNickeau $this->markupRef = MarkupRef::createLinkFromRef($ref); 11304fd306cSNickeau 11404fd306cSNickeau $this->collectStylingAttributeInUrl(); 11504fd306cSNickeau 11604fd306cSNickeau 11704fd306cSNickeau } 11804fd306cSNickeau 11904fd306cSNickeau public static function createFromPageIdOrPath($id): LinkMarkup 12004fd306cSNickeau { 12104fd306cSNickeau WikiPath::addRootSeparatorIfNotPresent($id); 12204fd306cSNickeau try { 12304fd306cSNickeau return new LinkMarkup($id); 12404fd306cSNickeau } catch (ExceptionBadArgument|ExceptionBadSyntax|ExceptionNotFound $e) { 12504fd306cSNickeau throw new ExceptionRuntime("Internal error: an id should be a good reference"); 12604fd306cSNickeau } 12704fd306cSNickeau } 12804fd306cSNickeau 12904fd306cSNickeau /** 13004fd306cSNickeau * @throws ExceptionBadArgument 13104fd306cSNickeau * @throws ExceptionBadSyntax 13204fd306cSNickeau * @throws ExceptionNotFound 13304fd306cSNickeau */ 13404fd306cSNickeau public static function createFromRef(string $ref): LinkMarkup 13504fd306cSNickeau { 13604fd306cSNickeau return new LinkMarkup($ref); 13704fd306cSNickeau } 13804fd306cSNickeau 13904fd306cSNickeau public static function getHtmlClassLocalLink(): string 14004fd306cSNickeau { 14104fd306cSNickeau return "link-local"; 14204fd306cSNickeau } 14304fd306cSNickeau 14404fd306cSNickeau 14504fd306cSNickeau /** 14604fd306cSNickeau * 14704fd306cSNickeau * @throws ExceptionNotFound 14804fd306cSNickeau */ 14904fd306cSNickeau public function toAttributes(): TagAttributes 15004fd306cSNickeau { 15104fd306cSNickeau 15204fd306cSNickeau $outputAttributes = $this->stylingAttributes; 15304fd306cSNickeau 15404fd306cSNickeau 15504fd306cSNickeau $url = $this->getMarkupRef()->getUrl(); 15604fd306cSNickeau $outputAttributes->addOutputAttributeValue("href", $url->toString()); 15704fd306cSNickeau 15804fd306cSNickeau /** 15904fd306cSNickeau * The search term 16004fd306cSNickeau * Code adapted found at {@link Doku_Renderer_xhtml::internallink()} 16104fd306cSNickeau * We can't use the previous {@link wl function} 16204fd306cSNickeau * because it encode too much 16304fd306cSNickeau */ 16404fd306cSNickeau $snippetSystem = PluginUtility::getSnippetManager(); 16504fd306cSNickeau if ($url->hasProperty(self::SEARCH_HIGHLIGHT_QUERY_PROPERTY)) { 16604fd306cSNickeau $snippetSystem->attachCssInternalStyleSheet("search-hit"); 16704fd306cSNickeau } 16846021406Sgerardnico $snippetSystem->attachCssInternalStyleSheet(self::ANCHOR_HTML_SNIPPET_ID); 16904fd306cSNickeau 17004fd306cSNickeau global $conf; 17104fd306cSNickeau 17204fd306cSNickeau 17304fd306cSNickeau /** 17404fd306cSNickeau * Processing by type 17504fd306cSNickeau */ 17604fd306cSNickeau switch ($this->getMarkupRef()->getSchemeType()) { 17704fd306cSNickeau case MarkupRef::INTERWIKI_URI: 17804fd306cSNickeau try { 17904fd306cSNickeau $interWiki = $this->getMarkupRef()->getInterWiki(); 18004fd306cSNickeau } catch (ExceptionNotFound $e) { 18104fd306cSNickeau LogUtility::internalError("The interwiki should be available. We were unable to create the link attributes."); 18204fd306cSNickeau return $outputAttributes; 18304fd306cSNickeau } 18404fd306cSNickeau // normal link for the `this` wiki 18504fd306cSNickeau if ($interWiki->getWiki() !== "this") { 18604fd306cSNickeau $snippetSystem->attachCssInternalStyleSheet(MarkupRef::INTERWIKI_URI); 18704fd306cSNickeau } 18804fd306cSNickeau $cssRules = $interWiki->getDefaultCssRules(); 18904fd306cSNickeau $snippetSystem->attachCssInternalStyleSheet(MarkupRef::INTERWIKI_URI, $cssRules); 19004fd306cSNickeau try { 19104fd306cSNickeau $cssRules = $interWiki->getSpecificCssRules(); 19204fd306cSNickeau $snippetSystem->attachCssInternalStyleSheet(MarkupRef::INTERWIKI_URI . "-" . $interWiki->getWiki(), $cssRules); 19304fd306cSNickeau } catch (ExceptionNotFound $e) { 19404fd306cSNickeau // no media find for the wiki 19504fd306cSNickeau } 19604fd306cSNickeau /** 19704fd306cSNickeau * Target 19804fd306cSNickeau */ 19904fd306cSNickeau $interWikiConf = $conf['target']['interwiki']; 20004fd306cSNickeau if (!empty($interWikiConf)) { 20104fd306cSNickeau $outputAttributes->addOutputAttributeValue('target', $interWikiConf); 20204fd306cSNickeau $outputAttributes->addOutputAttributeValue('rel', 'noopener'); 20304fd306cSNickeau } 20404fd306cSNickeau $outputAttributes->addClassName($interWiki->getComponentClass()); 20504fd306cSNickeau $outputAttributes->addClassName($interWiki->getSubComponentClass()); 20604fd306cSNickeau break; 20704fd306cSNickeau case MarkupRef::WIKI_URI: 20804fd306cSNickeau /** 20904fd306cSNickeau * Derived from {@link Doku_Renderer_xhtml::internallink()} 21004fd306cSNickeau */ 21104fd306cSNickeau // https://www.dokuwiki.org/config:target 21204fd306cSNickeau $target = $conf['target']['wiki']; 21304fd306cSNickeau if (!empty($target)) { 21404fd306cSNickeau $outputAttributes->addOutputAttributeValue('target', $target); 21504fd306cSNickeau } 21604fd306cSNickeau /** 21704fd306cSNickeau * Internal Page 21804fd306cSNickeau */ 21904fd306cSNickeau try { 22004fd306cSNickeau $dokuPath = $this->getMarkupRef()->getPath(); 22104fd306cSNickeau } catch (ExceptionNotFound $e) { 22204fd306cSNickeau throw new ExceptionNotFound("We were unable to process the internal link dokuwiki id on the link. The path was not found. Error: {$e->getMessage()}"); 22304fd306cSNickeau } 22404fd306cSNickeau $page = MarkupPath::createPageFromPathObject($dokuPath); 22504fd306cSNickeau $outputAttributes->addOutputAttributeValue(self::DATA_WIKI_ID, $dokuPath->getWikiId()); 22604fd306cSNickeau 227*b90165b7Sgerardnico /** 228*b90165b7Sgerardnico * Preview, we add it here because even if the file does not exist 229*b90165b7Sgerardnico * we need to delete this attribute so that it's not in the HTML 230*b90165b7Sgerardnico */ 231*b90165b7Sgerardnico $previewConfig = SiteConfig::getConfValue(self::CONF_PREVIEW_LINK, self::CONF_PREVIEW_LINK_DEFAULT); 232*b90165b7Sgerardnico $preview = $outputAttributes->getBooleanValueAndRemoveIfPresent(self::PREVIEW_ATTRIBUTE, $previewConfig); 23304fd306cSNickeau 23404fd306cSNickeau if (!FileSystems::exists($dokuPath)) { 23504fd306cSNickeau 23604fd306cSNickeau /** 23704fd306cSNickeau * Red color 23804fd306cSNickeau * if not `do=edit` 23904fd306cSNickeau */ 24004fd306cSNickeau if (!$this->markupRef->getUrl()->hasProperty("do")) { 24104fd306cSNickeau $outputAttributes->addClassName(self::getHtmlClassNotExist()); 24204fd306cSNickeau $outputAttributes->addOutputAttributeValue("rel", 'nofollow'); 24304fd306cSNickeau } 24404fd306cSNickeau 24504fd306cSNickeau } else { 24604fd306cSNickeau 24704fd306cSNickeau /** 24804fd306cSNickeau * Internal Link Class 24904fd306cSNickeau */ 25004fd306cSNickeau $outputAttributes->addClassName(self::getHtmlClassInternalLink()); 25104fd306cSNickeau 25204fd306cSNickeau /** 25304fd306cSNickeau * Link Creation 25404fd306cSNickeau * Do we need to set the title or the tooltip 25504fd306cSNickeau * Processing variables 25604fd306cSNickeau */ 25704fd306cSNickeau $acronym = ""; 25804fd306cSNickeau 25904fd306cSNickeau /** 26004fd306cSNickeau * Preview tooltip 26104fd306cSNickeau */ 262*b90165b7Sgerardnico if ($preview) { 26304fd306cSNickeau Tooltip::addToolTipSnippetIfNeeded(); 26404fd306cSNickeau // We use as heading, the name and not the title of the resource because otherwise it would be to lengthy 26504fd306cSNickeau $tooltipHtml = <<<EOF 26604fd306cSNickeau<h3>{$page->getNameOrDefault()}</h3> 26704fd306cSNickeau<p>{$page->getDescriptionOrElseDokuWiki()}</p> 26804fd306cSNickeauEOF; 26904fd306cSNickeau $dataAttributeNamespace = Bootstrap::getDataNamespace(); 27004fd306cSNickeau $outputAttributes->addOutputAttributeValue("data{$dataAttributeNamespace}-toggle", "tooltip"); 27104fd306cSNickeau $outputAttributes->addOutputAttributeValue("data{$dataAttributeNamespace}-placement", "top"); 27204fd306cSNickeau $outputAttributes->addOutputAttributeValue("data{$dataAttributeNamespace}-html", "true"); 27304fd306cSNickeau $outputAttributes->addOutputAttributeValue("title", $tooltipHtml); 27404fd306cSNickeau } 27504fd306cSNickeau 27604fd306cSNickeau /** 27704fd306cSNickeau * Low quality Page 27804fd306cSNickeau * (It has a higher priority than preview and 27904fd306cSNickeau * the code comes then after) 28004fd306cSNickeau */ 28104fd306cSNickeau if ($page->isLowQualityPage()) { 28204fd306cSNickeau 28304fd306cSNickeau /** 28404fd306cSNickeau * Add a class to style it differently 28504fd306cSNickeau * (the acronym is added to the description, later) 28604fd306cSNickeau */ 28704fd306cSNickeau $acronym = LowQualityPage::LOW_QUALITY_PROTECTION_ACRONYM; 28804fd306cSNickeau $lowerCaseLowQualityAcronym = strtolower(LowQualityPage::LOW_QUALITY_PROTECTION_ACRONYM); 28904fd306cSNickeau $outputAttributes->addClassName(StyleAttribute::addComboStrapSuffix(LowQualityPage::CLASS_SUFFIX)); 29004fd306cSNickeau $snippetLowQualityPageId = $lowerCaseLowQualityAcronym; 29104fd306cSNickeau $snippetSystem->attachCssInternalStyleSheet($snippetLowQualityPageId); 29204fd306cSNickeau /** 29304fd306cSNickeau * Note The protection does occur on Javascript level, not on the HTML 29404fd306cSNickeau * because the created page is valid for a anonymous or logged-in user 29504fd306cSNickeau * Javascript is controlling 29604fd306cSNickeau */ 29704fd306cSNickeau if (LowQualityPage::isProtectionEnabled()) { 29804fd306cSNickeau 29904fd306cSNickeau $linkType = LowQualityPage::getLowQualityLinkType(); 30004fd306cSNickeau $outputAttributes->addOutputAttributeValue(PageProtection::DATA_PP_LINK, $linkType); 30104fd306cSNickeau $outputAttributes->addOutputAttributeValue(PageProtection::DATA_PP_SOURCE, $lowerCaseLowQualityAcronym); 30204fd306cSNickeau 30304fd306cSNickeau /** 30404fd306cSNickeau * Low Quality Page protection javascript is only for warning or login link 30504fd306cSNickeau */ 30604fd306cSNickeau if (in_array($linkType, [PageProtection::PAGE_PROTECTION_LINK_WARNING, PageProtection::PAGE_PROTECTION_LINK_LOGIN])) { 30704fd306cSNickeau PageProtection::addPageProtectionSnippet(); 30804fd306cSNickeau } 30904fd306cSNickeau 31004fd306cSNickeau } 31104fd306cSNickeau } 31204fd306cSNickeau 31304fd306cSNickeau /** 31404fd306cSNickeau * Late publication has a higher priority than 31504fd306cSNickeau * the late publication and the is therefore after 31604fd306cSNickeau * (In case this a low quality page late published) 31704fd306cSNickeau */ 31804fd306cSNickeau if ($page->isLatePublication()) { 31904fd306cSNickeau /** 32004fd306cSNickeau * Add a class to style it differently if needed 32104fd306cSNickeau */ 32204fd306cSNickeau $className = StyleAttribute::addComboStrapSuffix(PagePublicationDate::LATE_PUBLICATION_CLASS_PREFIX_NAME); 32304fd306cSNickeau $outputAttributes->addClassName($className); 32404fd306cSNickeau if (PagePublicationDate::isLatePublicationProtectionEnabled()) { 32504fd306cSNickeau $outputAttributes->removeOutputAttributeIfPresent(PageProtection::DATA_PP_LINK); 32604fd306cSNickeau $outputAttributes->removeOutputAttributeIfPresent(PageProtection::DATA_PP_SOURCE); 32704fd306cSNickeau $outputAttributes->addOutputAttributeValue(PageProtection::DATA_PP_LINK, PageProtection::PAGE_PROTECTION_LINK_LOGIN); 32804fd306cSNickeau $acronym = PagePublicationDate::LATE_PUBLICATION_PROTECTION_ACRONYM; 32904fd306cSNickeau $lowerCaseLatePublicationAcronym = strtolower(PagePublicationDate::LATE_PUBLICATION_PROTECTION_ACRONYM); 33004fd306cSNickeau $outputAttributes->addOutputAttributeValue(PageProtection::DATA_PP_SOURCE, $lowerCaseLatePublicationAcronym); 33104fd306cSNickeau PageProtection::addPageProtectionSnippet(); 33204fd306cSNickeau } 33304fd306cSNickeau 33404fd306cSNickeau } 33504fd306cSNickeau 33604fd306cSNickeau /** 33704fd306cSNickeau * Title (ie tooltip vs title html attribute) 33804fd306cSNickeau */ 33904fd306cSNickeau if (!$outputAttributes->hasAttribute("title")) { 34004fd306cSNickeau 34104fd306cSNickeau $description = PageDescription::createForPage($page)->getValueOrDefault(); 34204fd306cSNickeau if (!empty($acronym)) { 34304fd306cSNickeau $description = $description . " ($acronym)"; 34404fd306cSNickeau } 34504fd306cSNickeau $outputAttributes->addOutputAttributeValue("title", $description); 34604fd306cSNickeau 34704fd306cSNickeau } 34804fd306cSNickeau 34904fd306cSNickeau } 35004fd306cSNickeau 35104fd306cSNickeau break; 35204fd306cSNickeau 35304fd306cSNickeau case MarkupRef::WINDOWS_SHARE_URI: 35404fd306cSNickeau // https://www.dokuwiki.org/config:target 35504fd306cSNickeau $windowsTarget = $conf['target']['windows']; 35604fd306cSNickeau if (!empty($windowsTarget)) { 35704fd306cSNickeau $outputAttributes->addOutputAttributeValue('target', $windowsTarget); 35804fd306cSNickeau } 35904fd306cSNickeau $outputAttributes->addClassName("windows"); 36004fd306cSNickeau break; 36104fd306cSNickeau case MarkupRef::LOCAL_URI: 36204fd306cSNickeau $outputAttributes->addClassName(self::getHtmlClassLocalLink()); 36304fd306cSNickeau if (!$outputAttributes->hasAttribute("title")) { 36404fd306cSNickeau $description = ucfirst($this->markupRef->getUrl()->getFragment()); 36504fd306cSNickeau if ($description !== "") { 36604fd306cSNickeau $description = str_replace("_", " ", $description); 36704fd306cSNickeau $outputAttributes->addOutputAttributeValue("title", $description); 36804fd306cSNickeau } 36904fd306cSNickeau } 37004fd306cSNickeau break; 37104fd306cSNickeau case MarkupRef::EMAIL_URI: 37204fd306cSNickeau $outputAttributes->addClassName(self::getHtmlClassEmailLink()); 37304fd306cSNickeau /** 37404fd306cSNickeau * An email link is `<email>` 37504fd306cSNickeau * {@link Emaillink::connectTo()} 37604fd306cSNickeau * or 37704fd306cSNickeau * {@link PluginTrait::email() 37804fd306cSNickeau */ 37904fd306cSNickeau // common.php#obfsucate implements the $conf['mailguard'] 38004fd306cSNickeau $uri = $url->getPath(); 38104fd306cSNickeau $uri = $this->obfuscateEmail($uri); 38204fd306cSNickeau $uri = urlencode($uri); 38304fd306cSNickeau $queryParameters = $url->getQueryProperties(); 38404fd306cSNickeau if (sizeof($queryParameters) > 0) { 38504fd306cSNickeau $uri .= "?"; 38604fd306cSNickeau foreach ($queryParameters as $key => $value) { 38704fd306cSNickeau $value = urlencode($value); 38804fd306cSNickeau $key = urlencode($key); 38904fd306cSNickeau if (in_array($key, self::EMAIL_VALID_PARAMETERS)) { 39004fd306cSNickeau $uri .= "$key=$value"; 39104fd306cSNickeau } 39204fd306cSNickeau } 39304fd306cSNickeau } 39404fd306cSNickeau // replace href 39504fd306cSNickeau $outputAttributes->removeOutputAttributeIfPresent("href"); 39604fd306cSNickeau $outputAttributes->addOutputAttributeValue("href", 'mailto:' . $uri); 39704fd306cSNickeau break; 39804fd306cSNickeau case MarkupRef::WEB_URI: 39904fd306cSNickeau /** 40004fd306cSNickeau * It may be a absolute url 40104fd306cSNickeau * that points to the local website 40204fd306cSNickeau * (case of the {@link \syntax_plugin_combo_permalink} 40304fd306cSNickeau */ 40404fd306cSNickeau if ($url->isExternal()) { 40504fd306cSNickeau 40604fd306cSNickeau if ($conf['relnofollow']) { 40704fd306cSNickeau $outputAttributes->addOutputAttributeValue("rel", 'nofollow ugc'); 40804fd306cSNickeau } 40904fd306cSNickeau // https://www.dokuwiki.org/config:target 41004fd306cSNickeau $externTarget = $conf['target']['extern']; 41104fd306cSNickeau if (!empty($externTarget)) { 41204fd306cSNickeau $outputAttributes->addOutputAttributeValue('target', $externTarget); 41304fd306cSNickeau $outputAttributes->addOutputAttributeValue("rel", 'noopener'); 41404fd306cSNickeau } 41504fd306cSNickeau /** 41604fd306cSNickeau * Default class for default external link 41704fd306cSNickeau * To not interfere with other external link style 41804fd306cSNickeau * For instance, {@link \syntax_plugin_combo_share} 41904fd306cSNickeau */ 42004fd306cSNickeau $outputAttributes->addClassName(self::getHtmlClassExternalLink()); 42104fd306cSNickeau } 42204fd306cSNickeau break; 42304fd306cSNickeau default: 42404fd306cSNickeau /** 42504fd306cSNickeau * May be any external link 42604fd306cSNickeau * such as {@link \syntax_plugin_combo_share} 42704fd306cSNickeau */ 42804fd306cSNickeau break; 42904fd306cSNickeau 43004fd306cSNickeau } 43104fd306cSNickeau 43204fd306cSNickeau /** 43304fd306cSNickeau * An email URL and title 43404fd306cSNickeau * may be already encoded because of the vanguard configuration 43504fd306cSNickeau * 43604fd306cSNickeau * The url is not treated as an attribute 43704fd306cSNickeau * because the transformation function encodes the value 43804fd306cSNickeau * to mitigate XSS 43904fd306cSNickeau * 44004fd306cSNickeau */ 44104fd306cSNickeau if ($this->getMarkupRef()->getSchemeType() == MarkupRef::EMAIL_URI) { 44204fd306cSNickeau $emailAddress = $this->obfuscateEmail($this->markupRef->getUrl()->getPath()); 44304fd306cSNickeau $outputAttributes->addOutputAttributeValue("title", $emailAddress); 44404fd306cSNickeau } 44504fd306cSNickeau 44604fd306cSNickeau 44704fd306cSNickeau /** 44804fd306cSNickeau * Return 44904fd306cSNickeau */ 45004fd306cSNickeau return $outputAttributes; 45104fd306cSNickeau 45204fd306cSNickeau 45304fd306cSNickeau } 45404fd306cSNickeau 45504fd306cSNickeau 45604fd306cSNickeau /** 45704fd306cSNickeau * The label inside the anchor tag if there is none 45804fd306cSNickeau * @param false $navigation 45904fd306cSNickeau * @return string 46004fd306cSNickeau * @throws ExceptionNotFound|ExceptionBadArgument 46104fd306cSNickeau * 46204fd306cSNickeau */ 46304fd306cSNickeau public 46404fd306cSNickeau function getDefaultLabel(bool $navigation = false): string 46504fd306cSNickeau { 46604fd306cSNickeau 46704fd306cSNickeau switch ($this->getMarkupRef()->getSchemeType()) { 46804fd306cSNickeau case MarkupRef::WIKI_URI: 46904fd306cSNickeau $page = $this->getPage(); 47004fd306cSNickeau if ($navigation) { 47104fd306cSNickeau return ResourceName::createForResource($page)->getValueOrDefault(); 47204fd306cSNickeau } else { 47304fd306cSNickeau return PageTitle::createForMarkup($page)->getValueOrDefault(); 47404fd306cSNickeau } 47504fd306cSNickeau case MarkupRef::EMAIL_URI: 47604fd306cSNickeau global $conf; 47704fd306cSNickeau $email = $this->markupRef->getUrl()->getPath(); 47804fd306cSNickeau switch ($conf['mailguard']) { 47904fd306cSNickeau case 'none' : 48004fd306cSNickeau return $email; 48104fd306cSNickeau case 'visible' : 48204fd306cSNickeau default : 48304fd306cSNickeau $obfuscate = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] '); 48404fd306cSNickeau return strtr($email, $obfuscate); 48504fd306cSNickeau } 48604fd306cSNickeau case MarkupRef::INTERWIKI_URI: 48704fd306cSNickeau try { 48804fd306cSNickeau $path = $this->markupRef->getInterWiki()->toUrl()->getPath(); 48904fd306cSNickeau if ($path[0] === "/") { 49004fd306cSNickeau return substr($path, 1); 49104fd306cSNickeau } else { 49204fd306cSNickeau return $path; 49304fd306cSNickeau } 49404fd306cSNickeau } catch (ExceptionBadSyntax|ExceptionNotFound $e) { 49504fd306cSNickeau return "interwiki"; 49604fd306cSNickeau } 49704fd306cSNickeau case MarkupRef::LOCAL_URI: 49804fd306cSNickeau return $this->markupRef->getUrl()->getFragment(); 49904fd306cSNickeau default: 50004fd306cSNickeau return $this->markupRef->getRef(); 50104fd306cSNickeau } 50204fd306cSNickeau } 50304fd306cSNickeau 50404fd306cSNickeau 50504fd306cSNickeau private 50604fd306cSNickeau function obfuscateEmail($email, $inAttribute = true): string 50704fd306cSNickeau { 50804fd306cSNickeau /** 50904fd306cSNickeau * adapted from {@link obfuscate()} in common.php 51004fd306cSNickeau */ 51104fd306cSNickeau global $conf; 51204fd306cSNickeau 51304fd306cSNickeau $mailGuard = $conf['mailguard']; 51404fd306cSNickeau if ($mailGuard === "hex" && $inAttribute) { 51504fd306cSNickeau $mailGuard = "visible"; 51604fd306cSNickeau } 51704fd306cSNickeau switch ($mailGuard) { 51804fd306cSNickeau case 'visible' : 51904fd306cSNickeau $obfuscate = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] '); 52004fd306cSNickeau return strtr($email, $obfuscate); 52104fd306cSNickeau 52204fd306cSNickeau case 'hex' : 52304fd306cSNickeau return Conversion::toHtml($email, true); 52404fd306cSNickeau 52504fd306cSNickeau case 'none' : 52604fd306cSNickeau default : 52704fd306cSNickeau return $email; 52804fd306cSNickeau } 52904fd306cSNickeau } 53004fd306cSNickeau 53104fd306cSNickeau 53204fd306cSNickeau /** 53304fd306cSNickeau * @return bool 53404fd306cSNickeau * @deprecated should not be here ref does not have the notion of relative 53504fd306cSNickeau */ 53604fd306cSNickeau public 53704fd306cSNickeau function isRelative(): bool 53804fd306cSNickeau { 53904fd306cSNickeau return strpos($this->getMarkupRef()->getRef(), WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT) !== 0; 54004fd306cSNickeau } 54104fd306cSNickeau 54204fd306cSNickeau public 54304fd306cSNickeau function getMarkupRef(): MarkupRef 54404fd306cSNickeau { 54504fd306cSNickeau return $this->markupRef; 54604fd306cSNickeau } 54704fd306cSNickeau 54804fd306cSNickeau 54904fd306cSNickeau public 55004fd306cSNickeau static function getHtmlClassInternalLink(): string 55104fd306cSNickeau { 55204fd306cSNickeau $oldClassName = SiteConfig::getConfValue(self::CONF_USE_DOKUWIKI_CLASS_NAME); 55304fd306cSNickeau if ($oldClassName) { 55404fd306cSNickeau return "wikilink1"; 55504fd306cSNickeau } else { 55604fd306cSNickeau return "link-internal"; 55704fd306cSNickeau } 55804fd306cSNickeau } 55904fd306cSNickeau 56004fd306cSNickeau public 56104fd306cSNickeau static function getHtmlClassEmailLink(): string 56204fd306cSNickeau { 56304fd306cSNickeau $oldClassName = SiteConfig::getConfValue(self::CONF_USE_DOKUWIKI_CLASS_NAME); 56404fd306cSNickeau if ($oldClassName) { 56504fd306cSNickeau return "mail"; 56604fd306cSNickeau } else { 56704fd306cSNickeau return "link-mail"; 56804fd306cSNickeau } 56904fd306cSNickeau } 57004fd306cSNickeau 57104fd306cSNickeau public static function getHtmlClassExternalLink(): string 57204fd306cSNickeau { 57304fd306cSNickeau $oldClassName = SiteConfig::getConfValue(self::CONF_USE_DOKUWIKI_CLASS_NAME); 57404fd306cSNickeau if ($oldClassName) { 57504fd306cSNickeau return "urlextern"; 57604fd306cSNickeau } else { 57704fd306cSNickeau return "link-external"; 57804fd306cSNickeau } 57904fd306cSNickeau } 58004fd306cSNickeau 58104fd306cSNickeau//FYI: exist in dokuwiki is "wikilink1 but we let the control to the user 58204fd306cSNickeau public 58304fd306cSNickeau static function getHtmlClassNotExist(): string 58404fd306cSNickeau { 58504fd306cSNickeau $oldClassName = SiteConfig::getConfValue(self::CONF_USE_DOKUWIKI_CLASS_NAME); 58604fd306cSNickeau if ($oldClassName) { 58704fd306cSNickeau return "wikilink2"; 58804fd306cSNickeau } else { 58904fd306cSNickeau return self::TEXT_ERROR_CLASS; 59004fd306cSNickeau } 59104fd306cSNickeau } 59204fd306cSNickeau 59304fd306cSNickeau public 59404fd306cSNickeau function __toString() 59504fd306cSNickeau { 59604fd306cSNickeau return $this->getMarkupRef()->getRef(); 59704fd306cSNickeau } 59804fd306cSNickeau 59904fd306cSNickeau 60004fd306cSNickeau /** 60104fd306cSNickeau * @throws ExceptionNotFound 60204fd306cSNickeau */ 60304fd306cSNickeau private 60404fd306cSNickeau function getPage(): MarkupPath 60504fd306cSNickeau { 60604fd306cSNickeau return MarkupPath::createPageFromPathObject($this->getMarkupRef()->getPath()); 60704fd306cSNickeau } 60804fd306cSNickeau 60904fd306cSNickeau /** 61004fd306cSNickeau * Styling attribute 61104fd306cSNickeau * may be passed via parameters 61204fd306cSNickeau * for internal link 61304fd306cSNickeau * We don't want the styling attribute 61404fd306cSNickeau * in the URL 61504fd306cSNickeau */ 61604fd306cSNickeau private 61704fd306cSNickeau function collectStylingAttributeInUrl() 61804fd306cSNickeau { 61904fd306cSNickeau 62004fd306cSNickeau 62104fd306cSNickeau /** 62204fd306cSNickeau * We will not overwrite the parameters if this is an dokuwiki 62304fd306cSNickeau * action link (with the `do` property) 62404fd306cSNickeau */ 62504fd306cSNickeau if ($this->markupRef->getUrl()->hasProperty("do")) { 62604fd306cSNickeau return; 62704fd306cSNickeau } 62804fd306cSNickeau 62904fd306cSNickeau /** 63004fd306cSNickeau * Add the attribute from the URL 63104fd306cSNickeau * if this is not a `do` 63204fd306cSNickeau */ 63304fd306cSNickeau switch ($this->markupRef->getSchemeType()) { 63404fd306cSNickeau case MarkupRef::WIKI_URI: 63504fd306cSNickeau foreach ($this->getMarkupRef()->getUrl()->getQueryProperties() as $key => $value) { 63604fd306cSNickeau if (!in_array($key, self::PROTECTED_URL_PROPERTY)) { 63704fd306cSNickeau $this->getMarkupRef()->getUrl()->deleteQueryParameter($key); 63804fd306cSNickeau if (!TagAttributes::isEmptyValue($value)) { 63904fd306cSNickeau $this->stylingAttributes->addComponentAttributeValue($key, $value); 64004fd306cSNickeau } else { 64104fd306cSNickeau $this->stylingAttributes->addEmptyComponentAttributeValue($key); 64204fd306cSNickeau } 64304fd306cSNickeau } 64404fd306cSNickeau } 64504fd306cSNickeau break; 64604fd306cSNickeau case 64704fd306cSNickeau MarkupRef::EMAIL_URI: 64804fd306cSNickeau foreach ($this->getMarkupRef()->getUrl()->getQueryProperties() as $key => $value) { 64904fd306cSNickeau if (!in_array($key, self::EMAIL_VALID_PARAMETERS)) { 65004fd306cSNickeau $this->stylingAttributes->addComponentAttributeValue($key, $value); 65104fd306cSNickeau } 65204fd306cSNickeau } 65304fd306cSNickeau break; 65404fd306cSNickeau } 65504fd306cSNickeau 65604fd306cSNickeau } 65704fd306cSNickeau 65804fd306cSNickeau /** 65904fd306cSNickeau * @return TagAttributes - the unknown attributes in a url are collected as styling attributes if this not a do query 66004fd306cSNickeau * by {@link LinkMarkup::collectStylingAttributeInUrl()} 66104fd306cSNickeau */ 66204fd306cSNickeau public 66304fd306cSNickeau function getStylingAttributes(): TagAttributes 66404fd306cSNickeau { 66504fd306cSNickeau return $this->stylingAttributes; 66604fd306cSNickeau } 66704fd306cSNickeau 66804fd306cSNickeau 66904fd306cSNickeau} 670