1007225e5Sgerardnico<?php 2007225e5Sgerardnico 3007225e5Sgerardnico 437748cd8SNickeaurequire_once(__DIR__ . "/../ComboStrap/PluginUtility.php"); 5007225e5Sgerardnico 64cadd4f8SNickeauuse ComboStrap\ArrayUtility; 704fd306cSNickeauuse ComboStrap\ButtonTag; 84cadd4f8SNickeauuse ComboStrap\Call; 9531e725cSNickeauuse ComboStrap\CallStack; 1004fd306cSNickeauuse ComboStrap\DropDownTag; 1104fd306cSNickeauuse ComboStrap\ExceptionBadArgument; 1204fd306cSNickeauuse ComboStrap\ExceptionBadSyntax; 1304fd306cSNickeauuse ComboStrap\ExceptionCompile; 1404fd306cSNickeauuse ComboStrap\ExceptionNotFound; 1504fd306cSNickeauuse ComboStrap\FileSystems; 1604fd306cSNickeauuse ComboStrap\LinkMarkup; 174cadd4f8SNickeauuse ComboStrap\LogUtility; 1804fd306cSNickeauuse ComboStrap\MarkupRef; 1904fd306cSNickeauuse ComboStrap\MarkupPath; 20007225e5Sgerardnicouse ComboStrap\PluginUtility; 21531e725cSNickeauuse ComboStrap\TagAttributes; 22c3437056SNickeauuse ComboStrap\ThirdPartyPlugins; 2304fd306cSNickeauuse 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 * 3504fd306cSNickeau * popular [[ wiki ]] syntax for linking notes 3604fd306cSNickeau * and makes it easy to build personal wikis, 3704fd306cSNickeau * team knowledge bases, 3804fd306cSNickeau * or something like a Second Brain or a Zettelkasten. 3904fd306cSNickeau * 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 */ 6504fd306cSNickeau public const MARKUP_REF_ATTRIBUTE = 'ref'; 6604fd306cSNickeau 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"; 9604fd306cSNickeau const STRETCHED_LINK = "stretched-link"; 9704fd306cSNickeau 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 11104fd306cSNickeau $linkString = preg_replace(array('/^\[\[/', '/]]$/u'), '', $match); 1124cadd4f8SNickeau 1134cadd4f8SNickeau // Split title from URL 1144cadd4f8SNickeau $linkArray = explode('|', $linkString, 2); 1154cadd4f8SNickeau 1164cadd4f8SNickeau // Id 11704fd306cSNickeau $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 12504fd306cSNickeau 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); 26804fd306cSNickeau 2694cadd4f8SNickeau /** 27004fd306cSNickeau * The markup ref needs to be passed to the 27104fd306cSNickeau * instructions stack (because we support link with a variable as markup ref 27204fd306cSNickeau * via a {@link syntax_plugin_combo_fragment} in a {@link syntax_plugin_combo_iterator} 27304fd306cSNickeau * 27404fd306cSNickeau * The variable is replaced in the {@link syntax_plugin_combo_link::render()} 27504fd306cSNickeau * at runtime while rendering 2764cadd4f8SNickeau */ 27704fd306cSNickeau $markupRef = $parsedArray[self::MARKUP_REF_ATTRIBUTE]; 27804fd306cSNickeau if ($markupRef !== null) { 279*4fbd4ae2SNico /** 280*4fbd4ae2SNico * If the Rel is a wiki link, we make the path absolute and not relative 281*4fbd4ae2SNico * (this is for the {@link \ComboStrap\FetcherPageBundler)}} 282*4fbd4ae2SNico * otherwise the links are not good and are seen as non-existent 283*4fbd4ae2SNico */ 284*4fbd4ae2SNico try { 285*4fbd4ae2SNico $markupRefObject = MarkupRef::createLinkFromRef($markupRef); 286*4fbd4ae2SNico $scheme = $markupRefObject->getSchemeType(); 287*4fbd4ae2SNico if ($scheme === MarkupRef::WIKI_URI) { 288*4fbd4ae2SNico $markupRef = $markupRefObject->getPath()->toAbsoluteId(); 289*4fbd4ae2SNico } 290*4fbd4ae2SNico } catch (ExceptionBadArgument|ExceptionBadSyntax|ExceptionNotFound $e) { 291*4fbd4ae2SNico // no a valid ref 292*4fbd4ae2SNico } 29304fd306cSNickeau $htmlAttributes->addComponentAttributeValue(self::MARKUP_REF_ATTRIBUTE, $markupRef); 2944cadd4f8SNickeau } 2954cadd4f8SNickeau 2964cadd4f8SNickeau 2974cadd4f8SNickeau /** 2984cadd4f8SNickeau * Extra HTML attribute 2994cadd4f8SNickeau */ 300531e725cSNickeau $callStack = CallStack::createFromHandler($handler); 301531e725cSNickeau $parent = $callStack->moveToParent(); 302007225e5Sgerardnico $parentName = ""; 3034cadd4f8SNickeau if ($parent !== false) { 304531e725cSNickeau 305531e725cSNickeau /** 306531e725cSNickeau * Button Link 307531e725cSNickeau * Getting the attributes 308531e725cSNickeau */ 309531e725cSNickeau $parentName = $parent->getTagName(); 31004fd306cSNickeau if ($parentName == ButtonTag::MARKUP_LONG) { 3114cadd4f8SNickeau $htmlAttributes->mergeWithCallStackArray($parent->getAttributes()); 31221913ab3SNickeau } 313531e725cSNickeau 314531e725cSNickeau /** 315531e725cSNickeau * Searching Clickable parent 316531e725cSNickeau */ 317531e725cSNickeau $maxLevel = 3; 318531e725cSNickeau $level = 0; 319531e725cSNickeau while ( 320531e725cSNickeau $parent != false && 321531e725cSNickeau !$parent->hasAttribute(self::CLICKABLE_ATTRIBUTE) && 322531e725cSNickeau $level < $maxLevel 323531e725cSNickeau ) { 324531e725cSNickeau $parent = $callStack->moveToParent(); 325531e725cSNickeau $level++; 3265f891b7eSNickeau } 32704fd306cSNickeau if ($parent !== false) { 328531e725cSNickeau if ($parent->getAttribute(self::CLICKABLE_ATTRIBUTE)) { 32904fd306cSNickeau $htmlAttributes->addClassName(self::STRETCHED_LINK); 330531e725cSNickeau $parent->addClassName("position-relative"); 331531e725cSNickeau $parent->removeAttribute(self::CLICKABLE_ATTRIBUTE); 3325f891b7eSNickeau } 33321913ab3SNickeau } 33421913ab3SNickeau 335531e725cSNickeau } 3364cadd4f8SNickeau $returnedArray[PluginUtility::STATE] = $state; 3374cadd4f8SNickeau $returnedArray[PluginUtility::ATTRIBUTES] = $htmlAttributes->toCallStackArray(); 3384cadd4f8SNickeau $returnedArray[PluginUtility::CONTEXT] = $parentName; 3394cadd4f8SNickeau return $returnedArray; 3404cadd4f8SNickeau 3415f891b7eSNickeau case DOKU_LEXER_UNMATCHED: 3425f891b7eSNickeau 34332b85071SNickeau $data = PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler); 3445f891b7eSNickeau /** 34532b85071SNickeau * Delete the separator `|` between the ref and the description if any 3465f891b7eSNickeau */ 3471fa8c418SNickeau $tag = CallStack::createFromHandler($handler); 3481fa8c418SNickeau $parent = $tag->moveToParent(); 3491fa8c418SNickeau if ($parent->getTagName() == self::TAG) { 3505f891b7eSNickeau if (strpos($match, '|') === 0) { 35132b85071SNickeau $data[PluginUtility::PAYLOAD] = substr($match, 1); 3525f891b7eSNickeau } 353007225e5Sgerardnico } 35432b85071SNickeau return $data; 355007225e5Sgerardnico 3565f891b7eSNickeau case DOKU_LEXER_EXIT: 35704fd306cSNickeau 358531e725cSNickeau $callStack = CallStack::createFromHandler($handler); 359531e725cSNickeau $openingTag = $callStack->moveToPreviousCorrespondingOpeningCall(); 3604cadd4f8SNickeau 3615f891b7eSNickeau $openingAttributes = $openingTag->getAttributes(); 362531e725cSNickeau $openingPosition = $openingTag->getKey(); 3635f891b7eSNickeau 364531e725cSNickeau $callStack->moveToEnd(); 365531e725cSNickeau $previousCall = $callStack->previous(); 366531e725cSNickeau $previousCallPosition = $previousCall->getKey(); 367531e725cSNickeau $previousCallContent = $previousCall->getCapturedContent(); 368531e725cSNickeau 3694cadd4f8SNickeau /** 3704cadd4f8SNickeau * Link label 3714cadd4f8SNickeau * is set if there is no content 3724cadd4f8SNickeau * between enter and exit node 3734cadd4f8SNickeau */ 3744cadd4f8SNickeau $linkLabel = ""; 375531e725cSNickeau if ( 376531e725cSNickeau $openingPosition == $previousCallPosition // ie [[id]] 377531e725cSNickeau || 378531e725cSNickeau ($openingPosition == $previousCallPosition - 1 && $previousCallContent == "|") // ie [[id|]] 379531e725cSNickeau ) { 3805f891b7eSNickeau // There is no name 38104fd306cSNickeau $markupRef = $openingTag->getAttribute(self::MARKUP_REF_ATTRIBUTE); 38204fd306cSNickeau if ($markupRef !== null) { 38304fd306cSNickeau try { 38404fd306cSNickeau $linkLabel = LinkMarkup::createFromRef($markupRef) 38504fd306cSNickeau ->getDefaultLabel(); 38604fd306cSNickeau } catch (ExceptionCompile $e) { 38704fd306cSNickeau LogUtility::error("No default Label can be defined. Error while parsing the markup ref ($markupRef). Error: {$e->getMessage()}", self::CANONICAL, $e); 38804fd306cSNickeau } 38904fd306cSNickeau 3904cadd4f8SNickeau } 3915f891b7eSNickeau } 3925f891b7eSNickeau return array( 3935f891b7eSNickeau PluginUtility::STATE => $state, 3945f891b7eSNickeau PluginUtility::ATTRIBUTES => $openingAttributes, 3954cadd4f8SNickeau PluginUtility::PAYLOAD => $linkLabel, 39685e82846SNickeau PluginUtility::CONTEXT => $openingTag->getContext() 3975f891b7eSNickeau ); 3985f891b7eSNickeau } 3995f891b7eSNickeau return true; 4005f891b7eSNickeau 401007225e5Sgerardnico 402007225e5Sgerardnico } 403007225e5Sgerardnico 404007225e5Sgerardnico /** 405007225e5Sgerardnico * Render the output 406007225e5Sgerardnico * @param string $format 407007225e5Sgerardnico * @param Doku_Renderer $renderer 408007225e5Sgerardnico * @param array $data - what the function handle() return'ed 409007225e5Sgerardnico * @return boolean - rendered correctly? (however, returned value is not used at the moment) 410007225e5Sgerardnico * @see DokuWiki_Syntax_Plugin::render() 411007225e5Sgerardnico * 412007225e5Sgerardnico * 413007225e5Sgerardnico */ 414c3437056SNickeau function render($format, Doku_Renderer $renderer, $data): bool 415007225e5Sgerardnico { 416007225e5Sgerardnico // The data 41704fd306cSNickeau $state = $data[PluginUtility::STATE]; 418007225e5Sgerardnico switch ($format) { 419007225e5Sgerardnico case 'xhtml': 420007225e5Sgerardnico 421007225e5Sgerardnico /** @var Doku_Renderer_xhtml $renderer */ 422007225e5Sgerardnico /** 42319b0880dSgerardnico * Cache problem may occurs while releasing 424007225e5Sgerardnico */ 425007225e5Sgerardnico if (isset($data[PluginUtility::ATTRIBUTES])) { 426531e725cSNickeau $callStackAttributes = $data[PluginUtility::ATTRIBUTES]; 427007225e5Sgerardnico } else { 428531e725cSNickeau $callStackAttributes = $data; 429007225e5Sgerardnico } 4305f891b7eSNickeau 43104fd306cSNickeau PluginUtility::getSnippetManager()->attachCssInternalStyleSheet(self::TAG); 4325f891b7eSNickeau 4335f891b7eSNickeau switch ($state) { 4345f891b7eSNickeau case DOKU_LEXER_ENTER: 435531e725cSNickeau $tagAttributes = TagAttributes::createFromCallStackArray($callStackAttributes, self::TAG); 4364cadd4f8SNickeau 43704fd306cSNickeau $markupRef = $tagAttributes->getValueAndRemove(self::MARKUP_REF_ATTRIBUTE); 43804fd306cSNickeau if ($markupRef === null) { 43904fd306cSNickeau $message = "Internal Error: A link reference was not found"; 44004fd306cSNickeau LogUtility::internalError($message); 44104fd306cSNickeau $renderer->doc .= LogUtility::wrapInRedForHtml($message); 4424cadd4f8SNickeau return false; 4434cadd4f8SNickeau } 44404fd306cSNickeau try { 44504fd306cSNickeau $markupRef = syntax_plugin_combo_variable::replaceVariablesWithValuesFromContext($markupRef); 44604fd306cSNickeau $markupLink = LinkMarkup::createFromRef($markupRef); 44704fd306cSNickeau $markupAttributes = $markupLink->toAttributes(); 44804fd306cSNickeau } catch (ExceptionCompile $e) { 44904fd306cSNickeau // uncomment to get the original error stack trace in dev 45004fd306cSNickeau // and see where the exception comes from 45104fd306cSNickeau // Don't forget to comment back 45204fd306cSNickeau// if (PluginUtility::isDevOrTest()) { 45304fd306cSNickeau// throw new ExceptionRuntime("Error on markup ref", self::TAG, 0, $e); 45404fd306cSNickeau// } 45504fd306cSNickeau 45604fd306cSNickeau /** 45704fd306cSNickeau * Error. Example: unknown inter-wiki ... 45804fd306cSNickeau * We still create the a to be xhtml compliante 45904fd306cSNickeau */ 46004fd306cSNickeau $url = UrlEndpoint::createSupportUrl(); 46104fd306cSNickeau $markupAttributes = TagAttributes::createEmpty() 46204fd306cSNickeau ->addOutputAttributeValue("href", $url->toString()) 46304fd306cSNickeau ->addClassName(LinkMarkup::getHtmlClassNotExist()); 46404fd306cSNickeau $renderer->doc .= $markupAttributes->toHtmlEnterTag("a") . $e->getMessage(); 46504fd306cSNickeau 46604fd306cSNickeau LogUtility::warning($e->getMessage(), "link", $e); 46704fd306cSNickeau return false; 4684cadd4f8SNickeau 4694cadd4f8SNickeau } 47004fd306cSNickeau // markup attributes is leading because it has already output attribute such as href 47104fd306cSNickeau $markupAttributes->mergeWithCallStackArray($tagAttributes->toCallStackArray()); 47204fd306cSNickeau $tagAttributes = $markupAttributes; 47304fd306cSNickeau 474d262537cSgerardnico 47519b0880dSgerardnico /** 4765f891b7eSNickeau * Extra styling 47719b0880dSgerardnico */ 4785f891b7eSNickeau $parentTag = $data[PluginUtility::CONTEXT]; 4794cadd4f8SNickeau $htmlPrefix = ""; 4809f4383e9Sgerardnico switch ($parentTag) { 48121913ab3SNickeau /** 48221913ab3SNickeau * Button link 48321913ab3SNickeau */ 48404fd306cSNickeau case ButtonTag::MARKUP_LONG: 4854cadd4f8SNickeau $tagAttributes->addOutputAttributeValue("role", "button"); 48604fd306cSNickeau ButtonTag::processButtonAttributesToHtmlAttributes($tagAttributes); 4879f4383e9Sgerardnico break; 48804fd306cSNickeau case DropDownTag::TAG: 4894cadd4f8SNickeau $tagAttributes->addClassName("dropdown-item"); 4904cadd4f8SNickeau break; 4914cadd4f8SNickeau case syntax_plugin_combo_navbarcollapse::COMPONENT: 4924cadd4f8SNickeau $tagAttributes->addClassName("navbar-link"); 4934cadd4f8SNickeau $htmlPrefix = '<div class="navbar-nav">'; 4944cadd4f8SNickeau break; 4954cadd4f8SNickeau case syntax_plugin_combo_navbargroup::COMPONENT: 4964cadd4f8SNickeau $tagAttributes->addClassName("nav-link"); 4974cadd4f8SNickeau $htmlPrefix = '<li class="nav-item">'; 4984cadd4f8SNickeau break; 4994cadd4f8SNickeau default: 5005f891b7eSNickeau case syntax_plugin_combo_badge::TAG: 5019f4383e9Sgerardnico case syntax_plugin_combo_cite::TAG: 5029337a630SNickeau case syntax_plugin_combo_contentlistitem::DOKU_TAG: 5039f4383e9Sgerardnico case syntax_plugin_combo_preformatted::TAG: 5049f4383e9Sgerardnico break; 5050a517624Sgerardnico 5069f4383e9Sgerardnico } 5079f4383e9Sgerardnico 50819b0880dSgerardnico /** 50919b0880dSgerardnico * Add it to the rendering 51019b0880dSgerardnico */ 5114cadd4f8SNickeau $renderer->doc .= $htmlPrefix . $tagAttributes->toHtmlEnterTag("a"); 5125f891b7eSNickeau break; 5135f891b7eSNickeau case DOKU_LEXER_UNMATCHED: 51432b85071SNickeau $renderer->doc .= PluginUtility::renderUnmatched($data); 5155f891b7eSNickeau break; 5165f891b7eSNickeau case DOKU_LEXER_EXIT: 5175f891b7eSNickeau 5185f891b7eSNickeau // if there is no link name defined, we get the name as ref in the payload 5195f891b7eSNickeau // otherwise null string 52070bbd7f1Sgerardnico $renderer->doc .= $data[PluginUtility::PAYLOAD] ?? ''; 5215f891b7eSNickeau 522e3d0019cSgerardnico // Close the link 52385e82846SNickeau $renderer->doc .= "</a>"; 524e3d0019cSgerardnico 525e3d0019cSgerardnico // Close the html wrapper element 5265f891b7eSNickeau $context = $data[PluginUtility::CONTEXT]; 5275f891b7eSNickeau switch ($context) { 5285f891b7eSNickeau case syntax_plugin_combo_navbarcollapse::COMPONENT: 5295f891b7eSNickeau $renderer->doc .= '</div>'; 5305f891b7eSNickeau break; 5315f891b7eSNickeau case syntax_plugin_combo_navbargroup::COMPONENT: 5325f891b7eSNickeau $renderer->doc .= '</li>'; 5335f891b7eSNickeau break; 5345f891b7eSNickeau } 5355f891b7eSNickeau 5365f891b7eSNickeau } 537007225e5Sgerardnico return true; 5385f891b7eSNickeau case 'metadata': 539007225e5Sgerardnico 5404cadd4f8SNickeau /** 5414cadd4f8SNickeau * @var Doku_Renderer_metadata $renderer 5424cadd4f8SNickeau */ 5434cadd4f8SNickeau switch ($state) { 5444cadd4f8SNickeau case DOKU_LEXER_ENTER: 545007225e5Sgerardnico /** 546007225e5Sgerardnico * Keep track of the backlinks ie meta['relation']['references'] 547007225e5Sgerardnico * @var Doku_Renderer_metadata $renderer 548007225e5Sgerardnico */ 5494cadd4f8SNickeau $tagAttributes = TagAttributes::createFromCallStackArray($data[PluginUtility::ATTRIBUTES]); 55004fd306cSNickeau 55104fd306cSNickeau $markupRef = $tagAttributes->getValue(self::MARKUP_REF_ATTRIBUTE); 55204fd306cSNickeau if ($markupRef === null) { 55304fd306cSNickeau LogUtility::internalError("The markup ref was not found for a link."); 5544cadd4f8SNickeau return false; 555007225e5Sgerardnico } 55604fd306cSNickeau try { 55704fd306cSNickeau $type = MarkupRef::createLinkFromRef($markupRef) 55804fd306cSNickeau ->getSchemeType(); 55904fd306cSNickeau } catch (ExceptionCompile $e) { 56004fd306cSNickeau LogUtility::error("The markup ref could not be parsed. Error:{$e->getMessage()}"); 56104fd306cSNickeau return false; 56204fd306cSNickeau } 5634cadd4f8SNickeau $name = $tagAttributes->getValue(self::ATTRIBUTE_LABEL); 5649f4383e9Sgerardnico 5654cadd4f8SNickeau switch ($type) { 5664cadd4f8SNickeau case MarkupRef::WIKI_URI: 5674cadd4f8SNickeau /** 5684cadd4f8SNickeau * The relative link should be passed (ie the original) 5694cadd4f8SNickeau * Dokuwiki has a default description 5704cadd4f8SNickeau * We can't pass empty or the array(title), it does not work 5714cadd4f8SNickeau */ 5724cadd4f8SNickeau $descriptionToDelete = "b"; 57304fd306cSNickeau $renderer->internallink($markupRef, $descriptionToDelete); 5744cadd4f8SNickeau $renderer->doc = substr($renderer->doc, 0, -strlen($descriptionToDelete)); 5754cadd4f8SNickeau break; 5764cadd4f8SNickeau case MarkupRef::WEB_URI: 57704fd306cSNickeau $renderer->externallink($markupRef, $name); 5784cadd4f8SNickeau break; 5794cadd4f8SNickeau case MarkupRef::LOCAL_URI: 58004fd306cSNickeau $renderer->locallink($markupRef, $name); 5814cadd4f8SNickeau break; 5824cadd4f8SNickeau case MarkupRef::EMAIL_URI: 58304fd306cSNickeau $renderer->emaillink($markupRef, $name); 5844cadd4f8SNickeau break; 5854cadd4f8SNickeau case MarkupRef::INTERWIKI_URI: 58604fd306cSNickeau $interWikiSplit = preg_split("/>/", $markupRef); 58704fd306cSNickeau $renderer->interwikilink($markupRef, $name, $interWikiSplit[0], $interWikiSplit[1]); 5884cadd4f8SNickeau break; 5894cadd4f8SNickeau case MarkupRef::WINDOWS_SHARE_URI: 59004fd306cSNickeau $renderer->windowssharelink($markupRef, $name); 5914cadd4f8SNickeau break; 5924cadd4f8SNickeau case MarkupRef::VARIABLE_URI: 5934cadd4f8SNickeau // No backlinks for link template 5944cadd4f8SNickeau break; 5954cadd4f8SNickeau default: 59604fd306cSNickeau LogUtility::msg("The markup reference ({$markupRef}) with the type $type was not processed into the metadata"); 5979f4383e9Sgerardnico } 598007225e5Sgerardnico 599007225e5Sgerardnico return true; 6004cadd4f8SNickeau case DOKU_LEXER_UNMATCHED: 6014cadd4f8SNickeau $renderer->doc .= PluginUtility::renderUnmatched($data); 6024cadd4f8SNickeau break; 6035f891b7eSNickeau } 604007225e5Sgerardnico break; 605007225e5Sgerardnico 606531e725cSNickeau case renderer_plugin_combo_analytics::RENDERER_FORMAT: 6075f891b7eSNickeau 60804fd306cSNickeau if ($state === DOKU_LEXER_ENTER) { 609007225e5Sgerardnico /** 610007225e5Sgerardnico * 611007225e5Sgerardnico * @var renderer_plugin_combo_analytics $renderer 612007225e5Sgerardnico */ 6134cadd4f8SNickeau $tagAttributes = TagAttributes::createFromCallStackArray($data[PluginUtility::ATTRIBUTES]); 61404fd306cSNickeau $ref = $tagAttributes->getValue(self::MARKUP_REF_ATTRIBUTE); 61504fd306cSNickeau try { 61604fd306cSNickeau $markupRef = LinkMarkup::createFromRef($ref); 61704fd306cSNickeau } catch (ExceptionCompile $e) { 61804fd306cSNickeau LogUtility::error("Error while parsing the ref ($ref). Error: {$e->getMessage()}. No further analytics."); 6194cadd4f8SNickeau return false; 6204cadd4f8SNickeau } 62104fd306cSNickeau $refType = $markupRef->getMarkupRef()->getSchemeType(); 6224cadd4f8SNickeau 6234cadd4f8SNickeau 6244cadd4f8SNickeau /** 6254cadd4f8SNickeau * @param array $stats 6264cadd4f8SNickeau * Calculate internal link statistics 6274cadd4f8SNickeau */ 6284cadd4f8SNickeau $stats = &$renderer->stats; 6294cadd4f8SNickeau switch ($refType) { 6304cadd4f8SNickeau 6314cadd4f8SNickeau case MarkupRef::WIKI_URI: 6324cadd4f8SNickeau 6334cadd4f8SNickeau /** 6344cadd4f8SNickeau * Internal link count 6354cadd4f8SNickeau */ 63604fd306cSNickeau if (!array_key_exists(renderer_plugin_combo_analytics::INTERNAL_LINK_COUNT, $stats)) { 63704fd306cSNickeau $stats[renderer_plugin_combo_analytics::INTERNAL_LINK_COUNT] = 0; 6384cadd4f8SNickeau } 63904fd306cSNickeau $stats[renderer_plugin_combo_analytics::INTERNAL_LINK_COUNT]++; 6404cadd4f8SNickeau 6414cadd4f8SNickeau 6424cadd4f8SNickeau /** 6434cadd4f8SNickeau * Broken link ? 6444cadd4f8SNickeau */ 64504fd306cSNickeau try { 64604fd306cSNickeau $path = $markupRef->getMarkupRef()->getPath(); 64704fd306cSNickeau $linkedPage = MarkupPath::createPageFromPathObject($path); 64804fd306cSNickeau if (!FileSystems::exists($path)) { 64970bbd7f1Sgerardnico $internalLinkBroken = $stats[renderer_plugin_combo_analytics::INTERNAL_LINK_BROKEN_COUNT] ?? 0; 65070bbd7f1Sgerardnico $stats[renderer_plugin_combo_analytics::INTERNAL_LINK_BROKEN_COUNT] = $internalLinkBroken + 1; 65104fd306cSNickeau $stats[renderer_plugin_combo_analytics::INFO][] = "The internal linked page `{$linkedPage}` does not exist"; 65204fd306cSNickeau } 65304fd306cSNickeau } catch (ExceptionNotFound $e) { 65404fd306cSNickeau // no local path 6554cadd4f8SNickeau } 6564cadd4f8SNickeau 6574cadd4f8SNickeau /** 6584cadd4f8SNickeau * Calculate link distance 6594cadd4f8SNickeau */ 6604cadd4f8SNickeau global $ID; 66104fd306cSNickeau $id = $linkedPage->getWikiId(); 6624cadd4f8SNickeau $a = explode(':', getNS($ID)); 6634cadd4f8SNickeau $b = explode(':', getNS($id)); 6644cadd4f8SNickeau while (isset($a[0]) && $a[0] == $b[0]) { 6654cadd4f8SNickeau array_shift($a); 6664cadd4f8SNickeau array_shift($b); 6674cadd4f8SNickeau } 6684cadd4f8SNickeau $length = count($a) + count($b); 66904fd306cSNickeau $stats[renderer_plugin_combo_analytics::INTERNAL_LINK_DISTANCE][] = $length; 6704cadd4f8SNickeau break; 6714cadd4f8SNickeau 6724cadd4f8SNickeau case MarkupRef::WEB_URI: 6734cadd4f8SNickeau 67404fd306cSNickeau if (!array_key_exists(renderer_plugin_combo_analytics::EXTERNAL_LINK_COUNT, $stats)) { 67504fd306cSNickeau $stats[renderer_plugin_combo_analytics::EXTERNAL_LINK_COUNT] = 0; 6764cadd4f8SNickeau } 67704fd306cSNickeau $stats[renderer_plugin_combo_analytics::EXTERNAL_LINK_COUNT]++; 6784cadd4f8SNickeau break; 6794cadd4f8SNickeau 6804cadd4f8SNickeau case MarkupRef::LOCAL_URI: 6814cadd4f8SNickeau 68204fd306cSNickeau if (!array_key_exists(renderer_plugin_combo_analytics::LOCAL_LINK_COUNT, $stats)) { 68304fd306cSNickeau $stats[renderer_plugin_combo_analytics::LOCAL_LINK_COUNT] = 0; 6844cadd4f8SNickeau } 68504fd306cSNickeau $stats[renderer_plugin_combo_analytics::LOCAL_LINK_COUNT]++; 6864cadd4f8SNickeau break; 6874cadd4f8SNickeau 6884cadd4f8SNickeau case MarkupRef::INTERWIKI_URI: 6894cadd4f8SNickeau 69004fd306cSNickeau if (!array_key_exists(renderer_plugin_combo_analytics::INTERWIKI_LINK_COUNT, $stats)) { 69104fd306cSNickeau $stats[renderer_plugin_combo_analytics::INTERWIKI_LINK_COUNT] = 0; 6924cadd4f8SNickeau } 69304fd306cSNickeau $stats[renderer_plugin_combo_analytics::INTERWIKI_LINK_COUNT]++; 6944cadd4f8SNickeau break; 6954cadd4f8SNickeau 6964cadd4f8SNickeau case MarkupRef::EMAIL_URI: 6974cadd4f8SNickeau 69804fd306cSNickeau if (!array_key_exists(renderer_plugin_combo_analytics::EMAIL_COUNT, $stats)) { 69904fd306cSNickeau $stats[renderer_plugin_combo_analytics::EMAIL_COUNT] = 0; 7004cadd4f8SNickeau } 70104fd306cSNickeau $stats[renderer_plugin_combo_analytics::EMAIL_COUNT]++; 7024cadd4f8SNickeau break; 7034cadd4f8SNickeau 7044cadd4f8SNickeau case MarkupRef::WINDOWS_SHARE_URI: 7054cadd4f8SNickeau 70604fd306cSNickeau if (!array_key_exists(renderer_plugin_combo_analytics::WINDOWS_SHARE_COUNT, $stats)) { 70704fd306cSNickeau $stats[renderer_plugin_combo_analytics::WINDOWS_SHARE_COUNT] = 0; 7084cadd4f8SNickeau } 70904fd306cSNickeau $stats[renderer_plugin_combo_analytics::WINDOWS_SHARE_COUNT]++; 7104cadd4f8SNickeau break; 7114cadd4f8SNickeau 7124cadd4f8SNickeau case MarkupRef::VARIABLE_URI: 7134cadd4f8SNickeau 71404fd306cSNickeau if (!array_key_exists(renderer_plugin_combo_analytics::TEMPLATE_LINK_COUNT, $stats)) { 71504fd306cSNickeau $stats[renderer_plugin_combo_analytics::TEMPLATE_LINK_COUNT] = 0; 7164cadd4f8SNickeau } 71704fd306cSNickeau $stats[renderer_plugin_combo_analytics::TEMPLATE_LINK_COUNT]++; 7184cadd4f8SNickeau break; 7194cadd4f8SNickeau 7204cadd4f8SNickeau default: 7214cadd4f8SNickeau 7224cadd4f8SNickeau LogUtility::msg("The link `{$ref}` with the type ($refType) is not taken into account into the statistics"); 7234cadd4f8SNickeau 7244cadd4f8SNickeau } 7254cadd4f8SNickeau 7264cadd4f8SNickeau 727007225e5Sgerardnico break; 7285f891b7eSNickeau } 729007225e5Sgerardnico 730007225e5Sgerardnico } 73104fd306cSNickeau 732007225e5Sgerardnico return false; 733007225e5Sgerardnico } 734007225e5Sgerardnico 735007225e5Sgerardnico 7364cadd4f8SNickeau /** 7374cadd4f8SNickeau * Utility function to add a link into the callstack 7384cadd4f8SNickeau * @param CallStack $callStack 7394cadd4f8SNickeau * @param TagAttributes $tagAttributes 7404cadd4f8SNickeau */ 74104fd306cSNickeau public 74204fd306cSNickeau static function addOpenLinkTagInCallStack(CallStack $callStack, TagAttributes $tagAttributes) 7434cadd4f8SNickeau { 7444cadd4f8SNickeau $parent = $callStack->moveToParent(); 7454cadd4f8SNickeau $context = ""; 7464cadd4f8SNickeau $attributes = $tagAttributes->toCallStackArray(); 7474cadd4f8SNickeau if ($parent !== false) { 7484cadd4f8SNickeau $context = $parent->getTagName(); 74904fd306cSNickeau if ($context === ButtonTag::MARKUP_LONG) { 7504cadd4f8SNickeau // the link takes by default the data from the button 7514cadd4f8SNickeau $parentAttributes = $parent->getAttributes(); 7524cadd4f8SNickeau if ($parentAttributes !== null) { 7534cadd4f8SNickeau $attributes = ArrayUtility::mergeByValue($parentAttributes, $attributes); 7544cadd4f8SNickeau } 7554cadd4f8SNickeau } 7564cadd4f8SNickeau } 7574cadd4f8SNickeau $callStack->appendCallAtTheEnd( 7584cadd4f8SNickeau Call::createComboCall( 7594cadd4f8SNickeau syntax_plugin_combo_link::TAG, 7604cadd4f8SNickeau DOKU_LEXER_ENTER, 7614cadd4f8SNickeau $attributes, 7624cadd4f8SNickeau $context 7634cadd4f8SNickeau )); 7644cadd4f8SNickeau } 7654cadd4f8SNickeau 76604fd306cSNickeau public 76704fd306cSNickeau static function addExitLinkTagInCallStack(CallStack $callStack) 7684cadd4f8SNickeau { 7694cadd4f8SNickeau $callStack->appendCallAtTheEnd( 7704cadd4f8SNickeau Call::createComboCall( 7714cadd4f8SNickeau syntax_plugin_combo_link::TAG, 7724cadd4f8SNickeau DOKU_LEXER_EXIT 7734cadd4f8SNickeau )); 7744cadd4f8SNickeau } 775007225e5Sgerardnico} 776007225e5Sgerardnico 777