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