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