1007225e5Sgerardnico<?php 2007225e5Sgerardnico 3007225e5Sgerardnico 437748cd8SNickeaurequire_once(__DIR__ . "/../ComboStrap/Analytics.php"); 537748cd8SNickeaurequire_once(__DIR__ . "/../ComboStrap/PluginUtility.php"); 637748cd8SNickeaurequire_once(__DIR__ . "/../ComboStrap/LinkUtility.php"); 737748cd8SNickeaurequire_once(__DIR__ . "/../ComboStrap/XhtmlUtility.php"); 8007225e5Sgerardnico 9531e725cSNickeauuse ComboStrap\CallStack; 10007225e5Sgerardnicouse ComboStrap\LinkUtility; 1137748cd8SNickeauuse ComboStrap\ThirdPartyPlugins; 12007225e5Sgerardnicouse ComboStrap\PluginUtility; 13007225e5Sgerardnicouse ComboStrap\Tag; 14531e725cSNickeauuse ComboStrap\TagAttributes; 15007225e5Sgerardnico 16007225e5Sgerardnicoif (!defined('DOKU_INC')) die(); 17007225e5Sgerardnico 18007225e5Sgerardnico/** 19007225e5Sgerardnico * 20007225e5Sgerardnico * A link pattern to take over the link of Dokuwiki 21007225e5Sgerardnico * and transform it as a bootstrap link 22007225e5Sgerardnico * 23007225e5Sgerardnico * The handle of the move of link is to be found in the 24007225e5Sgerardnico * admin action {@link action_plugin_combo_linkmove} 25007225e5Sgerardnico * 26007225e5Sgerardnico */ 27007225e5Sgerardnicoclass syntax_plugin_combo_link extends DokuWiki_Syntax_Plugin 28007225e5Sgerardnico{ 29007225e5Sgerardnico const TAG = 'link'; 30ef295d81Sgerardnico const COMPONENT = 'combo_link'; 31007225e5Sgerardnico 325f891b7eSNickeau /** 3385e82846SNickeau * Disable the link component 3421913ab3SNickeau */ 3521913ab3SNickeau const CONF_DISABLE_LINK = "disableLink"; 3621913ab3SNickeau 3721913ab3SNickeau /** 385f891b7eSNickeau * The link Tag 39531e725cSNickeau * a or p 405f891b7eSNickeau */ 415f891b7eSNickeau const LINK_TAG = "linkTag"; 425f891b7eSNickeau 4321913ab3SNickeau /** 4421913ab3SNickeau * Do the link component allows to be spawn on multilines 4521913ab3SNickeau */ 46531e725cSNickeau 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 8637748cd8SNickeau /** 8737748cd8SNickeau * @param string $mode 8837748cd8SNickeau * @return bool 8937748cd8SNickeau * Accepts inside 9037748cd8SNickeau */ 915f891b7eSNickeau public function accepts($mode) 925f891b7eSNickeau { 935f891b7eSNickeau /** 945f891b7eSNickeau * To avoid that the description if it contains a link 955f891b7eSNickeau * will be taken by the links mode 965f891b7eSNickeau * 975f891b7eSNickeau * For instance, [[https://hallo|https://hallo]] will send https://hallo 985f891b7eSNickeau * to the external link mode 995f891b7eSNickeau */ 1005f891b7eSNickeau $linkModes = [ 1015f891b7eSNickeau "externallink", 1025f891b7eSNickeau "locallink", 1035f891b7eSNickeau "internallink", 1045f891b7eSNickeau "interwikilink", 1055f891b7eSNickeau "emaillink", 106fc45fbf7Sgerardnico "emphasis", // double slash can not be used inside to preserve the possibility to write an URL in the description 1075f891b7eSNickeau //"emphasis_open", // italic use // and therefore take over a link as description which is not handy when copying a tweet 1085f891b7eSNickeau //"emphasis_close", 1095f891b7eSNickeau //"acrnonym" 1105f891b7eSNickeau ]; 1115f891b7eSNickeau if (in_array($mode, $linkModes)) { 1125f891b7eSNickeau return false; 1135f891b7eSNickeau } else { 1145f891b7eSNickeau return true; 1155f891b7eSNickeau } 1165f891b7eSNickeau } 1175f891b7eSNickeau 1185f891b7eSNickeau 119007225e5Sgerardnico /** 120007225e5Sgerardnico * @see Doku_Parser_Mode::getSort() 121007225e5Sgerardnico * The mode with the lowest sort number will win out 122007225e5Sgerardnico */ 123007225e5Sgerardnico function getSort() 124007225e5Sgerardnico { 125e8b2ff59SNickeau /** 126e8b2ff59SNickeau * It should be less than the number 127e8b2ff59SNickeau * at {@link \dokuwiki\Parsing\ParserMode\Internallink::getSort} 128e8b2ff59SNickeau * and the like 129e8b2ff59SNickeau * 130e8b2ff59SNickeau * For whatever reason, the number below should be less than 100, 131e8b2ff59SNickeau * otherwise on windows with DokuWiki Stick, the link syntax may be not taken 132e8b2ff59SNickeau * into account 133e8b2ff59SNickeau */ 134e8b2ff59SNickeau return 99; 135007225e5Sgerardnico } 136007225e5Sgerardnico 137007225e5Sgerardnico 138007225e5Sgerardnico function connectTo($mode) 139007225e5Sgerardnico { 140d262537cSgerardnico 14137748cd8SNickeau if (!$this->getConf(self::CONF_DISABLE_LINK, false) 14237748cd8SNickeau && 14337748cd8SNickeau $mode !== PluginUtility::getModeFromPluginName(ThirdPartyPlugins::IMAGE_MAPPING_NAME) 14437748cd8SNickeau ) { 14537748cd8SNickeau 14621913ab3SNickeau $pattern = LinkUtility::ENTRY_PATTERN_SINGLE_LINE; 1479337a630SNickeau $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeFromTag($this->getPluginComponent())); 14837748cd8SNickeau 14921913ab3SNickeau } 150d262537cSgerardnico 151007225e5Sgerardnico } 152007225e5Sgerardnico 1535f891b7eSNickeau public function postConnect() 1545f891b7eSNickeau { 15521913ab3SNickeau if (!$this->getConf(self::CONF_DISABLE_LINK, false)) { 1569337a630SNickeau $this->Lexer->addExitPattern(LinkUtility::EXIT_PATTERN, PluginUtility::getModeFromTag($this->getPluginComponent())); 1575f891b7eSNickeau } 15821913ab3SNickeau } 1595f891b7eSNickeau 160007225e5Sgerardnico 161007225e5Sgerardnico /** 162007225e5Sgerardnico * The handler for an internal link 163007225e5Sgerardnico * based on `internallink` in {@link Doku_Handler} 164007225e5Sgerardnico * The handler call the good renderer in {@link Doku_Renderer_xhtml} with 165007225e5Sgerardnico * the parameters (ie for instance internallink) 166007225e5Sgerardnico * @param string $match 167007225e5Sgerardnico * @param int $state 168007225e5Sgerardnico * @param int $pos 169007225e5Sgerardnico * @param Doku_Handler $handler 170007225e5Sgerardnico * @return array|bool 171007225e5Sgerardnico */ 172007225e5Sgerardnico function handle($match, $state, $pos, Doku_Handler $handler) 173007225e5Sgerardnico { 174007225e5Sgerardnico 175531e725cSNickeau 1765f891b7eSNickeau switch ($state) { 1775f891b7eSNickeau case DOKU_LEXER_ENTER: 178531e725cSNickeau $tagAttributes = TagAttributes::createFromCallStackArray(LinkUtility::parse($match)); 179531e725cSNickeau $callStack = CallStack::createFromHandler($handler); 180531e725cSNickeau 181531e725cSNickeau 182531e725cSNickeau $parent = $callStack->moveToParent(); 183007225e5Sgerardnico $parentName = ""; 184531e725cSNickeau if ($parent != false) { 185531e725cSNickeau 186531e725cSNickeau /** 187531e725cSNickeau * Button Link 188531e725cSNickeau * Getting the attributes 189531e725cSNickeau */ 190531e725cSNickeau $parentName = $parent->getTagName(); 191531e725cSNickeau if ($parentName == syntax_plugin_combo_button::TAG) { 192531e725cSNickeau $tagAttributes->mergeWithCallStackArray($parent->getAttributes()); 19321913ab3SNickeau } 194531e725cSNickeau 195531e725cSNickeau /** 196531e725cSNickeau * Searching Clickable parent 197531e725cSNickeau */ 198531e725cSNickeau $maxLevel = 3; 199531e725cSNickeau $level = 0; 200531e725cSNickeau while ( 201531e725cSNickeau $parent != false && 202531e725cSNickeau !$parent->hasAttribute(self::CLICKABLE_ATTRIBUTE) && 203531e725cSNickeau $level < $maxLevel 204531e725cSNickeau ) { 205531e725cSNickeau $parent = $callStack->moveToParent(); 206531e725cSNickeau $level++; 2075f891b7eSNickeau } 208531e725cSNickeau if ($parent != false) { 209531e725cSNickeau if ($parent->getAttribute(self::CLICKABLE_ATTRIBUTE)) { 210531e725cSNickeau $tagAttributes->addClassName("stretched-link"); 211531e725cSNickeau $parent->addClassName("position-relative"); 212531e725cSNickeau $parent->removeAttribute(self::CLICKABLE_ATTRIBUTE); 2135f891b7eSNickeau } 21421913ab3SNickeau } 21521913ab3SNickeau 216531e725cSNickeau } 21785e82846SNickeau $callStackAttributes = $tagAttributes->toCallStackArray(); 2185f891b7eSNickeau return array( 2195f891b7eSNickeau PluginUtility::STATE => $state, 22085e82846SNickeau PluginUtility::ATTRIBUTES => $callStackAttributes, 22185e82846SNickeau PluginUtility::CONTEXT => $parentName 2225f891b7eSNickeau ); 2235f891b7eSNickeau case DOKU_LEXER_UNMATCHED: 2245f891b7eSNickeau 22532b85071SNickeau $data = PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler); 2265f891b7eSNickeau /** 22732b85071SNickeau * Delete the separator `|` between the ref and the description if any 2285f891b7eSNickeau */ 229*1fa8c418SNickeau $tag = CallStack::createFromHandler( $handler); 230*1fa8c418SNickeau $parent = $tag->moveToParent(); 231*1fa8c418SNickeau if ($parent->getTagName() == self::TAG) { 2325f891b7eSNickeau if (strpos($match, '|') === 0) { 23332b85071SNickeau $data[PluginUtility::PAYLOAD] = substr($match, 1); 2345f891b7eSNickeau } 235007225e5Sgerardnico } 23632b85071SNickeau return $data; 237007225e5Sgerardnico 2385f891b7eSNickeau case DOKU_LEXER_EXIT: 239531e725cSNickeau $callStack = CallStack::createFromHandler($handler); 240531e725cSNickeau $openingTag = $callStack->moveToPreviousCorrespondingOpeningCall(); 2415f891b7eSNickeau $openingAttributes = $openingTag->getAttributes(); 242531e725cSNickeau $openingPosition = $openingTag->getKey(); 2435f891b7eSNickeau 244531e725cSNickeau $callStack->moveToEnd(); 245531e725cSNickeau $previousCall = $callStack->previous(); 246531e725cSNickeau $previousCallPosition = $previousCall->getKey(); 247531e725cSNickeau $previousCallContent = $previousCall->getCapturedContent(); 248531e725cSNickeau 249531e725cSNickeau if ( 250531e725cSNickeau $openingPosition == $previousCallPosition // ie [[id]] 251531e725cSNickeau || 252531e725cSNickeau ($openingPosition == $previousCallPosition - 1 && $previousCallContent == "|") // ie [[id|]] 253531e725cSNickeau ) { 2545f891b7eSNickeau // There is no name 2555f891b7eSNickeau $link = new LinkUtility($openingAttributes[LinkUtility::ATTRIBUTE_REF]); 2565f891b7eSNickeau $linkName = $link->getName(); 2575f891b7eSNickeau } else { 2585f891b7eSNickeau $linkName = ""; 2595f891b7eSNickeau } 2605f891b7eSNickeau return array( 2615f891b7eSNickeau PluginUtility::STATE => $state, 2625f891b7eSNickeau PluginUtility::ATTRIBUTES => $openingAttributes, 2635f891b7eSNickeau PluginUtility::PAYLOAD => $linkName, 26485e82846SNickeau PluginUtility::CONTEXT => $openingTag->getContext() 2655f891b7eSNickeau ); 2665f891b7eSNickeau } 2675f891b7eSNickeau return true; 2685f891b7eSNickeau 269007225e5Sgerardnico 270007225e5Sgerardnico } 271007225e5Sgerardnico 272007225e5Sgerardnico /** 273007225e5Sgerardnico * Render the output 274007225e5Sgerardnico * @param string $format 275007225e5Sgerardnico * @param Doku_Renderer $renderer 276007225e5Sgerardnico * @param array $data - what the function handle() return'ed 277007225e5Sgerardnico * @return boolean - rendered correctly? (however, returned value is not used at the moment) 278007225e5Sgerardnico * @see DokuWiki_Syntax_Plugin::render() 279007225e5Sgerardnico * 280007225e5Sgerardnico * 281007225e5Sgerardnico */ 282007225e5Sgerardnico function render($format, Doku_Renderer $renderer, $data) 283007225e5Sgerardnico { 284007225e5Sgerardnico // The data 285007225e5Sgerardnico switch ($format) { 286007225e5Sgerardnico case 'xhtml': 287007225e5Sgerardnico 288007225e5Sgerardnico /** @var Doku_Renderer_xhtml $renderer */ 289007225e5Sgerardnico /** 29019b0880dSgerardnico * Cache problem may occurs while releasing 291007225e5Sgerardnico */ 292007225e5Sgerardnico if (isset($data[PluginUtility::ATTRIBUTES])) { 293531e725cSNickeau $callStackAttributes = $data[PluginUtility::ATTRIBUTES]; 294007225e5Sgerardnico } else { 295531e725cSNickeau $callStackAttributes = $data; 296007225e5Sgerardnico } 2975f891b7eSNickeau 29821913ab3SNickeau PluginUtility::getSnippetManager()->attachCssSnippetForBar(self::TAG); 2995f891b7eSNickeau 3005f891b7eSNickeau $state = $data[PluginUtility::STATE]; 3015f891b7eSNickeau switch ($state) { 3025f891b7eSNickeau case DOKU_LEXER_ENTER: 303531e725cSNickeau $tagAttributes = TagAttributes::createFromCallStackArray($callStackAttributes, self::TAG); 304531e725cSNickeau $ref = $tagAttributes->getValueAndRemove(LinkUtility::ATTRIBUTE_REF); 305531e725cSNickeau $link = new LinkUtility($ref, $tagAttributes); 306d262537cSgerardnico 30719b0880dSgerardnico /** 3085f891b7eSNickeau * Extra styling 30919b0880dSgerardnico */ 3105f891b7eSNickeau $parentTag = $data[PluginUtility::CONTEXT]; 3119f4383e9Sgerardnico switch ($parentTag) { 31221913ab3SNickeau /** 31321913ab3SNickeau * Button link 31421913ab3SNickeau */ 3159f4383e9Sgerardnico case syntax_plugin_combo_button::TAG: 316531e725cSNickeau $tagAttributes->addHtmlAttributeValue("role", "button"); 317531e725cSNickeau syntax_plugin_combo_button::processButtonAttributesToHtmlAttributes($tagAttributes); 3185f891b7eSNickeau $htmlLink = $link->renderOpenTag($renderer); 3199f4383e9Sgerardnico break; 3205f891b7eSNickeau case syntax_plugin_combo_badge::TAG: 3219f4383e9Sgerardnico case syntax_plugin_combo_cite::TAG: 3229337a630SNickeau case syntax_plugin_combo_contentlistitem::DOKU_TAG: 3239f4383e9Sgerardnico case syntax_plugin_combo_preformatted::TAG: 3245f891b7eSNickeau $htmlLink = $link->renderOpenTag($renderer); 3259f4383e9Sgerardnico break; 3260a517624Sgerardnico case syntax_plugin_combo_dropdown::TAG: 327531e725cSNickeau $tagAttributes->addClassName("dropdown-item"); 3285f891b7eSNickeau $htmlLink = $link->renderOpenTag($renderer); 3290a517624Sgerardnico break; 3309f4383e9Sgerardnico case syntax_plugin_combo_navbarcollapse::COMPONENT: 331531e725cSNickeau $tagAttributes->addClassName("navbar-link"); 3325f891b7eSNickeau $htmlLink = '<div class="navbar-nav">' . $link->renderOpenTag($renderer); 3339f4383e9Sgerardnico break; 3340a517624Sgerardnico case syntax_plugin_combo_navbargroup::COMPONENT: 335531e725cSNickeau $tagAttributes->addClassName("nav-link"); 3365f891b7eSNickeau $htmlLink = '<li class="nav-item">' . $link->renderOpenTag($renderer); 3370a517624Sgerardnico break; 3385f891b7eSNickeau default: 3395f891b7eSNickeau $htmlLink = $link->renderOpenTag($renderer); 3400a517624Sgerardnico 3419f4383e9Sgerardnico } 3429f4383e9Sgerardnico 34319b0880dSgerardnico 34419b0880dSgerardnico /** 34519b0880dSgerardnico * Add it to the rendering 34619b0880dSgerardnico */ 347007225e5Sgerardnico $renderer->doc .= $htmlLink; 3485f891b7eSNickeau break; 3495f891b7eSNickeau case DOKU_LEXER_UNMATCHED: 35032b85071SNickeau $renderer->doc .= PluginUtility::renderUnmatched($data); 3515f891b7eSNickeau break; 3525f891b7eSNickeau case DOKU_LEXER_EXIT: 3535f891b7eSNickeau 3545f891b7eSNickeau // if there is no link name defined, we get the name as ref in the payload 3555f891b7eSNickeau // otherwise null string 35685e82846SNickeau $renderer->doc .= $data[PluginUtility::PAYLOAD]; 3575f891b7eSNickeau 358e3d0019cSgerardnico // Close the link 35985e82846SNickeau $renderer->doc .= "</a>"; 360e3d0019cSgerardnico 361e3d0019cSgerardnico // Close the html wrapper element 3625f891b7eSNickeau $context = $data[PluginUtility::CONTEXT]; 3635f891b7eSNickeau switch ($context) { 3645f891b7eSNickeau case syntax_plugin_combo_navbarcollapse::COMPONENT: 3655f891b7eSNickeau $renderer->doc .= '</div>'; 3665f891b7eSNickeau break; 3675f891b7eSNickeau case syntax_plugin_combo_navbargroup::COMPONENT: 3685f891b7eSNickeau $renderer->doc .= '</li>'; 3695f891b7eSNickeau break; 3705f891b7eSNickeau } 3715f891b7eSNickeau 372e3d0019cSgerardnico 3735f891b7eSNickeau } 3745f891b7eSNickeau 375007225e5Sgerardnico 376007225e5Sgerardnico return true; 377007225e5Sgerardnico 3785f891b7eSNickeau case 'metadata': 379007225e5Sgerardnico 3805f891b7eSNickeau $state = $data[PluginUtility::STATE]; 3815f891b7eSNickeau if ($state == DOKU_LEXER_ENTER) { 382007225e5Sgerardnico /** 383007225e5Sgerardnico * Keep track of the backlinks ie meta['relation']['references'] 384007225e5Sgerardnico * @var Doku_Renderer_metadata $renderer 385007225e5Sgerardnico */ 386007225e5Sgerardnico if (isset($data[PluginUtility::ATTRIBUTES])) { 387531e725cSNickeau $tagAttributes = $data[PluginUtility::ATTRIBUTES]; 388007225e5Sgerardnico } else { 389531e725cSNickeau $tagAttributes = $data; 390007225e5Sgerardnico } 391531e725cSNickeau $ref = $tagAttributes[LinkUtility::ATTRIBUTE_REF]; 3929f4383e9Sgerardnico 3939f4383e9Sgerardnico $link = new LinkUtility($ref); 394531e725cSNickeau $name = $tagAttributes[LinkUtility::ATTRIBUTE_NAME]; 3959f4383e9Sgerardnico if ($name != null) { 3969f4383e9Sgerardnico $link->setName($name); 3979f4383e9Sgerardnico } 3989f4383e9Sgerardnico $link->handleMetadata($renderer); 399007225e5Sgerardnico 400007225e5Sgerardnico return true; 4015f891b7eSNickeau } 402007225e5Sgerardnico break; 403007225e5Sgerardnico 404531e725cSNickeau case renderer_plugin_combo_analytics::RENDERER_FORMAT: 4055f891b7eSNickeau 4065f891b7eSNickeau $state = $data[PluginUtility::STATE]; 4075f891b7eSNickeau if ($state == DOKU_LEXER_ENTER) { 408007225e5Sgerardnico /** 409007225e5Sgerardnico * 410007225e5Sgerardnico * @var renderer_plugin_combo_analytics $renderer 411007225e5Sgerardnico */ 412531e725cSNickeau $tagAttributes = $data[PluginUtility::ATTRIBUTES]; 413531e725cSNickeau $ref = $tagAttributes[LinkUtility::ATTRIBUTE_REF]; 4149f4383e9Sgerardnico $link = new LinkUtility($ref); 4159f4383e9Sgerardnico $link->processLinkStats($renderer->stats); 416007225e5Sgerardnico break; 4175f891b7eSNickeau } 418007225e5Sgerardnico 419007225e5Sgerardnico } 420007225e5Sgerardnico // unsupported $mode 421007225e5Sgerardnico return false; 422007225e5Sgerardnico } 423007225e5Sgerardnico 424007225e5Sgerardnico 425007225e5Sgerardnico} 426007225e5Sgerardnico 427