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", // double slash can not be used inside to preserve the possibility to write an URL in the description 99 //"emphasis_open", // italic use // and therefore take over a link as description which is not handy when copying a tweet 100 //"emphasis_close", 101 //"acrnonym" 102 ]; 103 if (in_array($mode, $linkModes)) { 104 return false; 105 } else { 106 return true; 107 } 108 } 109 110 111 /** 112 * @see Doku_Parser_Mode::getSort() 113 * The mode with the lowest sort number will win out 114 */ 115 function getSort() 116 { 117 return 100; 118 } 119 120 121 function connectTo($mode) 122 { 123 124 if (!$this->getConf(self::CONF_DISABLE_LINK, false)) { 125 $pattern = LinkUtility::ENTRY_PATTERN_SINGLE_LINE; 126 if ($this->getConf(self::CONF_ENABLE_MULTI_LINES_LINK,false)){ 127 $pattern = LinkUtility::ENTRY_PATTERN_MULTI_LINE; 128 } 129 $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeForComponent($this->getPluginComponent())); 130 } 131 132 } 133 134 public function postConnect() 135 { 136 if (!$this->getConf(self::CONF_DISABLE_LINK, false)) { 137 $this->Lexer->addExitPattern(LinkUtility::EXIT_PATTERN, PluginUtility::getModeForComponent($this->getPluginComponent())); 138 } 139 } 140 141 142 /** 143 * The handler for an internal link 144 * based on `internallink` in {@link Doku_Handler} 145 * The handler call the good renderer in {@link Doku_Renderer_xhtml} with 146 * the parameters (ie for instance internallink) 147 * @param string $match 148 * @param int $state 149 * @param int $pos 150 * @param Doku_Handler $handler 151 * @return array|bool 152 */ 153 function handle($match, $state, $pos, Doku_Handler $handler) 154 { 155 156 /** 157 * Because we use the specialPattern, there is only one state ie DOKU_LEXER_SPECIAL 158 */ 159 switch ($state) { 160 case DOKU_LEXER_ENTER: 161 $attributes = LinkUtility::parse($match); 162 $tag = new Tag(self::TAG, $attributes, $state, $handler); 163 $parent = $tag->getParent(); 164 $parentName = ""; 165 if ($parent != null) { 166 $parentName = $parent->getName(); 167 switch ($parentName) { 168 case syntax_plugin_combo_button::TAG: 169 $attributes = PluginUtility::mergeAttributes($attributes, $parent->getAttributes()); 170 $firstContainingBlock = $parent->getParent(); 171 break; 172 case syntax_plugin_combo_column::TAG: 173 // A col is in a row 174 $firstContainingBlock = $parent->getParent(); 175 break; 176 case "section": 177 // When editing, there is a section 178 $firstContainingBlock = $parent->getParent(); 179 break; 180 default: 181 $firstContainingBlock = $parent; 182 } 183 if ($firstContainingBlock != null) { 184 if ($firstContainingBlock->getAttribute("clickable")) { 185 PluginUtility::addClass2Attributes("stretched-link", $attributes); 186 $firstContainingBlock->addClass("position-relative"); 187 $firstContainingBlock->unsetAttribute("clickable"); 188 } 189 } 190 } 191 192 $link = new LinkUtility($attributes[LinkUtility::ATTRIBUTE_REF]); 193 $linkTag = $link->getHtmlTag(); 194 return array( 195 PluginUtility::STATE => $state, 196 PluginUtility::ATTRIBUTES => $attributes, 197 PluginUtility::CONTEXT => $parentName, 198 self::LINK_TAG => $linkTag 199 ); 200 case DOKU_LEXER_UNMATCHED: 201 202 $data = PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler); 203 /** 204 * Delete the separator `|` between the ref and the description if any 205 */ 206 $tag = new Tag(self::TAG, array(), $state, $handler); 207 $parent = $tag->getParent(); 208 if ($parent->getName() == self::TAG) { 209 if (strpos($match, '|') === 0) { 210 $data[PluginUtility::PAYLOAD] = substr($match, 1); 211 } 212 } 213 return $data; 214 215 case DOKU_LEXER_EXIT: 216 $tag = new Tag(self::TAG, array(), $state, $handler); 217 $openingTag = $tag->getOpeningTag(); 218 $openingAttributes = $openingTag->getAttributes(); 219 $linkTag = $openingTag->getData()[self::LINK_TAG]; 220 221 if ($openingTag->getActualPosition() == $tag->getActualPosition() - 1) { 222 // There is no name 223 $link = new LinkUtility($openingAttributes[LinkUtility::ATTRIBUTE_REF]); 224 $linkName = $link->getName(); 225 } else { 226 $linkName = ""; 227 } 228 return array( 229 PluginUtility::STATE => $state, 230 PluginUtility::ATTRIBUTES => $openingAttributes, 231 PluginUtility::PAYLOAD => $linkName, 232 PluginUtility::CONTEXT => $openingTag->getContext(), 233 self::LINK_TAG => $linkTag 234 ); 235 } 236 return true; 237 238 239 } 240 241 /** 242 * Render the output 243 * @param string $format 244 * @param Doku_Renderer $renderer 245 * @param array $data - what the function handle() return'ed 246 * @return boolean - rendered correctly? (however, returned value is not used at the moment) 247 * @see DokuWiki_Syntax_Plugin::render() 248 * 249 * 250 */ 251 function render($format, Doku_Renderer $renderer, $data) 252 { 253 // The data 254 switch ($format) { 255 case 'xhtml': 256 257 /** @var Doku_Renderer_xhtml $renderer */ 258 /** 259 * Cache problem may occurs while releasing 260 */ 261 if (isset($data[PluginUtility::ATTRIBUTES])) { 262 $attributes = $data[PluginUtility::ATTRIBUTES]; 263 } else { 264 $attributes = $data; 265 } 266 267 PluginUtility::getSnippetManager()->attachCssSnippetForBar(self::TAG); 268 269 270 $state = $data[PluginUtility::STATE]; 271 $payload = $data[PluginUtility::PAYLOAD]; 272 switch ($state) { 273 case DOKU_LEXER_ENTER: 274 $ref = $attributes[LinkUtility::ATTRIBUTE_REF]; 275 unset($attributes[LinkUtility::ATTRIBUTE_REF]); 276 $name = $attributes[LinkUtility::ATTRIBUTE_NAME]; 277 unset($attributes[LinkUtility::ATTRIBUTE_NAME]); 278 $link = new LinkUtility($ref); 279 if ($name != null) { 280 $link->setName($name); 281 } 282 $link->setAttributes($attributes); 283 284 285 /** 286 * Extra styling 287 */ 288 $parentTag = $data[PluginUtility::CONTEXT]; 289 switch ($parentTag) { 290 /** 291 * Button link 292 */ 293 case syntax_plugin_combo_button::TAG: 294 $attributes["role"] = "button"; 295 syntax_plugin_combo_button::processButtonAttributesToHtmlAttributes($attributes); 296 $htmlLink = $link->renderOpenTag($renderer); 297 break; 298 case syntax_plugin_combo_badge::TAG: 299 case syntax_plugin_combo_cite::TAG: 300 case syntax_plugin_combo_listitem::TAG: 301 case syntax_plugin_combo_preformatted::TAG: 302 $htmlLink = $link->renderOpenTag($renderer); 303 break; 304 case syntax_plugin_combo_dropdown::TAG: 305 PluginUtility::addClass2Attributes("dropdown-item", $attributes); 306 $htmlLink = $link->renderOpenTag($renderer); 307 break; 308 case syntax_plugin_combo_navbarcollapse::COMPONENT: 309 PluginUtility::addClass2Attributes("navbar-link", $attributes); 310 $htmlLink = '<div class="navbar-nav">' . $link->renderOpenTag($renderer); 311 break; 312 case syntax_plugin_combo_navbargroup::COMPONENT: 313 PluginUtility::addClass2Attributes("nav-link", $attributes); 314 $htmlLink = '<li class="nav-item">' . $link->renderOpenTag($renderer); 315 break; 316 default: 317 318 $htmlLink = $link->renderOpenTag($renderer); 319 320 } 321 322 323 /** 324 * Add it to the rendering 325 */ 326 $renderer->doc .= $htmlLink; 327 break; 328 case DOKU_LEXER_UNMATCHED: 329 $renderer->doc .= PluginUtility::renderUnmatched($data); 330 break; 331 case DOKU_LEXER_EXIT: 332 333 // if there is no link name defined, we get the name as ref in the payload 334 // otherwise null string 335 $renderer->doc .= $payload; 336 337 // Close the link 338 $linkTag = $data[self::LINK_TAG]; 339 $renderer->doc .= "</$linkTag>"; 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 break; 358 359 case 'metadata': 360 361 $state = $data[PluginUtility::STATE]; 362 if ($state == DOKU_LEXER_ENTER) { 363 /** 364 * Keep track of the backlinks ie meta['relation']['references'] 365 * @var Doku_Renderer_metadata $renderer 366 */ 367 if (isset($data[PluginUtility::ATTRIBUTES])) { 368 $attributes = $data[PluginUtility::ATTRIBUTES]; 369 } else { 370 $attributes = $data; 371 } 372 $ref = $attributes[LinkUtility::ATTRIBUTE_REF]; 373 374 $link = new LinkUtility($ref); 375 $name = $attributes[LinkUtility::ATTRIBUTE_NAME]; 376 if ($name != null) { 377 $link->setName($name); 378 } 379 $link->handleMetadata($renderer); 380 381 return true; 382 } 383 break; 384 385 case Analytics::RENDERER_FORMAT: 386 387 $state = $data[PluginUtility::STATE]; 388 if ($state == DOKU_LEXER_ENTER) { 389 /** 390 * 391 * @var renderer_plugin_combo_analytics $renderer 392 */ 393 $attributes = $data[PluginUtility::ATTRIBUTES]; 394 $ref = $attributes[LinkUtility::ATTRIBUTE_REF]; 395 $link = new LinkUtility($ref); 396 $link->processLinkStats($renderer->stats); 397 break; 398 } 399 400 } 401 // unsupported $mode 402 return false; 403 } 404 405 406} 407 408