1007225e5Sgerardnico<?php 2007225e5Sgerardnico 3007225e5Sgerardnico 471f916b9Sgerardnicorequire_once(__DIR__ . "/../class/Analytics.php"); 5007225e5Sgerardnicorequire_once(__DIR__ . "/../class/PluginUtility.php"); 6007225e5Sgerardnicorequire_once(__DIR__ . "/../class/LinkUtility.php"); 7007225e5Sgerardnicorequire_once(__DIR__ . "/../class/HtmlUtility.php"); 8007225e5Sgerardnico 9*531e725cSNickeauuse ComboStrap\CallStack; 10007225e5Sgerardnicouse ComboStrap\LinkUtility; 11007225e5Sgerardnicouse ComboStrap\PluginUtility; 12007225e5Sgerardnicouse ComboStrap\Tag; 13*531e725cSNickeauuse ComboStrap\TagAttributes; 14007225e5Sgerardnico 15007225e5Sgerardnicoif (!defined('DOKU_INC')) die(); 16007225e5Sgerardnico 17007225e5Sgerardnico/** 18007225e5Sgerardnico * 19007225e5Sgerardnico * A link pattern to take over the link of Dokuwiki 20007225e5Sgerardnico * and transform it as a bootstrap link 21007225e5Sgerardnico * 22007225e5Sgerardnico * The handle of the move of link is to be found in the 23007225e5Sgerardnico * admin action {@link action_plugin_combo_linkmove} 24007225e5Sgerardnico * 25007225e5Sgerardnico */ 26007225e5Sgerardnicoclass syntax_plugin_combo_link extends DokuWiki_Syntax_Plugin 27007225e5Sgerardnico{ 28007225e5Sgerardnico const TAG = 'link'; 29ef295d81Sgerardnico const COMPONENT = 'combo_link'; 30007225e5Sgerardnico 315f891b7eSNickeau /** 3221913ab3SNickeau * Disable the link 3321913ab3SNickeau */ 3421913ab3SNickeau const CONF_DISABLE_LINK = "disableLink"; 3521913ab3SNickeau 3621913ab3SNickeau /** 375f891b7eSNickeau * The link Tag 38*531e725cSNickeau * a or p 395f891b7eSNickeau */ 405f891b7eSNickeau const LINK_TAG = "linkTag"; 415f891b7eSNickeau 4221913ab3SNickeau /** 4321913ab3SNickeau * Do the link component allows to be spawn on multilines 4421913ab3SNickeau */ 4521913ab3SNickeau const CONF_ENABLE_MULTI_LINES_LINK = "enableMultiLinesLink"; 46*531e725cSNickeau const CLICKABLE_ATTRIBUTE = "clickable"; 4721913ab3SNickeau 485f891b7eSNickeau 49007225e5Sgerardnico /** 50007225e5Sgerardnico * Syntax Type. 51007225e5Sgerardnico * 52007225e5Sgerardnico * Needs to return one of the mode types defined in $PARSER_MODES in parser.php 53007225e5Sgerardnico * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types 54007225e5Sgerardnico */ 55007225e5Sgerardnico function getType() 56007225e5Sgerardnico { 57007225e5Sgerardnico return 'substition'; 58007225e5Sgerardnico } 59007225e5Sgerardnico 60007225e5Sgerardnico /** 61007225e5Sgerardnico * How Dokuwiki will add P element 62007225e5Sgerardnico * 63007225e5Sgerardnico * * 'normal' - The plugin can be used inside paragraphs 64007225e5Sgerardnico * * 'block' - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs 65007225e5Sgerardnico * * 'stack' - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs 66007225e5Sgerardnico * 67007225e5Sgerardnico * @see DokuWiki_Syntax_Plugin::getPType() 68007225e5Sgerardnico */ 69007225e5Sgerardnico function getPType() 70007225e5Sgerardnico { 71007225e5Sgerardnico return 'normal'; 72007225e5Sgerardnico } 73007225e5Sgerardnico 74007225e5Sgerardnico /** 75007225e5Sgerardnico * @return array 76007225e5Sgerardnico * Allow which kind of plugin inside 77007225e5Sgerardnico * 78007225e5Sgerardnico * No one of array('container', 'baseonly', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs') 79007225e5Sgerardnico * because we manage self the content and we call self the parser 80007225e5Sgerardnico */ 81007225e5Sgerardnico function getAllowedTypes() 82007225e5Sgerardnico { 83007225e5Sgerardnico return array('substition', 'formatting', 'disabled'); 84007225e5Sgerardnico } 85007225e5Sgerardnico 865f891b7eSNickeau public function accepts($mode) 875f891b7eSNickeau { 885f891b7eSNickeau /** 895f891b7eSNickeau * To avoid that the description if it contains a link 905f891b7eSNickeau * will be taken by the links mode 915f891b7eSNickeau * 925f891b7eSNickeau * For instance, [[https://hallo|https://hallo]] will send https://hallo 935f891b7eSNickeau * to the external link mode 945f891b7eSNickeau */ 955f891b7eSNickeau $linkModes = [ 965f891b7eSNickeau "externallink", 975f891b7eSNickeau "locallink", 985f891b7eSNickeau "internallink", 995f891b7eSNickeau "interwikilink", 1005f891b7eSNickeau "emaillink", 101fc45fbf7Sgerardnico "emphasis", // double slash can not be used inside to preserve the possibility to write an URL in the description 1025f891b7eSNickeau //"emphasis_open", // italic use // and therefore take over a link as description which is not handy when copying a tweet 1035f891b7eSNickeau //"emphasis_close", 1045f891b7eSNickeau //"acrnonym" 1055f891b7eSNickeau ]; 1065f891b7eSNickeau if (in_array($mode, $linkModes)) { 1075f891b7eSNickeau return false; 1085f891b7eSNickeau } else { 1095f891b7eSNickeau return true; 1105f891b7eSNickeau } 1115f891b7eSNickeau } 1125f891b7eSNickeau 1135f891b7eSNickeau 114007225e5Sgerardnico /** 115007225e5Sgerardnico * @see Doku_Parser_Mode::getSort() 116007225e5Sgerardnico * The mode with the lowest sort number will win out 117007225e5Sgerardnico */ 118007225e5Sgerardnico function getSort() 119007225e5Sgerardnico { 120007225e5Sgerardnico return 100; 121007225e5Sgerardnico } 122007225e5Sgerardnico 123007225e5Sgerardnico 124007225e5Sgerardnico function connectTo($mode) 125007225e5Sgerardnico { 126d262537cSgerardnico 12721913ab3SNickeau if (!$this->getConf(self::CONF_DISABLE_LINK, false)) { 12821913ab3SNickeau $pattern = LinkUtility::ENTRY_PATTERN_SINGLE_LINE; 12921913ab3SNickeau if ($this->getConf(self::CONF_ENABLE_MULTI_LINES_LINK, false)) { 13021913ab3SNickeau $pattern = LinkUtility::ENTRY_PATTERN_MULTI_LINE; 13121913ab3SNickeau } 13221913ab3SNickeau $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeForComponent($this->getPluginComponent())); 13321913ab3SNickeau } 134d262537cSgerardnico 135007225e5Sgerardnico } 136007225e5Sgerardnico 1375f891b7eSNickeau public function postConnect() 1385f891b7eSNickeau { 13921913ab3SNickeau if (!$this->getConf(self::CONF_DISABLE_LINK, false)) { 1405f891b7eSNickeau $this->Lexer->addExitPattern(LinkUtility::EXIT_PATTERN, PluginUtility::getModeForComponent($this->getPluginComponent())); 1415f891b7eSNickeau } 14221913ab3SNickeau } 1435f891b7eSNickeau 144007225e5Sgerardnico 145007225e5Sgerardnico /** 146007225e5Sgerardnico * The handler for an internal link 147007225e5Sgerardnico * based on `internallink` in {@link Doku_Handler} 148007225e5Sgerardnico * The handler call the good renderer in {@link Doku_Renderer_xhtml} with 149007225e5Sgerardnico * the parameters (ie for instance internallink) 150007225e5Sgerardnico * @param string $match 151007225e5Sgerardnico * @param int $state 152007225e5Sgerardnico * @param int $pos 153007225e5Sgerardnico * @param Doku_Handler $handler 154007225e5Sgerardnico * @return array|bool 155007225e5Sgerardnico */ 156007225e5Sgerardnico function handle($match, $state, $pos, Doku_Handler $handler) 157007225e5Sgerardnico { 158007225e5Sgerardnico 159*531e725cSNickeau 1605f891b7eSNickeau switch ($state) { 1615f891b7eSNickeau case DOKU_LEXER_ENTER: 162*531e725cSNickeau $tagAttributes = TagAttributes::createFromCallStackArray(LinkUtility::parse($match)); 163*531e725cSNickeau $callStack = CallStack::createFromHandler($handler); 164*531e725cSNickeau 165*531e725cSNickeau 166*531e725cSNickeau $parent = $callStack->moveToParent(); 167007225e5Sgerardnico $parentName = ""; 168*531e725cSNickeau if ($parent != false) { 169*531e725cSNickeau 170*531e725cSNickeau /** 171*531e725cSNickeau * Button Link 172*531e725cSNickeau * Getting the attributes 173*531e725cSNickeau */ 174*531e725cSNickeau $parentName = $parent->getTagName(); 175*531e725cSNickeau if ($parentName == syntax_plugin_combo_button::TAG) { 176*531e725cSNickeau $tagAttributes->mergeWithCallStackArray($parent->getAttributes()); 17721913ab3SNickeau } 178*531e725cSNickeau 179*531e725cSNickeau /** 180*531e725cSNickeau * Searching Clickable parent 181*531e725cSNickeau */ 182*531e725cSNickeau $maxLevel = 3; 183*531e725cSNickeau $level = 0; 184*531e725cSNickeau while ( 185*531e725cSNickeau $parent != false && 186*531e725cSNickeau !$parent->hasAttribute(self::CLICKABLE_ATTRIBUTE) && 187*531e725cSNickeau $level < $maxLevel 188*531e725cSNickeau ) { 189*531e725cSNickeau $parent = $callStack->moveToParent(); 190*531e725cSNickeau $level++; 1915f891b7eSNickeau } 192*531e725cSNickeau if ($parent != false) { 193*531e725cSNickeau if ($parent->getAttribute(self::CLICKABLE_ATTRIBUTE)) { 194*531e725cSNickeau $tagAttributes->addClassName("stretched-link"); 195*531e725cSNickeau $parent->addClassName("position-relative"); 196*531e725cSNickeau $parent->removeAttribute(self::CLICKABLE_ATTRIBUTE); 1975f891b7eSNickeau } 19821913ab3SNickeau } 19921913ab3SNickeau 200*531e725cSNickeau } 201*531e725cSNickeau 202*531e725cSNickeau $link = new LinkUtility($tagAttributes->getValue(LinkUtility::ATTRIBUTE_REF)); 2035f891b7eSNickeau $linkTag = $link->getHtmlTag(); 2045f891b7eSNickeau return array( 2055f891b7eSNickeau PluginUtility::STATE => $state, 206*531e725cSNickeau PluginUtility::ATTRIBUTES => $tagAttributes->toCallStackArray(), 2075f891b7eSNickeau PluginUtility::CONTEXT => $parentName, 2085f891b7eSNickeau self::LINK_TAG => $linkTag 2095f891b7eSNickeau ); 2105f891b7eSNickeau case DOKU_LEXER_UNMATCHED: 2115f891b7eSNickeau 21232b85071SNickeau $data = PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler); 2135f891b7eSNickeau /** 21432b85071SNickeau * Delete the separator `|` between the ref and the description if any 2155f891b7eSNickeau */ 2165f891b7eSNickeau $tag = new Tag(self::TAG, array(), $state, $handler); 2175f891b7eSNickeau $parent = $tag->getParent(); 2185f891b7eSNickeau if ($parent->getName() == self::TAG) { 2195f891b7eSNickeau if (strpos($match, '|') === 0) { 22032b85071SNickeau $data[PluginUtility::PAYLOAD] = substr($match, 1); 2215f891b7eSNickeau } 222007225e5Sgerardnico } 22332b85071SNickeau return $data; 224007225e5Sgerardnico 2255f891b7eSNickeau case DOKU_LEXER_EXIT: 226*531e725cSNickeau $callStack = CallStack::createFromHandler($handler); 227*531e725cSNickeau $openingTag = $callStack->moveToPreviousCorrespondingOpeningCall(); 2285f891b7eSNickeau $openingAttributes = $openingTag->getAttributes(); 229*531e725cSNickeau $linkTag = $openingTag->getPluginData()[self::LINK_TAG]; 230*531e725cSNickeau $openingPosition = $openingTag->getKey(); 2315f891b7eSNickeau 232*531e725cSNickeau $callStack->moveToEnd(); 233*531e725cSNickeau $previousCall = $callStack->previous(); 234*531e725cSNickeau $previousCallPosition = $previousCall->getKey(); 235*531e725cSNickeau $previousCallContent = $previousCall->getCapturedContent(); 236*531e725cSNickeau 237*531e725cSNickeau if ( 238*531e725cSNickeau $openingPosition == $previousCallPosition // ie [[id]] 239*531e725cSNickeau || 240*531e725cSNickeau ($openingPosition == $previousCallPosition - 1 && $previousCallContent == "|") // ie [[id|]] 241*531e725cSNickeau ) { 2425f891b7eSNickeau // There is no name 2435f891b7eSNickeau $link = new LinkUtility($openingAttributes[LinkUtility::ATTRIBUTE_REF]); 2445f891b7eSNickeau $linkName = $link->getName(); 2455f891b7eSNickeau } else { 2465f891b7eSNickeau $linkName = ""; 2475f891b7eSNickeau } 2485f891b7eSNickeau return array( 2495f891b7eSNickeau PluginUtility::STATE => $state, 2505f891b7eSNickeau PluginUtility::ATTRIBUTES => $openingAttributes, 2515f891b7eSNickeau PluginUtility::PAYLOAD => $linkName, 2525f891b7eSNickeau PluginUtility::CONTEXT => $openingTag->getContext(), 2535f891b7eSNickeau self::LINK_TAG => $linkTag 2545f891b7eSNickeau ); 2555f891b7eSNickeau } 2565f891b7eSNickeau return true; 2575f891b7eSNickeau 258007225e5Sgerardnico 259007225e5Sgerardnico } 260007225e5Sgerardnico 261007225e5Sgerardnico /** 262007225e5Sgerardnico * Render the output 263007225e5Sgerardnico * @param string $format 264007225e5Sgerardnico * @param Doku_Renderer $renderer 265007225e5Sgerardnico * @param array $data - what the function handle() return'ed 266007225e5Sgerardnico * @return boolean - rendered correctly? (however, returned value is not used at the moment) 267007225e5Sgerardnico * @see DokuWiki_Syntax_Plugin::render() 268007225e5Sgerardnico * 269007225e5Sgerardnico * 270007225e5Sgerardnico */ 271007225e5Sgerardnico function render($format, Doku_Renderer $renderer, $data) 272007225e5Sgerardnico { 273007225e5Sgerardnico // The data 274007225e5Sgerardnico switch ($format) { 275007225e5Sgerardnico case 'xhtml': 276007225e5Sgerardnico 277007225e5Sgerardnico /** @var Doku_Renderer_xhtml $renderer */ 278007225e5Sgerardnico /** 27919b0880dSgerardnico * Cache problem may occurs while releasing 280007225e5Sgerardnico */ 281007225e5Sgerardnico if (isset($data[PluginUtility::ATTRIBUTES])) { 282*531e725cSNickeau $callStackAttributes = $data[PluginUtility::ATTRIBUTES]; 283007225e5Sgerardnico } else { 284*531e725cSNickeau $callStackAttributes = $data; 285007225e5Sgerardnico } 2865f891b7eSNickeau 28721913ab3SNickeau PluginUtility::getSnippetManager()->attachCssSnippetForBar(self::TAG); 2885f891b7eSNickeau 2895f891b7eSNickeau $state = $data[PluginUtility::STATE]; 2905f891b7eSNickeau switch ($state) { 2915f891b7eSNickeau case DOKU_LEXER_ENTER: 292*531e725cSNickeau $tagAttributes = TagAttributes::createFromCallStackArray($callStackAttributes, self::TAG); 293*531e725cSNickeau $ref = $tagAttributes->getValueAndRemove(LinkUtility::ATTRIBUTE_REF); 294*531e725cSNickeau $link = new LinkUtility($ref, $tagAttributes); 295d262537cSgerardnico 29619b0880dSgerardnico /** 2975f891b7eSNickeau * Extra styling 29819b0880dSgerardnico */ 2995f891b7eSNickeau $parentTag = $data[PluginUtility::CONTEXT]; 3009f4383e9Sgerardnico switch ($parentTag) { 30121913ab3SNickeau /** 30221913ab3SNickeau * Button link 30321913ab3SNickeau */ 3049f4383e9Sgerardnico case syntax_plugin_combo_button::TAG: 305*531e725cSNickeau $tagAttributes->addHtmlAttributeValue("role", "button"); 306*531e725cSNickeau syntax_plugin_combo_button::processButtonAttributesToHtmlAttributes($tagAttributes); 3075f891b7eSNickeau $htmlLink = $link->renderOpenTag($renderer); 3089f4383e9Sgerardnico break; 3095f891b7eSNickeau case syntax_plugin_combo_badge::TAG: 3109f4383e9Sgerardnico case syntax_plugin_combo_cite::TAG: 3119f4383e9Sgerardnico case syntax_plugin_combo_listitem::TAG: 3129f4383e9Sgerardnico case syntax_plugin_combo_preformatted::TAG: 3135f891b7eSNickeau $htmlLink = $link->renderOpenTag($renderer); 3149f4383e9Sgerardnico break; 3150a517624Sgerardnico case syntax_plugin_combo_dropdown::TAG: 316*531e725cSNickeau $tagAttributes->addClassName("dropdown-item"); 3175f891b7eSNickeau $htmlLink = $link->renderOpenTag($renderer); 3180a517624Sgerardnico break; 3199f4383e9Sgerardnico case syntax_plugin_combo_navbarcollapse::COMPONENT: 320*531e725cSNickeau $tagAttributes->addClassName("navbar-link"); 3215f891b7eSNickeau $htmlLink = '<div class="navbar-nav">' . $link->renderOpenTag($renderer); 3229f4383e9Sgerardnico break; 3230a517624Sgerardnico case syntax_plugin_combo_navbargroup::COMPONENT: 324*531e725cSNickeau $tagAttributes->addClassName("nav-link"); 3255f891b7eSNickeau $htmlLink = '<li class="nav-item">' . $link->renderOpenTag($renderer); 3260a517624Sgerardnico break; 3275f891b7eSNickeau default: 3285f891b7eSNickeau $htmlLink = $link->renderOpenTag($renderer); 3290a517624Sgerardnico 3309f4383e9Sgerardnico } 3319f4383e9Sgerardnico 33219b0880dSgerardnico 33319b0880dSgerardnico /** 33419b0880dSgerardnico * Add it to the rendering 33519b0880dSgerardnico */ 336007225e5Sgerardnico $renderer->doc .= $htmlLink; 3375f891b7eSNickeau break; 3385f891b7eSNickeau case DOKU_LEXER_UNMATCHED: 33932b85071SNickeau $renderer->doc .= PluginUtility::renderUnmatched($data); 3405f891b7eSNickeau break; 3415f891b7eSNickeau case DOKU_LEXER_EXIT: 3425f891b7eSNickeau 3435f891b7eSNickeau // if there is no link name defined, we get the name as ref in the payload 3445f891b7eSNickeau // otherwise null string 345*531e725cSNickeau $renderer->doc .= $data[PluginUtility::PAYLOAD];; 3465f891b7eSNickeau 347e3d0019cSgerardnico // Close the link 348e3d0019cSgerardnico $linkTag = $data[self::LINK_TAG]; 349e3d0019cSgerardnico $renderer->doc .= "</$linkTag>"; 350e3d0019cSgerardnico 351e3d0019cSgerardnico // Close the html wrapper element 3525f891b7eSNickeau $context = $data[PluginUtility::CONTEXT]; 3535f891b7eSNickeau switch ($context) { 3545f891b7eSNickeau case syntax_plugin_combo_navbarcollapse::COMPONENT: 3555f891b7eSNickeau $renderer->doc .= '</div>'; 3565f891b7eSNickeau break; 3575f891b7eSNickeau case syntax_plugin_combo_navbargroup::COMPONENT: 3585f891b7eSNickeau $renderer->doc .= '</li>'; 3595f891b7eSNickeau break; 3605f891b7eSNickeau } 3615f891b7eSNickeau 362e3d0019cSgerardnico 3635f891b7eSNickeau } 3645f891b7eSNickeau 365007225e5Sgerardnico 366007225e5Sgerardnico return true; 367007225e5Sgerardnico break; 368007225e5Sgerardnico 3695f891b7eSNickeau case 'metadata': 370007225e5Sgerardnico 3715f891b7eSNickeau $state = $data[PluginUtility::STATE]; 3725f891b7eSNickeau if ($state == DOKU_LEXER_ENTER) { 373007225e5Sgerardnico /** 374007225e5Sgerardnico * Keep track of the backlinks ie meta['relation']['references'] 375007225e5Sgerardnico * @var Doku_Renderer_metadata $renderer 376007225e5Sgerardnico */ 377007225e5Sgerardnico if (isset($data[PluginUtility::ATTRIBUTES])) { 378*531e725cSNickeau $tagAttributes = $data[PluginUtility::ATTRIBUTES]; 379007225e5Sgerardnico } else { 380*531e725cSNickeau $tagAttributes = $data; 381007225e5Sgerardnico } 382*531e725cSNickeau $ref = $tagAttributes[LinkUtility::ATTRIBUTE_REF]; 3839f4383e9Sgerardnico 3849f4383e9Sgerardnico $link = new LinkUtility($ref); 385*531e725cSNickeau $name = $tagAttributes[LinkUtility::ATTRIBUTE_NAME]; 3869f4383e9Sgerardnico if ($name != null) { 3879f4383e9Sgerardnico $link->setName($name); 3889f4383e9Sgerardnico } 3899f4383e9Sgerardnico $link->handleMetadata($renderer); 390007225e5Sgerardnico 391007225e5Sgerardnico return true; 3925f891b7eSNickeau } 393007225e5Sgerardnico break; 394007225e5Sgerardnico 395*531e725cSNickeau case renderer_plugin_combo_analytics::RENDERER_FORMAT: 3965f891b7eSNickeau 3975f891b7eSNickeau $state = $data[PluginUtility::STATE]; 3985f891b7eSNickeau if ($state == DOKU_LEXER_ENTER) { 399007225e5Sgerardnico /** 400007225e5Sgerardnico * 401007225e5Sgerardnico * @var renderer_plugin_combo_analytics $renderer 402007225e5Sgerardnico */ 403*531e725cSNickeau $tagAttributes = $data[PluginUtility::ATTRIBUTES]; 404*531e725cSNickeau $ref = $tagAttributes[LinkUtility::ATTRIBUTE_REF]; 4059f4383e9Sgerardnico $link = new LinkUtility($ref); 4069f4383e9Sgerardnico $link->processLinkStats($renderer->stats); 407007225e5Sgerardnico break; 4085f891b7eSNickeau } 409007225e5Sgerardnico 410007225e5Sgerardnico } 411007225e5Sgerardnico // unsupported $mode 412007225e5Sgerardnico return false; 413007225e5Sgerardnico } 414007225e5Sgerardnico 415007225e5Sgerardnico 416007225e5Sgerardnico} 417007225e5Sgerardnico 418