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 /** 280 * If the Rel is a wiki link, we make the path absolute and not relative 281 * (this is for the {@link \ComboStrap\FetcherPageBundler)}} 282 * otherwise the links are not good and are seen as non-existent 283 */ 284 try { 285 $markupRefObject = MarkupRef::createLinkFromRef($markupRef); 286 $scheme = $markupRefObject->getSchemeType(); 287 if ($scheme === MarkupRef::WIKI_URI) { 288 289 /** 290 * Properties? 291 */ 292 $newProperties = []; 293 $url = $markupRefObject->getUrl(); 294 foreach (array_keys($url->getQueryProperties()) as $propertyName) { 295 if ($propertyName === "id") { 296 continue; 297 } 298 $queryPropertyValue = $url->getQueryPropertyValue($propertyName); 299 if (is_array($queryPropertyValue)) { 300 foreach ($queryPropertyValue as $arrayValue) { 301 $newProperties[] = $propertyName . "=" . $arrayValue; 302 } 303 } else { 304 $newProperties[] = $propertyName . "=" . $queryPropertyValue; 305 } 306 } 307 $markupRef = $markupRefObject->getPath()->toAbsoluteId(); 308 if (count($newProperties) > 0) { 309 $queryString = implode("&", $newProperties); 310 $markupRef .= "?" . $queryString; 311 } 312 try { 313 $markupRef .= "#" . $url->getFragment(); 314 } catch (ExceptionNotFound $e) { 315 // no fragment 316 } 317 } 318 } catch (ExceptionBadArgument|ExceptionBadSyntax|ExceptionNotFound $e) { 319 // not a valid ref 320 } 321 $htmlAttributes->addComponentAttributeValue(self::MARKUP_REF_ATTRIBUTE, $markupRef); 322 } 323 324 325 /** 326 * Extra HTML attribute 327 */ 328 $callStack = CallStack::createFromHandler($handler); 329 $parent = $callStack->moveToParent(); 330 $parentName = ""; 331 if ($parent !== false) { 332 333 /** 334 * Button Link 335 * Getting the attributes 336 */ 337 $parentName = $parent->getTagName(); 338 if ($parentName == ButtonTag::MARKUP_LONG) { 339 $htmlAttributes->mergeWithCallStackArray($parent->getAttributes()); 340 } 341 342 /** 343 * Searching Clickable parent 344 */ 345 $maxLevel = 3; 346 $level = 0; 347 while ( 348 $parent != false && 349 !$parent->hasAttribute(self::CLICKABLE_ATTRIBUTE) && 350 $level < $maxLevel 351 ) { 352 $parent = $callStack->moveToParent(); 353 $level++; 354 } 355 if ($parent !== false) { 356 if ($parent->getAttribute(self::CLICKABLE_ATTRIBUTE)) { 357 $htmlAttributes->addClassName(self::STRETCHED_LINK); 358 $parent->addClassName("position-relative"); 359 $parent->removeAttribute(self::CLICKABLE_ATTRIBUTE); 360 } 361 } 362 363 } 364 $returnedArray[PluginUtility::STATE] = $state; 365 $returnedArray[PluginUtility::ATTRIBUTES] = $htmlAttributes->toCallStackArray(); 366 $returnedArray[PluginUtility::CONTEXT] = $parentName; 367 return $returnedArray; 368 369 case DOKU_LEXER_UNMATCHED: 370 371 $data = PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler); 372 /** 373 * Delete the separator `|` between the ref and the description if any 374 */ 375 $tag = CallStack::createFromHandler($handler); 376 $parent = $tag->moveToParent(); 377 if ($parent->getTagName() == self::TAG) { 378 if (strpos($match, '|') === 0) { 379 $data[PluginUtility::PAYLOAD] = substr($match, 1); 380 } 381 } 382 return $data; 383 384 case DOKU_LEXER_EXIT: 385 386 $callStack = CallStack::createFromHandler($handler); 387 $openingTag = $callStack->moveToPreviousCorrespondingOpeningCall(); 388 389 $openingAttributes = $openingTag->getAttributes(); 390 $openingPosition = $openingTag->getKey(); 391 392 $callStack->moveToEnd(); 393 $previousCall = $callStack->previous(); 394 $previousCallPosition = $previousCall->getKey(); 395 $previousCallContent = $previousCall->getCapturedContent(); 396 397 /** 398 * Link label 399 * is set if there is no content 400 * between enter and exit node 401 */ 402 $linkLabel = ""; 403 if ( 404 $openingPosition == $previousCallPosition // ie [[id]] 405 || 406 ($openingPosition == $previousCallPosition - 1 && $previousCallContent == "|") // ie [[id|]] 407 ) { 408 // There is no name 409 $markupRef = $openingTag->getAttribute(self::MARKUP_REF_ATTRIBUTE); 410 if ($markupRef !== null) { 411 try { 412 $linkLabel = LinkMarkup::createFromRef($markupRef) 413 ->getDefaultLabel(); 414 } catch (ExceptionCompile $e) { 415 LogUtility::error("No default Label can be defined. Error while parsing the markup ref ($markupRef). Error: {$e->getMessage()}", self::CANONICAL, $e); 416 } 417 418 } 419 } 420 return array( 421 PluginUtility::STATE => $state, 422 PluginUtility::ATTRIBUTES => $openingAttributes, 423 PluginUtility::PAYLOAD => $linkLabel, 424 PluginUtility::CONTEXT => $openingTag->getContext() 425 ); 426 } 427 return true; 428 429 430 } 431 432 /** 433 * Render the output 434 * @param string $format 435 * @param Doku_Renderer $renderer 436 * @param array $data - what the function handle() return'ed 437 * @return boolean - rendered correctly? (however, returned value is not used at the moment) 438 * @see DokuWiki_Syntax_Plugin::render() 439 * 440 * 441 */ 442 function render($format, Doku_Renderer $renderer, $data): bool 443 { 444 // The data 445 $state = $data[PluginUtility::STATE]; 446 switch ($format) { 447 case 'xhtml': 448 449 /** @var Doku_Renderer_xhtml $renderer */ 450 /** 451 * Cache problem may occurs while releasing 452 */ 453 if (isset($data[PluginUtility::ATTRIBUTES])) { 454 $callStackAttributes = $data[PluginUtility::ATTRIBUTES]; 455 } else { 456 $callStackAttributes = $data; 457 } 458 459 PluginUtility::getSnippetManager()->attachCssInternalStyleSheet(self::TAG); 460 461 switch ($state) { 462 case DOKU_LEXER_ENTER: 463 $tagAttributes = TagAttributes::createFromCallStackArray($callStackAttributes, self::TAG); 464 465 $markupRef = $tagAttributes->getValueAndRemove(self::MARKUP_REF_ATTRIBUTE); 466 if ($markupRef === null) { 467 $message = "Internal Error: A link reference was not found"; 468 LogUtility::internalError($message); 469 $renderer->doc .= LogUtility::wrapInRedForHtml($message); 470 return false; 471 } 472 try { 473 $markupRef = syntax_plugin_combo_variable::replaceVariablesWithValuesFromContext($markupRef); 474 $markupLink = LinkMarkup::createFromRef($markupRef); 475 $markupAttributes = $markupLink->toAttributes(); 476 } catch (ExceptionCompile $e) { 477 // uncomment to get the original error stack trace in dev 478 // and see where the exception comes from 479 // Don't forget to comment back 480// if (PluginUtility::isDevOrTest()) { 481// throw new ExceptionRuntime("Error on markup ref", self::TAG, 0, $e); 482// } 483 484 /** 485 * Error. Example: unknown inter-wiki ... 486 * We still create the a to be xhtml compliante 487 */ 488 $url = UrlEndpoint::createSupportUrl(); 489 $markupAttributes = TagAttributes::createEmpty() 490 ->addOutputAttributeValue("href", $url->toString()) 491 ->addClassName(LinkMarkup::getHtmlClassNotExist()); 492 $renderer->doc .= $markupAttributes->toHtmlEnterTag("a") . $e->getMessage(); 493 494 LogUtility::warning($e->getMessage(), "link", $e); 495 return false; 496 497 } 498 // markup attributes is leading because it has already output attribute such as href 499 $markupAttributes->mergeWithCallStackArray($tagAttributes->toCallStackArray()); 500 $tagAttributes = $markupAttributes; 501 502 503 /** 504 * Extra styling 505 */ 506 $parentTag = $data[PluginUtility::CONTEXT]; 507 $htmlPrefix = ""; 508 switch ($parentTag) { 509 /** 510 * Button link 511 */ 512 case ButtonTag::MARKUP_LONG: 513 $tagAttributes->addOutputAttributeValue("role", "button"); 514 ButtonTag::processButtonAttributesToHtmlAttributes($tagAttributes); 515 break; 516 case DropDownTag::TAG: 517 $tagAttributes->addClassName("dropdown-item"); 518 break; 519 case syntax_plugin_combo_navbarcollapse::COMPONENT: 520 $tagAttributes->addClassName("navbar-link"); 521 $htmlPrefix = '<div class="navbar-nav">'; 522 break; 523 case syntax_plugin_combo_navbargroup::COMPONENT: 524 $tagAttributes->addClassName("nav-link"); 525 $htmlPrefix = '<li class="nav-item">'; 526 break; 527 default: 528 case syntax_plugin_combo_badge::TAG: 529 case syntax_plugin_combo_cite::TAG: 530 case syntax_plugin_combo_contentlistitem::DOKU_TAG: 531 case syntax_plugin_combo_preformatted::TAG: 532 break; 533 534 } 535 536 /** 537 * Add it to the rendering 538 */ 539 $renderer->doc .= $htmlPrefix . $tagAttributes->toHtmlEnterTag("a"); 540 break; 541 case DOKU_LEXER_UNMATCHED: 542 $renderer->doc .= PluginUtility::renderUnmatched($data); 543 break; 544 case DOKU_LEXER_EXIT: 545 546 // if there is no link name defined, we get the name as ref in the payload 547 // otherwise null string 548 $renderer->doc .= $data[PluginUtility::PAYLOAD] ?? ''; 549 550 // Close the link 551 $renderer->doc .= "</a>"; 552 553 // Close the html wrapper element 554 $context = $data[PluginUtility::CONTEXT]; 555 switch ($context) { 556 case syntax_plugin_combo_navbarcollapse::COMPONENT: 557 $renderer->doc .= '</div>'; 558 break; 559 case syntax_plugin_combo_navbargroup::COMPONENT: 560 $renderer->doc .= '</li>'; 561 break; 562 } 563 564 } 565 return true; 566 case 'metadata': 567 568 /** 569 * @var Doku_Renderer_metadata $renderer 570 */ 571 switch ($state) { 572 case DOKU_LEXER_ENTER: 573 /** 574 * Keep track of the backlinks ie meta['relation']['references'] 575 * @var Doku_Renderer_metadata $renderer 576 */ 577 $tagAttributes = TagAttributes::createFromCallStackArray($data[PluginUtility::ATTRIBUTES]); 578 579 $markupRef = $tagAttributes->getValue(self::MARKUP_REF_ATTRIBUTE); 580 if ($markupRef === null) { 581 LogUtility::internalError("The markup ref was not found for a link."); 582 return false; 583 } 584 try { 585 $type = MarkupRef::createLinkFromRef($markupRef) 586 ->getSchemeType(); 587 } catch (ExceptionCompile $e) { 588 LogUtility::error("The markup ref could not be parsed. Error:{$e->getMessage()}"); 589 return false; 590 } 591 $name = $tagAttributes->getValue(self::ATTRIBUTE_LABEL); 592 593 switch ($type) { 594 case MarkupRef::WIKI_URI: 595 /** 596 * The relative link should be passed (ie the original) 597 * Dokuwiki has a default description 598 * We can't pass empty or the array(title), it does not work 599 */ 600 $descriptionToDelete = "b"; 601 $renderer->internallink($markupRef, $descriptionToDelete); 602 $renderer->doc = substr($renderer->doc, 0, -strlen($descriptionToDelete)); 603 break; 604 case MarkupRef::WEB_URI: 605 $renderer->externallink($markupRef, $name); 606 break; 607 case MarkupRef::LOCAL_URI: 608 $renderer->locallink($markupRef, $name); 609 break; 610 case MarkupRef::EMAIL_URI: 611 $renderer->emaillink($markupRef, $name); 612 break; 613 case MarkupRef::INTERWIKI_URI: 614 $interWikiSplit = preg_split("/>/", $markupRef); 615 $renderer->interwikilink($markupRef, $name, $interWikiSplit[0], $interWikiSplit[1]); 616 break; 617 case MarkupRef::WINDOWS_SHARE_URI: 618 $renderer->windowssharelink($markupRef, $name); 619 break; 620 case MarkupRef::VARIABLE_URI: 621 // No backlinks for link template 622 break; 623 default: 624 LogUtility::msg("The markup reference ({$markupRef}) with the type $type was not processed into the metadata"); 625 } 626 627 return true; 628 case DOKU_LEXER_UNMATCHED: 629 $renderer->doc .= PluginUtility::renderUnmatched($data); 630 break; 631 } 632 break; 633 634 case renderer_plugin_combo_analytics::RENDERER_FORMAT: 635 636 if ($state === DOKU_LEXER_ENTER) { 637 /** 638 * 639 * @var renderer_plugin_combo_analytics $renderer 640 */ 641 $tagAttributes = TagAttributes::createFromCallStackArray($data[PluginUtility::ATTRIBUTES]); 642 $ref = $tagAttributes->getValue(self::MARKUP_REF_ATTRIBUTE); 643 try { 644 $markupRef = LinkMarkup::createFromRef($ref); 645 } catch (ExceptionCompile $e) { 646 LogUtility::error("Error while parsing the ref ($ref). Error: {$e->getMessage()}. No further analytics."); 647 return false; 648 } 649 $refType = $markupRef->getMarkupRef()->getSchemeType(); 650 651 652 /** 653 * @param array $stats 654 * Calculate internal link statistics 655 */ 656 $stats = &$renderer->stats; 657 switch ($refType) { 658 659 case MarkupRef::WIKI_URI: 660 661 /** 662 * Internal link count 663 */ 664 if (!array_key_exists(renderer_plugin_combo_analytics::INTERNAL_LINK_COUNT, $stats)) { 665 $stats[renderer_plugin_combo_analytics::INTERNAL_LINK_COUNT] = 0; 666 } 667 $stats[renderer_plugin_combo_analytics::INTERNAL_LINK_COUNT]++; 668 669 670 /** 671 * Broken link ? 672 */ 673 try { 674 $path = $markupRef->getMarkupRef()->getPath(); 675 $linkedPage = MarkupPath::createPageFromPathObject($path); 676 if (!FileSystems::exists($path)) { 677 $internalLinkBroken = $stats[renderer_plugin_combo_analytics::INTERNAL_LINK_BROKEN_COUNT] ?? 0; 678 $stats[renderer_plugin_combo_analytics::INTERNAL_LINK_BROKEN_COUNT] = $internalLinkBroken + 1; 679 $stats[renderer_plugin_combo_analytics::INFO][] = "The internal linked page `{$linkedPage}` does not exist"; 680 } 681 } catch (ExceptionNotFound $e) { 682 // no local path 683 } 684 685 /** 686 * Calculate link distance 687 */ 688 global $ID; 689 $id = $linkedPage->getWikiId(); 690 $a = explode(':', getNS($ID)); 691 $b = explode(':', getNS($id)); 692 while (isset($a[0]) && isset($b[0]) && $a[0] == $b[0]) { 693 array_shift($a); 694 array_shift($b); 695 } 696 $length = count($a) + count($b); 697 $stats[renderer_plugin_combo_analytics::INTERNAL_LINK_DISTANCE][] = $length; 698 break; 699 700 case MarkupRef::WEB_URI: 701 702 if (!array_key_exists(renderer_plugin_combo_analytics::EXTERNAL_LINK_COUNT, $stats)) { 703 $stats[renderer_plugin_combo_analytics::EXTERNAL_LINK_COUNT] = 0; 704 } 705 $stats[renderer_plugin_combo_analytics::EXTERNAL_LINK_COUNT]++; 706 break; 707 708 case MarkupRef::LOCAL_URI: 709 710 if (!array_key_exists(renderer_plugin_combo_analytics::LOCAL_LINK_COUNT, $stats)) { 711 $stats[renderer_plugin_combo_analytics::LOCAL_LINK_COUNT] = 0; 712 } 713 $stats[renderer_plugin_combo_analytics::LOCAL_LINK_COUNT]++; 714 break; 715 716 case MarkupRef::INTERWIKI_URI: 717 718 if (!array_key_exists(renderer_plugin_combo_analytics::INTERWIKI_LINK_COUNT, $stats)) { 719 $stats[renderer_plugin_combo_analytics::INTERWIKI_LINK_COUNT] = 0; 720 } 721 $stats[renderer_plugin_combo_analytics::INTERWIKI_LINK_COUNT]++; 722 break; 723 724 case MarkupRef::EMAIL_URI: 725 726 if (!array_key_exists(renderer_plugin_combo_analytics::EMAIL_COUNT, $stats)) { 727 $stats[renderer_plugin_combo_analytics::EMAIL_COUNT] = 0; 728 } 729 $stats[renderer_plugin_combo_analytics::EMAIL_COUNT]++; 730 break; 731 732 case MarkupRef::WINDOWS_SHARE_URI: 733 734 if (!array_key_exists(renderer_plugin_combo_analytics::WINDOWS_SHARE_COUNT, $stats)) { 735 $stats[renderer_plugin_combo_analytics::WINDOWS_SHARE_COUNT] = 0; 736 } 737 $stats[renderer_plugin_combo_analytics::WINDOWS_SHARE_COUNT]++; 738 break; 739 740 case MarkupRef::VARIABLE_URI: 741 742 if (!array_key_exists(renderer_plugin_combo_analytics::TEMPLATE_LINK_COUNT, $stats)) { 743 $stats[renderer_plugin_combo_analytics::TEMPLATE_LINK_COUNT] = 0; 744 } 745 $stats[renderer_plugin_combo_analytics::TEMPLATE_LINK_COUNT]++; 746 break; 747 748 default: 749 750 LogUtility::msg("The link `{$ref}` with the type ($refType) is not taken into account into the statistics"); 751 752 } 753 754 755 break; 756 } 757 758 } 759 760 return false; 761 } 762 763 764 /** 765 * Utility function to add a link into the callstack 766 * @param CallStack $callStack 767 * @param TagAttributes $tagAttributes 768 */ 769 public 770 static function addOpenLinkTagInCallStack(CallStack $callStack, TagAttributes $tagAttributes) 771 { 772 $parent = $callStack->moveToParent(); 773 $context = ""; 774 $attributes = $tagAttributes->toCallStackArray(); 775 if ($parent !== false) { 776 $context = $parent->getTagName(); 777 if ($context === ButtonTag::MARKUP_LONG) { 778 // the link takes by default the data from the button 779 $parentAttributes = $parent->getAttributes(); 780 if ($parentAttributes !== null) { 781 $attributes = ArrayUtility::mergeByValue($parentAttributes, $attributes); 782 } 783 } 784 } 785 $callStack->appendCallAtTheEnd( 786 Call::createComboCall( 787 syntax_plugin_combo_link::TAG, 788 DOKU_LEXER_ENTER, 789 $attributes, 790 $context 791 )); 792 } 793 794 public 795 static function addExitLinkTagInCallStack(CallStack $callStack) 796 { 797 $callStack->appendCallAtTheEnd( 798 Call::createComboCall( 799 syntax_plugin_combo_link::TAG, 800 DOKU_LEXER_EXIT 801 )); 802 } 803} 804 805