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