1007225e5Sgerardnico<?php 2007225e5Sgerardnico 3007225e5Sgerardnico 437748cd8SNickeaurequire_once(__DIR__ . "/../ComboStrap/PluginUtility.php"); 5007225e5Sgerardnico 6*4cadd4f8SNickeauuse ComboStrap\AnalyticsDocument; 7*4cadd4f8SNickeauuse ComboStrap\ArrayUtility; 8*4cadd4f8SNickeauuse ComboStrap\Call; 9531e725cSNickeauuse ComboStrap\CallStack; 10*4cadd4f8SNickeauuse ComboStrap\ExceptionCombo; 11*4cadd4f8SNickeauuse ComboStrap\MarkupRef; 12*4cadd4f8SNickeauuse ComboStrap\LogUtility; 13*4cadd4f8SNickeauuse ComboStrap\Page; 14007225e5Sgerardnicouse ComboStrap\PluginUtility; 15531e725cSNickeauuse ComboStrap\TagAttributes; 16c3437056SNickeauuse ComboStrap\ThirdPartyPlugins; 17007225e5Sgerardnico 18007225e5Sgerardnicoif (!defined('DOKU_INC')) die(); 19007225e5Sgerardnico 20007225e5Sgerardnico/** 21007225e5Sgerardnico * 22007225e5Sgerardnico * A link pattern to take over the link of Dokuwiki 23007225e5Sgerardnico * and transform it as a bootstrap link 24007225e5Sgerardnico * 25007225e5Sgerardnico * The handle of the move of link is to be found in the 26007225e5Sgerardnico * admin action {@link action_plugin_combo_linkmove} 27007225e5Sgerardnico * 28007225e5Sgerardnico */ 29007225e5Sgerardnicoclass syntax_plugin_combo_link extends DokuWiki_Syntax_Plugin 30007225e5Sgerardnico{ 31007225e5Sgerardnico const TAG = 'link'; 32ef295d81Sgerardnico const COMPONENT = 'combo_link'; 33007225e5Sgerardnico 345f891b7eSNickeau /** 3585e82846SNickeau * Disable the link component 3621913ab3SNickeau */ 3721913ab3SNickeau const CONF_DISABLE_LINK = "disableLink"; 3821913ab3SNickeau 3921913ab3SNickeau /** 405f891b7eSNickeau * The link Tag 41531e725cSNickeau * a or p 425f891b7eSNickeau */ 435f891b7eSNickeau const LINK_TAG = "linkTag"; 445f891b7eSNickeau 4521913ab3SNickeau /** 4621913ab3SNickeau * Do the link component allows to be spawn on multilines 4721913ab3SNickeau */ 48531e725cSNickeau const CLICKABLE_ATTRIBUTE = "clickable"; 49*4cadd4f8SNickeau public const ATTRIBUTE_LABEL = 'label'; 50*4cadd4f8SNickeau /** 51*4cadd4f8SNickeau * The key of the array for the handle cache 52*4cadd4f8SNickeau */ 53*4cadd4f8SNickeau public const ATTRIBUTE_HREF = 'href'; 54*4cadd4f8SNickeau /** 55*4cadd4f8SNickeau * Indicate if the href is a {@link MarkupRef} 56*4cadd4f8SNickeau * (ie the syntax from the markup document) 57*4cadd4f8SNickeau * or is a html href added by {@link syntax_plugin_combo_share} 58*4cadd4f8SNickeau * for instance 59*4cadd4f8SNickeau */ 60*4cadd4f8SNickeau const ATTRIBUTE_HREF_TYPE = "href-type"; 61*4cadd4f8SNickeau const HREF_MARKUP_TYPE_VALUE = "markup"; 62*4cadd4f8SNickeau public const ATTRIBUTE_IMAGE_IN_LABEL = 'image-in-label'; 63*4cadd4f8SNickeau 64*4cadd4f8SNickeau /** 65*4cadd4f8SNickeau * A link may have a title or not 66*4cadd4f8SNickeau * ie 67*4cadd4f8SNickeau * [[path:page]] 68*4cadd4f8SNickeau * [[path:page|title]] 69*4cadd4f8SNickeau * are valid 70*4cadd4f8SNickeau * 71*4cadd4f8SNickeau * Get the content until one of this character is found: 72*4cadd4f8SNickeau * * | 73*4cadd4f8SNickeau * * or ]] 74*4cadd4f8SNickeau * * or \n (No line break allowed, too much difficult to debug) 75*4cadd4f8SNickeau * * and not [ (for two links on the same line) 76*4cadd4f8SNickeau */ 77*4cadd4f8SNickeau public const ENTRY_PATTERN_SINGLE_LINE = "\[\[[^\|\]]*(?=[^\n\[]*\]\])"; 78*4cadd4f8SNickeau public const EXIT_PATTERN = "\]\]"; 79*4cadd4f8SNickeau 80*4cadd4f8SNickeau 81*4cadd4f8SNickeau /** 82*4cadd4f8SNickeau * Dokuwiki Link pattern ter info 83*4cadd4f8SNickeau * Found in {@link \dokuwiki\Parsing\ParserMode\Internallink} 84*4cadd4f8SNickeau */ 85*4cadd4f8SNickeau const SPECIAL_PATTERN = "\[\[.*?\]\](?!\])"; 86*4cadd4f8SNickeau 87*4cadd4f8SNickeau /** 88*4cadd4f8SNickeau * The link title attribute (ie popup) 89*4cadd4f8SNickeau */ 90*4cadd4f8SNickeau const TITLE_ATTRIBUTE = "title"; 91*4cadd4f8SNickeau 92*4cadd4f8SNickeau 93*4cadd4f8SNickeau /** 94*4cadd4f8SNickeau * Parse the match of a syntax {@link DokuWiki_Syntax_Plugin} handle function 95*4cadd4f8SNickeau * @param $match 96*4cadd4f8SNickeau * @return string[] - an array with the attributes constant `ATTRIBUTE_xxxx` as key 97*4cadd4f8SNickeau * 98*4cadd4f8SNickeau * Code adapted from {@link Doku_Handler::internallink()} 99*4cadd4f8SNickeau */ 100*4cadd4f8SNickeau public static function parse($match): array 101*4cadd4f8SNickeau { 102*4cadd4f8SNickeau 103*4cadd4f8SNickeau // Strip the opening and closing markup 104*4cadd4f8SNickeau $linkString = preg_replace(array('/^\[\[/', '/\]\]$/u'), '', $match); 105*4cadd4f8SNickeau 106*4cadd4f8SNickeau // Split title from URL 107*4cadd4f8SNickeau $linkArray = explode('|', $linkString, 2); 108*4cadd4f8SNickeau 109*4cadd4f8SNickeau // Id 110*4cadd4f8SNickeau $attributes[self::ATTRIBUTE_HREF] = trim($linkArray[0]); 111*4cadd4f8SNickeau 112*4cadd4f8SNickeau 113*4cadd4f8SNickeau // Text or image 114*4cadd4f8SNickeau if (!isset($linkArray[1])) { 115*4cadd4f8SNickeau $attributes[self::ATTRIBUTE_LABEL] = null; 116*4cadd4f8SNickeau } else { 117*4cadd4f8SNickeau // An image in the title 118*4cadd4f8SNickeau if (preg_match('/^\{\{[^\}]+\}\}$/', $linkArray[1])) { 119*4cadd4f8SNickeau // If the title is an image, convert it to an array containing the image details 120*4cadd4f8SNickeau $attributes[self::ATTRIBUTE_IMAGE_IN_LABEL] = Doku_Handler_Parse_Media($linkArray[1]); 121*4cadd4f8SNickeau } else { 122*4cadd4f8SNickeau $attributes[self::ATTRIBUTE_LABEL] = $linkArray[1]; 123*4cadd4f8SNickeau } 124*4cadd4f8SNickeau } 125*4cadd4f8SNickeau 126*4cadd4f8SNickeau return $attributes; 127*4cadd4f8SNickeau 128*4cadd4f8SNickeau } 12921913ab3SNickeau 1305f891b7eSNickeau 131007225e5Sgerardnico /** 132007225e5Sgerardnico * Syntax Type. 133007225e5Sgerardnico * 134007225e5Sgerardnico * Needs to return one of the mode types defined in $PARSER_MODES in parser.php 135007225e5Sgerardnico * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types 136007225e5Sgerardnico */ 137007225e5Sgerardnico function getType() 138007225e5Sgerardnico { 139007225e5Sgerardnico return 'substition'; 140007225e5Sgerardnico } 141007225e5Sgerardnico 142007225e5Sgerardnico /** 143007225e5Sgerardnico * How Dokuwiki will add P element 144007225e5Sgerardnico * 145007225e5Sgerardnico * * 'normal' - The plugin can be used inside paragraphs 146007225e5Sgerardnico * * 'block' - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs 147007225e5Sgerardnico * * 'stack' - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs 148007225e5Sgerardnico * 149007225e5Sgerardnico * @see DokuWiki_Syntax_Plugin::getPType() 150007225e5Sgerardnico */ 151007225e5Sgerardnico function getPType() 152007225e5Sgerardnico { 153007225e5Sgerardnico return 'normal'; 154007225e5Sgerardnico } 155007225e5Sgerardnico 156007225e5Sgerardnico /** 157007225e5Sgerardnico * @return array 158007225e5Sgerardnico * Allow which kind of plugin inside 159007225e5Sgerardnico * 160007225e5Sgerardnico * No one of array('container', 'baseonly', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs') 161007225e5Sgerardnico * because we manage self the content and we call self the parser 162007225e5Sgerardnico */ 163*4cadd4f8SNickeau function getAllowedTypes(): array 164007225e5Sgerardnico { 165007225e5Sgerardnico return array('substition', 'formatting', 'disabled'); 166007225e5Sgerardnico } 167007225e5Sgerardnico 16837748cd8SNickeau /** 16937748cd8SNickeau * @param string $mode 17037748cd8SNickeau * @return bool 17137748cd8SNickeau * Accepts inside 17237748cd8SNickeau */ 173*4cadd4f8SNickeau public function accepts($mode): bool 1745f891b7eSNickeau { 1755f891b7eSNickeau /** 1765f891b7eSNickeau * To avoid that the description if it contains a link 1775f891b7eSNickeau * will be taken by the links mode 1785f891b7eSNickeau * 1795f891b7eSNickeau * For instance, [[https://hallo|https://hallo]] will send https://hallo 1805f891b7eSNickeau * to the external link mode 1815f891b7eSNickeau */ 1825f891b7eSNickeau $linkModes = [ 1835f891b7eSNickeau "externallink", 1845f891b7eSNickeau "locallink", 1855f891b7eSNickeau "internallink", 1865f891b7eSNickeau "interwikilink", 1875f891b7eSNickeau "emaillink", 188fc45fbf7Sgerardnico "emphasis", // double slash can not be used inside to preserve the possibility to write an URL in the description 1895f891b7eSNickeau //"emphasis_open", // italic use // and therefore take over a link as description which is not handy when copying a tweet 1905f891b7eSNickeau //"emphasis_close", 1915f891b7eSNickeau //"acrnonym" 1925f891b7eSNickeau ]; 1935f891b7eSNickeau if (in_array($mode, $linkModes)) { 1945f891b7eSNickeau return false; 1955f891b7eSNickeau } else { 1965f891b7eSNickeau return true; 1975f891b7eSNickeau } 1985f891b7eSNickeau } 1995f891b7eSNickeau 2005f891b7eSNickeau 201007225e5Sgerardnico /** 202007225e5Sgerardnico * @see Doku_Parser_Mode::getSort() 203007225e5Sgerardnico * The mode with the lowest sort number will win out 204007225e5Sgerardnico */ 205007225e5Sgerardnico function getSort() 206007225e5Sgerardnico { 207e8b2ff59SNickeau /** 208e8b2ff59SNickeau * It should be less than the number 209e8b2ff59SNickeau * at {@link \dokuwiki\Parsing\ParserMode\Internallink::getSort} 210e8b2ff59SNickeau * and the like 211e8b2ff59SNickeau * 212e8b2ff59SNickeau * For whatever reason, the number below should be less than 100, 213e8b2ff59SNickeau * otherwise on windows with DokuWiki Stick, the link syntax may be not taken 214e8b2ff59SNickeau * into account 215e8b2ff59SNickeau */ 216e8b2ff59SNickeau return 99; 217007225e5Sgerardnico } 218007225e5Sgerardnico 219007225e5Sgerardnico 220007225e5Sgerardnico function connectTo($mode) 221007225e5Sgerardnico { 222d262537cSgerardnico 22337748cd8SNickeau if (!$this->getConf(self::CONF_DISABLE_LINK, false) 22437748cd8SNickeau && 22537748cd8SNickeau $mode !== PluginUtility::getModeFromPluginName(ThirdPartyPlugins::IMAGE_MAPPING_NAME) 22637748cd8SNickeau ) { 22737748cd8SNickeau 228*4cadd4f8SNickeau $pattern = self::ENTRY_PATTERN_SINGLE_LINE; 2299337a630SNickeau $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeFromTag($this->getPluginComponent())); 23037748cd8SNickeau 23121913ab3SNickeau } 232d262537cSgerardnico 233007225e5Sgerardnico } 234007225e5Sgerardnico 2355f891b7eSNickeau public function postConnect() 2365f891b7eSNickeau { 23721913ab3SNickeau if (!$this->getConf(self::CONF_DISABLE_LINK, false)) { 238*4cadd4f8SNickeau $this->Lexer->addExitPattern(self::EXIT_PATTERN, PluginUtility::getModeFromTag($this->getPluginComponent())); 2395f891b7eSNickeau } 24021913ab3SNickeau } 2415f891b7eSNickeau 242007225e5Sgerardnico 243007225e5Sgerardnico /** 244007225e5Sgerardnico * The handler for an internal link 245007225e5Sgerardnico * based on `internallink` in {@link Doku_Handler} 246007225e5Sgerardnico * The handler call the good renderer in {@link Doku_Renderer_xhtml} with 247007225e5Sgerardnico * the parameters (ie for instance internallink) 248007225e5Sgerardnico * @param string $match 249007225e5Sgerardnico * @param int $state 250007225e5Sgerardnico * @param int $pos 251007225e5Sgerardnico * @param Doku_Handler $handler 252007225e5Sgerardnico * @return array|bool 253007225e5Sgerardnico */ 254007225e5Sgerardnico function handle($match, $state, $pos, Doku_Handler $handler) 255007225e5Sgerardnico { 256007225e5Sgerardnico 2575f891b7eSNickeau switch ($state) { 2585f891b7eSNickeau case DOKU_LEXER_ENTER: 259*4cadd4f8SNickeau $parsedArray = self::parse($match); 260*4cadd4f8SNickeau $htmlAttributes = TagAttributes::createEmpty(self::TAG); 261*4cadd4f8SNickeau /** 262*4cadd4f8SNickeau * Href needs to be passed to the 263*4cadd4f8SNickeau * instructions stack (because we support) 264*4cadd4f8SNickeau * dynamic link call href with {@link syntax_plugin_combo_template} 265*4cadd4f8SNickeau */ 266*4cadd4f8SNickeau $href = $parsedArray[self::ATTRIBUTE_HREF]; 267*4cadd4f8SNickeau if ($href !== null) { 268*4cadd4f8SNickeau $htmlAttributes 269*4cadd4f8SNickeau ->addComponentAttributeValue(self::ATTRIBUTE_HREF, $href) 270*4cadd4f8SNickeau ->addComponentAttributeValue(self::ATTRIBUTE_HREF_TYPE, self::HREF_MARKUP_TYPE_VALUE); 271*4cadd4f8SNickeau } 272*4cadd4f8SNickeau 273*4cadd4f8SNickeau 274*4cadd4f8SNickeau /** 275*4cadd4f8SNickeau * Extra HTML attribute 276*4cadd4f8SNickeau */ 277531e725cSNickeau $callStack = CallStack::createFromHandler($handler); 278531e725cSNickeau $parent = $callStack->moveToParent(); 279007225e5Sgerardnico $parentName = ""; 280*4cadd4f8SNickeau if ($parent !== false) { 281531e725cSNickeau 282531e725cSNickeau /** 283531e725cSNickeau * Button Link 284531e725cSNickeau * Getting the attributes 285531e725cSNickeau */ 286531e725cSNickeau $parentName = $parent->getTagName(); 287531e725cSNickeau if ($parentName == syntax_plugin_combo_button::TAG) { 288*4cadd4f8SNickeau $htmlAttributes->mergeWithCallStackArray($parent->getAttributes()); 28921913ab3SNickeau } 290531e725cSNickeau 291531e725cSNickeau /** 292531e725cSNickeau * Searching Clickable parent 293531e725cSNickeau */ 294531e725cSNickeau $maxLevel = 3; 295531e725cSNickeau $level = 0; 296531e725cSNickeau while ( 297531e725cSNickeau $parent != false && 298531e725cSNickeau !$parent->hasAttribute(self::CLICKABLE_ATTRIBUTE) && 299531e725cSNickeau $level < $maxLevel 300531e725cSNickeau ) { 301531e725cSNickeau $parent = $callStack->moveToParent(); 302531e725cSNickeau $level++; 3035f891b7eSNickeau } 304531e725cSNickeau if ($parent != false) { 305531e725cSNickeau if ($parent->getAttribute(self::CLICKABLE_ATTRIBUTE)) { 306*4cadd4f8SNickeau $htmlAttributes->addClassName("stretched-link"); 307531e725cSNickeau $parent->addClassName("position-relative"); 308531e725cSNickeau $parent->removeAttribute(self::CLICKABLE_ATTRIBUTE); 3095f891b7eSNickeau } 31021913ab3SNickeau } 31121913ab3SNickeau 312531e725cSNickeau } 313*4cadd4f8SNickeau $returnedArray[PluginUtility::STATE] = $state; 314*4cadd4f8SNickeau $returnedArray[PluginUtility::ATTRIBUTES] = $htmlAttributes->toCallStackArray(); 315*4cadd4f8SNickeau $returnedArray[PluginUtility::CONTEXT] = $parentName; 316*4cadd4f8SNickeau return $returnedArray; 317*4cadd4f8SNickeau 3185f891b7eSNickeau case DOKU_LEXER_UNMATCHED: 3195f891b7eSNickeau 32032b85071SNickeau $data = PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler); 3215f891b7eSNickeau /** 32232b85071SNickeau * Delete the separator `|` between the ref and the description if any 3235f891b7eSNickeau */ 3241fa8c418SNickeau $tag = CallStack::createFromHandler($handler); 3251fa8c418SNickeau $parent = $tag->moveToParent(); 3261fa8c418SNickeau if ($parent->getTagName() == self::TAG) { 3275f891b7eSNickeau if (strpos($match, '|') === 0) { 32832b85071SNickeau $data[PluginUtility::PAYLOAD] = substr($match, 1); 3295f891b7eSNickeau } 330007225e5Sgerardnico } 33132b85071SNickeau return $data; 332007225e5Sgerardnico 3335f891b7eSNickeau case DOKU_LEXER_EXIT: 334531e725cSNickeau $callStack = CallStack::createFromHandler($handler); 335531e725cSNickeau $openingTag = $callStack->moveToPreviousCorrespondingOpeningCall(); 336*4cadd4f8SNickeau 3375f891b7eSNickeau $openingAttributes = $openingTag->getAttributes(); 338531e725cSNickeau $openingPosition = $openingTag->getKey(); 3395f891b7eSNickeau 340531e725cSNickeau $callStack->moveToEnd(); 341531e725cSNickeau $previousCall = $callStack->previous(); 342531e725cSNickeau $previousCallPosition = $previousCall->getKey(); 343531e725cSNickeau $previousCallContent = $previousCall->getCapturedContent(); 344531e725cSNickeau 345*4cadd4f8SNickeau /** 346*4cadd4f8SNickeau * Link label 347*4cadd4f8SNickeau * is set if there is no content 348*4cadd4f8SNickeau * between enter and exit node 349*4cadd4f8SNickeau */ 350*4cadd4f8SNickeau $linkLabel = ""; 351531e725cSNickeau if ( 352531e725cSNickeau $openingPosition == $previousCallPosition // ie [[id]] 353531e725cSNickeau || 354531e725cSNickeau ($openingPosition == $previousCallPosition - 1 && $previousCallContent == "|") // ie [[id|]] 355531e725cSNickeau ) { 3565f891b7eSNickeau // There is no name 357*4cadd4f8SNickeau $href = $openingTag->getAttribute(self::ATTRIBUTE_HREF); 358*4cadd4f8SNickeau if ($href !== null) { 359*4cadd4f8SNickeau $markup = MarkupRef::createFromRef($href); 360*4cadd4f8SNickeau $linkLabel = $markup->getLabel(); 361*4cadd4f8SNickeau } 3625f891b7eSNickeau } 3635f891b7eSNickeau return array( 3645f891b7eSNickeau PluginUtility::STATE => $state, 3655f891b7eSNickeau PluginUtility::ATTRIBUTES => $openingAttributes, 366*4cadd4f8SNickeau PluginUtility::PAYLOAD => $linkLabel, 36785e82846SNickeau PluginUtility::CONTEXT => $openingTag->getContext() 3685f891b7eSNickeau ); 3695f891b7eSNickeau } 3705f891b7eSNickeau return true; 3715f891b7eSNickeau 372007225e5Sgerardnico 373007225e5Sgerardnico } 374007225e5Sgerardnico 375007225e5Sgerardnico /** 376007225e5Sgerardnico * Render the output 377007225e5Sgerardnico * @param string $format 378007225e5Sgerardnico * @param Doku_Renderer $renderer 379007225e5Sgerardnico * @param array $data - what the function handle() return'ed 380007225e5Sgerardnico * @return boolean - rendered correctly? (however, returned value is not used at the moment) 381007225e5Sgerardnico * @see DokuWiki_Syntax_Plugin::render() 382007225e5Sgerardnico * 383007225e5Sgerardnico * 384007225e5Sgerardnico */ 385c3437056SNickeau function render($format, Doku_Renderer $renderer, $data): bool 386007225e5Sgerardnico { 387007225e5Sgerardnico // The data 388007225e5Sgerardnico switch ($format) { 389007225e5Sgerardnico case 'xhtml': 390007225e5Sgerardnico 391007225e5Sgerardnico /** @var Doku_Renderer_xhtml $renderer */ 392007225e5Sgerardnico /** 39319b0880dSgerardnico * Cache problem may occurs while releasing 394007225e5Sgerardnico */ 395007225e5Sgerardnico if (isset($data[PluginUtility::ATTRIBUTES])) { 396531e725cSNickeau $callStackAttributes = $data[PluginUtility::ATTRIBUTES]; 397007225e5Sgerardnico } else { 398531e725cSNickeau $callStackAttributes = $data; 399007225e5Sgerardnico } 4005f891b7eSNickeau 401*4cadd4f8SNickeau PluginUtility::getSnippetManager()->attachCssInternalStyleSheetForSlot(self::TAG); 4025f891b7eSNickeau 4035f891b7eSNickeau $state = $data[PluginUtility::STATE]; 4045f891b7eSNickeau switch ($state) { 4055f891b7eSNickeau case DOKU_LEXER_ENTER: 406531e725cSNickeau $tagAttributes = TagAttributes::createFromCallStackArray($callStackAttributes, self::TAG); 407*4cadd4f8SNickeau 408*4cadd4f8SNickeau $href = $tagAttributes->getValue(self::ATTRIBUTE_HREF); 409*4cadd4f8SNickeau 410*4cadd4f8SNickeau /** 411*4cadd4f8SNickeau * HrefMarkup ? 412*4cadd4f8SNickeau */ 413*4cadd4f8SNickeau $hrefSource = $tagAttributes->getValueAndRemoveIfPresent(self::ATTRIBUTE_HREF_TYPE); 414*4cadd4f8SNickeau if ($hrefSource !== null) { 415*4cadd4f8SNickeau try { 416*4cadd4f8SNickeau $markupRef = MarkupRef::createFromRef($href); 417*4cadd4f8SNickeau $url = $markupRef->getUrl(); 418*4cadd4f8SNickeau $markupRefAttributes = $markupRef->toAttributes(); 419*4cadd4f8SNickeau } catch (ExceptionCombo $e) { 420*4cadd4f8SNickeau $message = "Error while parsing the markup href ($href). Error: {$e->getMessage()}"; 421*4cadd4f8SNickeau $renderer->doc .= "<a>." . LogUtility::wrapInRedForHtml($message); 422*4cadd4f8SNickeau return false; 423*4cadd4f8SNickeau } 424*4cadd4f8SNickeau $tagAttributes->mergeWithCallStackArray($markupRefAttributes->toCallStackArray()); 425*4cadd4f8SNickeau // No href if the url could not be calculated 426*4cadd4f8SNickeau // such as a bad interwiki link 427*4cadd4f8SNickeau if (!empty($url)) { 428*4cadd4f8SNickeau $tagAttributes->setComponentAttributeValue(self::ATTRIBUTE_HREF, $url); 429*4cadd4f8SNickeau } else { 430*4cadd4f8SNickeau $tagAttributes->removeComponentAttributeIfPresent(self::ATTRIBUTE_HREF); 431*4cadd4f8SNickeau } 432*4cadd4f8SNickeau 433*4cadd4f8SNickeau } 434d262537cSgerardnico 43519b0880dSgerardnico /** 4365f891b7eSNickeau * Extra styling 43719b0880dSgerardnico */ 4385f891b7eSNickeau $parentTag = $data[PluginUtility::CONTEXT]; 439*4cadd4f8SNickeau $htmlPrefix = ""; 4409f4383e9Sgerardnico switch ($parentTag) { 44121913ab3SNickeau /** 44221913ab3SNickeau * Button link 44321913ab3SNickeau */ 4449f4383e9Sgerardnico case syntax_plugin_combo_button::TAG: 445*4cadd4f8SNickeau $tagAttributes->addOutputAttributeValue("role", "button"); 446531e725cSNickeau syntax_plugin_combo_button::processButtonAttributesToHtmlAttributes($tagAttributes); 4479f4383e9Sgerardnico break; 448*4cadd4f8SNickeau case syntax_plugin_combo_dropdown::TAG: 449*4cadd4f8SNickeau $tagAttributes->addClassName("dropdown-item"); 450*4cadd4f8SNickeau break; 451*4cadd4f8SNickeau case syntax_plugin_combo_navbarcollapse::COMPONENT: 452*4cadd4f8SNickeau $tagAttributes->addClassName("navbar-link"); 453*4cadd4f8SNickeau $htmlPrefix = '<div class="navbar-nav">'; 454*4cadd4f8SNickeau break; 455*4cadd4f8SNickeau case syntax_plugin_combo_navbargroup::COMPONENT: 456*4cadd4f8SNickeau $tagAttributes->addClassName("nav-link"); 457*4cadd4f8SNickeau $htmlPrefix = '<li class="nav-item">'; 458*4cadd4f8SNickeau break; 459*4cadd4f8SNickeau default: 4605f891b7eSNickeau case syntax_plugin_combo_badge::TAG: 4619f4383e9Sgerardnico case syntax_plugin_combo_cite::TAG: 4629337a630SNickeau case syntax_plugin_combo_contentlistitem::DOKU_TAG: 4639f4383e9Sgerardnico case syntax_plugin_combo_preformatted::TAG: 4649f4383e9Sgerardnico break; 4650a517624Sgerardnico 4669f4383e9Sgerardnico } 4679f4383e9Sgerardnico 46819b0880dSgerardnico /** 46919b0880dSgerardnico * Add it to the rendering 47019b0880dSgerardnico */ 471*4cadd4f8SNickeau $renderer->doc .= $htmlPrefix . $tagAttributes->toHtmlEnterTag("a"); 4725f891b7eSNickeau break; 4735f891b7eSNickeau case DOKU_LEXER_UNMATCHED: 47432b85071SNickeau $renderer->doc .= PluginUtility::renderUnmatched($data); 4755f891b7eSNickeau break; 4765f891b7eSNickeau case DOKU_LEXER_EXIT: 4775f891b7eSNickeau 4785f891b7eSNickeau // if there is no link name defined, we get the name as ref in the payload 4795f891b7eSNickeau // otherwise null string 48085e82846SNickeau $renderer->doc .= $data[PluginUtility::PAYLOAD]; 4815f891b7eSNickeau 482e3d0019cSgerardnico // Close the link 48385e82846SNickeau $renderer->doc .= "</a>"; 484e3d0019cSgerardnico 485e3d0019cSgerardnico // Close the html wrapper element 4865f891b7eSNickeau $context = $data[PluginUtility::CONTEXT]; 4875f891b7eSNickeau switch ($context) { 4885f891b7eSNickeau case syntax_plugin_combo_navbarcollapse::COMPONENT: 4895f891b7eSNickeau $renderer->doc .= '</div>'; 4905f891b7eSNickeau break; 4915f891b7eSNickeau case syntax_plugin_combo_navbargroup::COMPONENT: 4925f891b7eSNickeau $renderer->doc .= '</li>'; 4935f891b7eSNickeau break; 4945f891b7eSNickeau } 4955f891b7eSNickeau 496e3d0019cSgerardnico 4975f891b7eSNickeau } 4985f891b7eSNickeau 499007225e5Sgerardnico 500007225e5Sgerardnico return true; 501007225e5Sgerardnico 5025f891b7eSNickeau case 'metadata': 503007225e5Sgerardnico 504*4cadd4f8SNickeau /** 505*4cadd4f8SNickeau * @var Doku_Renderer_metadata $renderer 506*4cadd4f8SNickeau */ 5075f891b7eSNickeau $state = $data[PluginUtility::STATE]; 508*4cadd4f8SNickeau switch ($state) { 509*4cadd4f8SNickeau case DOKU_LEXER_ENTER: 510007225e5Sgerardnico /** 511007225e5Sgerardnico * Keep track of the backlinks ie meta['relation']['references'] 512007225e5Sgerardnico * @var Doku_Renderer_metadata $renderer 513007225e5Sgerardnico */ 514*4cadd4f8SNickeau $tagAttributes = TagAttributes::createFromCallStackArray($data[PluginUtility::ATTRIBUTES]); 515*4cadd4f8SNickeau $hrefSource = $tagAttributes->getValue(self::ATTRIBUTE_HREF_TYPE); 516*4cadd4f8SNickeau if ($hrefSource === null || $hrefSource !== self::HREF_MARKUP_TYPE_VALUE) { 517*4cadd4f8SNickeau /** 518*4cadd4f8SNickeau * This is not a markup link 519*4cadd4f8SNickeau * (ie an external link created by a plugin {@link syntax_plugin_combo_share}) 520*4cadd4f8SNickeau */ 521*4cadd4f8SNickeau return false; 522007225e5Sgerardnico } 523*4cadd4f8SNickeau $href = $tagAttributes->getValue(self::ATTRIBUTE_HREF); 524*4cadd4f8SNickeau $type = MarkupRef::createFromRef($href) 525*4cadd4f8SNickeau ->getUriType(); 526*4cadd4f8SNickeau $name = $tagAttributes->getValue(self::ATTRIBUTE_LABEL); 5279f4383e9Sgerardnico 528*4cadd4f8SNickeau switch ($type) { 529*4cadd4f8SNickeau case MarkupRef::WIKI_URI: 530*4cadd4f8SNickeau /** 531*4cadd4f8SNickeau * The relative link should be passed (ie the original) 532*4cadd4f8SNickeau * Dokuwiki has a default description 533*4cadd4f8SNickeau * We can't pass empty or the array(title), it does not work 534*4cadd4f8SNickeau */ 535*4cadd4f8SNickeau $descriptionToDelete = "b"; 536*4cadd4f8SNickeau $renderer->internallink($href, $descriptionToDelete); 537*4cadd4f8SNickeau $renderer->doc = substr($renderer->doc,0,-strlen($descriptionToDelete)); 538*4cadd4f8SNickeau break; 539*4cadd4f8SNickeau case MarkupRef::WEB_URI: 540*4cadd4f8SNickeau $renderer->externallink($href, $name); 541*4cadd4f8SNickeau break; 542*4cadd4f8SNickeau case MarkupRef::LOCAL_URI: 543*4cadd4f8SNickeau $renderer->locallink($href, $name); 544*4cadd4f8SNickeau break; 545*4cadd4f8SNickeau case MarkupRef::EMAIL_URI: 546*4cadd4f8SNickeau $renderer->emaillink($href, $name); 547*4cadd4f8SNickeau break; 548*4cadd4f8SNickeau case MarkupRef::INTERWIKI_URI: 549*4cadd4f8SNickeau $interWikiSplit = preg_split("/>/", $href); 550*4cadd4f8SNickeau $renderer->interwikilink($href, $name, $interWikiSplit[0], $interWikiSplit[1]); 551*4cadd4f8SNickeau break; 552*4cadd4f8SNickeau case MarkupRef::WINDOWS_SHARE_URI: 553*4cadd4f8SNickeau $renderer->windowssharelink($href, $name); 554*4cadd4f8SNickeau break; 555*4cadd4f8SNickeau case MarkupRef::VARIABLE_URI: 556*4cadd4f8SNickeau // No backlinks for link template 557*4cadd4f8SNickeau break; 558*4cadd4f8SNickeau default: 559*4cadd4f8SNickeau LogUtility::msg("The markup reference ({$href}) with the type $type was not processed into the metadata"); 5609f4383e9Sgerardnico } 561007225e5Sgerardnico 562007225e5Sgerardnico return true; 563*4cadd4f8SNickeau case DOKU_LEXER_UNMATCHED: 564*4cadd4f8SNickeau $renderer->doc .= PluginUtility::renderUnmatched($data); 565*4cadd4f8SNickeau break; 5665f891b7eSNickeau } 567007225e5Sgerardnico break; 568007225e5Sgerardnico 569531e725cSNickeau case renderer_plugin_combo_analytics::RENDERER_FORMAT: 5705f891b7eSNickeau 5715f891b7eSNickeau $state = $data[PluginUtility::STATE]; 5725f891b7eSNickeau if ($state == DOKU_LEXER_ENTER) { 573007225e5Sgerardnico /** 574007225e5Sgerardnico * 575007225e5Sgerardnico * @var renderer_plugin_combo_analytics $renderer 576007225e5Sgerardnico */ 577*4cadd4f8SNickeau $tagAttributes = TagAttributes::createFromCallStackArray($data[PluginUtility::ATTRIBUTES]); 578*4cadd4f8SNickeau $refSource = $tagAttributes->getValue(self::ATTRIBUTE_HREF_TYPE); 579*4cadd4f8SNickeau if ($refSource === null || $refSource !== self::HREF_MARKUP_TYPE_VALUE) { 580*4cadd4f8SNickeau /** 581*4cadd4f8SNickeau * Link added programmatically 582*4cadd4f8SNickeau */ 583*4cadd4f8SNickeau return false; 584*4cadd4f8SNickeau } 585*4cadd4f8SNickeau $ref = $tagAttributes->getValue(self::ATTRIBUTE_HREF); 586*4cadd4f8SNickeau $href = MarkupRef::createFromRef($ref); 587*4cadd4f8SNickeau $refType = $href->getUriType(); 588*4cadd4f8SNickeau 589*4cadd4f8SNickeau 590*4cadd4f8SNickeau /** 591*4cadd4f8SNickeau * @param array $stats 592*4cadd4f8SNickeau * Calculate internal link statistics 593*4cadd4f8SNickeau */ 594*4cadd4f8SNickeau 595*4cadd4f8SNickeau $stats = &$renderer->stats; 596*4cadd4f8SNickeau switch ($refType) { 597*4cadd4f8SNickeau 598*4cadd4f8SNickeau case MarkupRef::WIKI_URI: 599*4cadd4f8SNickeau 600*4cadd4f8SNickeau /** 601*4cadd4f8SNickeau * Internal link count 602*4cadd4f8SNickeau */ 603*4cadd4f8SNickeau if (!array_key_exists(AnalyticsDocument::INTERNAL_LINK_COUNT, $stats)) { 604*4cadd4f8SNickeau $stats[AnalyticsDocument::INTERNAL_LINK_COUNT] = 0; 605*4cadd4f8SNickeau } 606*4cadd4f8SNickeau $stats[AnalyticsDocument::INTERNAL_LINK_COUNT]++; 607*4cadd4f8SNickeau 608*4cadd4f8SNickeau 609*4cadd4f8SNickeau /** 610*4cadd4f8SNickeau * Broken link ? 611*4cadd4f8SNickeau */ 612*4cadd4f8SNickeau 613*4cadd4f8SNickeau $linkedPage = $href->getInternalPage(); 614*4cadd4f8SNickeau if (!$linkedPage->exists()) { 615*4cadd4f8SNickeau $stats[AnalyticsDocument::INTERNAL_LINK_BROKEN_COUNT]++; 616*4cadd4f8SNickeau $stats[AnalyticsDocument::INFO][] = "The internal linked page `{$href->getInternalPage()}` does not exist"; 617*4cadd4f8SNickeau } 618*4cadd4f8SNickeau 619*4cadd4f8SNickeau /** 620*4cadd4f8SNickeau * Calculate link distance 621*4cadd4f8SNickeau */ 622*4cadd4f8SNickeau global $ID; 623*4cadd4f8SNickeau $id = $href->getInternalPage()->getDokuwikiId(); 624*4cadd4f8SNickeau $a = explode(':', getNS($ID)); 625*4cadd4f8SNickeau $b = explode(':', getNS($id)); 626*4cadd4f8SNickeau while (isset($a[0]) && $a[0] == $b[0]) { 627*4cadd4f8SNickeau array_shift($a); 628*4cadd4f8SNickeau array_shift($b); 629*4cadd4f8SNickeau } 630*4cadd4f8SNickeau $length = count($a) + count($b); 631*4cadd4f8SNickeau $stats[AnalyticsDocument::INTERNAL_LINK_DISTANCE][] = $length; 632*4cadd4f8SNickeau break; 633*4cadd4f8SNickeau 634*4cadd4f8SNickeau case MarkupRef::WEB_URI: 635*4cadd4f8SNickeau 636*4cadd4f8SNickeau if (!array_key_exists(AnalyticsDocument::EXTERNAL_LINK_COUNT, $stats)) { 637*4cadd4f8SNickeau $stats[AnalyticsDocument::EXTERNAL_LINK_COUNT] = 0; 638*4cadd4f8SNickeau } 639*4cadd4f8SNickeau $stats[AnalyticsDocument::EXTERNAL_LINK_COUNT]++; 640*4cadd4f8SNickeau break; 641*4cadd4f8SNickeau 642*4cadd4f8SNickeau case MarkupRef::LOCAL_URI: 643*4cadd4f8SNickeau 644*4cadd4f8SNickeau if (!array_key_exists(AnalyticsDocument::LOCAL_LINK_COUNT, $stats)) { 645*4cadd4f8SNickeau $stats[AnalyticsDocument::LOCAL_LINK_COUNT] = 0; 646*4cadd4f8SNickeau } 647*4cadd4f8SNickeau $stats[AnalyticsDocument::LOCAL_LINK_COUNT]++; 648*4cadd4f8SNickeau break; 649*4cadd4f8SNickeau 650*4cadd4f8SNickeau case MarkupRef::INTERWIKI_URI: 651*4cadd4f8SNickeau 652*4cadd4f8SNickeau if (!array_key_exists(AnalyticsDocument::INTERWIKI_LINK_COUNT, $stats)) { 653*4cadd4f8SNickeau $stats[AnalyticsDocument::INTERWIKI_LINK_COUNT] = 0; 654*4cadd4f8SNickeau } 655*4cadd4f8SNickeau $stats[AnalyticsDocument::INTERWIKI_LINK_COUNT]++; 656*4cadd4f8SNickeau break; 657*4cadd4f8SNickeau 658*4cadd4f8SNickeau case MarkupRef::EMAIL_URI: 659*4cadd4f8SNickeau 660*4cadd4f8SNickeau if (!array_key_exists(AnalyticsDocument::EMAIL_COUNT, $stats)) { 661*4cadd4f8SNickeau $stats[AnalyticsDocument::EMAIL_COUNT] = 0; 662*4cadd4f8SNickeau } 663*4cadd4f8SNickeau $stats[AnalyticsDocument::EMAIL_COUNT]++; 664*4cadd4f8SNickeau break; 665*4cadd4f8SNickeau 666*4cadd4f8SNickeau case MarkupRef::WINDOWS_SHARE_URI: 667*4cadd4f8SNickeau 668*4cadd4f8SNickeau if (!array_key_exists(AnalyticsDocument::WINDOWS_SHARE_COUNT, $stats)) { 669*4cadd4f8SNickeau $stats[AnalyticsDocument::WINDOWS_SHARE_COUNT] = 0; 670*4cadd4f8SNickeau } 671*4cadd4f8SNickeau $stats[AnalyticsDocument::WINDOWS_SHARE_COUNT]++; 672*4cadd4f8SNickeau break; 673*4cadd4f8SNickeau 674*4cadd4f8SNickeau case MarkupRef::VARIABLE_URI: 675*4cadd4f8SNickeau 676*4cadd4f8SNickeau if (!array_key_exists(AnalyticsDocument::TEMPLATE_LINK_COUNT, $stats)) { 677*4cadd4f8SNickeau $stats[AnalyticsDocument::TEMPLATE_LINK_COUNT] = 0; 678*4cadd4f8SNickeau } 679*4cadd4f8SNickeau $stats[AnalyticsDocument::TEMPLATE_LINK_COUNT]++; 680*4cadd4f8SNickeau break; 681*4cadd4f8SNickeau 682*4cadd4f8SNickeau default: 683*4cadd4f8SNickeau 684*4cadd4f8SNickeau LogUtility::msg("The link `{$ref}` with the type ($refType) is not taken into account into the statistics"); 685*4cadd4f8SNickeau 686*4cadd4f8SNickeau } 687*4cadd4f8SNickeau 688*4cadd4f8SNickeau 689007225e5Sgerardnico break; 6905f891b7eSNickeau } 691007225e5Sgerardnico 692007225e5Sgerardnico } 693007225e5Sgerardnico // unsupported $mode 694007225e5Sgerardnico return false; 695007225e5Sgerardnico } 696007225e5Sgerardnico 697007225e5Sgerardnico 698*4cadd4f8SNickeau /** 699*4cadd4f8SNickeau * Utility function to add a link into the callstack 700*4cadd4f8SNickeau * @param CallStack $callStack 701*4cadd4f8SNickeau * @param TagAttributes $tagAttributes 702*4cadd4f8SNickeau */ 703*4cadd4f8SNickeau public static function addOpenLinkTagInCallStack(CallStack $callStack, TagAttributes $tagAttributes) 704*4cadd4f8SNickeau { 705*4cadd4f8SNickeau $parent = $callStack->moveToParent(); 706*4cadd4f8SNickeau $context = ""; 707*4cadd4f8SNickeau $attributes = $tagAttributes->toCallStackArray(); 708*4cadd4f8SNickeau if ($parent !== false) { 709*4cadd4f8SNickeau $context = $parent->getTagName(); 710*4cadd4f8SNickeau if ($context === syntax_plugin_combo_button::TAG) { 711*4cadd4f8SNickeau // the link takes by default the data from the button 712*4cadd4f8SNickeau $parentAttributes = $parent->getAttributes(); 713*4cadd4f8SNickeau if ($parentAttributes !== null) { 714*4cadd4f8SNickeau $attributes = ArrayUtility::mergeByValue($parentAttributes, $attributes); 715*4cadd4f8SNickeau } 716*4cadd4f8SNickeau } 717*4cadd4f8SNickeau } 718*4cadd4f8SNickeau $callStack->appendCallAtTheEnd( 719*4cadd4f8SNickeau Call::createComboCall( 720*4cadd4f8SNickeau syntax_plugin_combo_link::TAG, 721*4cadd4f8SNickeau DOKU_LEXER_ENTER, 722*4cadd4f8SNickeau $attributes, 723*4cadd4f8SNickeau $context 724*4cadd4f8SNickeau )); 725*4cadd4f8SNickeau } 726*4cadd4f8SNickeau 727*4cadd4f8SNickeau public static function addExitLinkTagInCallStack(CallStack $callStack) 728*4cadd4f8SNickeau { 729*4cadd4f8SNickeau $callStack->appendCallAtTheEnd( 730*4cadd4f8SNickeau Call::createComboCall( 731*4cadd4f8SNickeau syntax_plugin_combo_link::TAG, 732*4cadd4f8SNickeau DOKU_LEXER_EXIT 733*4cadd4f8SNickeau )); 734*4cadd4f8SNickeau } 735007225e5Sgerardnico} 736007225e5Sgerardnico 737