xref: /plugin/combo/syntax/link.php (revision 4cadd4f8c541149bdda95f080e38a6d4e3a640ca)
1007225e5Sgerardnico<?php
2007225e5Sgerardnico
3007225e5Sgerardnico
437748cd8SNickeaurequire_once(__DIR__ . "/../ComboStrap/PluginUtility.php");
5007225e5Sgerardnico
6*4cadd4f8SNickeauuse ComboStrap\AnalyticsDocument;
7*4cadd4f8SNickeauuse ComboStrap\ArrayUtility;
8*4cadd4f8SNickeauuse ComboStrap\Call;
9531e725cSNickeauuse ComboStrap\CallStack;
10*4cadd4f8SNickeauuse ComboStrap\ExceptionCombo;
11*4cadd4f8SNickeauuse ComboStrap\MarkupRef;
12*4cadd4f8SNickeauuse ComboStrap\LogUtility;
13*4cadd4f8SNickeauuse ComboStrap\Page;
14007225e5Sgerardnicouse ComboStrap\PluginUtility;
15531e725cSNickeauuse ComboStrap\TagAttributes;
16c3437056SNickeauuse ComboStrap\ThirdPartyPlugins;
17007225e5Sgerardnico
18007225e5Sgerardnicoif (!defined('DOKU_INC')) die();
19007225e5Sgerardnico
20007225e5Sgerardnico/**
21007225e5Sgerardnico *
22007225e5Sgerardnico * A link pattern to take over the link of Dokuwiki
23007225e5Sgerardnico * and transform it as a bootstrap link
24007225e5Sgerardnico *
25007225e5Sgerardnico * The handle of the move of link is to be found in the
26007225e5Sgerardnico * admin action {@link action_plugin_combo_linkmove}
27007225e5Sgerardnico *
28007225e5Sgerardnico */
29007225e5Sgerardnicoclass syntax_plugin_combo_link extends DokuWiki_Syntax_Plugin
30007225e5Sgerardnico{
31007225e5Sgerardnico    const TAG = 'link';
32ef295d81Sgerardnico    const COMPONENT = 'combo_link';
33007225e5Sgerardnico
345f891b7eSNickeau    /**
3585e82846SNickeau     * Disable the link component
3621913ab3SNickeau     */
3721913ab3SNickeau    const CONF_DISABLE_LINK = "disableLink";
3821913ab3SNickeau
3921913ab3SNickeau    /**
405f891b7eSNickeau     * The link Tag
41531e725cSNickeau     * a or p
425f891b7eSNickeau     */
435f891b7eSNickeau    const LINK_TAG = "linkTag";
445f891b7eSNickeau
4521913ab3SNickeau    /**
4621913ab3SNickeau     * Do the link component allows to be spawn on multilines
4721913ab3SNickeau     */
48531e725cSNickeau    const CLICKABLE_ATTRIBUTE = "clickable";
49*4cadd4f8SNickeau    public const ATTRIBUTE_LABEL = 'label';
50*4cadd4f8SNickeau    /**
51*4cadd4f8SNickeau     * The key of the array for the handle cache
52*4cadd4f8SNickeau     */
53*4cadd4f8SNickeau    public const ATTRIBUTE_HREF = 'href';
54*4cadd4f8SNickeau    /**
55*4cadd4f8SNickeau     * Indicate if the href is a {@link MarkupRef}
56*4cadd4f8SNickeau     * (ie the syntax from the markup document)
57*4cadd4f8SNickeau     * or is a html href added by {@link syntax_plugin_combo_share}
58*4cadd4f8SNickeau     * for instance
59*4cadd4f8SNickeau     */
60*4cadd4f8SNickeau    const ATTRIBUTE_HREF_TYPE = "href-type";
61*4cadd4f8SNickeau    const HREF_MARKUP_TYPE_VALUE = "markup";
62*4cadd4f8SNickeau    public const ATTRIBUTE_IMAGE_IN_LABEL = 'image-in-label';
63*4cadd4f8SNickeau
64*4cadd4f8SNickeau    /**
65*4cadd4f8SNickeau     * A link may have a title or not
66*4cadd4f8SNickeau     * ie
67*4cadd4f8SNickeau     * [[path:page]]
68*4cadd4f8SNickeau     * [[path:page|title]]
69*4cadd4f8SNickeau     * are valid
70*4cadd4f8SNickeau     *
71*4cadd4f8SNickeau     * Get the content until one of this character is found:
72*4cadd4f8SNickeau     *   * |
73*4cadd4f8SNickeau     *   * or ]]
74*4cadd4f8SNickeau     *   * or \n (No line break allowed, too much difficult to debug)
75*4cadd4f8SNickeau     *   * and not [ (for two links on the same line)
76*4cadd4f8SNickeau     */
77*4cadd4f8SNickeau    public const ENTRY_PATTERN_SINGLE_LINE = "\[\[[^\|\]]*(?=[^\n\[]*\]\])";
78*4cadd4f8SNickeau    public const EXIT_PATTERN = "\]\]";
79*4cadd4f8SNickeau
80*4cadd4f8SNickeau
81*4cadd4f8SNickeau    /**
82*4cadd4f8SNickeau     * Dokuwiki Link pattern ter info
83*4cadd4f8SNickeau     * Found in {@link \dokuwiki\Parsing\ParserMode\Internallink}
84*4cadd4f8SNickeau     */
85*4cadd4f8SNickeau    const SPECIAL_PATTERN = "\[\[.*?\]\](?!\])";
86*4cadd4f8SNickeau
87*4cadd4f8SNickeau    /**
88*4cadd4f8SNickeau     * The link title attribute (ie popup)
89*4cadd4f8SNickeau     */
90*4cadd4f8SNickeau    const TITLE_ATTRIBUTE = "title";
91*4cadd4f8SNickeau
92*4cadd4f8SNickeau
93*4cadd4f8SNickeau    /**
94*4cadd4f8SNickeau     * Parse the match of a syntax {@link DokuWiki_Syntax_Plugin} handle function
95*4cadd4f8SNickeau     * @param $match
96*4cadd4f8SNickeau     * @return string[] - an array with the attributes constant `ATTRIBUTE_xxxx` as key
97*4cadd4f8SNickeau     *
98*4cadd4f8SNickeau     * Code adapted from  {@link Doku_Handler::internallink()}
99*4cadd4f8SNickeau     */
100*4cadd4f8SNickeau    public static function parse($match): array
101*4cadd4f8SNickeau    {
102*4cadd4f8SNickeau
103*4cadd4f8SNickeau        // Strip the opening and closing markup
104*4cadd4f8SNickeau        $linkString = preg_replace(array('/^\[\[/', '/\]\]$/u'), '', $match);
105*4cadd4f8SNickeau
106*4cadd4f8SNickeau        // Split title from URL
107*4cadd4f8SNickeau        $linkArray = explode('|', $linkString, 2);
108*4cadd4f8SNickeau
109*4cadd4f8SNickeau        // Id
110*4cadd4f8SNickeau        $attributes[self::ATTRIBUTE_HREF] = trim($linkArray[0]);
111*4cadd4f8SNickeau
112*4cadd4f8SNickeau
113*4cadd4f8SNickeau        // Text or image
114*4cadd4f8SNickeau        if (!isset($linkArray[1])) {
115*4cadd4f8SNickeau            $attributes[self::ATTRIBUTE_LABEL] = null;
116*4cadd4f8SNickeau        } else {
117*4cadd4f8SNickeau            // An image in the title
118*4cadd4f8SNickeau            if (preg_match('/^\{\{[^\}]+\}\}$/', $linkArray[1])) {
119*4cadd4f8SNickeau                // If the title is an image, convert it to an array containing the image details
120*4cadd4f8SNickeau                $attributes[self::ATTRIBUTE_IMAGE_IN_LABEL] = Doku_Handler_Parse_Media($linkArray[1]);
121*4cadd4f8SNickeau            } else {
122*4cadd4f8SNickeau                $attributes[self::ATTRIBUTE_LABEL] = $linkArray[1];
123*4cadd4f8SNickeau            }
124*4cadd4f8SNickeau        }
125*4cadd4f8SNickeau
126*4cadd4f8SNickeau        return $attributes;
127*4cadd4f8SNickeau
128*4cadd4f8SNickeau    }
12921913ab3SNickeau
1305f891b7eSNickeau
131007225e5Sgerardnico    /**
132007225e5Sgerardnico     * Syntax Type.
133007225e5Sgerardnico     *
134007225e5Sgerardnico     * Needs to return one of the mode types defined in $PARSER_MODES in parser.php
135007225e5Sgerardnico     * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types
136007225e5Sgerardnico     */
137007225e5Sgerardnico    function getType()
138007225e5Sgerardnico    {
139007225e5Sgerardnico        return 'substition';
140007225e5Sgerardnico    }
141007225e5Sgerardnico
142007225e5Sgerardnico    /**
143007225e5Sgerardnico     * How Dokuwiki will add P element
144007225e5Sgerardnico     *
145007225e5Sgerardnico     *  * 'normal' - The plugin can be used inside paragraphs
146007225e5Sgerardnico     *  * 'block'  - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs
147007225e5Sgerardnico     *  * 'stack'  - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs
148007225e5Sgerardnico     *
149007225e5Sgerardnico     * @see DokuWiki_Syntax_Plugin::getPType()
150007225e5Sgerardnico     */
151007225e5Sgerardnico    function getPType()
152007225e5Sgerardnico    {
153007225e5Sgerardnico        return 'normal';
154007225e5Sgerardnico    }
155007225e5Sgerardnico
156007225e5Sgerardnico    /**
157007225e5Sgerardnico     * @return array
158007225e5Sgerardnico     * Allow which kind of plugin inside
159007225e5Sgerardnico     *
160007225e5Sgerardnico     * No one of array('container', 'baseonly', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs')
161007225e5Sgerardnico     * because we manage self the content and we call self the parser
162007225e5Sgerardnico     */
163*4cadd4f8SNickeau    function getAllowedTypes(): array
164007225e5Sgerardnico    {
165007225e5Sgerardnico        return array('substition', 'formatting', 'disabled');
166007225e5Sgerardnico    }
167007225e5Sgerardnico
16837748cd8SNickeau    /**
16937748cd8SNickeau     * @param string $mode
17037748cd8SNickeau     * @return bool
17137748cd8SNickeau     * Accepts inside
17237748cd8SNickeau     */
173*4cadd4f8SNickeau    public function accepts($mode): bool
1745f891b7eSNickeau    {
1755f891b7eSNickeau        /**
1765f891b7eSNickeau         * To avoid that the description if it contains a link
1775f891b7eSNickeau         * will be taken by the links mode
1785f891b7eSNickeau         *
1795f891b7eSNickeau         * For instance, [[https://hallo|https://hallo]] will send https://hallo
1805f891b7eSNickeau         * to the external link mode
1815f891b7eSNickeau         */
1825f891b7eSNickeau        $linkModes = [
1835f891b7eSNickeau            "externallink",
1845f891b7eSNickeau            "locallink",
1855f891b7eSNickeau            "internallink",
1865f891b7eSNickeau            "interwikilink",
1875f891b7eSNickeau            "emaillink",
188fc45fbf7Sgerardnico            "emphasis", // double slash can not be used inside to preserve the possibility to write an URL in the description
1895f891b7eSNickeau            //"emphasis_open", // italic use // and therefore take over a link as description which is not handy when copying a tweet
1905f891b7eSNickeau            //"emphasis_close",
1915f891b7eSNickeau            //"acrnonym"
1925f891b7eSNickeau        ];
1935f891b7eSNickeau        if (in_array($mode, $linkModes)) {
1945f891b7eSNickeau            return false;
1955f891b7eSNickeau        } else {
1965f891b7eSNickeau            return true;
1975f891b7eSNickeau        }
1985f891b7eSNickeau    }
1995f891b7eSNickeau
2005f891b7eSNickeau
201007225e5Sgerardnico    /**
202007225e5Sgerardnico     * @see Doku_Parser_Mode::getSort()
203007225e5Sgerardnico     * The mode with the lowest sort number will win out
204007225e5Sgerardnico     */
205007225e5Sgerardnico    function getSort()
206007225e5Sgerardnico    {
207e8b2ff59SNickeau        /**
208e8b2ff59SNickeau         * It should be less than the number
209e8b2ff59SNickeau         * at {@link \dokuwiki\Parsing\ParserMode\Internallink::getSort}
210e8b2ff59SNickeau         * and the like
211e8b2ff59SNickeau         *
212e8b2ff59SNickeau         * For whatever reason, the number below should be less than 100,
213e8b2ff59SNickeau         * otherwise on windows with DokuWiki Stick, the link syntax may be not taken
214e8b2ff59SNickeau         * into account
215e8b2ff59SNickeau         */
216e8b2ff59SNickeau        return 99;
217007225e5Sgerardnico    }
218007225e5Sgerardnico
219007225e5Sgerardnico
220007225e5Sgerardnico    function connectTo($mode)
221007225e5Sgerardnico    {
222d262537cSgerardnico
22337748cd8SNickeau        if (!$this->getConf(self::CONF_DISABLE_LINK, false)
22437748cd8SNickeau            &&
22537748cd8SNickeau            $mode !== PluginUtility::getModeFromPluginName(ThirdPartyPlugins::IMAGE_MAPPING_NAME)
22637748cd8SNickeau        ) {
22737748cd8SNickeau
228*4cadd4f8SNickeau            $pattern = self::ENTRY_PATTERN_SINGLE_LINE;
2299337a630SNickeau            $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeFromTag($this->getPluginComponent()));
23037748cd8SNickeau
23121913ab3SNickeau        }
232d262537cSgerardnico
233007225e5Sgerardnico    }
234007225e5Sgerardnico
2355f891b7eSNickeau    public function postConnect()
2365f891b7eSNickeau    {
23721913ab3SNickeau        if (!$this->getConf(self::CONF_DISABLE_LINK, false)) {
238*4cadd4f8SNickeau            $this->Lexer->addExitPattern(self::EXIT_PATTERN, PluginUtility::getModeFromTag($this->getPluginComponent()));
2395f891b7eSNickeau        }
24021913ab3SNickeau    }
2415f891b7eSNickeau
242007225e5Sgerardnico
243007225e5Sgerardnico    /**
244007225e5Sgerardnico     * The handler for an internal link
245007225e5Sgerardnico     * based on `internallink` in {@link Doku_Handler}
246007225e5Sgerardnico     * The handler call the good renderer in {@link Doku_Renderer_xhtml} with
247007225e5Sgerardnico     * the parameters (ie for instance internallink)
248007225e5Sgerardnico     * @param string $match
249007225e5Sgerardnico     * @param int $state
250007225e5Sgerardnico     * @param int $pos
251007225e5Sgerardnico     * @param Doku_Handler $handler
252007225e5Sgerardnico     * @return array|bool
253007225e5Sgerardnico     */
254007225e5Sgerardnico    function handle($match, $state, $pos, Doku_Handler $handler)
255007225e5Sgerardnico    {
256007225e5Sgerardnico
2575f891b7eSNickeau        switch ($state) {
2585f891b7eSNickeau            case DOKU_LEXER_ENTER:
259*4cadd4f8SNickeau                $parsedArray = self::parse($match);
260*4cadd4f8SNickeau                $htmlAttributes = TagAttributes::createEmpty(self::TAG);
261*4cadd4f8SNickeau                /**
262*4cadd4f8SNickeau                 * Href needs to be passed to the
263*4cadd4f8SNickeau                 * instructions stack (because we support)
264*4cadd4f8SNickeau                 * dynamic link call href with {@link syntax_plugin_combo_template}
265*4cadd4f8SNickeau                 */
266*4cadd4f8SNickeau                $href = $parsedArray[self::ATTRIBUTE_HREF];
267*4cadd4f8SNickeau                if ($href !== null) {
268*4cadd4f8SNickeau                    $htmlAttributes
269*4cadd4f8SNickeau                        ->addComponentAttributeValue(self::ATTRIBUTE_HREF, $href)
270*4cadd4f8SNickeau                        ->addComponentAttributeValue(self::ATTRIBUTE_HREF_TYPE, self::HREF_MARKUP_TYPE_VALUE);
271*4cadd4f8SNickeau                }
272*4cadd4f8SNickeau
273*4cadd4f8SNickeau
274*4cadd4f8SNickeau                /**
275*4cadd4f8SNickeau                 * Extra HTML attribute
276*4cadd4f8SNickeau                 */
277531e725cSNickeau                $callStack = CallStack::createFromHandler($handler);
278531e725cSNickeau                $parent = $callStack->moveToParent();
279007225e5Sgerardnico                $parentName = "";
280*4cadd4f8SNickeau                if ($parent !== false) {
281531e725cSNickeau
282531e725cSNickeau                    /**
283531e725cSNickeau                     * Button Link
284531e725cSNickeau                     * Getting the attributes
285531e725cSNickeau                     */
286531e725cSNickeau                    $parentName = $parent->getTagName();
287531e725cSNickeau                    if ($parentName == syntax_plugin_combo_button::TAG) {
288*4cadd4f8SNickeau                        $htmlAttributes->mergeWithCallStackArray($parent->getAttributes());
28921913ab3SNickeau                    }
290531e725cSNickeau
291531e725cSNickeau                    /**
292531e725cSNickeau                     * Searching Clickable parent
293531e725cSNickeau                     */
294531e725cSNickeau                    $maxLevel = 3;
295531e725cSNickeau                    $level = 0;
296531e725cSNickeau                    while (
297531e725cSNickeau                        $parent != false &&
298531e725cSNickeau                        !$parent->hasAttribute(self::CLICKABLE_ATTRIBUTE) &&
299531e725cSNickeau                        $level < $maxLevel
300531e725cSNickeau                    ) {
301531e725cSNickeau                        $parent = $callStack->moveToParent();
302531e725cSNickeau                        $level++;
3035f891b7eSNickeau                    }
304531e725cSNickeau                    if ($parent != false) {
305531e725cSNickeau                        if ($parent->getAttribute(self::CLICKABLE_ATTRIBUTE)) {
306*4cadd4f8SNickeau                            $htmlAttributes->addClassName("stretched-link");
307531e725cSNickeau                            $parent->addClassName("position-relative");
308531e725cSNickeau                            $parent->removeAttribute(self::CLICKABLE_ATTRIBUTE);
3095f891b7eSNickeau                        }
31021913ab3SNickeau                    }
31121913ab3SNickeau
312531e725cSNickeau                }
313*4cadd4f8SNickeau                $returnedArray[PluginUtility::STATE] = $state;
314*4cadd4f8SNickeau                $returnedArray[PluginUtility::ATTRIBUTES] = $htmlAttributes->toCallStackArray();
315*4cadd4f8SNickeau                $returnedArray[PluginUtility::CONTEXT] = $parentName;
316*4cadd4f8SNickeau                return $returnedArray;
317*4cadd4f8SNickeau
3185f891b7eSNickeau            case DOKU_LEXER_UNMATCHED:
3195f891b7eSNickeau
32032b85071SNickeau                $data = PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler);
3215f891b7eSNickeau                /**
32232b85071SNickeau                 * Delete the separator `|` between the ref and the description if any
3235f891b7eSNickeau                 */
3241fa8c418SNickeau                $tag = CallStack::createFromHandler($handler);
3251fa8c418SNickeau                $parent = $tag->moveToParent();
3261fa8c418SNickeau                if ($parent->getTagName() == self::TAG) {
3275f891b7eSNickeau                    if (strpos($match, '|') === 0) {
32832b85071SNickeau                        $data[PluginUtility::PAYLOAD] = substr($match, 1);
3295f891b7eSNickeau                    }
330007225e5Sgerardnico                }
33132b85071SNickeau                return $data;
332007225e5Sgerardnico
3335f891b7eSNickeau            case DOKU_LEXER_EXIT:
334531e725cSNickeau                $callStack = CallStack::createFromHandler($handler);
335531e725cSNickeau                $openingTag = $callStack->moveToPreviousCorrespondingOpeningCall();
336*4cadd4f8SNickeau
3375f891b7eSNickeau                $openingAttributes = $openingTag->getAttributes();
338531e725cSNickeau                $openingPosition = $openingTag->getKey();
3395f891b7eSNickeau
340531e725cSNickeau                $callStack->moveToEnd();
341531e725cSNickeau                $previousCall = $callStack->previous();
342531e725cSNickeau                $previousCallPosition = $previousCall->getKey();
343531e725cSNickeau                $previousCallContent = $previousCall->getCapturedContent();
344531e725cSNickeau
345*4cadd4f8SNickeau                /**
346*4cadd4f8SNickeau                 * Link label
347*4cadd4f8SNickeau                 * is set if there is no content
348*4cadd4f8SNickeau                 * between enter and exit node
349*4cadd4f8SNickeau                 */
350*4cadd4f8SNickeau                $linkLabel = "";
351531e725cSNickeau                if (
352531e725cSNickeau                    $openingPosition == $previousCallPosition // ie [[id]]
353531e725cSNickeau                    ||
354531e725cSNickeau                    ($openingPosition == $previousCallPosition - 1 && $previousCallContent == "|") // ie [[id|]]
355531e725cSNickeau                ) {
3565f891b7eSNickeau                    // There is no name
357*4cadd4f8SNickeau                    $href = $openingTag->getAttribute(self::ATTRIBUTE_HREF);
358*4cadd4f8SNickeau                    if ($href !== null) {
359*4cadd4f8SNickeau                        $markup = MarkupRef::createFromRef($href);
360*4cadd4f8SNickeau                        $linkLabel = $markup->getLabel();
361*4cadd4f8SNickeau                    }
3625f891b7eSNickeau                }
3635f891b7eSNickeau                return array(
3645f891b7eSNickeau                    PluginUtility::STATE => $state,
3655f891b7eSNickeau                    PluginUtility::ATTRIBUTES => $openingAttributes,
366*4cadd4f8SNickeau                    PluginUtility::PAYLOAD => $linkLabel,
36785e82846SNickeau                    PluginUtility::CONTEXT => $openingTag->getContext()
3685f891b7eSNickeau                );
3695f891b7eSNickeau        }
3705f891b7eSNickeau        return true;
3715f891b7eSNickeau
372007225e5Sgerardnico
373007225e5Sgerardnico    }
374007225e5Sgerardnico
375007225e5Sgerardnico    /**
376007225e5Sgerardnico     * Render the output
377007225e5Sgerardnico     * @param string $format
378007225e5Sgerardnico     * @param Doku_Renderer $renderer
379007225e5Sgerardnico     * @param array $data - what the function handle() return'ed
380007225e5Sgerardnico     * @return boolean - rendered correctly? (however, returned value is not used at the moment)
381007225e5Sgerardnico     * @see DokuWiki_Syntax_Plugin::render()
382007225e5Sgerardnico     *
383007225e5Sgerardnico     *
384007225e5Sgerardnico     */
385c3437056SNickeau    function render($format, Doku_Renderer $renderer, $data): bool
386007225e5Sgerardnico    {
387007225e5Sgerardnico        // The data
388007225e5Sgerardnico        switch ($format) {
389007225e5Sgerardnico            case 'xhtml':
390007225e5Sgerardnico
391007225e5Sgerardnico                /** @var Doku_Renderer_xhtml $renderer */
392007225e5Sgerardnico                /**
39319b0880dSgerardnico                 * Cache problem may occurs while releasing
394007225e5Sgerardnico                 */
395007225e5Sgerardnico                if (isset($data[PluginUtility::ATTRIBUTES])) {
396531e725cSNickeau                    $callStackAttributes = $data[PluginUtility::ATTRIBUTES];
397007225e5Sgerardnico                } else {
398531e725cSNickeau                    $callStackAttributes = $data;
399007225e5Sgerardnico                }
4005f891b7eSNickeau
401*4cadd4f8SNickeau                PluginUtility::getSnippetManager()->attachCssInternalStyleSheetForSlot(self::TAG);
4025f891b7eSNickeau
4035f891b7eSNickeau                $state = $data[PluginUtility::STATE];
4045f891b7eSNickeau                switch ($state) {
4055f891b7eSNickeau                    case DOKU_LEXER_ENTER:
406531e725cSNickeau                        $tagAttributes = TagAttributes::createFromCallStackArray($callStackAttributes, self::TAG);
407*4cadd4f8SNickeau
408*4cadd4f8SNickeau                        $href = $tagAttributes->getValue(self::ATTRIBUTE_HREF);
409*4cadd4f8SNickeau
410*4cadd4f8SNickeau                        /**
411*4cadd4f8SNickeau                         * HrefMarkup ?
412*4cadd4f8SNickeau                         */
413*4cadd4f8SNickeau                        $hrefSource = $tagAttributes->getValueAndRemoveIfPresent(self::ATTRIBUTE_HREF_TYPE);
414*4cadd4f8SNickeau                        if ($hrefSource !== null) {
415*4cadd4f8SNickeau                            try {
416*4cadd4f8SNickeau                                $markupRef = MarkupRef::createFromRef($href);
417*4cadd4f8SNickeau                                $url = $markupRef->getUrl();
418*4cadd4f8SNickeau                                $markupRefAttributes = $markupRef->toAttributes();
419*4cadd4f8SNickeau                            } catch (ExceptionCombo $e) {
420*4cadd4f8SNickeau                                $message = "Error while parsing the markup href ($href). Error: {$e->getMessage()}";
421*4cadd4f8SNickeau                                $renderer->doc .= "<a>." . LogUtility::wrapInRedForHtml($message);
422*4cadd4f8SNickeau                                return false;
423*4cadd4f8SNickeau                            }
424*4cadd4f8SNickeau                            $tagAttributes->mergeWithCallStackArray($markupRefAttributes->toCallStackArray());
425*4cadd4f8SNickeau                            // No href if the url could not be calculated
426*4cadd4f8SNickeau                            // such as a bad interwiki link
427*4cadd4f8SNickeau                            if (!empty($url)) {
428*4cadd4f8SNickeau                                $tagAttributes->setComponentAttributeValue(self::ATTRIBUTE_HREF, $url);
429*4cadd4f8SNickeau                            } else {
430*4cadd4f8SNickeau                                $tagAttributes->removeComponentAttributeIfPresent(self::ATTRIBUTE_HREF);
431*4cadd4f8SNickeau                            }
432*4cadd4f8SNickeau
433*4cadd4f8SNickeau                        }
434d262537cSgerardnico
43519b0880dSgerardnico                        /**
4365f891b7eSNickeau                         * Extra styling
43719b0880dSgerardnico                         */
4385f891b7eSNickeau                        $parentTag = $data[PluginUtility::CONTEXT];
439*4cadd4f8SNickeau                        $htmlPrefix = "";
4409f4383e9Sgerardnico                        switch ($parentTag) {
44121913ab3SNickeau                            /**
44221913ab3SNickeau                             * Button link
44321913ab3SNickeau                             */
4449f4383e9Sgerardnico                            case syntax_plugin_combo_button::TAG:
445*4cadd4f8SNickeau                                $tagAttributes->addOutputAttributeValue("role", "button");
446531e725cSNickeau                                syntax_plugin_combo_button::processButtonAttributesToHtmlAttributes($tagAttributes);
4479f4383e9Sgerardnico                                break;
448*4cadd4f8SNickeau                            case syntax_plugin_combo_dropdown::TAG:
449*4cadd4f8SNickeau                                $tagAttributes->addClassName("dropdown-item");
450*4cadd4f8SNickeau                                break;
451*4cadd4f8SNickeau                            case syntax_plugin_combo_navbarcollapse::COMPONENT:
452*4cadd4f8SNickeau                                $tagAttributes->addClassName("navbar-link");
453*4cadd4f8SNickeau                                $htmlPrefix = '<div class="navbar-nav">';
454*4cadd4f8SNickeau                                break;
455*4cadd4f8SNickeau                            case syntax_plugin_combo_navbargroup::COMPONENT:
456*4cadd4f8SNickeau                                $tagAttributes->addClassName("nav-link");
457*4cadd4f8SNickeau                                $htmlPrefix = '<li class="nav-item">';
458*4cadd4f8SNickeau                                break;
459*4cadd4f8SNickeau                            default:
4605f891b7eSNickeau                            case syntax_plugin_combo_badge::TAG:
4619f4383e9Sgerardnico                            case syntax_plugin_combo_cite::TAG:
4629337a630SNickeau                            case syntax_plugin_combo_contentlistitem::DOKU_TAG:
4639f4383e9Sgerardnico                            case syntax_plugin_combo_preformatted::TAG:
4649f4383e9Sgerardnico                                break;
4650a517624Sgerardnico
4669f4383e9Sgerardnico                        }
4679f4383e9Sgerardnico
46819b0880dSgerardnico                        /**
46919b0880dSgerardnico                         * Add it to the rendering
47019b0880dSgerardnico                         */
471*4cadd4f8SNickeau                        $renderer->doc .= $htmlPrefix . $tagAttributes->toHtmlEnterTag("a");
4725f891b7eSNickeau                        break;
4735f891b7eSNickeau                    case DOKU_LEXER_UNMATCHED:
47432b85071SNickeau                        $renderer->doc .= PluginUtility::renderUnmatched($data);
4755f891b7eSNickeau                        break;
4765f891b7eSNickeau                    case DOKU_LEXER_EXIT:
4775f891b7eSNickeau
4785f891b7eSNickeau                        // if there is no link name defined, we get the name as ref in the payload
4795f891b7eSNickeau                        // otherwise null string
48085e82846SNickeau                        $renderer->doc .= $data[PluginUtility::PAYLOAD];
4815f891b7eSNickeau
482e3d0019cSgerardnico                        // Close the link
48385e82846SNickeau                        $renderer->doc .= "</a>";
484e3d0019cSgerardnico
485e3d0019cSgerardnico                        // Close the html wrapper element
4865f891b7eSNickeau                        $context = $data[PluginUtility::CONTEXT];
4875f891b7eSNickeau                        switch ($context) {
4885f891b7eSNickeau                            case syntax_plugin_combo_navbarcollapse::COMPONENT:
4895f891b7eSNickeau                                $renderer->doc .= '</div>';
4905f891b7eSNickeau                                break;
4915f891b7eSNickeau                            case syntax_plugin_combo_navbargroup::COMPONENT:
4925f891b7eSNickeau                                $renderer->doc .= '</li>';
4935f891b7eSNickeau                                break;
4945f891b7eSNickeau                        }
4955f891b7eSNickeau
496e3d0019cSgerardnico
4975f891b7eSNickeau                }
4985f891b7eSNickeau
499007225e5Sgerardnico
500007225e5Sgerardnico                return true;
501007225e5Sgerardnico
5025f891b7eSNickeau            case 'metadata':
503007225e5Sgerardnico
504*4cadd4f8SNickeau                /**
505*4cadd4f8SNickeau                 * @var Doku_Renderer_metadata $renderer
506*4cadd4f8SNickeau                 */
5075f891b7eSNickeau                $state = $data[PluginUtility::STATE];
508*4cadd4f8SNickeau                switch ($state) {
509*4cadd4f8SNickeau                    case DOKU_LEXER_ENTER:
510007225e5Sgerardnico                        /**
511007225e5Sgerardnico                         * Keep track of the backlinks ie meta['relation']['references']
512007225e5Sgerardnico                         * @var Doku_Renderer_metadata $renderer
513007225e5Sgerardnico                         */
514*4cadd4f8SNickeau                        $tagAttributes = TagAttributes::createFromCallStackArray($data[PluginUtility::ATTRIBUTES]);
515*4cadd4f8SNickeau                        $hrefSource = $tagAttributes->getValue(self::ATTRIBUTE_HREF_TYPE);
516*4cadd4f8SNickeau                        if ($hrefSource === null || $hrefSource !== self::HREF_MARKUP_TYPE_VALUE) {
517*4cadd4f8SNickeau                            /**
518*4cadd4f8SNickeau                             * This is not a markup link
519*4cadd4f8SNickeau                             * (ie an external link created by a plugin {@link syntax_plugin_combo_share})
520*4cadd4f8SNickeau                             */
521*4cadd4f8SNickeau                            return false;
522007225e5Sgerardnico                        }
523*4cadd4f8SNickeau                        $href = $tagAttributes->getValue(self::ATTRIBUTE_HREF);
524*4cadd4f8SNickeau                        $type = MarkupRef::createFromRef($href)
525*4cadd4f8SNickeau                            ->getUriType();
526*4cadd4f8SNickeau                        $name = $tagAttributes->getValue(self::ATTRIBUTE_LABEL);
5279f4383e9Sgerardnico
528*4cadd4f8SNickeau                        switch ($type) {
529*4cadd4f8SNickeau                            case MarkupRef::WIKI_URI:
530*4cadd4f8SNickeau                                /**
531*4cadd4f8SNickeau                                 * The relative link should be passed (ie the original)
532*4cadd4f8SNickeau                                 * Dokuwiki has a default description
533*4cadd4f8SNickeau                                 * We can't pass empty or the array(title), it does not work
534*4cadd4f8SNickeau                                 */
535*4cadd4f8SNickeau                                $descriptionToDelete = "b";
536*4cadd4f8SNickeau                                $renderer->internallink($href, $descriptionToDelete);
537*4cadd4f8SNickeau                                $renderer->doc = substr($renderer->doc,0,-strlen($descriptionToDelete));
538*4cadd4f8SNickeau                                break;
539*4cadd4f8SNickeau                            case MarkupRef::WEB_URI:
540*4cadd4f8SNickeau                                $renderer->externallink($href, $name);
541*4cadd4f8SNickeau                                break;
542*4cadd4f8SNickeau                            case MarkupRef::LOCAL_URI:
543*4cadd4f8SNickeau                                $renderer->locallink($href, $name);
544*4cadd4f8SNickeau                                break;
545*4cadd4f8SNickeau                            case MarkupRef::EMAIL_URI:
546*4cadd4f8SNickeau                                $renderer->emaillink($href, $name);
547*4cadd4f8SNickeau                                break;
548*4cadd4f8SNickeau                            case MarkupRef::INTERWIKI_URI:
549*4cadd4f8SNickeau                                $interWikiSplit = preg_split("/>/", $href);
550*4cadd4f8SNickeau                                $renderer->interwikilink($href, $name, $interWikiSplit[0], $interWikiSplit[1]);
551*4cadd4f8SNickeau                                break;
552*4cadd4f8SNickeau                            case MarkupRef::WINDOWS_SHARE_URI:
553*4cadd4f8SNickeau                                $renderer->windowssharelink($href, $name);
554*4cadd4f8SNickeau                                break;
555*4cadd4f8SNickeau                            case MarkupRef::VARIABLE_URI:
556*4cadd4f8SNickeau                                // No backlinks for link template
557*4cadd4f8SNickeau                                break;
558*4cadd4f8SNickeau                            default:
559*4cadd4f8SNickeau                                LogUtility::msg("The markup reference ({$href}) with the type $type was not processed into the metadata");
5609f4383e9Sgerardnico                        }
561007225e5Sgerardnico
562007225e5Sgerardnico                        return true;
563*4cadd4f8SNickeau                    case DOKU_LEXER_UNMATCHED:
564*4cadd4f8SNickeau                        $renderer->doc .= PluginUtility::renderUnmatched($data);
565*4cadd4f8SNickeau                        break;
5665f891b7eSNickeau                }
567007225e5Sgerardnico                break;
568007225e5Sgerardnico
569531e725cSNickeau            case renderer_plugin_combo_analytics::RENDERER_FORMAT:
5705f891b7eSNickeau
5715f891b7eSNickeau                $state = $data[PluginUtility::STATE];
5725f891b7eSNickeau                if ($state == DOKU_LEXER_ENTER) {
573007225e5Sgerardnico                    /**
574007225e5Sgerardnico                     *
575007225e5Sgerardnico                     * @var renderer_plugin_combo_analytics $renderer
576007225e5Sgerardnico                     */
577*4cadd4f8SNickeau                    $tagAttributes = TagAttributes::createFromCallStackArray($data[PluginUtility::ATTRIBUTES]);
578*4cadd4f8SNickeau                    $refSource = $tagAttributes->getValue(self::ATTRIBUTE_HREF_TYPE);
579*4cadd4f8SNickeau                    if ($refSource === null || $refSource !== self::HREF_MARKUP_TYPE_VALUE) {
580*4cadd4f8SNickeau                        /**
581*4cadd4f8SNickeau                         * Link added programmatically
582*4cadd4f8SNickeau                         */
583*4cadd4f8SNickeau                        return false;
584*4cadd4f8SNickeau                    }
585*4cadd4f8SNickeau                    $ref = $tagAttributes->getValue(self::ATTRIBUTE_HREF);
586*4cadd4f8SNickeau                    $href = MarkupRef::createFromRef($ref);
587*4cadd4f8SNickeau                    $refType = $href->getUriType();
588*4cadd4f8SNickeau
589*4cadd4f8SNickeau
590*4cadd4f8SNickeau                    /**
591*4cadd4f8SNickeau                     * @param array $stats
592*4cadd4f8SNickeau                     * Calculate internal link statistics
593*4cadd4f8SNickeau                     */
594*4cadd4f8SNickeau
595*4cadd4f8SNickeau                    $stats = &$renderer->stats;
596*4cadd4f8SNickeau                    switch ($refType) {
597*4cadd4f8SNickeau
598*4cadd4f8SNickeau                        case MarkupRef::WIKI_URI:
599*4cadd4f8SNickeau
600*4cadd4f8SNickeau                            /**
601*4cadd4f8SNickeau                             * Internal link count
602*4cadd4f8SNickeau                             */
603*4cadd4f8SNickeau                            if (!array_key_exists(AnalyticsDocument::INTERNAL_LINK_COUNT, $stats)) {
604*4cadd4f8SNickeau                                $stats[AnalyticsDocument::INTERNAL_LINK_COUNT] = 0;
605*4cadd4f8SNickeau                            }
606*4cadd4f8SNickeau                            $stats[AnalyticsDocument::INTERNAL_LINK_COUNT]++;
607*4cadd4f8SNickeau
608*4cadd4f8SNickeau
609*4cadd4f8SNickeau                            /**
610*4cadd4f8SNickeau                             * Broken link ?
611*4cadd4f8SNickeau                             */
612*4cadd4f8SNickeau
613*4cadd4f8SNickeau                            $linkedPage = $href->getInternalPage();
614*4cadd4f8SNickeau                            if (!$linkedPage->exists()) {
615*4cadd4f8SNickeau                                $stats[AnalyticsDocument::INTERNAL_LINK_BROKEN_COUNT]++;
616*4cadd4f8SNickeau                                $stats[AnalyticsDocument::INFO][] = "The internal linked page `{$href->getInternalPage()}` does not exist";
617*4cadd4f8SNickeau                            }
618*4cadd4f8SNickeau
619*4cadd4f8SNickeau                            /**
620*4cadd4f8SNickeau                             * Calculate link distance
621*4cadd4f8SNickeau                             */
622*4cadd4f8SNickeau                            global $ID;
623*4cadd4f8SNickeau                            $id = $href->getInternalPage()->getDokuwikiId();
624*4cadd4f8SNickeau                            $a = explode(':', getNS($ID));
625*4cadd4f8SNickeau                            $b = explode(':', getNS($id));
626*4cadd4f8SNickeau                            while (isset($a[0]) && $a[0] == $b[0]) {
627*4cadd4f8SNickeau                                array_shift($a);
628*4cadd4f8SNickeau                                array_shift($b);
629*4cadd4f8SNickeau                            }
630*4cadd4f8SNickeau                            $length = count($a) + count($b);
631*4cadd4f8SNickeau                            $stats[AnalyticsDocument::INTERNAL_LINK_DISTANCE][] = $length;
632*4cadd4f8SNickeau                            break;
633*4cadd4f8SNickeau
634*4cadd4f8SNickeau                        case MarkupRef::WEB_URI:
635*4cadd4f8SNickeau
636*4cadd4f8SNickeau                            if (!array_key_exists(AnalyticsDocument::EXTERNAL_LINK_COUNT, $stats)) {
637*4cadd4f8SNickeau                                $stats[AnalyticsDocument::EXTERNAL_LINK_COUNT] = 0;
638*4cadd4f8SNickeau                            }
639*4cadd4f8SNickeau                            $stats[AnalyticsDocument::EXTERNAL_LINK_COUNT]++;
640*4cadd4f8SNickeau                            break;
641*4cadd4f8SNickeau
642*4cadd4f8SNickeau                        case MarkupRef::LOCAL_URI:
643*4cadd4f8SNickeau
644*4cadd4f8SNickeau                            if (!array_key_exists(AnalyticsDocument::LOCAL_LINK_COUNT, $stats)) {
645*4cadd4f8SNickeau                                $stats[AnalyticsDocument::LOCAL_LINK_COUNT] = 0;
646*4cadd4f8SNickeau                            }
647*4cadd4f8SNickeau                            $stats[AnalyticsDocument::LOCAL_LINK_COUNT]++;
648*4cadd4f8SNickeau                            break;
649*4cadd4f8SNickeau
650*4cadd4f8SNickeau                        case MarkupRef::INTERWIKI_URI:
651*4cadd4f8SNickeau
652*4cadd4f8SNickeau                            if (!array_key_exists(AnalyticsDocument::INTERWIKI_LINK_COUNT, $stats)) {
653*4cadd4f8SNickeau                                $stats[AnalyticsDocument::INTERWIKI_LINK_COUNT] = 0;
654*4cadd4f8SNickeau                            }
655*4cadd4f8SNickeau                            $stats[AnalyticsDocument::INTERWIKI_LINK_COUNT]++;
656*4cadd4f8SNickeau                            break;
657*4cadd4f8SNickeau
658*4cadd4f8SNickeau                        case MarkupRef::EMAIL_URI:
659*4cadd4f8SNickeau
660*4cadd4f8SNickeau                            if (!array_key_exists(AnalyticsDocument::EMAIL_COUNT, $stats)) {
661*4cadd4f8SNickeau                                $stats[AnalyticsDocument::EMAIL_COUNT] = 0;
662*4cadd4f8SNickeau                            }
663*4cadd4f8SNickeau                            $stats[AnalyticsDocument::EMAIL_COUNT]++;
664*4cadd4f8SNickeau                            break;
665*4cadd4f8SNickeau
666*4cadd4f8SNickeau                        case MarkupRef::WINDOWS_SHARE_URI:
667*4cadd4f8SNickeau
668*4cadd4f8SNickeau                            if (!array_key_exists(AnalyticsDocument::WINDOWS_SHARE_COUNT, $stats)) {
669*4cadd4f8SNickeau                                $stats[AnalyticsDocument::WINDOWS_SHARE_COUNT] = 0;
670*4cadd4f8SNickeau                            }
671*4cadd4f8SNickeau                            $stats[AnalyticsDocument::WINDOWS_SHARE_COUNT]++;
672*4cadd4f8SNickeau                            break;
673*4cadd4f8SNickeau
674*4cadd4f8SNickeau                        case MarkupRef::VARIABLE_URI:
675*4cadd4f8SNickeau
676*4cadd4f8SNickeau                            if (!array_key_exists(AnalyticsDocument::TEMPLATE_LINK_COUNT, $stats)) {
677*4cadd4f8SNickeau                                $stats[AnalyticsDocument::TEMPLATE_LINK_COUNT] = 0;
678*4cadd4f8SNickeau                            }
679*4cadd4f8SNickeau                            $stats[AnalyticsDocument::TEMPLATE_LINK_COUNT]++;
680*4cadd4f8SNickeau                            break;
681*4cadd4f8SNickeau
682*4cadd4f8SNickeau                        default:
683*4cadd4f8SNickeau
684*4cadd4f8SNickeau                            LogUtility::msg("The link `{$ref}` with the type ($refType)  is not taken into account into the statistics");
685*4cadd4f8SNickeau
686*4cadd4f8SNickeau                    }
687*4cadd4f8SNickeau
688*4cadd4f8SNickeau
689007225e5Sgerardnico                    break;
6905f891b7eSNickeau                }
691007225e5Sgerardnico
692007225e5Sgerardnico        }
693007225e5Sgerardnico        // unsupported $mode
694007225e5Sgerardnico        return false;
695007225e5Sgerardnico    }
696007225e5Sgerardnico
697007225e5Sgerardnico
698*4cadd4f8SNickeau    /**
699*4cadd4f8SNickeau     * Utility function to add a link into the callstack
700*4cadd4f8SNickeau     * @param CallStack $callStack
701*4cadd4f8SNickeau     * @param TagAttributes $tagAttributes
702*4cadd4f8SNickeau     */
703*4cadd4f8SNickeau    public static function addOpenLinkTagInCallStack(CallStack $callStack, TagAttributes $tagAttributes)
704*4cadd4f8SNickeau    {
705*4cadd4f8SNickeau        $parent = $callStack->moveToParent();
706*4cadd4f8SNickeau        $context = "";
707*4cadd4f8SNickeau        $attributes = $tagAttributes->toCallStackArray();
708*4cadd4f8SNickeau        if ($parent !== false) {
709*4cadd4f8SNickeau            $context = $parent->getTagName();
710*4cadd4f8SNickeau            if ($context === syntax_plugin_combo_button::TAG) {
711*4cadd4f8SNickeau                // the link takes by default the data from the button
712*4cadd4f8SNickeau                $parentAttributes = $parent->getAttributes();
713*4cadd4f8SNickeau                if ($parentAttributes !== null) {
714*4cadd4f8SNickeau                    $attributes = ArrayUtility::mergeByValue($parentAttributes, $attributes);
715*4cadd4f8SNickeau                }
716*4cadd4f8SNickeau            }
717*4cadd4f8SNickeau        }
718*4cadd4f8SNickeau        $callStack->appendCallAtTheEnd(
719*4cadd4f8SNickeau            Call::createComboCall(
720*4cadd4f8SNickeau                syntax_plugin_combo_link::TAG,
721*4cadd4f8SNickeau                DOKU_LEXER_ENTER,
722*4cadd4f8SNickeau                $attributes,
723*4cadd4f8SNickeau                $context
724*4cadd4f8SNickeau            ));
725*4cadd4f8SNickeau    }
726*4cadd4f8SNickeau
727*4cadd4f8SNickeau    public static function addExitLinkTagInCallStack(CallStack $callStack)
728*4cadd4f8SNickeau    {
729*4cadd4f8SNickeau        $callStack->appendCallAtTheEnd(
730*4cadd4f8SNickeau            Call::createComboCall(
731*4cadd4f8SNickeau                syntax_plugin_combo_link::TAG,
732*4cadd4f8SNickeau                DOKU_LEXER_EXIT
733*4cadd4f8SNickeau            ));
734*4cadd4f8SNickeau    }
735007225e5Sgerardnico}
736007225e5Sgerardnico
737