1<?php 2 3 4require_once(__DIR__ . "/../ComboStrap/PluginUtility.php"); 5 6use ComboStrap\ArrayUtility; 7use ComboStrap\ButtonTag; 8use ComboStrap\Call; 9use ComboStrap\CallStack; 10use ComboStrap\DropDownTag; 11use ComboStrap\ExceptionBadArgument; 12use ComboStrap\ExceptionBadSyntax; 13use ComboStrap\ExceptionCompile; 14use ComboStrap\ExceptionNotFound; 15use ComboStrap\FileSystems; 16use ComboStrap\LinkMarkup; 17use ComboStrap\LogUtility; 18use ComboStrap\MarkupRef; 19use ComboStrap\MarkupPath; 20use ComboStrap\PluginUtility; 21use ComboStrap\TagAttributes; 22use ComboStrap\ThirdPartyPlugins; 23use ComboStrap\Web\UrlEndpoint; 24 25if (!defined('DOKU_INC')) die(); 26 27/** 28 * 29 * A link pattern to take over the link of Dokuwiki 30 * and transform it as a bootstrap link 31 * 32 * The handle of the move of link is to be found in the 33 * admin action {@link action_plugin_combo_linkmove} 34 * 35 * popular [[ wiki ]] syntax for linking notes 36 * and makes it easy to build personal wikis, 37 * team knowledge bases, 38 * or something like a Second Brain or a Zettelkasten. 39 * 40 */ 41class syntax_plugin_combo_link extends DokuWiki_Syntax_Plugin 42{ 43 const TAG = 'link'; 44 const COMPONENT = 'combo_link'; 45 46 /** 47 * Disable the link component 48 */ 49 const CONF_DISABLE_LINK = "disableLink"; 50 51 /** 52 * The link Tag 53 * a or p 54 */ 55 const LINK_TAG = "linkTag"; 56 57 /** 58 * Do the link component allows to be spawn on multilines 59 */ 60 const CLICKABLE_ATTRIBUTE = "clickable"; 61 public const ATTRIBUTE_LABEL = 'label'; 62 /** 63 * The key of the array for the handle cache 64 */ 65 public const MARKUP_REF_ATTRIBUTE = 'ref'; 66 67 public const ATTRIBUTE_IMAGE_IN_LABEL = 'image-in-label'; 68 69 /** 70 * A link may have a title or not 71 * ie 72 * [[path:page]] 73 * [[path:page|title]] 74 * are valid 75 * 76 * Get the content until one of this character is found: 77 * * | 78 * * or ]] 79 * * or \n (No line break allowed, too much difficult to debug) 80 * * and not [ (for two links on the same line) 81 */ 82 public const ENTRY_PATTERN_SINGLE_LINE = "\[\[[^\|\]]*(?=[^\n\[]*\]\])"; 83 public const EXIT_PATTERN = "\]\]"; 84 85 86 /** 87 * Dokuwiki Link pattern ter info 88 * Found in {@link \dokuwiki\Parsing\ParserMode\Internallink} 89 */ 90 const SPECIAL_PATTERN = "\[\[.*?\]\](?!\])"; 91 92 /** 93 * The link title attribute (ie popup) 94 */ 95 const TITLE_ATTRIBUTE = "title"; 96 const STRETCHED_LINK = "stretched-link"; 97 const CANONICAL = "link"; 98 99 100 /** 101 * Parse the match of a syntax {@link DokuWiki_Syntax_Plugin} handle function 102 * @param $match 103 * @return string[] - an array with the attributes constant `ATTRIBUTE_xxxx` as key 104 * 105 * Code adapted from {@link Doku_Handler::internallink()} 106 */ 107 public static function parse($match): array 108 { 109 110 // Strip the opening and closing markup 111 $linkString = preg_replace(array('/^\[\[/', '/]]$/u'), '', $match); 112 113 // Split title from URL 114 $linkArray = explode('|', $linkString, 2); 115 116 // Id 117 $attributes[self::MARKUP_REF_ATTRIBUTE] = trim($linkArray[0]); 118 119 120 // Text or image 121 if (!isset($linkArray[1])) { 122 $attributes[self::ATTRIBUTE_LABEL] = null; 123 } else { 124 // An image in the title 125 if (preg_match('/^{{[^}]+}}$/', $linkArray[1])) { 126 // If the title is an image, convert it to an array containing the image details 127 $attributes[self::ATTRIBUTE_IMAGE_IN_LABEL] = Doku_Handler_Parse_Media($linkArray[1]); 128 } else { 129 $attributes[self::ATTRIBUTE_LABEL] = $linkArray[1]; 130 } 131 } 132 133 return $attributes; 134 135 } 136 137 138 /** 139 * Syntax Type. 140 * 141 * Needs to return one of the mode types defined in $PARSER_MODES in parser.php 142 * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types 143 */ 144 function getType() 145 { 146 return 'substition'; 147 } 148 149 /** 150 * How Dokuwiki will add P element 151 * 152 * * 'normal' - The plugin can be used inside paragraphs 153 * * 'block' - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs 154 * * 'stack' - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs 155 * 156 * @see DokuWiki_Syntax_Plugin::getPType() 157 */ 158 function getPType() 159 { 160 return 'normal'; 161 } 162 163 /** 164 * @return array 165 * Allow which kind of plugin inside 166 * 167 * No one of array('container', 'baseonly', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs') 168 * because we manage self the content and we call self the parser 169 */ 170 function getAllowedTypes(): array 171 { 172 return array('substition', 'formatting', 'disabled'); 173 } 174 175 /** 176 * @param string $mode 177 * @return bool 178 * Accepts inside 179 */ 180 public function accepts($mode): bool 181 { 182 /** 183 * To avoid that the description if it contains a link 184 * will be taken by the links mode 185 * 186 * For instance, [[https://hallo|https://hallo]] will send https://hallo 187 * to the external link mode 188 */ 189 $linkModes = [ 190 "externallink", 191 "locallink", 192 "internallink", 193 "interwikilink", 194 "emaillink", 195 "emphasis", // double slash can not be used inside to preserve the possibility to write an URL in the description 196 //"emphasis_open", // italic use // and therefore take over a link as description which is not handy when copying a tweet 197 //"emphasis_close", 198 //"acrnonym" 199 ]; 200 if (in_array($mode, $linkModes)) { 201 return false; 202 } else { 203 return true; 204 } 205 } 206 207 208 /** 209 * @see Doku_Parser_Mode::getSort() 210 * The mode with the lowest sort number will win out 211 */ 212 function getSort() 213 { 214 /** 215 * It should be less than the number 216 * at {@link \dokuwiki\Parsing\ParserMode\Internallink::getSort} 217 * and the like 218 * 219 * For whatever reason, the number below should be less than 100, 220 * otherwise on windows with DokuWiki Stick, the link syntax may be not taken 221 * into account 222 */ 223 return 99; 224 } 225 226 227 function connectTo($mode) 228 { 229 230 if (!$this->getConf(self::CONF_DISABLE_LINK, false) 231 && 232 $mode !== PluginUtility::getModeFromPluginName(ThirdPartyPlugins::IMAGE_MAPPING_NAME) 233 ) { 234 235 $pattern = self::ENTRY_PATTERN_SINGLE_LINE; 236 $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeFromTag($this->getPluginComponent())); 237 238 } 239 240 } 241 242 public function postConnect() 243 { 244 if (!$this->getConf(self::CONF_DISABLE_LINK, false)) { 245 $this->Lexer->addExitPattern(self::EXIT_PATTERN, PluginUtility::getModeFromTag($this->getPluginComponent())); 246 } 247 } 248 249 250 /** 251 * The handler for an internal link 252 * based on `internallink` in {@link Doku_Handler} 253 * The handler call the good renderer in {@link Doku_Renderer_xhtml} with 254 * the parameters (ie for instance internallink) 255 * @param string $match 256 * @param int $state 257 * @param int $pos 258 * @param Doku_Handler $handler 259 * @return array|bool 260 */ 261 function handle($match, $state, $pos, Doku_Handler $handler) 262 { 263 264 switch ($state) { 265 case DOKU_LEXER_ENTER: 266 $parsedArray = self::parse($match); 267 $htmlAttributes = TagAttributes::createEmpty(self::TAG); 268 269 /** 270 * The markup ref needs to be passed to the 271 * instructions stack (because we support link with a variable as markup ref 272 * via a {@link syntax_plugin_combo_fragment} in a {@link syntax_plugin_combo_iterator} 273 * 274 * The variable is replaced in the {@link syntax_plugin_combo_link::render()} 275 * at runtime while rendering 276 */ 277 $markupRef = $parsedArray[self::MARKUP_REF_ATTRIBUTE]; 278 if ($markupRef !== null) { 279 $htmlAttributes->addComponentAttributeValue(self::MARKUP_REF_ATTRIBUTE, $markupRef); 280 } 281 282 283 /** 284 * Extra HTML attribute 285 */ 286 $callStack = CallStack::createFromHandler($handler); 287 $parent = $callStack->moveToParent(); 288 $parentName = ""; 289 if ($parent !== false) { 290 291 /** 292 * Button Link 293 * Getting the attributes 294 */ 295 $parentName = $parent->getTagName(); 296 if ($parentName == ButtonTag::MARKUP_LONG) { 297 $htmlAttributes->mergeWithCallStackArray($parent->getAttributes()); 298 } 299 300 /** 301 * Searching Clickable parent 302 */ 303 $maxLevel = 3; 304 $level = 0; 305 while ( 306 $parent != false && 307 !$parent->hasAttribute(self::CLICKABLE_ATTRIBUTE) && 308 $level < $maxLevel 309 ) { 310 $parent = $callStack->moveToParent(); 311 $level++; 312 } 313 if ($parent !== false) { 314 if ($parent->getAttribute(self::CLICKABLE_ATTRIBUTE)) { 315 $htmlAttributes->addClassName(self::STRETCHED_LINK); 316 $parent->addClassName("position-relative"); 317 $parent->removeAttribute(self::CLICKABLE_ATTRIBUTE); 318 } 319 } 320 321 } 322 $returnedArray[PluginUtility::STATE] = $state; 323 $returnedArray[PluginUtility::ATTRIBUTES] = $htmlAttributes->toCallStackArray(); 324 $returnedArray[PluginUtility::CONTEXT] = $parentName; 325 return $returnedArray; 326 327 case DOKU_LEXER_UNMATCHED: 328 329 $data = PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler); 330 /** 331 * Delete the separator `|` between the ref and the description if any 332 */ 333 $tag = CallStack::createFromHandler($handler); 334 $parent = $tag->moveToParent(); 335 if ($parent->getTagName() == self::TAG) { 336 if (strpos($match, '|') === 0) { 337 $data[PluginUtility::PAYLOAD] = substr($match, 1); 338 } 339 } 340 return $data; 341 342 case DOKU_LEXER_EXIT: 343 344 $callStack = CallStack::createFromHandler($handler); 345 $openingTag = $callStack->moveToPreviousCorrespondingOpeningCall(); 346 347 $openingAttributes = $openingTag->getAttributes(); 348 $openingPosition = $openingTag->getKey(); 349 350 $callStack->moveToEnd(); 351 $previousCall = $callStack->previous(); 352 $previousCallPosition = $previousCall->getKey(); 353 $previousCallContent = $previousCall->getCapturedContent(); 354 355 /** 356 * Link label 357 * is set if there is no content 358 * between enter and exit node 359 */ 360 $linkLabel = ""; 361 if ( 362 $openingPosition == $previousCallPosition // ie [[id]] 363 || 364 ($openingPosition == $previousCallPosition - 1 && $previousCallContent == "|") // ie [[id|]] 365 ) { 366 // There is no name 367 $markupRef = $openingTag->getAttribute(self::MARKUP_REF_ATTRIBUTE); 368 if ($markupRef !== null) { 369 try { 370 $linkLabel = LinkMarkup::createFromRef($markupRef) 371 ->getDefaultLabel(); 372 } catch (ExceptionCompile $e) { 373 LogUtility::error("No default Label can be defined. Error while parsing the markup ref ($markupRef). Error: {$e->getMessage()}", self::CANONICAL, $e); 374 } 375 376 } 377 } 378 return array( 379 PluginUtility::STATE => $state, 380 PluginUtility::ATTRIBUTES => $openingAttributes, 381 PluginUtility::PAYLOAD => $linkLabel, 382 PluginUtility::CONTEXT => $openingTag->getContext() 383 ); 384 } 385 return true; 386 387 388 } 389 390 /** 391 * Render the output 392 * @param string $format 393 * @param Doku_Renderer $renderer 394 * @param array $data - what the function handle() return'ed 395 * @return boolean - rendered correctly? (however, returned value is not used at the moment) 396 * @see DokuWiki_Syntax_Plugin::render() 397 * 398 * 399 */ 400 function render($format, Doku_Renderer $renderer, $data): bool 401 { 402 // The data 403 $state = $data[PluginUtility::STATE]; 404 switch ($format) { 405 case 'xhtml': 406 407 /** @var Doku_Renderer_xhtml $renderer */ 408 /** 409 * Cache problem may occurs while releasing 410 */ 411 if (isset($data[PluginUtility::ATTRIBUTES])) { 412 $callStackAttributes = $data[PluginUtility::ATTRIBUTES]; 413 } else { 414 $callStackAttributes = $data; 415 } 416 417 PluginUtility::getSnippetManager()->attachCssInternalStyleSheet(self::TAG); 418 419 switch ($state) { 420 case DOKU_LEXER_ENTER: 421 $tagAttributes = TagAttributes::createFromCallStackArray($callStackAttributes, self::TAG); 422 423 $markupRef = $tagAttributes->getValueAndRemove(self::MARKUP_REF_ATTRIBUTE); 424 if ($markupRef === null) { 425 $message = "Internal Error: A link reference was not found"; 426 LogUtility::internalError($message); 427 $renderer->doc .= LogUtility::wrapInRedForHtml($message); 428 return false; 429 } 430 try { 431 $markupRef = syntax_plugin_combo_variable::replaceVariablesWithValuesFromContext($markupRef); 432 $markupLink = LinkMarkup::createFromRef($markupRef); 433 $markupAttributes = $markupLink->toAttributes(); 434 } catch (ExceptionCompile $e) { 435 // uncomment to get the original error stack trace in dev 436 // and see where the exception comes from 437 // Don't forget to comment back 438// if (PluginUtility::isDevOrTest()) { 439// throw new ExceptionRuntime("Error on markup ref", self::TAG, 0, $e); 440// } 441 442 /** 443 * Error. Example: unknown inter-wiki ... 444 * We still create the a to be xhtml compliante 445 */ 446 $url = UrlEndpoint::createSupportUrl(); 447 $markupAttributes = TagAttributes::createEmpty() 448 ->addOutputAttributeValue("href", $url->toString()) 449 ->addClassName(LinkMarkup::getHtmlClassNotExist()); 450 $renderer->doc .= $markupAttributes->toHtmlEnterTag("a") . $e->getMessage(); 451 452 LogUtility::warning($e->getMessage(), "link", $e); 453 return false; 454 455 } 456 // markup attributes is leading because it has already output attribute such as href 457 $markupAttributes->mergeWithCallStackArray($tagAttributes->toCallStackArray()); 458 $tagAttributes = $markupAttributes; 459 460 461 /** 462 * Extra styling 463 */ 464 $parentTag = $data[PluginUtility::CONTEXT]; 465 $htmlPrefix = ""; 466 switch ($parentTag) { 467 /** 468 * Button link 469 */ 470 case ButtonTag::MARKUP_LONG: 471 $tagAttributes->addOutputAttributeValue("role", "button"); 472 ButtonTag::processButtonAttributesToHtmlAttributes($tagAttributes); 473 break; 474 case DropDownTag::TAG: 475 $tagAttributes->addClassName("dropdown-item"); 476 break; 477 case syntax_plugin_combo_navbarcollapse::COMPONENT: 478 $tagAttributes->addClassName("navbar-link"); 479 $htmlPrefix = '<div class="navbar-nav">'; 480 break; 481 case syntax_plugin_combo_navbargroup::COMPONENT: 482 $tagAttributes->addClassName("nav-link"); 483 $htmlPrefix = '<li class="nav-item">'; 484 break; 485 default: 486 case syntax_plugin_combo_badge::TAG: 487 case syntax_plugin_combo_cite::TAG: 488 case syntax_plugin_combo_contentlistitem::DOKU_TAG: 489 case syntax_plugin_combo_preformatted::TAG: 490 break; 491 492 } 493 494 /** 495 * Add it to the rendering 496 */ 497 $renderer->doc .= $htmlPrefix . $tagAttributes->toHtmlEnterTag("a"); 498 break; 499 case DOKU_LEXER_UNMATCHED: 500 $renderer->doc .= PluginUtility::renderUnmatched($data); 501 break; 502 case DOKU_LEXER_EXIT: 503 504 // if there is no link name defined, we get the name as ref in the payload 505 // otherwise null string 506 $renderer->doc .= $data[PluginUtility::PAYLOAD] ?? ''; 507 508 // Close the link 509 $renderer->doc .= "</a>"; 510 511 // Close the html wrapper element 512 $context = $data[PluginUtility::CONTEXT]; 513 switch ($context) { 514 case syntax_plugin_combo_navbarcollapse::COMPONENT: 515 $renderer->doc .= '</div>'; 516 break; 517 case syntax_plugin_combo_navbargroup::COMPONENT: 518 $renderer->doc .= '</li>'; 519 break; 520 } 521 522 } 523 return true; 524 case 'metadata': 525 526 /** 527 * @var Doku_Renderer_metadata $renderer 528 */ 529 switch ($state) { 530 case DOKU_LEXER_ENTER: 531 /** 532 * Keep track of the backlinks ie meta['relation']['references'] 533 * @var Doku_Renderer_metadata $renderer 534 */ 535 $tagAttributes = TagAttributes::createFromCallStackArray($data[PluginUtility::ATTRIBUTES]); 536 537 $markupRef = $tagAttributes->getValue(self::MARKUP_REF_ATTRIBUTE); 538 if ($markupRef === null) { 539 LogUtility::internalError("The markup ref was not found for a link."); 540 return false; 541 } 542 try { 543 $type = MarkupRef::createLinkFromRef($markupRef) 544 ->getSchemeType(); 545 } catch (ExceptionCompile $e) { 546 LogUtility::error("The markup ref could not be parsed. Error:{$e->getMessage()}"); 547 return false; 548 } 549 $name = $tagAttributes->getValue(self::ATTRIBUTE_LABEL); 550 551 switch ($type) { 552 case MarkupRef::WIKI_URI: 553 /** 554 * The relative link should be passed (ie the original) 555 * Dokuwiki has a default description 556 * We can't pass empty or the array(title), it does not work 557 */ 558 $descriptionToDelete = "b"; 559 $renderer->internallink($markupRef, $descriptionToDelete); 560 $renderer->doc = substr($renderer->doc, 0, -strlen($descriptionToDelete)); 561 break; 562 case MarkupRef::WEB_URI: 563 $renderer->externallink($markupRef, $name); 564 break; 565 case MarkupRef::LOCAL_URI: 566 $renderer->locallink($markupRef, $name); 567 break; 568 case MarkupRef::EMAIL_URI: 569 $renderer->emaillink($markupRef, $name); 570 break; 571 case MarkupRef::INTERWIKI_URI: 572 $interWikiSplit = preg_split("/>/", $markupRef); 573 $renderer->interwikilink($markupRef, $name, $interWikiSplit[0], $interWikiSplit[1]); 574 break; 575 case MarkupRef::WINDOWS_SHARE_URI: 576 $renderer->windowssharelink($markupRef, $name); 577 break; 578 case MarkupRef::VARIABLE_URI: 579 // No backlinks for link template 580 break; 581 default: 582 LogUtility::msg("The markup reference ({$markupRef}) with the type $type was not processed into the metadata"); 583 } 584 585 return true; 586 case DOKU_LEXER_UNMATCHED: 587 $renderer->doc .= PluginUtility::renderUnmatched($data); 588 break; 589 } 590 break; 591 592 case renderer_plugin_combo_analytics::RENDERER_FORMAT: 593 594 if ($state === DOKU_LEXER_ENTER) { 595 /** 596 * 597 * @var renderer_plugin_combo_analytics $renderer 598 */ 599 $tagAttributes = TagAttributes::createFromCallStackArray($data[PluginUtility::ATTRIBUTES]); 600 $ref = $tagAttributes->getValue(self::MARKUP_REF_ATTRIBUTE); 601 try { 602 $markupRef = LinkMarkup::createFromRef($ref); 603 } catch (ExceptionCompile $e) { 604 LogUtility::error("Error while parsing the ref ($ref). Error: {$e->getMessage()}. No further analytics."); 605 return false; 606 } 607 $refType = $markupRef->getMarkupRef()->getSchemeType(); 608 609 610 /** 611 * @param array $stats 612 * Calculate internal link statistics 613 */ 614 $stats = &$renderer->stats; 615 switch ($refType) { 616 617 case MarkupRef::WIKI_URI: 618 619 /** 620 * Internal link count 621 */ 622 if (!array_key_exists(renderer_plugin_combo_analytics::INTERNAL_LINK_COUNT, $stats)) { 623 $stats[renderer_plugin_combo_analytics::INTERNAL_LINK_COUNT] = 0; 624 } 625 $stats[renderer_plugin_combo_analytics::INTERNAL_LINK_COUNT]++; 626 627 628 /** 629 * Broken link ? 630 */ 631 try { 632 $path = $markupRef->getMarkupRef()->getPath(); 633 $linkedPage = MarkupPath::createPageFromPathObject($path); 634 if (!FileSystems::exists($path)) { 635 $internalLinkBroken = $stats[renderer_plugin_combo_analytics::INTERNAL_LINK_BROKEN_COUNT] ?? 0; 636 $stats[renderer_plugin_combo_analytics::INTERNAL_LINK_BROKEN_COUNT] = $internalLinkBroken + 1; 637 $stats[renderer_plugin_combo_analytics::INFO][] = "The internal linked page `{$linkedPage}` does not exist"; 638 } 639 } catch (ExceptionNotFound $e) { 640 // no local path 641 } 642 643 /** 644 * Calculate link distance 645 */ 646 global $ID; 647 $id = $linkedPage->getWikiId(); 648 $a = explode(':', getNS($ID)); 649 $b = explode(':', getNS($id)); 650 while (isset($a[0]) && $a[0] == $b[0]) { 651 array_shift($a); 652 array_shift($b); 653 } 654 $length = count($a) + count($b); 655 $stats[renderer_plugin_combo_analytics::INTERNAL_LINK_DISTANCE][] = $length; 656 break; 657 658 case MarkupRef::WEB_URI: 659 660 if (!array_key_exists(renderer_plugin_combo_analytics::EXTERNAL_LINK_COUNT, $stats)) { 661 $stats[renderer_plugin_combo_analytics::EXTERNAL_LINK_COUNT] = 0; 662 } 663 $stats[renderer_plugin_combo_analytics::EXTERNAL_LINK_COUNT]++; 664 break; 665 666 case MarkupRef::LOCAL_URI: 667 668 if (!array_key_exists(renderer_plugin_combo_analytics::LOCAL_LINK_COUNT, $stats)) { 669 $stats[renderer_plugin_combo_analytics::LOCAL_LINK_COUNT] = 0; 670 } 671 $stats[renderer_plugin_combo_analytics::LOCAL_LINK_COUNT]++; 672 break; 673 674 case MarkupRef::INTERWIKI_URI: 675 676 if (!array_key_exists(renderer_plugin_combo_analytics::INTERWIKI_LINK_COUNT, $stats)) { 677 $stats[renderer_plugin_combo_analytics::INTERWIKI_LINK_COUNT] = 0; 678 } 679 $stats[renderer_plugin_combo_analytics::INTERWIKI_LINK_COUNT]++; 680 break; 681 682 case MarkupRef::EMAIL_URI: 683 684 if (!array_key_exists(renderer_plugin_combo_analytics::EMAIL_COUNT, $stats)) { 685 $stats[renderer_plugin_combo_analytics::EMAIL_COUNT] = 0; 686 } 687 $stats[renderer_plugin_combo_analytics::EMAIL_COUNT]++; 688 break; 689 690 case MarkupRef::WINDOWS_SHARE_URI: 691 692 if (!array_key_exists(renderer_plugin_combo_analytics::WINDOWS_SHARE_COUNT, $stats)) { 693 $stats[renderer_plugin_combo_analytics::WINDOWS_SHARE_COUNT] = 0; 694 } 695 $stats[renderer_plugin_combo_analytics::WINDOWS_SHARE_COUNT]++; 696 break; 697 698 case MarkupRef::VARIABLE_URI: 699 700 if (!array_key_exists(renderer_plugin_combo_analytics::TEMPLATE_LINK_COUNT, $stats)) { 701 $stats[renderer_plugin_combo_analytics::TEMPLATE_LINK_COUNT] = 0; 702 } 703 $stats[renderer_plugin_combo_analytics::TEMPLATE_LINK_COUNT]++; 704 break; 705 706 default: 707 708 LogUtility::msg("The link `{$ref}` with the type ($refType) is not taken into account into the statistics"); 709 710 } 711 712 713 break; 714 } 715 716 } 717 718 return false; 719 } 720 721 722 /** 723 * Utility function to add a link into the callstack 724 * @param CallStack $callStack 725 * @param TagAttributes $tagAttributes 726 */ 727 public 728 static function addOpenLinkTagInCallStack(CallStack $callStack, TagAttributes $tagAttributes) 729 { 730 $parent = $callStack->moveToParent(); 731 $context = ""; 732 $attributes = $tagAttributes->toCallStackArray(); 733 if ($parent !== false) { 734 $context = $parent->getTagName(); 735 if ($context === ButtonTag::MARKUP_LONG) { 736 // the link takes by default the data from the button 737 $parentAttributes = $parent->getAttributes(); 738 if ($parentAttributes !== null) { 739 $attributes = ArrayUtility::mergeByValue($parentAttributes, $attributes); 740 } 741 } 742 } 743 $callStack->appendCallAtTheEnd( 744 Call::createComboCall( 745 syntax_plugin_combo_link::TAG, 746 DOKU_LEXER_ENTER, 747 $attributes, 748 $context 749 )); 750 } 751 752 public 753 static function addExitLinkTagInCallStack(CallStack $callStack) 754 { 755 $callStack->appendCallAtTheEnd( 756 Call::createComboCall( 757 syntax_plugin_combo_link::TAG, 758 DOKU_LEXER_EXIT 759 )); 760 } 761} 762 763