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/HtmlUtility.php"); 8 9use ComboStrap\Analytics; 10use ComboStrap\LinkUtility; 11use ComboStrap\PluginUtility; 12use ComboStrap\Tag; 13 14if (!defined('DOKU_INC')) die(); 15 16/** 17 * 18 * A link pattern to take over the link of Dokuwiki 19 * and transform it as a bootstrap link 20 * 21 * The handle of the move of link is to be found in the 22 * admin action {@link action_plugin_combo_linkmove} 23 * 24 */ 25class syntax_plugin_combo_link extends DokuWiki_Syntax_Plugin 26{ 27 const TAG = 'link'; 28 const COMPONENT = 'combo_link'; 29 30 /** 31 * Disable the link 32 */ 33 const CONF_DISABLE_LINK = "disableLink"; 34 35 /** 36 * The link Tag 37 */ 38 const LINK_TAG = "linkTag"; 39 40 /** 41 * Do the link component allows to be spawn on multilines 42 */ 43 const CONF_ENABLE_MULTI_LINES_LINK = "enableMultiLinesLink"; 44 45 46 /** 47 * Syntax Type. 48 * 49 * Needs to return one of the mode types defined in $PARSER_MODES in parser.php 50 * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types 51 */ 52 function getType() 53 { 54 return 'substition'; 55 } 56 57 /** 58 * How Dokuwiki will add P element 59 * 60 * * 'normal' - The plugin can be used inside paragraphs 61 * * 'block' - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs 62 * * 'stack' - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs 63 * 64 * @see DokuWiki_Syntax_Plugin::getPType() 65 */ 66 function getPType() 67 { 68 return 'normal'; 69 } 70 71 /** 72 * @return array 73 * Allow which kind of plugin inside 74 * 75 * No one of array('container', 'baseonly', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs') 76 * because we manage self the content and we call self the parser 77 */ 78 function getAllowedTypes() 79 { 80 return array('substition', 'formatting', 'disabled'); 81 } 82 83 public function accepts($mode) 84 { 85 /** 86 * To avoid that the description if it contains a link 87 * will be taken by the links mode 88 * 89 * For instance, [[https://hallo|https://hallo]] will send https://hallo 90 * to the external link mode 91 */ 92 $linkModes = [ 93 "externallink", 94 "locallink", 95 "internallink", 96 "interwikilink", 97 "emaillink", 98 //"emphasis_open", // italic use // and therefore take over a link as description which is not handy when copying a tweet 99 //"emphasis_close", 100 //"acrnonym" 101 ]; 102 if (in_array($mode, $linkModes)) { 103 return false; 104 } else { 105 return true; 106 } 107 } 108 109 110 /** 111 * @see Doku_Parser_Mode::getSort() 112 * The mode with the lowest sort number will win out 113 */ 114 function getSort() 115 { 116 return 100; 117 } 118 119 120 function connectTo($mode) 121 { 122 123 if (!$this->getConf(self::CONF_DISABLE_LINK, false)) { 124 $pattern = LinkUtility::ENTRY_PATTERN_SINGLE_LINE; 125 if ($this->getConf(self::CONF_ENABLE_MULTI_LINES_LINK,false)){ 126 $pattern = LinkUtility::ENTRY_PATTERN_MULTI_LINE; 127 } 128 $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeForComponent($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::getModeForComponent($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 * Because we use the specialPattern, there is only one state ie DOKU_LEXER_SPECIAL 157 */ 158 switch ($state) { 159 case DOKU_LEXER_ENTER: 160 $attributes = LinkUtility::parse($match); 161 $tag = new Tag(self::TAG, $attributes, $state, $handler); 162 $parent = $tag->getParent(); 163 $parentName = ""; 164 if ($parent != null) { 165 $parentName = $parent->getName(); 166 switch ($parentName) { 167 case syntax_plugin_combo_button::TAG: 168 $attributes = PluginUtility::mergeAttributes($attributes, $parent->getAttributes()); 169 $firstContainingBlock = $parent->getParent(); 170 break; 171 case syntax_plugin_combo_column::TAG: 172 // A col is in a row 173 $firstContainingBlock = $parent->getParent(); 174 break; 175 case "section": 176 // When editing, there is a section 177 $firstContainingBlock = $parent->getParent(); 178 break; 179 default: 180 $firstContainingBlock = $parent; 181 } 182 if ($firstContainingBlock != null) { 183 if ($firstContainingBlock->getAttribute("clickable")) { 184 PluginUtility::addClass2Attributes("stretched-link", $attributes); 185 $firstContainingBlock->addClass("position-relative"); 186 $firstContainingBlock->unsetAttribute("clickable"); 187 } 188 } 189 } 190 191 $link = new LinkUtility($attributes[LinkUtility::ATTRIBUTE_REF]); 192 $linkTag = $link->getHtmlTag(); 193 return array( 194 PluginUtility::STATE => $state, 195 PluginUtility::ATTRIBUTES => $attributes, 196 PluginUtility::CONTEXT => $parentName, 197 self::LINK_TAG => $linkTag 198 ); 199 case DOKU_LEXER_UNMATCHED: 200 201 $data = PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler); 202 /** 203 * Delete the separator `|` between the ref and the description if any 204 */ 205 $tag = new Tag(self::TAG, array(), $state, $handler); 206 $parent = $tag->getParent(); 207 if ($parent->getName() == self::TAG) { 208 if (strpos($match, '|') === 0) { 209 $data[PluginUtility::PAYLOAD] = substr($match, 1); 210 } 211 } 212 return $data; 213 214 case DOKU_LEXER_EXIT: 215 $tag = new Tag(self::TAG, array(), $state, $handler); 216 $openingTag = $tag->getOpeningTag(); 217 $openingAttributes = $openingTag->getAttributes(); 218 $linkTag = $openingTag->getData()[self::LINK_TAG]; 219 220 if ($openingTag->getActualPosition() == $tag->getActualPosition() - 1) { 221 // There is no name 222 $link = new LinkUtility($openingAttributes[LinkUtility::ATTRIBUTE_REF]); 223 $linkName = $link->getName(); 224 } else { 225 $linkName = ""; 226 } 227 return array( 228 PluginUtility::STATE => $state, 229 PluginUtility::ATTRIBUTES => $openingAttributes, 230 PluginUtility::PAYLOAD => $linkName, 231 PluginUtility::CONTEXT => $openingTag->getContext(), 232 self::LINK_TAG => $linkTag 233 ); 234 } 235 return true; 236 237 238 } 239 240 /** 241 * Render the output 242 * @param string $format 243 * @param Doku_Renderer $renderer 244 * @param array $data - what the function handle() return'ed 245 * @return boolean - rendered correctly? (however, returned value is not used at the moment) 246 * @see DokuWiki_Syntax_Plugin::render() 247 * 248 * 249 */ 250 function render($format, Doku_Renderer $renderer, $data) 251 { 252 // The data 253 switch ($format) { 254 case 'xhtml': 255 256 /** @var Doku_Renderer_xhtml $renderer */ 257 /** 258 * Cache problem may occurs while releasing 259 */ 260 if (isset($data[PluginUtility::ATTRIBUTES])) { 261 $attributes = $data[PluginUtility::ATTRIBUTES]; 262 } else { 263 $attributes = $data; 264 } 265 266 PluginUtility::getSnippetManager()->attachCssSnippetForBar(self::TAG); 267 268 269 $state = $data[PluginUtility::STATE]; 270 $payload = $data[PluginUtility::PAYLOAD]; 271 switch ($state) { 272 case DOKU_LEXER_ENTER: 273 $ref = $attributes[LinkUtility::ATTRIBUTE_REF]; 274 unset($attributes[LinkUtility::ATTRIBUTE_REF]); 275 $name = $attributes[LinkUtility::ATTRIBUTE_NAME]; 276 unset($attributes[LinkUtility::ATTRIBUTE_NAME]); 277 $link = new LinkUtility($ref); 278 if ($name != null) { 279 $link->setName($name); 280 } 281 $link->setAttributes($attributes); 282 283 284 /** 285 * Extra styling 286 */ 287 $parentTag = $data[PluginUtility::CONTEXT]; 288 switch ($parentTag) { 289 /** 290 * Button link 291 */ 292 case syntax_plugin_combo_button::TAG: 293 $attributes["role"] = "button"; 294 syntax_plugin_combo_button::processButtonAttributesToHtmlAttributes($attributes); 295 $htmlLink = $link->renderOpenTag($renderer); 296 break; 297 case syntax_plugin_combo_badge::TAG: 298 case syntax_plugin_combo_cite::TAG: 299 case syntax_plugin_combo_listitem::TAG: 300 case syntax_plugin_combo_preformatted::TAG: 301 $htmlLink = $link->renderOpenTag($renderer); 302 break; 303 case syntax_plugin_combo_dropdown::TAG: 304 PluginUtility::addClass2Attributes("dropdown-item", $attributes); 305 $htmlLink = $link->renderOpenTag($renderer); 306 break; 307 case syntax_plugin_combo_navbarcollapse::COMPONENT: 308 PluginUtility::addClass2Attributes("navbar-link", $attributes); 309 $htmlLink = '<div class="navbar-nav">' . $link->renderOpenTag($renderer); 310 break; 311 case syntax_plugin_combo_navbargroup::COMPONENT: 312 PluginUtility::addClass2Attributes("nav-link", $attributes); 313 $htmlLink = '<li class="nav-item">' . $link->renderOpenTag($renderer); 314 break; 315 default: 316 317 $htmlLink = $link->renderOpenTag($renderer); 318 319 } 320 321 322 /** 323 * Add it to the rendering 324 */ 325 $renderer->doc .= $htmlLink; 326 break; 327 case DOKU_LEXER_UNMATCHED: 328 $renderer->doc .= PluginUtility::renderUnmatched($data); 329 break; 330 case DOKU_LEXER_EXIT: 331 332 // if there is no link name defined, we get the name as ref in the payload 333 // otherwise null string 334 $renderer->doc .= $payload; 335 336 // Close the link 337 $linkTag = $data[self::LINK_TAG]; 338 $renderer->doc .= "</$linkTag>"; 339 340 // Close the html wrapper element 341 $context = $data[PluginUtility::CONTEXT]; 342 switch ($context) { 343 case syntax_plugin_combo_navbarcollapse::COMPONENT: 344 $renderer->doc .= '</div>'; 345 break; 346 case syntax_plugin_combo_navbargroup::COMPONENT: 347 $renderer->doc .= '</li>'; 348 break; 349 } 350 351 352 } 353 354 355 return true; 356 break; 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 $attributes = $data[PluginUtility::ATTRIBUTES]; 368 } else { 369 $attributes = $data; 370 } 371 $ref = $attributes[LinkUtility::ATTRIBUTE_REF]; 372 373 $link = new LinkUtility($ref); 374 $name = $attributes[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 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 $attributes = $data[PluginUtility::ATTRIBUTES]; 393 $ref = $attributes[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