1<?php 2 3 4require_once(__DIR__ . "/../ComboStrap/Analytics.php"); 5require_once(__DIR__ . "/../ComboStrap/PluginUtility.php"); 6require_once(__DIR__ . "/../ComboStrap/LinkUtility.php"); 7require_once(__DIR__ . "/../ComboStrap/XhtmlUtility.php"); 8 9use ComboStrap\CallStack; 10use ComboStrap\LinkUtility; 11use ComboStrap\ThirdPartyPlugins; 12use ComboStrap\PluginUtility; 13use ComboStrap\Tag; 14use ComboStrap\TagAttributes; 15 16if (!defined('DOKU_INC')) die(); 17 18/** 19 * 20 * A link pattern to take over the link of Dokuwiki 21 * and transform it as a bootstrap link 22 * 23 * The handle of the move of link is to be found in the 24 * admin action {@link action_plugin_combo_linkmove} 25 * 26 */ 27class syntax_plugin_combo_link extends DokuWiki_Syntax_Plugin 28{ 29 const TAG = 'link'; 30 const COMPONENT = 'combo_link'; 31 32 /** 33 * Disable the link component 34 */ 35 const CONF_DISABLE_LINK = "disableLink"; 36 37 /** 38 * The link Tag 39 * a or p 40 */ 41 const LINK_TAG = "linkTag"; 42 43 /** 44 * Do the link component allows to be spawn on multilines 45 */ 46 const CLICKABLE_ATTRIBUTE = "clickable"; 47 48 49 /** 50 * Syntax Type. 51 * 52 * Needs to return one of the mode types defined in $PARSER_MODES in parser.php 53 * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types 54 */ 55 function getType() 56 { 57 return 'substition'; 58 } 59 60 /** 61 * How Dokuwiki will add P element 62 * 63 * * 'normal' - The plugin can be used inside paragraphs 64 * * 'block' - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs 65 * * 'stack' - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs 66 * 67 * @see DokuWiki_Syntax_Plugin::getPType() 68 */ 69 function getPType() 70 { 71 return 'normal'; 72 } 73 74 /** 75 * @return array 76 * Allow which kind of plugin inside 77 * 78 * No one of array('container', 'baseonly', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs') 79 * because we manage self the content and we call self the parser 80 */ 81 function getAllowedTypes() 82 { 83 return array('substition', 'formatting', 'disabled'); 84 } 85 86 /** 87 * @param string $mode 88 * @return bool 89 * Accepts inside 90 */ 91 public function accepts($mode) 92 { 93 /** 94 * To avoid that the description if it contains a link 95 * will be taken by the links mode 96 * 97 * For instance, [[https://hallo|https://hallo]] will send https://hallo 98 * to the external link mode 99 */ 100 $linkModes = [ 101 "externallink", 102 "locallink", 103 "internallink", 104 "interwikilink", 105 "emaillink", 106 "emphasis", // double slash can not be used inside to preserve the possibility to write an URL in the description 107 //"emphasis_open", // italic use // and therefore take over a link as description which is not handy when copying a tweet 108 //"emphasis_close", 109 //"acrnonym" 110 ]; 111 if (in_array($mode, $linkModes)) { 112 return false; 113 } else { 114 return true; 115 } 116 } 117 118 119 /** 120 * @see Doku_Parser_Mode::getSort() 121 * The mode with the lowest sort number will win out 122 */ 123 function getSort() 124 { 125 /** 126 * It should be less than the number 127 * at {@link \dokuwiki\Parsing\ParserMode\Internallink::getSort} 128 * and the like 129 * 130 * For whatever reason, the number below should be less than 100, 131 * otherwise on windows with DokuWiki Stick, the link syntax may be not taken 132 * into account 133 */ 134 return 99; 135 } 136 137 138 function connectTo($mode) 139 { 140 141 if (!$this->getConf(self::CONF_DISABLE_LINK, false) 142 && 143 $mode !== PluginUtility::getModeFromPluginName(ThirdPartyPlugins::IMAGE_MAPPING_NAME) 144 ) { 145 146 $pattern = LinkUtility::ENTRY_PATTERN_SINGLE_LINE; 147 $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeFromTag($this->getPluginComponent())); 148 149 } 150 151 } 152 153 public function postConnect() 154 { 155 if (!$this->getConf(self::CONF_DISABLE_LINK, false)) { 156 $this->Lexer->addExitPattern(LinkUtility::EXIT_PATTERN, PluginUtility::getModeFromTag($this->getPluginComponent())); 157 } 158 } 159 160 161 /** 162 * The handler for an internal link 163 * based on `internallink` in {@link Doku_Handler} 164 * The handler call the good renderer in {@link Doku_Renderer_xhtml} with 165 * the parameters (ie for instance internallink) 166 * @param string $match 167 * @param int $state 168 * @param int $pos 169 * @param Doku_Handler $handler 170 * @return array|bool 171 */ 172 function handle($match, $state, $pos, Doku_Handler $handler) 173 { 174 175 176 switch ($state) { 177 case DOKU_LEXER_ENTER: 178 $tagAttributes = TagAttributes::createFromCallStackArray(LinkUtility::parse($match)); 179 $callStack = CallStack::createFromHandler($handler); 180 181 182 $parent = $callStack->moveToParent(); 183 $parentName = ""; 184 if ($parent != false) { 185 186 /** 187 * Button Link 188 * Getting the attributes 189 */ 190 $parentName = $parent->getTagName(); 191 if ($parentName == syntax_plugin_combo_button::TAG) { 192 $tagAttributes->mergeWithCallStackArray($parent->getAttributes()); 193 } 194 195 /** 196 * Searching Clickable parent 197 */ 198 $maxLevel = 3; 199 $level = 0; 200 while ( 201 $parent != false && 202 !$parent->hasAttribute(self::CLICKABLE_ATTRIBUTE) && 203 $level < $maxLevel 204 ) { 205 $parent = $callStack->moveToParent(); 206 $level++; 207 } 208 if ($parent != false) { 209 if ($parent->getAttribute(self::CLICKABLE_ATTRIBUTE)) { 210 $tagAttributes->addClassName("stretched-link"); 211 $parent->addClassName("position-relative"); 212 $parent->removeAttribute(self::CLICKABLE_ATTRIBUTE); 213 } 214 } 215 216 } 217 $callStackAttributes = $tagAttributes->toCallStackArray(); 218 return array( 219 PluginUtility::STATE => $state, 220 PluginUtility::ATTRIBUTES => $callStackAttributes, 221 PluginUtility::CONTEXT => $parentName 222 ); 223 case DOKU_LEXER_UNMATCHED: 224 225 $data = PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler); 226 /** 227 * Delete the separator `|` between the ref and the description if any 228 */ 229 $tag = new Tag(self::TAG, array(), $state, $handler); 230 $parent = $tag->getParent(); 231 if ($parent->getName() == self::TAG) { 232 if (strpos($match, '|') === 0) { 233 $data[PluginUtility::PAYLOAD] = substr($match, 1); 234 } 235 } 236 return $data; 237 238 case DOKU_LEXER_EXIT: 239 $callStack = CallStack::createFromHandler($handler); 240 $openingTag = $callStack->moveToPreviousCorrespondingOpeningCall(); 241 $openingAttributes = $openingTag->getAttributes(); 242 $openingPosition = $openingTag->getKey(); 243 244 $callStack->moveToEnd(); 245 $previousCall = $callStack->previous(); 246 $previousCallPosition = $previousCall->getKey(); 247 $previousCallContent = $previousCall->getCapturedContent(); 248 249 if ( 250 $openingPosition == $previousCallPosition // ie [[id]] 251 || 252 ($openingPosition == $previousCallPosition - 1 && $previousCallContent == "|") // ie [[id|]] 253 ) { 254 // There is no name 255 $link = new LinkUtility($openingAttributes[LinkUtility::ATTRIBUTE_REF]); 256 $linkName = $link->getName(); 257 } else { 258 $linkName = ""; 259 } 260 return array( 261 PluginUtility::STATE => $state, 262 PluginUtility::ATTRIBUTES => $openingAttributes, 263 PluginUtility::PAYLOAD => $linkName, 264 PluginUtility::CONTEXT => $openingTag->getContext() 265 ); 266 } 267 return true; 268 269 270 } 271 272 /** 273 * Render the output 274 * @param string $format 275 * @param Doku_Renderer $renderer 276 * @param array $data - what the function handle() return'ed 277 * @return boolean - rendered correctly? (however, returned value is not used at the moment) 278 * @see DokuWiki_Syntax_Plugin::render() 279 * 280 * 281 */ 282 function render($format, Doku_Renderer $renderer, $data) 283 { 284 // The data 285 switch ($format) { 286 case 'xhtml': 287 288 /** @var Doku_Renderer_xhtml $renderer */ 289 /** 290 * Cache problem may occurs while releasing 291 */ 292 if (isset($data[PluginUtility::ATTRIBUTES])) { 293 $callStackAttributes = $data[PluginUtility::ATTRIBUTES]; 294 } else { 295 $callStackAttributes = $data; 296 } 297 298 PluginUtility::getSnippetManager()->attachCssSnippetForBar(self::TAG); 299 300 $state = $data[PluginUtility::STATE]; 301 switch ($state) { 302 case DOKU_LEXER_ENTER: 303 $tagAttributes = TagAttributes::createFromCallStackArray($callStackAttributes, self::TAG); 304 $ref = $tagAttributes->getValueAndRemove(LinkUtility::ATTRIBUTE_REF); 305 $link = new LinkUtility($ref, $tagAttributes); 306 307 /** 308 * Extra styling 309 */ 310 $parentTag = $data[PluginUtility::CONTEXT]; 311 switch ($parentTag) { 312 /** 313 * Button link 314 */ 315 case syntax_plugin_combo_button::TAG: 316 $tagAttributes->addHtmlAttributeValue("role", "button"); 317 syntax_plugin_combo_button::processButtonAttributesToHtmlAttributes($tagAttributes); 318 $htmlLink = $link->renderOpenTag($renderer); 319 break; 320 case syntax_plugin_combo_badge::TAG: 321 case syntax_plugin_combo_cite::TAG: 322 case syntax_plugin_combo_contentlistitem::DOKU_TAG: 323 case syntax_plugin_combo_preformatted::TAG: 324 $htmlLink = $link->renderOpenTag($renderer); 325 break; 326 case syntax_plugin_combo_dropdown::TAG: 327 $tagAttributes->addClassName("dropdown-item"); 328 $htmlLink = $link->renderOpenTag($renderer); 329 break; 330 case syntax_plugin_combo_navbarcollapse::COMPONENT: 331 $tagAttributes->addClassName("navbar-link"); 332 $htmlLink = '<div class="navbar-nav">' . $link->renderOpenTag($renderer); 333 break; 334 case syntax_plugin_combo_navbargroup::COMPONENT: 335 $tagAttributes->addClassName("nav-link"); 336 $htmlLink = '<li class="nav-item">' . $link->renderOpenTag($renderer); 337 break; 338 default: 339 $htmlLink = $link->renderOpenTag($renderer); 340 341 } 342 343 344 /** 345 * Add it to the rendering 346 */ 347 $renderer->doc .= $htmlLink; 348 break; 349 case DOKU_LEXER_UNMATCHED: 350 $renderer->doc .= PluginUtility::renderUnmatched($data); 351 break; 352 case DOKU_LEXER_EXIT: 353 354 // if there is no link name defined, we get the name as ref in the payload 355 // otherwise null string 356 $renderer->doc .= $data[PluginUtility::PAYLOAD]; 357 358 // Close the link 359 $renderer->doc .= "</a>"; 360 361 // Close the html wrapper element 362 $context = $data[PluginUtility::CONTEXT]; 363 switch ($context) { 364 case syntax_plugin_combo_navbarcollapse::COMPONENT: 365 $renderer->doc .= '</div>'; 366 break; 367 case syntax_plugin_combo_navbargroup::COMPONENT: 368 $renderer->doc .= '</li>'; 369 break; 370 } 371 372 373 } 374 375 376 return true; 377 378 case 'metadata': 379 380 $state = $data[PluginUtility::STATE]; 381 if ($state == DOKU_LEXER_ENTER) { 382 /** 383 * Keep track of the backlinks ie meta['relation']['references'] 384 * @var Doku_Renderer_metadata $renderer 385 */ 386 if (isset($data[PluginUtility::ATTRIBUTES])) { 387 $tagAttributes = $data[PluginUtility::ATTRIBUTES]; 388 } else { 389 $tagAttributes = $data; 390 } 391 $ref = $tagAttributes[LinkUtility::ATTRIBUTE_REF]; 392 393 $link = new LinkUtility($ref); 394 $name = $tagAttributes[LinkUtility::ATTRIBUTE_NAME]; 395 if ($name != null) { 396 $link->setName($name); 397 } 398 $link->handleMetadata($renderer); 399 400 return true; 401 } 402 break; 403 404 case renderer_plugin_combo_analytics::RENDERER_FORMAT: 405 406 $state = $data[PluginUtility::STATE]; 407 if ($state == DOKU_LEXER_ENTER) { 408 /** 409 * 410 * @var renderer_plugin_combo_analytics $renderer 411 */ 412 $tagAttributes = $data[PluginUtility::ATTRIBUTES]; 413 $ref = $tagAttributes[LinkUtility::ATTRIBUTE_REF]; 414 $link = new LinkUtility($ref); 415 $link->processLinkStats($renderer->stats); 416 break; 417 } 418 419 } 420 // unsupported $mode 421 return false; 422 } 423 424 425} 426 427