1*04fd306cSNickeau<?php 2*04fd306cSNickeau/** 3*04fd306cSNickeau * Copyright (c) 2020. ComboStrap, Inc. and its affiliates. All Rights Reserved. 4*04fd306cSNickeau * 5*04fd306cSNickeau * This source code is licensed under the GPL license found in the 6*04fd306cSNickeau * COPYING file in the root directory of this source tree. 7*04fd306cSNickeau * 8*04fd306cSNickeau * @license GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html) 9*04fd306cSNickeau * @author ComboStrap <support@combostrap.com> 10*04fd306cSNickeau * 11*04fd306cSNickeau */ 12*04fd306cSNickeau 13*04fd306cSNickeaunamespace ComboStrap; 14*04fd306cSNickeau 15*04fd306cSNickeau 16*04fd306cSNickeauuse ComboStrap\Meta\Field\PageTemplateName; 17*04fd306cSNickeauuse ComboStrap\TagAttribute\StyleAttribute; 18*04fd306cSNickeauuse Doku_Renderer_xhtml; 19*04fd306cSNickeauuse dokuwiki\Extension\PluginTrait; 20*04fd306cSNickeauuse dokuwiki\Utf8\Conversion; 21*04fd306cSNickeauuse syntax_plugin_combo_link; 22*04fd306cSNickeau 23*04fd306cSNickeau 24*04fd306cSNickeau/** 25*04fd306cSNickeau * 26*04fd306cSNickeau * @package ComboStrap 27*04fd306cSNickeau * 28*04fd306cSNickeau * Parse the ref found in a markup link 29*04fd306cSNickeau * and return an {@link LinkMarkup::toAttributes()} array for an anchor (a) 30*04fd306cSNickeau * with href, style, ... attributes 31*04fd306cSNickeau * 32*04fd306cSNickeau */ 33*04fd306cSNickeauclass LinkMarkup 34*04fd306cSNickeau{ 35*04fd306cSNickeau 36*04fd306cSNickeau 37*04fd306cSNickeau /** 38*04fd306cSNickeau * Class added to the type of link 39*04fd306cSNickeau * Class have styling rule conflict, they are by default not set 40*04fd306cSNickeau * but this configuration permits to turn it back 41*04fd306cSNickeau */ 42*04fd306cSNickeau const CONF_USE_DOKUWIKI_CLASS_NAME = "useDokuwikiLinkClassName"; 43*04fd306cSNickeau 44*04fd306cSNickeau /** 45*04fd306cSNickeau * This configuration will set for all internal link 46*04fd306cSNickeau * the {@link LinkMarkup::PREVIEW_ATTRIBUTE} preview attribute 47*04fd306cSNickeau */ 48*04fd306cSNickeau const CONF_PREVIEW_LINK = "previewLink"; 49*04fd306cSNickeau const CONF_PREVIEW_LINK_DEFAULT = 0; 50*04fd306cSNickeau 51*04fd306cSNickeau 52*04fd306cSNickeau const TEXT_ERROR_CLASS = "text-danger"; 53*04fd306cSNickeau 54*04fd306cSNickeau /** 55*04fd306cSNickeau * The known parameters for an email url 56*04fd306cSNickeau * The other are styling attribute :) 57*04fd306cSNickeau */ 58*04fd306cSNickeau const EMAIL_VALID_PARAMETERS = ["subject"]; 59*04fd306cSNickeau 60*04fd306cSNickeau /** 61*04fd306cSNickeau * If set, it will show a page preview 62*04fd306cSNickeau */ 63*04fd306cSNickeau const PREVIEW_ATTRIBUTE = "preview"; 64*04fd306cSNickeau 65*04fd306cSNickeau 66*04fd306cSNickeau /** 67*04fd306cSNickeau * Highlight Key 68*04fd306cSNickeau * Adding this property to the internal query will highlight the words 69*04fd306cSNickeau * 70*04fd306cSNickeau * See {@link html_hilight} 71*04fd306cSNickeau */ 72*04fd306cSNickeau const SEARCH_HIGHLIGHT_QUERY_PROPERTY = "s"; 73*04fd306cSNickeau const DATA_WIKI_ID = "data-wiki-id"; 74*04fd306cSNickeau 75*04fd306cSNickeau /** 76*04fd306cSNickeau * For styling on the anchor tag (ie a) 77*04fd306cSNickeau */ 78*04fd306cSNickeau public const ANCHOR_HTML_SNIPPET_ID = "anchor-branding"; 79*04fd306cSNickeau 80*04fd306cSNickeau /** 81*04fd306cSNickeau * Url properties 82*04fd306cSNickeau * that are not seen as styling properties 83*04fd306cSNickeau * for a page 84*04fd306cSNickeau * We could also build the {@link FetcherPage} 85*04fd306cSNickeau * and see which attributes were not taken ? 86*04fd306cSNickeau */ 87*04fd306cSNickeau const PROTECTED_URL_PROPERTY = [ 88*04fd306cSNickeau self::SEARCH_HIGHLIGHT_QUERY_PROPERTY, 89*04fd306cSNickeau DokuWikiId::DOKUWIKI_ID_ATTRIBUTE, 90*04fd306cSNickeau PageTemplateName::PROPERTY_NAME, 91*04fd306cSNickeau FetcherPage::PURGE 92*04fd306cSNickeau ]; 93*04fd306cSNickeau 94*04fd306cSNickeau 95*04fd306cSNickeau private MarkupRef $markupRef; 96*04fd306cSNickeau 97*04fd306cSNickeau 98*04fd306cSNickeau private TagAttributes $stylingAttributes; 99*04fd306cSNickeau 100*04fd306cSNickeau /** 101*04fd306cSNickeau * Link constructor. 102*04fd306cSNickeau * @param String $ref 103*04fd306cSNickeau * @throws ExceptionBadArgument 104*04fd306cSNickeau * @throws ExceptionBadSyntax 105*04fd306cSNickeau * @throws ExceptionNotFound 106*04fd306cSNickeau */ 107*04fd306cSNickeau public function __construct(string $ref) 108*04fd306cSNickeau { 109*04fd306cSNickeau 110*04fd306cSNickeau $this->stylingAttributes = TagAttributes::createEmpty(syntax_plugin_combo_link::TAG); 111*04fd306cSNickeau 112*04fd306cSNickeau $this->markupRef = MarkupRef::createLinkFromRef($ref); 113*04fd306cSNickeau 114*04fd306cSNickeau $this->collectStylingAttributeInUrl(); 115*04fd306cSNickeau 116*04fd306cSNickeau 117*04fd306cSNickeau } 118*04fd306cSNickeau 119*04fd306cSNickeau public static function createFromPageIdOrPath($id): LinkMarkup 120*04fd306cSNickeau { 121*04fd306cSNickeau WikiPath::addRootSeparatorIfNotPresent($id); 122*04fd306cSNickeau try { 123*04fd306cSNickeau return new LinkMarkup($id); 124*04fd306cSNickeau } catch (ExceptionBadArgument|ExceptionBadSyntax|ExceptionNotFound $e) { 125*04fd306cSNickeau throw new ExceptionRuntime("Internal error: an id should be a good reference"); 126*04fd306cSNickeau } 127*04fd306cSNickeau } 128*04fd306cSNickeau 129*04fd306cSNickeau /** 130*04fd306cSNickeau * @throws ExceptionBadArgument 131*04fd306cSNickeau * @throws ExceptionBadSyntax 132*04fd306cSNickeau * @throws ExceptionNotFound 133*04fd306cSNickeau */ 134*04fd306cSNickeau public static function createFromRef(string $ref): LinkMarkup 135*04fd306cSNickeau { 136*04fd306cSNickeau return new LinkMarkup($ref); 137*04fd306cSNickeau } 138*04fd306cSNickeau 139*04fd306cSNickeau public static function getHtmlClassLocalLink(): string 140*04fd306cSNickeau { 141*04fd306cSNickeau return "link-local"; 142*04fd306cSNickeau } 143*04fd306cSNickeau 144*04fd306cSNickeau 145*04fd306cSNickeau /** 146*04fd306cSNickeau * 147*04fd306cSNickeau * @throws ExceptionNotFound 148*04fd306cSNickeau */ 149*04fd306cSNickeau public function toAttributes(): TagAttributes 150*04fd306cSNickeau { 151*04fd306cSNickeau 152*04fd306cSNickeau $outputAttributes = $this->stylingAttributes; 153*04fd306cSNickeau 154*04fd306cSNickeau 155*04fd306cSNickeau $url = $this->getMarkupRef()->getUrl(); 156*04fd306cSNickeau $outputAttributes->addOutputAttributeValue("href", $url->toString()); 157*04fd306cSNickeau 158*04fd306cSNickeau /** 159*04fd306cSNickeau * The search term 160*04fd306cSNickeau * Code adapted found at {@link Doku_Renderer_xhtml::internallink()} 161*04fd306cSNickeau * We can't use the previous {@link wl function} 162*04fd306cSNickeau * because it encode too much 163*04fd306cSNickeau */ 164*04fd306cSNickeau $snippetSystem = PluginUtility::getSnippetManager(); 165*04fd306cSNickeau if ($url->hasProperty(self::SEARCH_HIGHLIGHT_QUERY_PROPERTY)) { 166*04fd306cSNickeau $snippetSystem->attachCssInternalStyleSheet("search-hit"); 167*04fd306cSNickeau } 168*04fd306cSNickeau 169*04fd306cSNickeau /** 170*04fd306cSNickeau * Default Link Color 171*04fd306cSNickeau * Saturation and lightness comes from the 172*04fd306cSNickeau * Note: 173*04fd306cSNickeau * * blue color of Bootstrap #0d6efd s: 98, l: 52 174*04fd306cSNickeau * * blue color of twitter #1d9bf0 s: 88, l: 53 175*04fd306cSNickeau * * reddit gray with s: 16, l : 31 176*04fd306cSNickeau * * the text is s: 11, l: 15 177*04fd306cSNickeau * We choose the gray/tone rendering to be close to black 178*04fd306cSNickeau * the color of the text 179*04fd306cSNickeau */ 180*04fd306cSNickeau try { 181*04fd306cSNickeau $primaryColor = ExecutionContext::getActualOrCreateFromEnv()->getConfig()->getPrimaryColor(); 182*04fd306cSNickeau } catch (ExceptionNotFound $e) { 183*04fd306cSNickeau $primaryColor = null; 184*04fd306cSNickeau } 185*04fd306cSNickeau if (Site::isBrandingColorInheritanceEnabled() && $primaryColor !== null) { 186*04fd306cSNickeau 187*04fd306cSNickeau $primaryColorText = ColorSystem::toTextColor($primaryColor); 188*04fd306cSNickeau $primaryColorHoverText = ColorSystem::toTextHoverColor($primaryColor); 189*04fd306cSNickeau /** 190*04fd306cSNickeau * There is also a link primary 191*04fd306cSNickeau * https://getbootstrap.com/docs/5.2/helpers/colored-links/ 192*04fd306cSNickeau */ 193*04fd306cSNickeau $aCss = <<<EOF 194*04fd306cSNickeau.link-primary { color: {$primaryColorText->toRgbHex()}; } 195*04fd306cSNickeau.link-primary:hover { color: {$primaryColorHoverText->toRgbHex()}; } 196*04fd306cSNickeaumain a { color: {$primaryColorText->toRgbHex()}; } 197*04fd306cSNickeaumain a:hover { color: {$primaryColorHoverText->toRgbHex()}; } 198*04fd306cSNickeauEOF; 199*04fd306cSNickeau SnippetSystem::getFromContext()->attachCssInternalStylesheet(self::ANCHOR_HTML_SNIPPET_ID, $aCss); 200*04fd306cSNickeau 201*04fd306cSNickeau } 202*04fd306cSNickeau 203*04fd306cSNickeau 204*04fd306cSNickeau global $conf; 205*04fd306cSNickeau 206*04fd306cSNickeau 207*04fd306cSNickeau /** 208*04fd306cSNickeau * Processing by type 209*04fd306cSNickeau */ 210*04fd306cSNickeau switch ($this->getMarkupRef()->getSchemeType()) { 211*04fd306cSNickeau case MarkupRef::INTERWIKI_URI: 212*04fd306cSNickeau try { 213*04fd306cSNickeau $interWiki = $this->getMarkupRef()->getInterWiki(); 214*04fd306cSNickeau } catch (ExceptionNotFound $e) { 215*04fd306cSNickeau LogUtility::internalError("The interwiki should be available. We were unable to create the link attributes."); 216*04fd306cSNickeau return $outputAttributes; 217*04fd306cSNickeau } 218*04fd306cSNickeau // normal link for the `this` wiki 219*04fd306cSNickeau if ($interWiki->getWiki() !== "this") { 220*04fd306cSNickeau $snippetSystem->attachCssInternalStyleSheet(MarkupRef::INTERWIKI_URI); 221*04fd306cSNickeau } 222*04fd306cSNickeau $cssRules = $interWiki->getDefaultCssRules(); 223*04fd306cSNickeau $snippetSystem->attachCssInternalStyleSheet(MarkupRef::INTERWIKI_URI, $cssRules); 224*04fd306cSNickeau try { 225*04fd306cSNickeau $cssRules = $interWiki->getSpecificCssRules(); 226*04fd306cSNickeau $snippetSystem->attachCssInternalStyleSheet(MarkupRef::INTERWIKI_URI . "-" . $interWiki->getWiki(), $cssRules); 227*04fd306cSNickeau } catch (ExceptionNotFound $e) { 228*04fd306cSNickeau // no media find for the wiki 229*04fd306cSNickeau } 230*04fd306cSNickeau /** 231*04fd306cSNickeau * Target 232*04fd306cSNickeau */ 233*04fd306cSNickeau $interWikiConf = $conf['target']['interwiki']; 234*04fd306cSNickeau if (!empty($interWikiConf)) { 235*04fd306cSNickeau $outputAttributes->addOutputAttributeValue('target', $interWikiConf); 236*04fd306cSNickeau $outputAttributes->addOutputAttributeValue('rel', 'noopener'); 237*04fd306cSNickeau } 238*04fd306cSNickeau $outputAttributes->addClassName($interWiki->getComponentClass()); 239*04fd306cSNickeau $outputAttributes->addClassName($interWiki->getSubComponentClass()); 240*04fd306cSNickeau break; 241*04fd306cSNickeau case MarkupRef::WIKI_URI: 242*04fd306cSNickeau /** 243*04fd306cSNickeau * Derived from {@link Doku_Renderer_xhtml::internallink()} 244*04fd306cSNickeau */ 245*04fd306cSNickeau // https://www.dokuwiki.org/config:target 246*04fd306cSNickeau $target = $conf['target']['wiki']; 247*04fd306cSNickeau if (!empty($target)) { 248*04fd306cSNickeau $outputAttributes->addOutputAttributeValue('target', $target); 249*04fd306cSNickeau } 250*04fd306cSNickeau /** 251*04fd306cSNickeau * Internal Page 252*04fd306cSNickeau */ 253*04fd306cSNickeau try { 254*04fd306cSNickeau $dokuPath = $this->getMarkupRef()->getPath(); 255*04fd306cSNickeau } catch (ExceptionNotFound $e) { 256*04fd306cSNickeau throw new ExceptionNotFound("We were unable to process the internal link dokuwiki id on the link. The path was not found. Error: {$e->getMessage()}"); 257*04fd306cSNickeau } 258*04fd306cSNickeau $page = MarkupPath::createPageFromPathObject($dokuPath); 259*04fd306cSNickeau $outputAttributes->addOutputAttributeValue(self::DATA_WIKI_ID, $dokuPath->getWikiId()); 260*04fd306cSNickeau 261*04fd306cSNickeau 262*04fd306cSNickeau if (!FileSystems::exists($dokuPath)) { 263*04fd306cSNickeau 264*04fd306cSNickeau /** 265*04fd306cSNickeau * Red color 266*04fd306cSNickeau * if not `do=edit` 267*04fd306cSNickeau */ 268*04fd306cSNickeau if (!$this->markupRef->getUrl()->hasProperty("do")) { 269*04fd306cSNickeau $outputAttributes->addClassName(self::getHtmlClassNotExist()); 270*04fd306cSNickeau $outputAttributes->addOutputAttributeValue("rel", 'nofollow'); 271*04fd306cSNickeau } 272*04fd306cSNickeau 273*04fd306cSNickeau } else { 274*04fd306cSNickeau 275*04fd306cSNickeau /** 276*04fd306cSNickeau * Internal Link Class 277*04fd306cSNickeau */ 278*04fd306cSNickeau $outputAttributes->addClassName(self::getHtmlClassInternalLink()); 279*04fd306cSNickeau 280*04fd306cSNickeau /** 281*04fd306cSNickeau * Link Creation 282*04fd306cSNickeau * Do we need to set the title or the tooltip 283*04fd306cSNickeau * Processing variables 284*04fd306cSNickeau */ 285*04fd306cSNickeau $acronym = ""; 286*04fd306cSNickeau 287*04fd306cSNickeau /** 288*04fd306cSNickeau * Preview tooltip 289*04fd306cSNickeau */ 290*04fd306cSNickeau $previewConfig = SiteConfig::getConfValue(self::CONF_PREVIEW_LINK, self::CONF_PREVIEW_LINK_DEFAULT); 291*04fd306cSNickeau $preview = $outputAttributes->hasComponentAttributeAndRemove(self::PREVIEW_ATTRIBUTE); 292*04fd306cSNickeau if ($preview || $previewConfig === 1) { 293*04fd306cSNickeau Tooltip::addToolTipSnippetIfNeeded(); 294*04fd306cSNickeau // We use as heading, the name and not the title of the resource because otherwise it would be to lengthy 295*04fd306cSNickeau $tooltipHtml = <<<EOF 296*04fd306cSNickeau<h3>{$page->getNameOrDefault()}</h3> 297*04fd306cSNickeau<p>{$page->getDescriptionOrElseDokuWiki()}</p> 298*04fd306cSNickeauEOF; 299*04fd306cSNickeau $dataAttributeNamespace = Bootstrap::getDataNamespace(); 300*04fd306cSNickeau $outputAttributes->addOutputAttributeValue("data{$dataAttributeNamespace}-toggle", "tooltip"); 301*04fd306cSNickeau $outputAttributes->addOutputAttributeValue("data{$dataAttributeNamespace}-placement", "top"); 302*04fd306cSNickeau $outputAttributes->addOutputAttributeValue("data{$dataAttributeNamespace}-html", "true"); 303*04fd306cSNickeau $outputAttributes->addOutputAttributeValue("title", $tooltipHtml); 304*04fd306cSNickeau } 305*04fd306cSNickeau 306*04fd306cSNickeau /** 307*04fd306cSNickeau * Low quality Page 308*04fd306cSNickeau * (It has a higher priority than preview and 309*04fd306cSNickeau * the code comes then after) 310*04fd306cSNickeau */ 311*04fd306cSNickeau if ($page->isLowQualityPage()) { 312*04fd306cSNickeau 313*04fd306cSNickeau /** 314*04fd306cSNickeau * Add a class to style it differently 315*04fd306cSNickeau * (the acronym is added to the description, later) 316*04fd306cSNickeau */ 317*04fd306cSNickeau $acronym = LowQualityPage::LOW_QUALITY_PROTECTION_ACRONYM; 318*04fd306cSNickeau $lowerCaseLowQualityAcronym = strtolower(LowQualityPage::LOW_QUALITY_PROTECTION_ACRONYM); 319*04fd306cSNickeau $outputAttributes->addClassName(StyleAttribute::addComboStrapSuffix(LowQualityPage::CLASS_SUFFIX)); 320*04fd306cSNickeau $snippetLowQualityPageId = $lowerCaseLowQualityAcronym; 321*04fd306cSNickeau $snippetSystem->attachCssInternalStyleSheet($snippetLowQualityPageId); 322*04fd306cSNickeau /** 323*04fd306cSNickeau * Note The protection does occur on Javascript level, not on the HTML 324*04fd306cSNickeau * because the created page is valid for a anonymous or logged-in user 325*04fd306cSNickeau * Javascript is controlling 326*04fd306cSNickeau */ 327*04fd306cSNickeau if (LowQualityPage::isProtectionEnabled()) { 328*04fd306cSNickeau 329*04fd306cSNickeau $linkType = LowQualityPage::getLowQualityLinkType(); 330*04fd306cSNickeau $outputAttributes->addOutputAttributeValue(PageProtection::DATA_PP_LINK, $linkType); 331*04fd306cSNickeau $outputAttributes->addOutputAttributeValue(PageProtection::DATA_PP_SOURCE, $lowerCaseLowQualityAcronym); 332*04fd306cSNickeau 333*04fd306cSNickeau /** 334*04fd306cSNickeau * Low Quality Page protection javascript is only for warning or login link 335*04fd306cSNickeau */ 336*04fd306cSNickeau if (in_array($linkType, [PageProtection::PAGE_PROTECTION_LINK_WARNING, PageProtection::PAGE_PROTECTION_LINK_LOGIN])) { 337*04fd306cSNickeau PageProtection::addPageProtectionSnippet(); 338*04fd306cSNickeau } 339*04fd306cSNickeau 340*04fd306cSNickeau } 341*04fd306cSNickeau } 342*04fd306cSNickeau 343*04fd306cSNickeau /** 344*04fd306cSNickeau * Late publication has a higher priority than 345*04fd306cSNickeau * the late publication and the is therefore after 346*04fd306cSNickeau * (In case this a low quality page late published) 347*04fd306cSNickeau */ 348*04fd306cSNickeau if ($page->isLatePublication()) { 349*04fd306cSNickeau /** 350*04fd306cSNickeau * Add a class to style it differently if needed 351*04fd306cSNickeau */ 352*04fd306cSNickeau $className = StyleAttribute::addComboStrapSuffix(PagePublicationDate::LATE_PUBLICATION_CLASS_PREFIX_NAME); 353*04fd306cSNickeau $outputAttributes->addClassName($className); 354*04fd306cSNickeau if (PagePublicationDate::isLatePublicationProtectionEnabled()) { 355*04fd306cSNickeau $outputAttributes->removeOutputAttributeIfPresent(PageProtection::DATA_PP_LINK); 356*04fd306cSNickeau $outputAttributes->removeOutputAttributeIfPresent(PageProtection::DATA_PP_SOURCE); 357*04fd306cSNickeau $outputAttributes->addOutputAttributeValue(PageProtection::DATA_PP_LINK, PageProtection::PAGE_PROTECTION_LINK_LOGIN); 358*04fd306cSNickeau $acronym = PagePublicationDate::LATE_PUBLICATION_PROTECTION_ACRONYM; 359*04fd306cSNickeau $lowerCaseLatePublicationAcronym = strtolower(PagePublicationDate::LATE_PUBLICATION_PROTECTION_ACRONYM); 360*04fd306cSNickeau $outputAttributes->addOutputAttributeValue(PageProtection::DATA_PP_SOURCE, $lowerCaseLatePublicationAcronym); 361*04fd306cSNickeau PageProtection::addPageProtectionSnippet(); 362*04fd306cSNickeau } 363*04fd306cSNickeau 364*04fd306cSNickeau } 365*04fd306cSNickeau 366*04fd306cSNickeau /** 367*04fd306cSNickeau * Title (ie tooltip vs title html attribute) 368*04fd306cSNickeau */ 369*04fd306cSNickeau if (!$outputAttributes->hasAttribute("title")) { 370*04fd306cSNickeau 371*04fd306cSNickeau $description = PageDescription::createForPage($page)->getValueOrDefault(); 372*04fd306cSNickeau if (!empty($acronym)) { 373*04fd306cSNickeau $description = $description . " ($acronym)"; 374*04fd306cSNickeau } 375*04fd306cSNickeau $outputAttributes->addOutputAttributeValue("title", $description); 376*04fd306cSNickeau 377*04fd306cSNickeau } 378*04fd306cSNickeau 379*04fd306cSNickeau } 380*04fd306cSNickeau 381*04fd306cSNickeau break; 382*04fd306cSNickeau 383*04fd306cSNickeau case MarkupRef::WINDOWS_SHARE_URI: 384*04fd306cSNickeau // https://www.dokuwiki.org/config:target 385*04fd306cSNickeau $windowsTarget = $conf['target']['windows']; 386*04fd306cSNickeau if (!empty($windowsTarget)) { 387*04fd306cSNickeau $outputAttributes->addOutputAttributeValue('target', $windowsTarget); 388*04fd306cSNickeau } 389*04fd306cSNickeau $outputAttributes->addClassName("windows"); 390*04fd306cSNickeau break; 391*04fd306cSNickeau case MarkupRef::LOCAL_URI: 392*04fd306cSNickeau $outputAttributes->addClassName(self::getHtmlClassLocalLink()); 393*04fd306cSNickeau if (!$outputAttributes->hasAttribute("title")) { 394*04fd306cSNickeau $description = ucfirst($this->markupRef->getUrl()->getFragment()); 395*04fd306cSNickeau if ($description !== "") { 396*04fd306cSNickeau $description = str_replace("_", " ", $description); 397*04fd306cSNickeau $outputAttributes->addOutputAttributeValue("title", $description); 398*04fd306cSNickeau } 399*04fd306cSNickeau } 400*04fd306cSNickeau break; 401*04fd306cSNickeau case MarkupRef::EMAIL_URI: 402*04fd306cSNickeau $outputAttributes->addClassName(self::getHtmlClassEmailLink()); 403*04fd306cSNickeau /** 404*04fd306cSNickeau * An email link is `<email>` 405*04fd306cSNickeau * {@link Emaillink::connectTo()} 406*04fd306cSNickeau * or 407*04fd306cSNickeau * {@link PluginTrait::email() 408*04fd306cSNickeau */ 409*04fd306cSNickeau // common.php#obfsucate implements the $conf['mailguard'] 410*04fd306cSNickeau $uri = $url->getPath(); 411*04fd306cSNickeau $uri = $this->obfuscateEmail($uri); 412*04fd306cSNickeau $uri = urlencode($uri); 413*04fd306cSNickeau $queryParameters = $url->getQueryProperties(); 414*04fd306cSNickeau if (sizeof($queryParameters) > 0) { 415*04fd306cSNickeau $uri .= "?"; 416*04fd306cSNickeau foreach ($queryParameters as $key => $value) { 417*04fd306cSNickeau $value = urlencode($value); 418*04fd306cSNickeau $key = urlencode($key); 419*04fd306cSNickeau if (in_array($key, self::EMAIL_VALID_PARAMETERS)) { 420*04fd306cSNickeau $uri .= "$key=$value"; 421*04fd306cSNickeau } 422*04fd306cSNickeau } 423*04fd306cSNickeau } 424*04fd306cSNickeau // replace href 425*04fd306cSNickeau $outputAttributes->removeOutputAttributeIfPresent("href"); 426*04fd306cSNickeau $outputAttributes->addOutputAttributeValue("href", 'mailto:' . $uri); 427*04fd306cSNickeau break; 428*04fd306cSNickeau case MarkupRef::WEB_URI: 429*04fd306cSNickeau /** 430*04fd306cSNickeau * It may be a absolute url 431*04fd306cSNickeau * that points to the local website 432*04fd306cSNickeau * (case of the {@link \syntax_plugin_combo_permalink} 433*04fd306cSNickeau */ 434*04fd306cSNickeau if ($url->isExternal()) { 435*04fd306cSNickeau 436*04fd306cSNickeau if ($conf['relnofollow']) { 437*04fd306cSNickeau $outputAttributes->addOutputAttributeValue("rel", 'nofollow ugc'); 438*04fd306cSNickeau } 439*04fd306cSNickeau // https://www.dokuwiki.org/config:target 440*04fd306cSNickeau $externTarget = $conf['target']['extern']; 441*04fd306cSNickeau if (!empty($externTarget)) { 442*04fd306cSNickeau $outputAttributes->addOutputAttributeValue('target', $externTarget); 443*04fd306cSNickeau $outputAttributes->addOutputAttributeValue("rel", 'noopener'); 444*04fd306cSNickeau } 445*04fd306cSNickeau /** 446*04fd306cSNickeau * Default class for default external link 447*04fd306cSNickeau * To not interfere with other external link style 448*04fd306cSNickeau * For instance, {@link \syntax_plugin_combo_share} 449*04fd306cSNickeau */ 450*04fd306cSNickeau $outputAttributes->addClassName(self::getHtmlClassExternalLink()); 451*04fd306cSNickeau } 452*04fd306cSNickeau break; 453*04fd306cSNickeau default: 454*04fd306cSNickeau /** 455*04fd306cSNickeau * May be any external link 456*04fd306cSNickeau * such as {@link \syntax_plugin_combo_share} 457*04fd306cSNickeau */ 458*04fd306cSNickeau break; 459*04fd306cSNickeau 460*04fd306cSNickeau } 461*04fd306cSNickeau 462*04fd306cSNickeau /** 463*04fd306cSNickeau * An email URL and title 464*04fd306cSNickeau * may be already encoded because of the vanguard configuration 465*04fd306cSNickeau * 466*04fd306cSNickeau * The url is not treated as an attribute 467*04fd306cSNickeau * because the transformation function encodes the value 468*04fd306cSNickeau * to mitigate XSS 469*04fd306cSNickeau * 470*04fd306cSNickeau */ 471*04fd306cSNickeau if ($this->getMarkupRef()->getSchemeType() == MarkupRef::EMAIL_URI) { 472*04fd306cSNickeau $emailAddress = $this->obfuscateEmail($this->markupRef->getUrl()->getPath()); 473*04fd306cSNickeau $outputAttributes->addOutputAttributeValue("title", $emailAddress); 474*04fd306cSNickeau } 475*04fd306cSNickeau 476*04fd306cSNickeau 477*04fd306cSNickeau /** 478*04fd306cSNickeau * Return 479*04fd306cSNickeau */ 480*04fd306cSNickeau return $outputAttributes; 481*04fd306cSNickeau 482*04fd306cSNickeau 483*04fd306cSNickeau } 484*04fd306cSNickeau 485*04fd306cSNickeau 486*04fd306cSNickeau /** 487*04fd306cSNickeau * The label inside the anchor tag if there is none 488*04fd306cSNickeau * @param false $navigation 489*04fd306cSNickeau * @return string 490*04fd306cSNickeau * @throws ExceptionNotFound|ExceptionBadArgument 491*04fd306cSNickeau * 492*04fd306cSNickeau */ 493*04fd306cSNickeau public 494*04fd306cSNickeau function getDefaultLabel(bool $navigation = false): string 495*04fd306cSNickeau { 496*04fd306cSNickeau 497*04fd306cSNickeau switch ($this->getMarkupRef()->getSchemeType()) { 498*04fd306cSNickeau case MarkupRef::WIKI_URI: 499*04fd306cSNickeau $page = $this->getPage(); 500*04fd306cSNickeau if ($navigation) { 501*04fd306cSNickeau return ResourceName::createForResource($page)->getValueOrDefault(); 502*04fd306cSNickeau } else { 503*04fd306cSNickeau return PageTitle::createForMarkup($page)->getValueOrDefault(); 504*04fd306cSNickeau } 505*04fd306cSNickeau case MarkupRef::EMAIL_URI: 506*04fd306cSNickeau global $conf; 507*04fd306cSNickeau $email = $this->markupRef->getUrl()->getPath(); 508*04fd306cSNickeau switch ($conf['mailguard']) { 509*04fd306cSNickeau case 'none' : 510*04fd306cSNickeau return $email; 511*04fd306cSNickeau case 'visible' : 512*04fd306cSNickeau default : 513*04fd306cSNickeau $obfuscate = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] '); 514*04fd306cSNickeau return strtr($email, $obfuscate); 515*04fd306cSNickeau } 516*04fd306cSNickeau case MarkupRef::INTERWIKI_URI: 517*04fd306cSNickeau try { 518*04fd306cSNickeau $path = $this->markupRef->getInterWiki()->toUrl()->getPath(); 519*04fd306cSNickeau if ($path[0] === "/") { 520*04fd306cSNickeau return substr($path, 1); 521*04fd306cSNickeau } else { 522*04fd306cSNickeau return $path; 523*04fd306cSNickeau } 524*04fd306cSNickeau } catch (ExceptionBadSyntax|ExceptionNotFound $e) { 525*04fd306cSNickeau return "interwiki"; 526*04fd306cSNickeau } 527*04fd306cSNickeau case MarkupRef::LOCAL_URI: 528*04fd306cSNickeau return $this->markupRef->getUrl()->getFragment(); 529*04fd306cSNickeau default: 530*04fd306cSNickeau return $this->markupRef->getRef(); 531*04fd306cSNickeau } 532*04fd306cSNickeau } 533*04fd306cSNickeau 534*04fd306cSNickeau 535*04fd306cSNickeau private 536*04fd306cSNickeau function obfuscateEmail($email, $inAttribute = true): string 537*04fd306cSNickeau { 538*04fd306cSNickeau /** 539*04fd306cSNickeau * adapted from {@link obfuscate()} in common.php 540*04fd306cSNickeau */ 541*04fd306cSNickeau global $conf; 542*04fd306cSNickeau 543*04fd306cSNickeau $mailGuard = $conf['mailguard']; 544*04fd306cSNickeau if ($mailGuard === "hex" && $inAttribute) { 545*04fd306cSNickeau $mailGuard = "visible"; 546*04fd306cSNickeau } 547*04fd306cSNickeau switch ($mailGuard) { 548*04fd306cSNickeau case 'visible' : 549*04fd306cSNickeau $obfuscate = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] '); 550*04fd306cSNickeau return strtr($email, $obfuscate); 551*04fd306cSNickeau 552*04fd306cSNickeau case 'hex' : 553*04fd306cSNickeau return Conversion::toHtml($email, true); 554*04fd306cSNickeau 555*04fd306cSNickeau case 'none' : 556*04fd306cSNickeau default : 557*04fd306cSNickeau return $email; 558*04fd306cSNickeau } 559*04fd306cSNickeau } 560*04fd306cSNickeau 561*04fd306cSNickeau 562*04fd306cSNickeau /** 563*04fd306cSNickeau * @return bool 564*04fd306cSNickeau * @deprecated should not be here ref does not have the notion of relative 565*04fd306cSNickeau */ 566*04fd306cSNickeau public 567*04fd306cSNickeau function isRelative(): bool 568*04fd306cSNickeau { 569*04fd306cSNickeau return strpos($this->getMarkupRef()->getRef(), WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT) !== 0; 570*04fd306cSNickeau } 571*04fd306cSNickeau 572*04fd306cSNickeau public 573*04fd306cSNickeau function getMarkupRef(): MarkupRef 574*04fd306cSNickeau { 575*04fd306cSNickeau return $this->markupRef; 576*04fd306cSNickeau } 577*04fd306cSNickeau 578*04fd306cSNickeau 579*04fd306cSNickeau public 580*04fd306cSNickeau static function getHtmlClassInternalLink(): string 581*04fd306cSNickeau { 582*04fd306cSNickeau $oldClassName = SiteConfig::getConfValue(self::CONF_USE_DOKUWIKI_CLASS_NAME); 583*04fd306cSNickeau if ($oldClassName) { 584*04fd306cSNickeau return "wikilink1"; 585*04fd306cSNickeau } else { 586*04fd306cSNickeau return "link-internal"; 587*04fd306cSNickeau } 588*04fd306cSNickeau } 589*04fd306cSNickeau 590*04fd306cSNickeau public 591*04fd306cSNickeau static function getHtmlClassEmailLink(): string 592*04fd306cSNickeau { 593*04fd306cSNickeau $oldClassName = SiteConfig::getConfValue(self::CONF_USE_DOKUWIKI_CLASS_NAME); 594*04fd306cSNickeau if ($oldClassName) { 595*04fd306cSNickeau return "mail"; 596*04fd306cSNickeau } else { 597*04fd306cSNickeau return "link-mail"; 598*04fd306cSNickeau } 599*04fd306cSNickeau } 600*04fd306cSNickeau 601*04fd306cSNickeau public static function getHtmlClassExternalLink(): string 602*04fd306cSNickeau { 603*04fd306cSNickeau $oldClassName = SiteConfig::getConfValue(self::CONF_USE_DOKUWIKI_CLASS_NAME); 604*04fd306cSNickeau if ($oldClassName) { 605*04fd306cSNickeau return "urlextern"; 606*04fd306cSNickeau } else { 607*04fd306cSNickeau return "link-external"; 608*04fd306cSNickeau } 609*04fd306cSNickeau } 610*04fd306cSNickeau 611*04fd306cSNickeau//FYI: exist in dokuwiki is "wikilink1 but we let the control to the user 612*04fd306cSNickeau public 613*04fd306cSNickeau static function getHtmlClassNotExist(): string 614*04fd306cSNickeau { 615*04fd306cSNickeau $oldClassName = SiteConfig::getConfValue(self::CONF_USE_DOKUWIKI_CLASS_NAME); 616*04fd306cSNickeau if ($oldClassName) { 617*04fd306cSNickeau return "wikilink2"; 618*04fd306cSNickeau } else { 619*04fd306cSNickeau return self::TEXT_ERROR_CLASS; 620*04fd306cSNickeau } 621*04fd306cSNickeau } 622*04fd306cSNickeau 623*04fd306cSNickeau public 624*04fd306cSNickeau function __toString() 625*04fd306cSNickeau { 626*04fd306cSNickeau return $this->getMarkupRef()->getRef(); 627*04fd306cSNickeau } 628*04fd306cSNickeau 629*04fd306cSNickeau 630*04fd306cSNickeau /** 631*04fd306cSNickeau * @throws ExceptionNotFound 632*04fd306cSNickeau */ 633*04fd306cSNickeau private 634*04fd306cSNickeau function getPage(): MarkupPath 635*04fd306cSNickeau { 636*04fd306cSNickeau return MarkupPath::createPageFromPathObject($this->getMarkupRef()->getPath()); 637*04fd306cSNickeau } 638*04fd306cSNickeau 639*04fd306cSNickeau /** 640*04fd306cSNickeau * Styling attribute 641*04fd306cSNickeau * may be passed via parameters 642*04fd306cSNickeau * for internal link 643*04fd306cSNickeau * We don't want the styling attribute 644*04fd306cSNickeau * in the URL 645*04fd306cSNickeau */ 646*04fd306cSNickeau private 647*04fd306cSNickeau function collectStylingAttributeInUrl() 648*04fd306cSNickeau { 649*04fd306cSNickeau 650*04fd306cSNickeau 651*04fd306cSNickeau /** 652*04fd306cSNickeau * We will not overwrite the parameters if this is an dokuwiki 653*04fd306cSNickeau * action link (with the `do` property) 654*04fd306cSNickeau */ 655*04fd306cSNickeau if ($this->markupRef->getUrl()->hasProperty("do")) { 656*04fd306cSNickeau return; 657*04fd306cSNickeau } 658*04fd306cSNickeau 659*04fd306cSNickeau /** 660*04fd306cSNickeau * Add the attribute from the URL 661*04fd306cSNickeau * if this is not a `do` 662*04fd306cSNickeau */ 663*04fd306cSNickeau switch ($this->markupRef->getSchemeType()) { 664*04fd306cSNickeau case MarkupRef::WIKI_URI: 665*04fd306cSNickeau foreach ($this->getMarkupRef()->getUrl()->getQueryProperties() as $key => $value) { 666*04fd306cSNickeau if (!in_array($key, self::PROTECTED_URL_PROPERTY)) { 667*04fd306cSNickeau $this->getMarkupRef()->getUrl()->deleteQueryParameter($key); 668*04fd306cSNickeau if (!TagAttributes::isEmptyValue($value)) { 669*04fd306cSNickeau $this->stylingAttributes->addComponentAttributeValue($key, $value); 670*04fd306cSNickeau } else { 671*04fd306cSNickeau $this->stylingAttributes->addEmptyComponentAttributeValue($key); 672*04fd306cSNickeau } 673*04fd306cSNickeau } 674*04fd306cSNickeau } 675*04fd306cSNickeau break; 676*04fd306cSNickeau case 677*04fd306cSNickeau MarkupRef::EMAIL_URI: 678*04fd306cSNickeau foreach ($this->getMarkupRef()->getUrl()->getQueryProperties() as $key => $value) { 679*04fd306cSNickeau if (!in_array($key, self::EMAIL_VALID_PARAMETERS)) { 680*04fd306cSNickeau $this->stylingAttributes->addComponentAttributeValue($key, $value); 681*04fd306cSNickeau } 682*04fd306cSNickeau } 683*04fd306cSNickeau break; 684*04fd306cSNickeau } 685*04fd306cSNickeau 686*04fd306cSNickeau } 687*04fd306cSNickeau 688*04fd306cSNickeau /** 689*04fd306cSNickeau * @return TagAttributes - the unknown attributes in a url are collected as styling attributes if this not a do query 690*04fd306cSNickeau * by {@link LinkMarkup::collectStylingAttributeInUrl()} 691*04fd306cSNickeau */ 692*04fd306cSNickeau public 693*04fd306cSNickeau function getStylingAttributes(): TagAttributes 694*04fd306cSNickeau { 695*04fd306cSNickeau return $this->stylingAttributes; 696*04fd306cSNickeau } 697*04fd306cSNickeau 698*04fd306cSNickeau 699*04fd306cSNickeau} 700