1007225e5Sgerardnico<?php 2007225e5Sgerardnico 3007225e5Sgerardnico 437748cd8SNickeaurequire_once(__DIR__ . "/../ComboStrap/PluginUtility.php"); 5007225e5Sgerardnico 64cadd4f8SNickeauuse ComboStrap\ArrayUtility; 7*04fd306cSNickeauuse ComboStrap\ButtonTag; 84cadd4f8SNickeauuse ComboStrap\Call; 9531e725cSNickeauuse ComboStrap\CallStack; 10*04fd306cSNickeauuse ComboStrap\DropDownTag; 11*04fd306cSNickeauuse ComboStrap\ExceptionBadArgument; 12*04fd306cSNickeauuse ComboStrap\ExceptionBadSyntax; 13*04fd306cSNickeauuse ComboStrap\ExceptionCompile; 14*04fd306cSNickeauuse ComboStrap\ExceptionNotFound; 15*04fd306cSNickeauuse ComboStrap\FileSystems; 16*04fd306cSNickeauuse ComboStrap\LinkMarkup; 174cadd4f8SNickeauuse ComboStrap\LogUtility; 18*04fd306cSNickeauuse ComboStrap\MarkupRef; 19*04fd306cSNickeauuse ComboStrap\MarkupPath; 20007225e5Sgerardnicouse ComboStrap\PluginUtility; 21531e725cSNickeauuse ComboStrap\TagAttributes; 22c3437056SNickeauuse ComboStrap\ThirdPartyPlugins; 23*04fd306cSNickeauuse ComboStrap\Web\UrlEndpoint; 24007225e5Sgerardnico 25007225e5Sgerardnicoif (!defined('DOKU_INC')) die(); 26007225e5Sgerardnico 27007225e5Sgerardnico/** 28007225e5Sgerardnico * 29007225e5Sgerardnico * A link pattern to take over the link of Dokuwiki 30007225e5Sgerardnico * and transform it as a bootstrap link 31007225e5Sgerardnico * 32007225e5Sgerardnico * The handle of the move of link is to be found in the 33007225e5Sgerardnico * admin action {@link action_plugin_combo_linkmove} 34007225e5Sgerardnico * 35*04fd306cSNickeau * popular [[ wiki ]] syntax for linking notes 36*04fd306cSNickeau * and makes it easy to build personal wikis, 37*04fd306cSNickeau * team knowledge bases, 38*04fd306cSNickeau * or something like a Second Brain or a Zettelkasten. 39*04fd306cSNickeau * 40007225e5Sgerardnico */ 41007225e5Sgerardnicoclass syntax_plugin_combo_link extends DokuWiki_Syntax_Plugin 42007225e5Sgerardnico{ 43007225e5Sgerardnico const TAG = 'link'; 44ef295d81Sgerardnico const COMPONENT = 'combo_link'; 45007225e5Sgerardnico 465f891b7eSNickeau /** 4785e82846SNickeau * Disable the link component 4821913ab3SNickeau */ 4921913ab3SNickeau const CONF_DISABLE_LINK = "disableLink"; 5021913ab3SNickeau 5121913ab3SNickeau /** 525f891b7eSNickeau * The link Tag 53531e725cSNickeau * a or p 545f891b7eSNickeau */ 555f891b7eSNickeau const LINK_TAG = "linkTag"; 565f891b7eSNickeau 5721913ab3SNickeau /** 5821913ab3SNickeau * Do the link component allows to be spawn on multilines 5921913ab3SNickeau */ 60531e725cSNickeau const CLICKABLE_ATTRIBUTE = "clickable"; 614cadd4f8SNickeau public const ATTRIBUTE_LABEL = 'label'; 624cadd4f8SNickeau /** 634cadd4f8SNickeau * The key of the array for the handle cache 644cadd4f8SNickeau */ 65*04fd306cSNickeau public const MARKUP_REF_ATTRIBUTE = 'ref'; 66*04fd306cSNickeau 674cadd4f8SNickeau public const ATTRIBUTE_IMAGE_IN_LABEL = 'image-in-label'; 684cadd4f8SNickeau 694cadd4f8SNickeau /** 704cadd4f8SNickeau * A link may have a title or not 714cadd4f8SNickeau * ie 724cadd4f8SNickeau * [[path:page]] 734cadd4f8SNickeau * [[path:page|title]] 744cadd4f8SNickeau * are valid 754cadd4f8SNickeau * 764cadd4f8SNickeau * Get the content until one of this character is found: 774cadd4f8SNickeau * * | 784cadd4f8SNickeau * * or ]] 794cadd4f8SNickeau * * or \n (No line break allowed, too much difficult to debug) 804cadd4f8SNickeau * * and not [ (for two links on the same line) 814cadd4f8SNickeau */ 824cadd4f8SNickeau public const ENTRY_PATTERN_SINGLE_LINE = "\[\[[^\|\]]*(?=[^\n\[]*\]\])"; 834cadd4f8SNickeau public const EXIT_PATTERN = "\]\]"; 844cadd4f8SNickeau 854cadd4f8SNickeau 864cadd4f8SNickeau /** 874cadd4f8SNickeau * Dokuwiki Link pattern ter info 884cadd4f8SNickeau * Found in {@link \dokuwiki\Parsing\ParserMode\Internallink} 894cadd4f8SNickeau */ 904cadd4f8SNickeau const SPECIAL_PATTERN = "\[\[.*?\]\](?!\])"; 914cadd4f8SNickeau 924cadd4f8SNickeau /** 934cadd4f8SNickeau * The link title attribute (ie popup) 944cadd4f8SNickeau */ 954cadd4f8SNickeau const TITLE_ATTRIBUTE = "title"; 96*04fd306cSNickeau const STRETCHED_LINK = "stretched-link"; 97*04fd306cSNickeau const CANONICAL = "link"; 984cadd4f8SNickeau 994cadd4f8SNickeau 1004cadd4f8SNickeau /** 1014cadd4f8SNickeau * Parse the match of a syntax {@link DokuWiki_Syntax_Plugin} handle function 1024cadd4f8SNickeau * @param $match 1034cadd4f8SNickeau * @return string[] - an array with the attributes constant `ATTRIBUTE_xxxx` as key 1044cadd4f8SNickeau * 1054cadd4f8SNickeau * Code adapted from {@link Doku_Handler::internallink()} 1064cadd4f8SNickeau */ 1074cadd4f8SNickeau public static function parse($match): array 1084cadd4f8SNickeau { 1094cadd4f8SNickeau 1104cadd4f8SNickeau // Strip the opening and closing markup 111*04fd306cSNickeau $linkString = preg_replace(array('/^\[\[/', '/]]$/u'), '', $match); 1124cadd4f8SNickeau 1134cadd4f8SNickeau // Split title from URL 1144cadd4f8SNickeau $linkArray = explode('|', $linkString, 2); 1154cadd4f8SNickeau 1164cadd4f8SNickeau // Id 117*04fd306cSNickeau $attributes[self::MARKUP_REF_ATTRIBUTE] = trim($linkArray[0]); 1184cadd4f8SNickeau 1194cadd4f8SNickeau 1204cadd4f8SNickeau // Text or image 1214cadd4f8SNickeau if (!isset($linkArray[1])) { 1224cadd4f8SNickeau $attributes[self::ATTRIBUTE_LABEL] = null; 1234cadd4f8SNickeau } else { 1244cadd4f8SNickeau // An image in the title 125*04fd306cSNickeau if (preg_match('/^{{[^}]+}}$/', $linkArray[1])) { 1264cadd4f8SNickeau // If the title is an image, convert it to an array containing the image details 1274cadd4f8SNickeau $attributes[self::ATTRIBUTE_IMAGE_IN_LABEL] = Doku_Handler_Parse_Media($linkArray[1]); 1284cadd4f8SNickeau } else { 1294cadd4f8SNickeau $attributes[self::ATTRIBUTE_LABEL] = $linkArray[1]; 1304cadd4f8SNickeau } 1314cadd4f8SNickeau } 1324cadd4f8SNickeau 1334cadd4f8SNickeau return $attributes; 1344cadd4f8SNickeau 1354cadd4f8SNickeau } 13621913ab3SNickeau 1375f891b7eSNickeau 138007225e5Sgerardnico /** 139007225e5Sgerardnico * Syntax Type. 140007225e5Sgerardnico * 141007225e5Sgerardnico * Needs to return one of the mode types defined in $PARSER_MODES in parser.php 142007225e5Sgerardnico * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types 143007225e5Sgerardnico */ 144007225e5Sgerardnico function getType() 145007225e5Sgerardnico { 146007225e5Sgerardnico return 'substition'; 147007225e5Sgerardnico } 148007225e5Sgerardnico 149007225e5Sgerardnico /** 150007225e5Sgerardnico * How Dokuwiki will add P element 151007225e5Sgerardnico * 152007225e5Sgerardnico * * 'normal' - The plugin can be used inside paragraphs 153007225e5Sgerardnico * * 'block' - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs 154007225e5Sgerardnico * * 'stack' - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs 155007225e5Sgerardnico * 156007225e5Sgerardnico * @see DokuWiki_Syntax_Plugin::getPType() 157007225e5Sgerardnico */ 158007225e5Sgerardnico function getPType() 159007225e5Sgerardnico { 160007225e5Sgerardnico return 'normal'; 161007225e5Sgerardnico } 162007225e5Sgerardnico 163007225e5Sgerardnico /** 164007225e5Sgerardnico * @return array 165007225e5Sgerardnico * Allow which kind of plugin inside 166007225e5Sgerardnico * 167007225e5Sgerardnico * No one of array('container', 'baseonly', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs') 168007225e5Sgerardnico * because we manage self the content and we call self the parser 169007225e5Sgerardnico */ 1704cadd4f8SNickeau function getAllowedTypes(): array 171007225e5Sgerardnico { 172007225e5Sgerardnico return array('substition', 'formatting', 'disabled'); 173007225e5Sgerardnico } 174007225e5Sgerardnico 17537748cd8SNickeau /** 17637748cd8SNickeau * @param string $mode 17737748cd8SNickeau * @return bool 17837748cd8SNickeau * Accepts inside 17937748cd8SNickeau */ 1804cadd4f8SNickeau public function accepts($mode): bool 1815f891b7eSNickeau { 1825f891b7eSNickeau /** 1835f891b7eSNickeau * To avoid that the description if it contains a link 1845f891b7eSNickeau * will be taken by the links mode 1855f891b7eSNickeau * 1865f891b7eSNickeau * For instance, [[https://hallo|https://hallo]] will send https://hallo 1875f891b7eSNickeau * to the external link mode 1885f891b7eSNickeau */ 1895f891b7eSNickeau $linkModes = [ 1905f891b7eSNickeau "externallink", 1915f891b7eSNickeau "locallink", 1925f891b7eSNickeau "internallink", 1935f891b7eSNickeau "interwikilink", 1945f891b7eSNickeau "emaillink", 195fc45fbf7Sgerardnico "emphasis", // double slash can not be used inside to preserve the possibility to write an URL in the description 1965f891b7eSNickeau //"emphasis_open", // italic use // and therefore take over a link as description which is not handy when copying a tweet 1975f891b7eSNickeau //"emphasis_close", 1985f891b7eSNickeau //"acrnonym" 1995f891b7eSNickeau ]; 2005f891b7eSNickeau if (in_array($mode, $linkModes)) { 2015f891b7eSNickeau return false; 2025f891b7eSNickeau } else { 2035f891b7eSNickeau return true; 2045f891b7eSNickeau } 2055f891b7eSNickeau } 2065f891b7eSNickeau 2075f891b7eSNickeau 208007225e5Sgerardnico /** 209007225e5Sgerardnico * @see Doku_Parser_Mode::getSort() 210007225e5Sgerardnico * The mode with the lowest sort number will win out 211007225e5Sgerardnico */ 212007225e5Sgerardnico function getSort() 213007225e5Sgerardnico { 214e8b2ff59SNickeau /** 215e8b2ff59SNickeau * It should be less than the number 216e8b2ff59SNickeau * at {@link \dokuwiki\Parsing\ParserMode\Internallink::getSort} 217e8b2ff59SNickeau * and the like 218e8b2ff59SNickeau * 219e8b2ff59SNickeau * For whatever reason, the number below should be less than 100, 220e8b2ff59SNickeau * otherwise on windows with DokuWiki Stick, the link syntax may be not taken 221e8b2ff59SNickeau * into account 222e8b2ff59SNickeau */ 223e8b2ff59SNickeau return 99; 224007225e5Sgerardnico } 225007225e5Sgerardnico 226007225e5Sgerardnico 227007225e5Sgerardnico function connectTo($mode) 228007225e5Sgerardnico { 229d262537cSgerardnico 23037748cd8SNickeau if (!$this->getConf(self::CONF_DISABLE_LINK, false) 23137748cd8SNickeau && 23237748cd8SNickeau $mode !== PluginUtility::getModeFromPluginName(ThirdPartyPlugins::IMAGE_MAPPING_NAME) 23337748cd8SNickeau ) { 23437748cd8SNickeau 2354cadd4f8SNickeau $pattern = self::ENTRY_PATTERN_SINGLE_LINE; 2369337a630SNickeau $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeFromTag($this->getPluginComponent())); 23737748cd8SNickeau 23821913ab3SNickeau } 239d262537cSgerardnico 240007225e5Sgerardnico } 241007225e5Sgerardnico 2425f891b7eSNickeau public function postConnect() 2435f891b7eSNickeau { 24421913ab3SNickeau if (!$this->getConf(self::CONF_DISABLE_LINK, false)) { 2454cadd4f8SNickeau $this->Lexer->addExitPattern(self::EXIT_PATTERN, PluginUtility::getModeFromTag($this->getPluginComponent())); 2465f891b7eSNickeau } 24721913ab3SNickeau } 2485f891b7eSNickeau 249007225e5Sgerardnico 250007225e5Sgerardnico /** 251007225e5Sgerardnico * The handler for an internal link 252007225e5Sgerardnico * based on `internallink` in {@link Doku_Handler} 253007225e5Sgerardnico * The handler call the good renderer in {@link Doku_Renderer_xhtml} with 254007225e5Sgerardnico * the parameters (ie for instance internallink) 255007225e5Sgerardnico * @param string $match 256007225e5Sgerardnico * @param int $state 257007225e5Sgerardnico * @param int $pos 258007225e5Sgerardnico * @param Doku_Handler $handler 259007225e5Sgerardnico * @return array|bool 260007225e5Sgerardnico */ 261007225e5Sgerardnico function handle($match, $state, $pos, Doku_Handler $handler) 262007225e5Sgerardnico { 263007225e5Sgerardnico 2645f891b7eSNickeau switch ($state) { 2655f891b7eSNickeau case DOKU_LEXER_ENTER: 2664cadd4f8SNickeau $parsedArray = self::parse($match); 2674cadd4f8SNickeau $htmlAttributes = TagAttributes::createEmpty(self::TAG); 268*04fd306cSNickeau 2694cadd4f8SNickeau /** 270*04fd306cSNickeau * The markup ref needs to be passed to the 271*04fd306cSNickeau * instructions stack (because we support link with a variable as markup ref 272*04fd306cSNickeau * via a {@link syntax_plugin_combo_fragment} in a {@link syntax_plugin_combo_iterator} 273*04fd306cSNickeau * 274*04fd306cSNickeau * The variable is replaced in the {@link syntax_plugin_combo_link::render()} 275*04fd306cSNickeau * at runtime while rendering 2764cadd4f8SNickeau */ 277*04fd306cSNickeau $markupRef = $parsedArray[self::MARKUP_REF_ATTRIBUTE]; 278*04fd306cSNickeau if ($markupRef !== null) { 279*04fd306cSNickeau $htmlAttributes->addComponentAttributeValue(self::MARKUP_REF_ATTRIBUTE, $markupRef); 2804cadd4f8SNickeau } 2814cadd4f8SNickeau 2824cadd4f8SNickeau 2834cadd4f8SNickeau /** 2844cadd4f8SNickeau * Extra HTML attribute 2854cadd4f8SNickeau */ 286531e725cSNickeau $callStack = CallStack::createFromHandler($handler); 287531e725cSNickeau $parent = $callStack->moveToParent(); 288007225e5Sgerardnico $parentName = ""; 2894cadd4f8SNickeau if ($parent !== false) { 290531e725cSNickeau 291531e725cSNickeau /** 292531e725cSNickeau * Button Link 293531e725cSNickeau * Getting the attributes 294531e725cSNickeau */ 295531e725cSNickeau $parentName = $parent->getTagName(); 296*04fd306cSNickeau if ($parentName == ButtonTag::MARKUP_LONG) { 2974cadd4f8SNickeau $htmlAttributes->mergeWithCallStackArray($parent->getAttributes()); 29821913ab3SNickeau } 299531e725cSNickeau 300531e725cSNickeau /** 301531e725cSNickeau * Searching Clickable parent 302531e725cSNickeau */ 303531e725cSNickeau $maxLevel = 3; 304531e725cSNickeau $level = 0; 305531e725cSNickeau while ( 306531e725cSNickeau $parent != false && 307531e725cSNickeau !$parent->hasAttribute(self::CLICKABLE_ATTRIBUTE) && 308531e725cSNickeau $level < $maxLevel 309531e725cSNickeau ) { 310531e725cSNickeau $parent = $callStack->moveToParent(); 311531e725cSNickeau $level++; 3125f891b7eSNickeau } 313*04fd306cSNickeau if ($parent !== false) { 314531e725cSNickeau if ($parent->getAttribute(self::CLICKABLE_ATTRIBUTE)) { 315*04fd306cSNickeau $htmlAttributes->addClassName(self::STRETCHED_LINK); 316531e725cSNickeau $parent->addClassName("position-relative"); 317531e725cSNickeau $parent->removeAttribute(self::CLICKABLE_ATTRIBUTE); 3185f891b7eSNickeau } 31921913ab3SNickeau } 32021913ab3SNickeau 321531e725cSNickeau } 3224cadd4f8SNickeau $returnedArray[PluginUtility::STATE] = $state; 3234cadd4f8SNickeau $returnedArray[PluginUtility::ATTRIBUTES] = $htmlAttributes->toCallStackArray(); 3244cadd4f8SNickeau $returnedArray[PluginUtility::CONTEXT] = $parentName; 3254cadd4f8SNickeau return $returnedArray; 3264cadd4f8SNickeau 3275f891b7eSNickeau case DOKU_LEXER_UNMATCHED: 3285f891b7eSNickeau 32932b85071SNickeau $data = PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler); 3305f891b7eSNickeau /** 33132b85071SNickeau * Delete the separator `|` between the ref and the description if any 3325f891b7eSNickeau */ 3331fa8c418SNickeau $tag = CallStack::createFromHandler($handler); 3341fa8c418SNickeau $parent = $tag->moveToParent(); 3351fa8c418SNickeau if ($parent->getTagName() == self::TAG) { 3365f891b7eSNickeau if (strpos($match, '|') === 0) { 33732b85071SNickeau $data[PluginUtility::PAYLOAD] = substr($match, 1); 3385f891b7eSNickeau } 339007225e5Sgerardnico } 34032b85071SNickeau return $data; 341007225e5Sgerardnico 3425f891b7eSNickeau case DOKU_LEXER_EXIT: 343*04fd306cSNickeau 344531e725cSNickeau $callStack = CallStack::createFromHandler($handler); 345531e725cSNickeau $openingTag = $callStack->moveToPreviousCorrespondingOpeningCall(); 3464cadd4f8SNickeau 3475f891b7eSNickeau $openingAttributes = $openingTag->getAttributes(); 348531e725cSNickeau $openingPosition = $openingTag->getKey(); 3495f891b7eSNickeau 350531e725cSNickeau $callStack->moveToEnd(); 351531e725cSNickeau $previousCall = $callStack->previous(); 352531e725cSNickeau $previousCallPosition = $previousCall->getKey(); 353531e725cSNickeau $previousCallContent = $previousCall->getCapturedContent(); 354531e725cSNickeau 3554cadd4f8SNickeau /** 3564cadd4f8SNickeau * Link label 3574cadd4f8SNickeau * is set if there is no content 3584cadd4f8SNickeau * between enter and exit node 3594cadd4f8SNickeau */ 3604cadd4f8SNickeau $linkLabel = ""; 361531e725cSNickeau if ( 362531e725cSNickeau $openingPosition == $previousCallPosition // ie [[id]] 363531e725cSNickeau || 364531e725cSNickeau ($openingPosition == $previousCallPosition - 1 && $previousCallContent == "|") // ie [[id|]] 365531e725cSNickeau ) { 3665f891b7eSNickeau // There is no name 367*04fd306cSNickeau $markupRef = $openingTag->getAttribute(self::MARKUP_REF_ATTRIBUTE); 368*04fd306cSNickeau if ($markupRef !== null) { 369*04fd306cSNickeau try { 370*04fd306cSNickeau $linkLabel = LinkMarkup::createFromRef($markupRef) 371*04fd306cSNickeau ->getDefaultLabel(); 372*04fd306cSNickeau } catch (ExceptionCompile $e) { 373*04fd306cSNickeau LogUtility::error("No default Label can be defined. Error while parsing the markup ref ($markupRef). Error: {$e->getMessage()}",self::CANONICAL, $e); 374*04fd306cSNickeau } 375*04fd306cSNickeau 3764cadd4f8SNickeau } 3775f891b7eSNickeau } 3785f891b7eSNickeau return array( 3795f891b7eSNickeau PluginUtility::STATE => $state, 3805f891b7eSNickeau PluginUtility::ATTRIBUTES => $openingAttributes, 3814cadd4f8SNickeau PluginUtility::PAYLOAD => $linkLabel, 38285e82846SNickeau PluginUtility::CONTEXT => $openingTag->getContext() 3835f891b7eSNickeau ); 3845f891b7eSNickeau } 3855f891b7eSNickeau return true; 3865f891b7eSNickeau 387007225e5Sgerardnico 388007225e5Sgerardnico } 389007225e5Sgerardnico 390007225e5Sgerardnico /** 391007225e5Sgerardnico * Render the output 392007225e5Sgerardnico * @param string $format 393007225e5Sgerardnico * @param Doku_Renderer $renderer 394007225e5Sgerardnico * @param array $data - what the function handle() return'ed 395007225e5Sgerardnico * @return boolean - rendered correctly? (however, returned value is not used at the moment) 396007225e5Sgerardnico * @see DokuWiki_Syntax_Plugin::render() 397007225e5Sgerardnico * 398007225e5Sgerardnico * 399007225e5Sgerardnico */ 400c3437056SNickeau function render($format, Doku_Renderer $renderer, $data): bool 401007225e5Sgerardnico { 402007225e5Sgerardnico // The data 403*04fd306cSNickeau $state = $data[PluginUtility::STATE]; 404007225e5Sgerardnico switch ($format) { 405007225e5Sgerardnico case 'xhtml': 406007225e5Sgerardnico 407007225e5Sgerardnico /** @var Doku_Renderer_xhtml $renderer */ 408007225e5Sgerardnico /** 40919b0880dSgerardnico * Cache problem may occurs while releasing 410007225e5Sgerardnico */ 411007225e5Sgerardnico if (isset($data[PluginUtility::ATTRIBUTES])) { 412531e725cSNickeau $callStackAttributes = $data[PluginUtility::ATTRIBUTES]; 413007225e5Sgerardnico } else { 414531e725cSNickeau $callStackAttributes = $data; 415007225e5Sgerardnico } 4165f891b7eSNickeau 417*04fd306cSNickeau PluginUtility::getSnippetManager()->attachCssInternalStyleSheet(self::TAG); 4185f891b7eSNickeau 4195f891b7eSNickeau switch ($state) { 4205f891b7eSNickeau case DOKU_LEXER_ENTER: 421531e725cSNickeau $tagAttributes = TagAttributes::createFromCallStackArray($callStackAttributes, self::TAG); 4224cadd4f8SNickeau 423*04fd306cSNickeau $markupRef = $tagAttributes->getValueAndRemove(self::MARKUP_REF_ATTRIBUTE); 424*04fd306cSNickeau if ($markupRef === null) { 425*04fd306cSNickeau $message = "Internal Error: A link reference was not found"; 426*04fd306cSNickeau LogUtility::internalError($message); 427*04fd306cSNickeau $renderer->doc .= LogUtility::wrapInRedForHtml($message); 4284cadd4f8SNickeau return false; 4294cadd4f8SNickeau } 430*04fd306cSNickeau try { 431*04fd306cSNickeau $markupRef = syntax_plugin_combo_variable::replaceVariablesWithValuesFromContext($markupRef); 432*04fd306cSNickeau $markupLink = LinkMarkup::createFromRef($markupRef); 433*04fd306cSNickeau $markupAttributes = $markupLink->toAttributes(); 434*04fd306cSNickeau } catch (ExceptionCompile $e) { 435*04fd306cSNickeau // uncomment to get the original error stack trace in dev 436*04fd306cSNickeau // and see where the exception comes from 437*04fd306cSNickeau // Don't forget to comment back 438*04fd306cSNickeau// if (PluginUtility::isDevOrTest()) { 439*04fd306cSNickeau// throw new ExceptionRuntime("Error on markup ref", self::TAG, 0, $e); 440*04fd306cSNickeau// } 441*04fd306cSNickeau 442*04fd306cSNickeau /** 443*04fd306cSNickeau * Error. Example: unknown inter-wiki ... 444*04fd306cSNickeau * We still create the a to be xhtml compliante 445*04fd306cSNickeau */ 446*04fd306cSNickeau $url = UrlEndpoint::createSupportUrl(); 447*04fd306cSNickeau $markupAttributes = TagAttributes::createEmpty() 448*04fd306cSNickeau ->addOutputAttributeValue("href", $url->toString()) 449*04fd306cSNickeau ->addClassName(LinkMarkup::getHtmlClassNotExist()); 450*04fd306cSNickeau $renderer->doc .= $markupAttributes->toHtmlEnterTag("a") . $e->getMessage(); 451*04fd306cSNickeau 452*04fd306cSNickeau LogUtility::warning($e->getMessage(), "link", $e); 453*04fd306cSNickeau return false; 4544cadd4f8SNickeau 4554cadd4f8SNickeau } 456*04fd306cSNickeau // markup attributes is leading because it has already output attribute such as href 457*04fd306cSNickeau $markupAttributes->mergeWithCallStackArray($tagAttributes->toCallStackArray()); 458*04fd306cSNickeau $tagAttributes = $markupAttributes; 459*04fd306cSNickeau 460d262537cSgerardnico 46119b0880dSgerardnico /** 4625f891b7eSNickeau * Extra styling 46319b0880dSgerardnico */ 4645f891b7eSNickeau $parentTag = $data[PluginUtility::CONTEXT]; 4654cadd4f8SNickeau $htmlPrefix = ""; 4669f4383e9Sgerardnico switch ($parentTag) { 46721913ab3SNickeau /** 46821913ab3SNickeau * Button link 46921913ab3SNickeau */ 470*04fd306cSNickeau case ButtonTag::MARKUP_LONG: 4714cadd4f8SNickeau $tagAttributes->addOutputAttributeValue("role", "button"); 472*04fd306cSNickeau ButtonTag::processButtonAttributesToHtmlAttributes($tagAttributes); 4739f4383e9Sgerardnico break; 474*04fd306cSNickeau case DropDownTag::TAG: 4754cadd4f8SNickeau $tagAttributes->addClassName("dropdown-item"); 4764cadd4f8SNickeau break; 4774cadd4f8SNickeau case syntax_plugin_combo_navbarcollapse::COMPONENT: 4784cadd4f8SNickeau $tagAttributes->addClassName("navbar-link"); 4794cadd4f8SNickeau $htmlPrefix = '<div class="navbar-nav">'; 4804cadd4f8SNickeau break; 4814cadd4f8SNickeau case syntax_plugin_combo_navbargroup::COMPONENT: 4824cadd4f8SNickeau $tagAttributes->addClassName("nav-link"); 4834cadd4f8SNickeau $htmlPrefix = '<li class="nav-item">'; 4844cadd4f8SNickeau break; 4854cadd4f8SNickeau default: 4865f891b7eSNickeau case syntax_plugin_combo_badge::TAG: 4879f4383e9Sgerardnico case syntax_plugin_combo_cite::TAG: 4889337a630SNickeau case syntax_plugin_combo_contentlistitem::DOKU_TAG: 4899f4383e9Sgerardnico case syntax_plugin_combo_preformatted::TAG: 4909f4383e9Sgerardnico break; 4910a517624Sgerardnico 4929f4383e9Sgerardnico } 4939f4383e9Sgerardnico 49419b0880dSgerardnico /** 49519b0880dSgerardnico * Add it to the rendering 49619b0880dSgerardnico */ 4974cadd4f8SNickeau $renderer->doc .= $htmlPrefix . $tagAttributes->toHtmlEnterTag("a"); 4985f891b7eSNickeau break; 4995f891b7eSNickeau case DOKU_LEXER_UNMATCHED: 50032b85071SNickeau $renderer->doc .= PluginUtility::renderUnmatched($data); 5015f891b7eSNickeau break; 5025f891b7eSNickeau case DOKU_LEXER_EXIT: 5035f891b7eSNickeau 5045f891b7eSNickeau // if there is no link name defined, we get the name as ref in the payload 5055f891b7eSNickeau // otherwise null string 50685e82846SNickeau $renderer->doc .= $data[PluginUtility::PAYLOAD]; 5075f891b7eSNickeau 508e3d0019cSgerardnico // Close the link 50985e82846SNickeau $renderer->doc .= "</a>"; 510e3d0019cSgerardnico 511e3d0019cSgerardnico // Close the html wrapper element 5125f891b7eSNickeau $context = $data[PluginUtility::CONTEXT]; 5135f891b7eSNickeau switch ($context) { 5145f891b7eSNickeau case syntax_plugin_combo_navbarcollapse::COMPONENT: 5155f891b7eSNickeau $renderer->doc .= '</div>'; 5165f891b7eSNickeau break; 5175f891b7eSNickeau case syntax_plugin_combo_navbargroup::COMPONENT: 5185f891b7eSNickeau $renderer->doc .= '</li>'; 5195f891b7eSNickeau break; 5205f891b7eSNickeau } 5215f891b7eSNickeau 5225f891b7eSNickeau } 523007225e5Sgerardnico return true; 5245f891b7eSNickeau case 'metadata': 525007225e5Sgerardnico 5264cadd4f8SNickeau /** 5274cadd4f8SNickeau * @var Doku_Renderer_metadata $renderer 5284cadd4f8SNickeau */ 5294cadd4f8SNickeau switch ($state) { 5304cadd4f8SNickeau case DOKU_LEXER_ENTER: 531007225e5Sgerardnico /** 532007225e5Sgerardnico * Keep track of the backlinks ie meta['relation']['references'] 533007225e5Sgerardnico * @var Doku_Renderer_metadata $renderer 534007225e5Sgerardnico */ 5354cadd4f8SNickeau $tagAttributes = TagAttributes::createFromCallStackArray($data[PluginUtility::ATTRIBUTES]); 536*04fd306cSNickeau 537*04fd306cSNickeau $markupRef = $tagAttributes->getValue(self::MARKUP_REF_ATTRIBUTE); 538*04fd306cSNickeau if ($markupRef === null) { 539*04fd306cSNickeau LogUtility::internalError("The markup ref was not found for a link."); 5404cadd4f8SNickeau return false; 541007225e5Sgerardnico } 542*04fd306cSNickeau try { 543*04fd306cSNickeau $type = MarkupRef::createLinkFromRef($markupRef) 544*04fd306cSNickeau ->getSchemeType(); 545*04fd306cSNickeau } catch (ExceptionCompile $e) { 546*04fd306cSNickeau LogUtility::error("The markup ref could not be parsed. Error:{$e->getMessage()}"); 547*04fd306cSNickeau return false; 548*04fd306cSNickeau } 5494cadd4f8SNickeau $name = $tagAttributes->getValue(self::ATTRIBUTE_LABEL); 5509f4383e9Sgerardnico 5514cadd4f8SNickeau switch ($type) { 5524cadd4f8SNickeau case MarkupRef::WIKI_URI: 5534cadd4f8SNickeau /** 5544cadd4f8SNickeau * The relative link should be passed (ie the original) 5554cadd4f8SNickeau * Dokuwiki has a default description 5564cadd4f8SNickeau * We can't pass empty or the array(title), it does not work 5574cadd4f8SNickeau */ 5584cadd4f8SNickeau $descriptionToDelete = "b"; 559*04fd306cSNickeau $renderer->internallink($markupRef, $descriptionToDelete); 5604cadd4f8SNickeau $renderer->doc = substr($renderer->doc, 0, -strlen($descriptionToDelete)); 5614cadd4f8SNickeau break; 5624cadd4f8SNickeau case MarkupRef::WEB_URI: 563*04fd306cSNickeau $renderer->externallink($markupRef, $name); 5644cadd4f8SNickeau break; 5654cadd4f8SNickeau case MarkupRef::LOCAL_URI: 566*04fd306cSNickeau $renderer->locallink($markupRef, $name); 5674cadd4f8SNickeau break; 5684cadd4f8SNickeau case MarkupRef::EMAIL_URI: 569*04fd306cSNickeau $renderer->emaillink($markupRef, $name); 5704cadd4f8SNickeau break; 5714cadd4f8SNickeau case MarkupRef::INTERWIKI_URI: 572*04fd306cSNickeau $interWikiSplit = preg_split("/>/", $markupRef); 573*04fd306cSNickeau $renderer->interwikilink($markupRef, $name, $interWikiSplit[0], $interWikiSplit[1]); 5744cadd4f8SNickeau break; 5754cadd4f8SNickeau case MarkupRef::WINDOWS_SHARE_URI: 576*04fd306cSNickeau $renderer->windowssharelink($markupRef, $name); 5774cadd4f8SNickeau break; 5784cadd4f8SNickeau case MarkupRef::VARIABLE_URI: 5794cadd4f8SNickeau // No backlinks for link template 5804cadd4f8SNickeau break; 5814cadd4f8SNickeau default: 582*04fd306cSNickeau LogUtility::msg("The markup reference ({$markupRef}) with the type $type was not processed into the metadata"); 5839f4383e9Sgerardnico } 584007225e5Sgerardnico 585007225e5Sgerardnico return true; 5864cadd4f8SNickeau case DOKU_LEXER_UNMATCHED: 5874cadd4f8SNickeau $renderer->doc .= PluginUtility::renderUnmatched($data); 5884cadd4f8SNickeau break; 5895f891b7eSNickeau } 590007225e5Sgerardnico break; 591007225e5Sgerardnico 592531e725cSNickeau case renderer_plugin_combo_analytics::RENDERER_FORMAT: 5935f891b7eSNickeau 594*04fd306cSNickeau if ($state === DOKU_LEXER_ENTER) { 595007225e5Sgerardnico /** 596007225e5Sgerardnico * 597007225e5Sgerardnico * @var renderer_plugin_combo_analytics $renderer 598007225e5Sgerardnico */ 5994cadd4f8SNickeau $tagAttributes = TagAttributes::createFromCallStackArray($data[PluginUtility::ATTRIBUTES]); 600*04fd306cSNickeau $ref = $tagAttributes->getValue(self::MARKUP_REF_ATTRIBUTE); 601*04fd306cSNickeau try { 602*04fd306cSNickeau $markupRef = LinkMarkup::createFromRef($ref); 603*04fd306cSNickeau } catch (ExceptionCompile $e) { 604*04fd306cSNickeau LogUtility::error("Error while parsing the ref ($ref). Error: {$e->getMessage()}. No further analytics."); 6054cadd4f8SNickeau return false; 6064cadd4f8SNickeau } 607*04fd306cSNickeau $refType = $markupRef->getMarkupRef()->getSchemeType(); 6084cadd4f8SNickeau 6094cadd4f8SNickeau 6104cadd4f8SNickeau /** 6114cadd4f8SNickeau * @param array $stats 6124cadd4f8SNickeau * Calculate internal link statistics 6134cadd4f8SNickeau */ 6144cadd4f8SNickeau $stats = &$renderer->stats; 6154cadd4f8SNickeau switch ($refType) { 6164cadd4f8SNickeau 6174cadd4f8SNickeau case MarkupRef::WIKI_URI: 6184cadd4f8SNickeau 6194cadd4f8SNickeau /** 6204cadd4f8SNickeau * Internal link count 6214cadd4f8SNickeau */ 622*04fd306cSNickeau if (!array_key_exists(renderer_plugin_combo_analytics::INTERNAL_LINK_COUNT, $stats)) { 623*04fd306cSNickeau $stats[renderer_plugin_combo_analytics::INTERNAL_LINK_COUNT] = 0; 6244cadd4f8SNickeau } 625*04fd306cSNickeau $stats[renderer_plugin_combo_analytics::INTERNAL_LINK_COUNT]++; 6264cadd4f8SNickeau 6274cadd4f8SNickeau 6284cadd4f8SNickeau /** 6294cadd4f8SNickeau * Broken link ? 6304cadd4f8SNickeau */ 631*04fd306cSNickeau try { 632*04fd306cSNickeau $path = $markupRef->getMarkupRef()->getPath(); 633*04fd306cSNickeau $linkedPage = MarkupPath::createPageFromPathObject($path); 634*04fd306cSNickeau if (!FileSystems::exists($path)) { 635*04fd306cSNickeau $stats[renderer_plugin_combo_analytics::INTERNAL_LINK_BROKEN_COUNT]++; 636*04fd306cSNickeau $stats[renderer_plugin_combo_analytics::INFO][] = "The internal linked page `{$linkedPage}` does not exist"; 637*04fd306cSNickeau } 638*04fd306cSNickeau } catch (ExceptionNotFound $e) { 639*04fd306cSNickeau // no local path 6404cadd4f8SNickeau } 6414cadd4f8SNickeau 6424cadd4f8SNickeau /** 6434cadd4f8SNickeau * Calculate link distance 6444cadd4f8SNickeau */ 6454cadd4f8SNickeau global $ID; 646*04fd306cSNickeau $id = $linkedPage->getWikiId(); 6474cadd4f8SNickeau $a = explode(':', getNS($ID)); 6484cadd4f8SNickeau $b = explode(':', getNS($id)); 6494cadd4f8SNickeau while (isset($a[0]) && $a[0] == $b[0]) { 6504cadd4f8SNickeau array_shift($a); 6514cadd4f8SNickeau array_shift($b); 6524cadd4f8SNickeau } 6534cadd4f8SNickeau $length = count($a) + count($b); 654*04fd306cSNickeau $stats[renderer_plugin_combo_analytics::INTERNAL_LINK_DISTANCE][] = $length; 6554cadd4f8SNickeau break; 6564cadd4f8SNickeau 6574cadd4f8SNickeau case MarkupRef::WEB_URI: 6584cadd4f8SNickeau 659*04fd306cSNickeau if (!array_key_exists(renderer_plugin_combo_analytics::EXTERNAL_LINK_COUNT, $stats)) { 660*04fd306cSNickeau $stats[renderer_plugin_combo_analytics::EXTERNAL_LINK_COUNT] = 0; 6614cadd4f8SNickeau } 662*04fd306cSNickeau $stats[renderer_plugin_combo_analytics::EXTERNAL_LINK_COUNT]++; 6634cadd4f8SNickeau break; 6644cadd4f8SNickeau 6654cadd4f8SNickeau case MarkupRef::LOCAL_URI: 6664cadd4f8SNickeau 667*04fd306cSNickeau if (!array_key_exists(renderer_plugin_combo_analytics::LOCAL_LINK_COUNT, $stats)) { 668*04fd306cSNickeau $stats[renderer_plugin_combo_analytics::LOCAL_LINK_COUNT] = 0; 6694cadd4f8SNickeau } 670*04fd306cSNickeau $stats[renderer_plugin_combo_analytics::LOCAL_LINK_COUNT]++; 6714cadd4f8SNickeau break; 6724cadd4f8SNickeau 6734cadd4f8SNickeau case MarkupRef::INTERWIKI_URI: 6744cadd4f8SNickeau 675*04fd306cSNickeau if (!array_key_exists(renderer_plugin_combo_analytics::INTERWIKI_LINK_COUNT, $stats)) { 676*04fd306cSNickeau $stats[renderer_plugin_combo_analytics::INTERWIKI_LINK_COUNT] = 0; 6774cadd4f8SNickeau } 678*04fd306cSNickeau $stats[renderer_plugin_combo_analytics::INTERWIKI_LINK_COUNT]++; 6794cadd4f8SNickeau break; 6804cadd4f8SNickeau 6814cadd4f8SNickeau case MarkupRef::EMAIL_URI: 6824cadd4f8SNickeau 683*04fd306cSNickeau if (!array_key_exists(renderer_plugin_combo_analytics::EMAIL_COUNT, $stats)) { 684*04fd306cSNickeau $stats[renderer_plugin_combo_analytics::EMAIL_COUNT] = 0; 6854cadd4f8SNickeau } 686*04fd306cSNickeau $stats[renderer_plugin_combo_analytics::EMAIL_COUNT]++; 6874cadd4f8SNickeau break; 6884cadd4f8SNickeau 6894cadd4f8SNickeau case MarkupRef::WINDOWS_SHARE_URI: 6904cadd4f8SNickeau 691*04fd306cSNickeau if (!array_key_exists(renderer_plugin_combo_analytics::WINDOWS_SHARE_COUNT, $stats)) { 692*04fd306cSNickeau $stats[renderer_plugin_combo_analytics::WINDOWS_SHARE_COUNT] = 0; 6934cadd4f8SNickeau } 694*04fd306cSNickeau $stats[renderer_plugin_combo_analytics::WINDOWS_SHARE_COUNT]++; 6954cadd4f8SNickeau break; 6964cadd4f8SNickeau 6974cadd4f8SNickeau case MarkupRef::VARIABLE_URI: 6984cadd4f8SNickeau 699*04fd306cSNickeau if (!array_key_exists(renderer_plugin_combo_analytics::TEMPLATE_LINK_COUNT, $stats)) { 700*04fd306cSNickeau $stats[renderer_plugin_combo_analytics::TEMPLATE_LINK_COUNT] = 0; 7014cadd4f8SNickeau } 702*04fd306cSNickeau $stats[renderer_plugin_combo_analytics::TEMPLATE_LINK_COUNT]++; 7034cadd4f8SNickeau break; 7044cadd4f8SNickeau 7054cadd4f8SNickeau default: 7064cadd4f8SNickeau 7074cadd4f8SNickeau LogUtility::msg("The link `{$ref}` with the type ($refType) is not taken into account into the statistics"); 7084cadd4f8SNickeau 7094cadd4f8SNickeau } 7104cadd4f8SNickeau 7114cadd4f8SNickeau 712007225e5Sgerardnico break; 7135f891b7eSNickeau } 714007225e5Sgerardnico 715007225e5Sgerardnico } 716*04fd306cSNickeau 717007225e5Sgerardnico return false; 718007225e5Sgerardnico } 719007225e5Sgerardnico 720007225e5Sgerardnico 7214cadd4f8SNickeau /** 7224cadd4f8SNickeau * Utility function to add a link into the callstack 7234cadd4f8SNickeau * @param CallStack $callStack 7244cadd4f8SNickeau * @param TagAttributes $tagAttributes 7254cadd4f8SNickeau */ 726*04fd306cSNickeau public 727*04fd306cSNickeau static function addOpenLinkTagInCallStack(CallStack $callStack, TagAttributes $tagAttributes) 7284cadd4f8SNickeau { 7294cadd4f8SNickeau $parent = $callStack->moveToParent(); 7304cadd4f8SNickeau $context = ""; 7314cadd4f8SNickeau $attributes = $tagAttributes->toCallStackArray(); 7324cadd4f8SNickeau if ($parent !== false) { 7334cadd4f8SNickeau $context = $parent->getTagName(); 734*04fd306cSNickeau if ($context === ButtonTag::MARKUP_LONG) { 7354cadd4f8SNickeau // the link takes by default the data from the button 7364cadd4f8SNickeau $parentAttributes = $parent->getAttributes(); 7374cadd4f8SNickeau if ($parentAttributes !== null) { 7384cadd4f8SNickeau $attributes = ArrayUtility::mergeByValue($parentAttributes, $attributes); 7394cadd4f8SNickeau } 7404cadd4f8SNickeau } 7414cadd4f8SNickeau } 7424cadd4f8SNickeau $callStack->appendCallAtTheEnd( 7434cadd4f8SNickeau Call::createComboCall( 7444cadd4f8SNickeau syntax_plugin_combo_link::TAG, 7454cadd4f8SNickeau DOKU_LEXER_ENTER, 7464cadd4f8SNickeau $attributes, 7474cadd4f8SNickeau $context 7484cadd4f8SNickeau )); 7494cadd4f8SNickeau } 7504cadd4f8SNickeau 751*04fd306cSNickeau public 752*04fd306cSNickeau static function addExitLinkTagInCallStack(CallStack $callStack) 7534cadd4f8SNickeau { 7544cadd4f8SNickeau $callStack->appendCallAtTheEnd( 7554cadd4f8SNickeau Call::createComboCall( 7564cadd4f8SNickeau syntax_plugin_combo_link::TAG, 7574cadd4f8SNickeau DOKU_LEXER_EXIT 7584cadd4f8SNickeau )); 7594cadd4f8SNickeau } 760007225e5Sgerardnico} 761007225e5Sgerardnico 762