xref: /plugin/combo/syntax/link.php (revision c3437056399326d621a01da73b649707fbb0ae69)
1007225e5Sgerardnico<?php
2007225e5Sgerardnico
3007225e5Sgerardnico
437748cd8SNickeaurequire_once(__DIR__ . "/../ComboStrap/PluginUtility.php");
5007225e5Sgerardnico
6531e725cSNickeauuse ComboStrap\CallStack;
7007225e5Sgerardnicouse ComboStrap\LinkUtility;
8007225e5Sgerardnicouse ComboStrap\PluginUtility;
9531e725cSNickeauuse ComboStrap\TagAttributes;
10*c3437056SNickeauuse ComboStrap\ThirdPartyPlugins;
11007225e5Sgerardnico
12007225e5Sgerardnicoif (!defined('DOKU_INC')) die();
13007225e5Sgerardnico
14007225e5Sgerardnico/**
15007225e5Sgerardnico *
16007225e5Sgerardnico * A link pattern to take over the link of Dokuwiki
17007225e5Sgerardnico * and transform it as a bootstrap link
18007225e5Sgerardnico *
19007225e5Sgerardnico * The handle of the move of link is to be found in the
20007225e5Sgerardnico * admin action {@link action_plugin_combo_linkmove}
21007225e5Sgerardnico *
22007225e5Sgerardnico */
23007225e5Sgerardnicoclass syntax_plugin_combo_link extends DokuWiki_Syntax_Plugin
24007225e5Sgerardnico{
25007225e5Sgerardnico    const TAG = 'link';
26ef295d81Sgerardnico    const COMPONENT = 'combo_link';
27007225e5Sgerardnico
285f891b7eSNickeau    /**
2985e82846SNickeau     * Disable the link component
3021913ab3SNickeau     */
3121913ab3SNickeau    const CONF_DISABLE_LINK = "disableLink";
3221913ab3SNickeau
3321913ab3SNickeau    /**
345f891b7eSNickeau     * The link Tag
35531e725cSNickeau     * a or p
365f891b7eSNickeau     */
375f891b7eSNickeau    const LINK_TAG = "linkTag";
385f891b7eSNickeau
3921913ab3SNickeau    /**
4021913ab3SNickeau     * Do the link component allows to be spawn on multilines
4121913ab3SNickeau     */
42531e725cSNickeau    const CLICKABLE_ATTRIBUTE = "clickable";
4321913ab3SNickeau
445f891b7eSNickeau
45007225e5Sgerardnico    /**
46007225e5Sgerardnico     * Syntax Type.
47007225e5Sgerardnico     *
48007225e5Sgerardnico     * Needs to return one of the mode types defined in $PARSER_MODES in parser.php
49007225e5Sgerardnico     * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types
50007225e5Sgerardnico     */
51007225e5Sgerardnico    function getType()
52007225e5Sgerardnico    {
53007225e5Sgerardnico        return 'substition';
54007225e5Sgerardnico    }
55007225e5Sgerardnico
56007225e5Sgerardnico    /**
57007225e5Sgerardnico     * How Dokuwiki will add P element
58007225e5Sgerardnico     *
59007225e5Sgerardnico     *  * 'normal' - The plugin can be used inside paragraphs
60007225e5Sgerardnico     *  * 'block'  - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs
61007225e5Sgerardnico     *  * 'stack'  - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs
62007225e5Sgerardnico     *
63007225e5Sgerardnico     * @see DokuWiki_Syntax_Plugin::getPType()
64007225e5Sgerardnico     */
65007225e5Sgerardnico    function getPType()
66007225e5Sgerardnico    {
67007225e5Sgerardnico        return 'normal';
68007225e5Sgerardnico    }
69007225e5Sgerardnico
70007225e5Sgerardnico    /**
71007225e5Sgerardnico     * @return array
72007225e5Sgerardnico     * Allow which kind of plugin inside
73007225e5Sgerardnico     *
74007225e5Sgerardnico     * No one of array('container', 'baseonly', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs')
75007225e5Sgerardnico     * because we manage self the content and we call self the parser
76007225e5Sgerardnico     */
77007225e5Sgerardnico    function getAllowedTypes()
78007225e5Sgerardnico    {
79007225e5Sgerardnico        return array('substition', 'formatting', 'disabled');
80007225e5Sgerardnico    }
81007225e5Sgerardnico
8237748cd8SNickeau    /**
8337748cd8SNickeau     * @param string $mode
8437748cd8SNickeau     * @return bool
8537748cd8SNickeau     * Accepts inside
8637748cd8SNickeau     */
875f891b7eSNickeau    public function accepts($mode)
885f891b7eSNickeau    {
895f891b7eSNickeau        /**
905f891b7eSNickeau         * To avoid that the description if it contains a link
915f891b7eSNickeau         * will be taken by the links mode
925f891b7eSNickeau         *
935f891b7eSNickeau         * For instance, [[https://hallo|https://hallo]] will send https://hallo
945f891b7eSNickeau         * to the external link mode
955f891b7eSNickeau         */
965f891b7eSNickeau        $linkModes = [
975f891b7eSNickeau            "externallink",
985f891b7eSNickeau            "locallink",
995f891b7eSNickeau            "internallink",
1005f891b7eSNickeau            "interwikilink",
1015f891b7eSNickeau            "emaillink",
102fc45fbf7Sgerardnico            "emphasis", // double slash can not be used inside to preserve the possibility to write an URL in the description
1035f891b7eSNickeau            //"emphasis_open", // italic use // and therefore take over a link as description which is not handy when copying a tweet
1045f891b7eSNickeau            //"emphasis_close",
1055f891b7eSNickeau            //"acrnonym"
1065f891b7eSNickeau        ];
1075f891b7eSNickeau        if (in_array($mode, $linkModes)) {
1085f891b7eSNickeau            return false;
1095f891b7eSNickeau        } else {
1105f891b7eSNickeau            return true;
1115f891b7eSNickeau        }
1125f891b7eSNickeau    }
1135f891b7eSNickeau
1145f891b7eSNickeau
115007225e5Sgerardnico    /**
116007225e5Sgerardnico     * @see Doku_Parser_Mode::getSort()
117007225e5Sgerardnico     * The mode with the lowest sort number will win out
118007225e5Sgerardnico     */
119007225e5Sgerardnico    function getSort()
120007225e5Sgerardnico    {
121e8b2ff59SNickeau        /**
122e8b2ff59SNickeau         * It should be less than the number
123e8b2ff59SNickeau         * at {@link \dokuwiki\Parsing\ParserMode\Internallink::getSort}
124e8b2ff59SNickeau         * and the like
125e8b2ff59SNickeau         *
126e8b2ff59SNickeau         * For whatever reason, the number below should be less than 100,
127e8b2ff59SNickeau         * otherwise on windows with DokuWiki Stick, the link syntax may be not taken
128e8b2ff59SNickeau         * into account
129e8b2ff59SNickeau         */
130e8b2ff59SNickeau        return 99;
131007225e5Sgerardnico    }
132007225e5Sgerardnico
133007225e5Sgerardnico
134007225e5Sgerardnico    function connectTo($mode)
135007225e5Sgerardnico    {
136d262537cSgerardnico
13737748cd8SNickeau        if (!$this->getConf(self::CONF_DISABLE_LINK, false)
13837748cd8SNickeau            &&
13937748cd8SNickeau            $mode !== PluginUtility::getModeFromPluginName(ThirdPartyPlugins::IMAGE_MAPPING_NAME)
14037748cd8SNickeau        ) {
14137748cd8SNickeau
14221913ab3SNickeau            $pattern = LinkUtility::ENTRY_PATTERN_SINGLE_LINE;
1439337a630SNickeau            $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeFromTag($this->getPluginComponent()));
14437748cd8SNickeau
14521913ab3SNickeau        }
146d262537cSgerardnico
147007225e5Sgerardnico    }
148007225e5Sgerardnico
1495f891b7eSNickeau    public function postConnect()
1505f891b7eSNickeau    {
15121913ab3SNickeau        if (!$this->getConf(self::CONF_DISABLE_LINK, false)) {
1529337a630SNickeau            $this->Lexer->addExitPattern(LinkUtility::EXIT_PATTERN, PluginUtility::getModeFromTag($this->getPluginComponent()));
1535f891b7eSNickeau        }
15421913ab3SNickeau    }
1555f891b7eSNickeau
156007225e5Sgerardnico
157007225e5Sgerardnico    /**
158007225e5Sgerardnico     * The handler for an internal link
159007225e5Sgerardnico     * based on `internallink` in {@link Doku_Handler}
160007225e5Sgerardnico     * The handler call the good renderer in {@link Doku_Renderer_xhtml} with
161007225e5Sgerardnico     * the parameters (ie for instance internallink)
162007225e5Sgerardnico     * @param string $match
163007225e5Sgerardnico     * @param int $state
164007225e5Sgerardnico     * @param int $pos
165007225e5Sgerardnico     * @param Doku_Handler $handler
166007225e5Sgerardnico     * @return array|bool
167007225e5Sgerardnico     */
168007225e5Sgerardnico    function handle($match, $state, $pos, Doku_Handler $handler)
169007225e5Sgerardnico    {
170007225e5Sgerardnico
171531e725cSNickeau
1725f891b7eSNickeau        switch ($state) {
1735f891b7eSNickeau            case DOKU_LEXER_ENTER:
174531e725cSNickeau                $tagAttributes = TagAttributes::createFromCallStackArray(LinkUtility::parse($match));
175531e725cSNickeau                $callStack = CallStack::createFromHandler($handler);
176531e725cSNickeau
177531e725cSNickeau
178531e725cSNickeau                $parent = $callStack->moveToParent();
179007225e5Sgerardnico                $parentName = "";
180531e725cSNickeau                if ($parent != false) {
181531e725cSNickeau
182531e725cSNickeau                    /**
183531e725cSNickeau                     * Button Link
184531e725cSNickeau                     * Getting the attributes
185531e725cSNickeau                     */
186531e725cSNickeau                    $parentName = $parent->getTagName();
187531e725cSNickeau                    if ($parentName == syntax_plugin_combo_button::TAG) {
188531e725cSNickeau                        $tagAttributes->mergeWithCallStackArray($parent->getAttributes());
18921913ab3SNickeau                    }
190531e725cSNickeau
191531e725cSNickeau                    /**
192531e725cSNickeau                     * Searching Clickable parent
193531e725cSNickeau                     */
194531e725cSNickeau                    $maxLevel = 3;
195531e725cSNickeau                    $level = 0;
196531e725cSNickeau                    while (
197531e725cSNickeau                        $parent != false &&
198531e725cSNickeau                        !$parent->hasAttribute(self::CLICKABLE_ATTRIBUTE) &&
199531e725cSNickeau                        $level < $maxLevel
200531e725cSNickeau                    ) {
201531e725cSNickeau                        $parent = $callStack->moveToParent();
202531e725cSNickeau                        $level++;
2035f891b7eSNickeau                    }
204531e725cSNickeau                    if ($parent != false) {
205531e725cSNickeau                        if ($parent->getAttribute(self::CLICKABLE_ATTRIBUTE)) {
206531e725cSNickeau                            $tagAttributes->addClassName("stretched-link");
207531e725cSNickeau                            $parent->addClassName("position-relative");
208531e725cSNickeau                            $parent->removeAttribute(self::CLICKABLE_ATTRIBUTE);
2095f891b7eSNickeau                        }
21021913ab3SNickeau                    }
21121913ab3SNickeau
212531e725cSNickeau                }
21385e82846SNickeau                $callStackAttributes = $tagAttributes->toCallStackArray();
2145f891b7eSNickeau                return array(
2155f891b7eSNickeau                    PluginUtility::STATE => $state,
21685e82846SNickeau                    PluginUtility::ATTRIBUTES => $callStackAttributes,
21785e82846SNickeau                    PluginUtility::CONTEXT => $parentName
2185f891b7eSNickeau                );
2195f891b7eSNickeau            case DOKU_LEXER_UNMATCHED:
2205f891b7eSNickeau
22132b85071SNickeau                $data = PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler);
2225f891b7eSNickeau                /**
22332b85071SNickeau                 * Delete the separator `|` between the ref and the description if any
2245f891b7eSNickeau                 */
2251fa8c418SNickeau                $tag = CallStack::createFromHandler($handler);
2261fa8c418SNickeau                $parent = $tag->moveToParent();
2271fa8c418SNickeau                if ($parent->getTagName() == self::TAG) {
2285f891b7eSNickeau                    if (strpos($match, '|') === 0) {
22932b85071SNickeau                        $data[PluginUtility::PAYLOAD] = substr($match, 1);
2305f891b7eSNickeau                    }
231007225e5Sgerardnico                }
23232b85071SNickeau                return $data;
233007225e5Sgerardnico
2345f891b7eSNickeau            case DOKU_LEXER_EXIT:
235531e725cSNickeau                $callStack = CallStack::createFromHandler($handler);
236531e725cSNickeau                $openingTag = $callStack->moveToPreviousCorrespondingOpeningCall();
2375f891b7eSNickeau                $openingAttributes = $openingTag->getAttributes();
238531e725cSNickeau                $openingPosition = $openingTag->getKey();
2395f891b7eSNickeau
240531e725cSNickeau                $callStack->moveToEnd();
241531e725cSNickeau                $previousCall = $callStack->previous();
242531e725cSNickeau                $previousCallPosition = $previousCall->getKey();
243531e725cSNickeau                $previousCallContent = $previousCall->getCapturedContent();
244531e725cSNickeau
245531e725cSNickeau                if (
246531e725cSNickeau                    $openingPosition == $previousCallPosition // ie [[id]]
247531e725cSNickeau                    ||
248531e725cSNickeau                    ($openingPosition == $previousCallPosition - 1 && $previousCallContent == "|") // ie [[id|]]
249531e725cSNickeau                ) {
2505f891b7eSNickeau                    // There is no name
2515f891b7eSNickeau                    $link = new LinkUtility($openingAttributes[LinkUtility::ATTRIBUTE_REF]);
2525f891b7eSNickeau                    $linkName = $link->getName();
2535f891b7eSNickeau                } else {
2545f891b7eSNickeau                    $linkName = "";
2555f891b7eSNickeau                }
2565f891b7eSNickeau                return array(
2575f891b7eSNickeau                    PluginUtility::STATE => $state,
2585f891b7eSNickeau                    PluginUtility::ATTRIBUTES => $openingAttributes,
2595f891b7eSNickeau                    PluginUtility::PAYLOAD => $linkName,
26085e82846SNickeau                    PluginUtility::CONTEXT => $openingTag->getContext()
2615f891b7eSNickeau                );
2625f891b7eSNickeau        }
2635f891b7eSNickeau        return true;
2645f891b7eSNickeau
265007225e5Sgerardnico
266007225e5Sgerardnico    }
267007225e5Sgerardnico
268007225e5Sgerardnico    /**
269007225e5Sgerardnico     * Render the output
270007225e5Sgerardnico     * @param string $format
271007225e5Sgerardnico     * @param Doku_Renderer $renderer
272007225e5Sgerardnico     * @param array $data - what the function handle() return'ed
273007225e5Sgerardnico     * @return boolean - rendered correctly? (however, returned value is not used at the moment)
274007225e5Sgerardnico     * @see DokuWiki_Syntax_Plugin::render()
275007225e5Sgerardnico     *
276007225e5Sgerardnico     *
277007225e5Sgerardnico     */
278*c3437056SNickeau    function render($format, Doku_Renderer $renderer, $data): bool
279007225e5Sgerardnico    {
280007225e5Sgerardnico        // The data
281007225e5Sgerardnico        switch ($format) {
282007225e5Sgerardnico            case 'xhtml':
283007225e5Sgerardnico
284007225e5Sgerardnico                /** @var Doku_Renderer_xhtml $renderer */
285007225e5Sgerardnico                /**
28619b0880dSgerardnico                 * Cache problem may occurs while releasing
287007225e5Sgerardnico                 */
288007225e5Sgerardnico                if (isset($data[PluginUtility::ATTRIBUTES])) {
289531e725cSNickeau                    $callStackAttributes = $data[PluginUtility::ATTRIBUTES];
290007225e5Sgerardnico                } else {
291531e725cSNickeau                    $callStackAttributes = $data;
292007225e5Sgerardnico                }
2935f891b7eSNickeau
29421913ab3SNickeau                PluginUtility::getSnippetManager()->attachCssSnippetForBar(self::TAG);
2955f891b7eSNickeau
2965f891b7eSNickeau                $state = $data[PluginUtility::STATE];
2975f891b7eSNickeau                switch ($state) {
2985f891b7eSNickeau                    case DOKU_LEXER_ENTER:
299531e725cSNickeau                        $tagAttributes = TagAttributes::createFromCallStackArray($callStackAttributes, self::TAG);
300531e725cSNickeau                        $ref = $tagAttributes->getValueAndRemove(LinkUtility::ATTRIBUTE_REF);
301*c3437056SNickeau                        $link = LinkUtility::createFromRef($ref, $tagAttributes);
302d262537cSgerardnico
30319b0880dSgerardnico                        /**
3045f891b7eSNickeau                         * Extra styling
30519b0880dSgerardnico                         */
3065f891b7eSNickeau                        $parentTag = $data[PluginUtility::CONTEXT];
3079f4383e9Sgerardnico                        switch ($parentTag) {
30821913ab3SNickeau                            /**
30921913ab3SNickeau                             * Button link
31021913ab3SNickeau                             */
3119f4383e9Sgerardnico                            case syntax_plugin_combo_button::TAG:
312531e725cSNickeau                                $tagAttributes->addHtmlAttributeValue("role", "button");
313531e725cSNickeau                                syntax_plugin_combo_button::processButtonAttributesToHtmlAttributes($tagAttributes);
3145f891b7eSNickeau                                $htmlLink = $link->renderOpenTag($renderer);
3159f4383e9Sgerardnico                                break;
3165f891b7eSNickeau                            case syntax_plugin_combo_badge::TAG:
3179f4383e9Sgerardnico                            case syntax_plugin_combo_cite::TAG:
3189337a630SNickeau                            case syntax_plugin_combo_contentlistitem::DOKU_TAG:
3199f4383e9Sgerardnico                            case syntax_plugin_combo_preformatted::TAG:
3205f891b7eSNickeau                                $htmlLink = $link->renderOpenTag($renderer);
3219f4383e9Sgerardnico                                break;
3220a517624Sgerardnico                            case syntax_plugin_combo_dropdown::TAG:
323531e725cSNickeau                                $tagAttributes->addClassName("dropdown-item");
3245f891b7eSNickeau                                $htmlLink = $link->renderOpenTag($renderer);
3250a517624Sgerardnico                                break;
3269f4383e9Sgerardnico                            case syntax_plugin_combo_navbarcollapse::COMPONENT:
327531e725cSNickeau                                $tagAttributes->addClassName("navbar-link");
3285f891b7eSNickeau                                $htmlLink = '<div class="navbar-nav">' . $link->renderOpenTag($renderer);
3299f4383e9Sgerardnico                                break;
3300a517624Sgerardnico                            case syntax_plugin_combo_navbargroup::COMPONENT:
331531e725cSNickeau                                $tagAttributes->addClassName("nav-link");
3325f891b7eSNickeau                                $htmlLink = '<li class="nav-item">' . $link->renderOpenTag($renderer);
3330a517624Sgerardnico                                break;
3345f891b7eSNickeau                            default:
3355f891b7eSNickeau                                $htmlLink = $link->renderOpenTag($renderer);
3360a517624Sgerardnico
3379f4383e9Sgerardnico                        }
3389f4383e9Sgerardnico
33919b0880dSgerardnico
34019b0880dSgerardnico                        /**
34119b0880dSgerardnico                         * Add it to the rendering
34219b0880dSgerardnico                         */
343007225e5Sgerardnico                        $renderer->doc .= $htmlLink;
3445f891b7eSNickeau                        break;
3455f891b7eSNickeau                    case DOKU_LEXER_UNMATCHED:
34632b85071SNickeau                        $renderer->doc .= PluginUtility::renderUnmatched($data);
3475f891b7eSNickeau                        break;
3485f891b7eSNickeau                    case DOKU_LEXER_EXIT:
3495f891b7eSNickeau
3505f891b7eSNickeau                        // if there is no link name defined, we get the name as ref in the payload
3515f891b7eSNickeau                        // otherwise null string
35285e82846SNickeau                        $renderer->doc .= $data[PluginUtility::PAYLOAD];
3535f891b7eSNickeau
354e3d0019cSgerardnico                        // Close the link
35585e82846SNickeau                        $renderer->doc .= "</a>";
356e3d0019cSgerardnico
357e3d0019cSgerardnico                        // Close the html wrapper element
3585f891b7eSNickeau                        $context = $data[PluginUtility::CONTEXT];
3595f891b7eSNickeau                        switch ($context) {
3605f891b7eSNickeau                            case syntax_plugin_combo_navbarcollapse::COMPONENT:
3615f891b7eSNickeau                                $renderer->doc .= '</div>';
3625f891b7eSNickeau                                break;
3635f891b7eSNickeau                            case syntax_plugin_combo_navbargroup::COMPONENT:
3645f891b7eSNickeau                                $renderer->doc .= '</li>';
3655f891b7eSNickeau                                break;
3665f891b7eSNickeau                        }
3675f891b7eSNickeau
368e3d0019cSgerardnico
3695f891b7eSNickeau                }
3705f891b7eSNickeau
371007225e5Sgerardnico
372007225e5Sgerardnico                return true;
373007225e5Sgerardnico
3745f891b7eSNickeau            case 'metadata':
375007225e5Sgerardnico
3765f891b7eSNickeau                $state = $data[PluginUtility::STATE];
3775f891b7eSNickeau                if ($state == DOKU_LEXER_ENTER) {
378007225e5Sgerardnico                    /**
379007225e5Sgerardnico                     * Keep track of the backlinks ie meta['relation']['references']
380007225e5Sgerardnico                     * @var Doku_Renderer_metadata $renderer
381007225e5Sgerardnico                     */
382007225e5Sgerardnico                    if (isset($data[PluginUtility::ATTRIBUTES])) {
383531e725cSNickeau                        $tagAttributes = $data[PluginUtility::ATTRIBUTES];
384007225e5Sgerardnico                    } else {
385531e725cSNickeau                        $tagAttributes = $data;
386007225e5Sgerardnico                    }
387531e725cSNickeau                    $ref = $tagAttributes[LinkUtility::ATTRIBUTE_REF];
3889f4383e9Sgerardnico
3899f4383e9Sgerardnico                    $link = new LinkUtility($ref);
390531e725cSNickeau                    $name = $tagAttributes[LinkUtility::ATTRIBUTE_NAME];
3919f4383e9Sgerardnico                    if ($name != null) {
3929f4383e9Sgerardnico                        $link->setName($name);
3939f4383e9Sgerardnico                    }
3949f4383e9Sgerardnico                    $link->handleMetadata($renderer);
395007225e5Sgerardnico
396007225e5Sgerardnico                    return true;
3975f891b7eSNickeau                }
398007225e5Sgerardnico                break;
399007225e5Sgerardnico
400531e725cSNickeau            case renderer_plugin_combo_analytics::RENDERER_FORMAT:
4015f891b7eSNickeau
4025f891b7eSNickeau                $state = $data[PluginUtility::STATE];
4035f891b7eSNickeau                if ($state == DOKU_LEXER_ENTER) {
404007225e5Sgerardnico                    /**
405007225e5Sgerardnico                     *
406007225e5Sgerardnico                     * @var renderer_plugin_combo_analytics $renderer
407007225e5Sgerardnico                     */
408531e725cSNickeau                    $tagAttributes = $data[PluginUtility::ATTRIBUTES];
409531e725cSNickeau                    $ref = $tagAttributes[LinkUtility::ATTRIBUTE_REF];
4109f4383e9Sgerardnico                    $link = new LinkUtility($ref);
4119f4383e9Sgerardnico                    $link->processLinkStats($renderer->stats);
412007225e5Sgerardnico                    break;
4135f891b7eSNickeau                }
414007225e5Sgerardnico
415007225e5Sgerardnico        }
416007225e5Sgerardnico        // unsupported $mode
417007225e5Sgerardnico        return false;
418007225e5Sgerardnico    }
419007225e5Sgerardnico
420007225e5Sgerardnico
421007225e5Sgerardnico}
422007225e5Sgerardnico
423