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