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