xref: /plugin/combo/syntax/link.php (revision 21913ab3235d516e2fa19c7e3929b555b3a2bda1)
1007225e5Sgerardnico<?php
2007225e5Sgerardnico
3007225e5Sgerardnico
471f916b9Sgerardnicorequire_once(__DIR__ . "/../class/Analytics.php");
5007225e5Sgerardnicorequire_once(__DIR__ . "/../class/PluginUtility.php");
6007225e5Sgerardnicorequire_once(__DIR__ . "/../class/LinkUtility.php");
7007225e5Sgerardnicorequire_once(__DIR__ . "/../class/HtmlUtility.php");
8007225e5Sgerardnico
9007225e5Sgerardnicouse ComboStrap\Analytics;
10007225e5Sgerardnicouse ComboStrap\LinkUtility;
11007225e5Sgerardnicouse ComboStrap\PluginUtility;
12007225e5Sgerardnicouse ComboStrap\Tag;
13007225e5Sgerardnico
14007225e5Sgerardnicoif (!defined('DOKU_INC')) die();
15007225e5Sgerardnico
16007225e5Sgerardnico/**
17007225e5Sgerardnico *
18007225e5Sgerardnico * A link pattern to take over the link of Dokuwiki
19007225e5Sgerardnico * and transform it as a bootstrap link
20007225e5Sgerardnico *
21007225e5Sgerardnico * The handle of the move of link is to be found in the
22007225e5Sgerardnico * admin action {@link action_plugin_combo_linkmove}
23007225e5Sgerardnico *
24007225e5Sgerardnico */
25007225e5Sgerardnicoclass syntax_plugin_combo_link extends DokuWiki_Syntax_Plugin
26007225e5Sgerardnico{
27007225e5Sgerardnico    const TAG = 'link';
28ef295d81Sgerardnico    const COMPONENT = 'combo_link';
29007225e5Sgerardnico
305f891b7eSNickeau    /**
31*21913ab3SNickeau     * Disable the link
32*21913ab3SNickeau     */
33*21913ab3SNickeau    const CONF_DISABLE_LINK = "disableLink";
34*21913ab3SNickeau
35*21913ab3SNickeau    /**
365f891b7eSNickeau     * The link Tag
375f891b7eSNickeau     */
385f891b7eSNickeau    const LINK_TAG = "linkTag";
395f891b7eSNickeau
40*21913ab3SNickeau    /**
41*21913ab3SNickeau     * Do the link component allows to be spawn on multilines
42*21913ab3SNickeau     */
43*21913ab3SNickeau    const CONF_ENABLE_MULTI_LINES_LINK = "enableMultiLinesLink";
44*21913ab3SNickeau
455f891b7eSNickeau
46007225e5Sgerardnico    /**
47007225e5Sgerardnico     * Syntax Type.
48007225e5Sgerardnico     *
49007225e5Sgerardnico     * Needs to return one of the mode types defined in $PARSER_MODES in parser.php
50007225e5Sgerardnico     * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types
51007225e5Sgerardnico     */
52007225e5Sgerardnico    function getType()
53007225e5Sgerardnico    {
54007225e5Sgerardnico        return 'substition';
55007225e5Sgerardnico    }
56007225e5Sgerardnico
57007225e5Sgerardnico    /**
58007225e5Sgerardnico     * How Dokuwiki will add P element
59007225e5Sgerardnico     *
60007225e5Sgerardnico     *  * 'normal' - The plugin can be used inside paragraphs
61007225e5Sgerardnico     *  * 'block'  - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs
62007225e5Sgerardnico     *  * 'stack'  - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs
63007225e5Sgerardnico     *
64007225e5Sgerardnico     * @see DokuWiki_Syntax_Plugin::getPType()
65007225e5Sgerardnico     */
66007225e5Sgerardnico    function getPType()
67007225e5Sgerardnico    {
68007225e5Sgerardnico        return 'normal';
69007225e5Sgerardnico    }
70007225e5Sgerardnico
71007225e5Sgerardnico    /**
72007225e5Sgerardnico     * @return array
73007225e5Sgerardnico     * Allow which kind of plugin inside
74007225e5Sgerardnico     *
75007225e5Sgerardnico     * No one of array('container', 'baseonly', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs')
76007225e5Sgerardnico     * because we manage self the content and we call self the parser
77007225e5Sgerardnico     */
78007225e5Sgerardnico    function getAllowedTypes()
79007225e5Sgerardnico    {
80007225e5Sgerardnico        return array('substition', 'formatting', 'disabled');
81007225e5Sgerardnico    }
82007225e5Sgerardnico
835f891b7eSNickeau    public function accepts($mode)
845f891b7eSNickeau    {
855f891b7eSNickeau        /**
865f891b7eSNickeau         * To avoid that the description if it contains a link
875f891b7eSNickeau         * will be taken by the links mode
885f891b7eSNickeau         *
895f891b7eSNickeau         * For instance, [[https://hallo|https://hallo]] will send https://hallo
905f891b7eSNickeau         * to the external link mode
915f891b7eSNickeau         */
925f891b7eSNickeau        $linkModes = [
935f891b7eSNickeau            "externallink",
945f891b7eSNickeau            "locallink",
955f891b7eSNickeau            "internallink",
965f891b7eSNickeau            "interwikilink",
975f891b7eSNickeau            "emaillink",
985f891b7eSNickeau            //"emphasis_open", // italic use // and therefore take over a link as description which is not handy when copying a tweet
995f891b7eSNickeau            //"emphasis_close",
1005f891b7eSNickeau            //"acrnonym"
1015f891b7eSNickeau        ];
1025f891b7eSNickeau        if (in_array($mode, $linkModes)) {
1035f891b7eSNickeau            return false;
1045f891b7eSNickeau        } else {
1055f891b7eSNickeau            return true;
1065f891b7eSNickeau        }
1075f891b7eSNickeau    }
1085f891b7eSNickeau
1095f891b7eSNickeau
110007225e5Sgerardnico    /**
111007225e5Sgerardnico     * @see Doku_Parser_Mode::getSort()
112007225e5Sgerardnico     * The mode with the lowest sort number will win out
113007225e5Sgerardnico     */
114007225e5Sgerardnico    function getSort()
115007225e5Sgerardnico    {
116007225e5Sgerardnico        return 100;
117007225e5Sgerardnico    }
118007225e5Sgerardnico
119007225e5Sgerardnico
120007225e5Sgerardnico    function connectTo($mode)
121007225e5Sgerardnico    {
122d262537cSgerardnico
123*21913ab3SNickeau        if (!$this->getConf(self::CONF_DISABLE_LINK, false)) {
124*21913ab3SNickeau            $pattern = LinkUtility::ENTRY_PATTERN_SINGLE_LINE;
125*21913ab3SNickeau            if ($this->getConf(self::CONF_ENABLE_MULTI_LINES_LINK,false)){
126*21913ab3SNickeau                $pattern = LinkUtility::ENTRY_PATTERN_MULTI_LINE;
127*21913ab3SNickeau            }
128*21913ab3SNickeau            $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeForComponent($this->getPluginComponent()));
129*21913ab3SNickeau        }
130d262537cSgerardnico
131007225e5Sgerardnico    }
132007225e5Sgerardnico
1335f891b7eSNickeau    public function postConnect()
1345f891b7eSNickeau    {
135*21913ab3SNickeau        if (!$this->getConf(self::CONF_DISABLE_LINK, false)) {
1365f891b7eSNickeau            $this->Lexer->addExitPattern(LinkUtility::EXIT_PATTERN, PluginUtility::getModeForComponent($this->getPluginComponent()));
1375f891b7eSNickeau        }
138*21913ab3SNickeau    }
1395f891b7eSNickeau
140007225e5Sgerardnico
141007225e5Sgerardnico    /**
142007225e5Sgerardnico     * The handler for an internal link
143007225e5Sgerardnico     * based on `internallink` in {@link Doku_Handler}
144007225e5Sgerardnico     * The handler call the good renderer in {@link Doku_Renderer_xhtml} with
145007225e5Sgerardnico     * the parameters (ie for instance internallink)
146007225e5Sgerardnico     * @param string $match
147007225e5Sgerardnico     * @param int $state
148007225e5Sgerardnico     * @param int $pos
149007225e5Sgerardnico     * @param Doku_Handler $handler
150007225e5Sgerardnico     * @return array|bool
151007225e5Sgerardnico     */
152007225e5Sgerardnico    function handle($match, $state, $pos, Doku_Handler $handler)
153007225e5Sgerardnico    {
154007225e5Sgerardnico
155007225e5Sgerardnico        /**
156007225e5Sgerardnico         * Because we use the specialPattern, there is only one state ie DOKU_LEXER_SPECIAL
157007225e5Sgerardnico         */
1585f891b7eSNickeau        switch ($state) {
1595f891b7eSNickeau            case DOKU_LEXER_ENTER:
1605f891b7eSNickeau                $attributes = LinkUtility::parse($match);
1615f891b7eSNickeau                $tag = new Tag(self::TAG, $attributes, $state, $handler);
162007225e5Sgerardnico                $parent = $tag->getParent();
163007225e5Sgerardnico                $parentName = "";
164007225e5Sgerardnico                if ($parent != null) {
165007225e5Sgerardnico                    $parentName = $parent->getName();
166*21913ab3SNickeau                    switch ($parentName) {
167*21913ab3SNickeau                        case syntax_plugin_combo_button::TAG:
1685f891b7eSNickeau                            $attributes = PluginUtility::mergeAttributes($attributes, $parent->getAttributes());
169*21913ab3SNickeau                            $firstContainingBlock = $parent->getParent();
170*21913ab3SNickeau                            break;
171*21913ab3SNickeau                        case syntax_plugin_combo_column::TAG:
172*21913ab3SNickeau                            // A col is in a row
173*21913ab3SNickeau                            $firstContainingBlock = $parent->getParent();
174*21913ab3SNickeau                            break;
175*21913ab3SNickeau                        case "section":
176*21913ab3SNickeau                            // When editing, there is a section
177*21913ab3SNickeau                            $firstContainingBlock = $parent->getParent();
178*21913ab3SNickeau                            break;
179*21913ab3SNickeau                        default:
180*21913ab3SNickeau                            $firstContainingBlock = $parent;
181*21913ab3SNickeau                    }
182*21913ab3SNickeau                    if ($firstContainingBlock != null) {
183*21913ab3SNickeau                        if ($firstContainingBlock->getAttribute("clickable")) {
184*21913ab3SNickeau                            PluginUtility::addClass2Attributes("stretched-link", $attributes);
185*21913ab3SNickeau                            $firstContainingBlock->addClass("position-relative");
186*21913ab3SNickeau                            $firstContainingBlock->unsetAttribute("clickable");
1875f891b7eSNickeau                        }
1885f891b7eSNickeau                    }
189*21913ab3SNickeau                }
190*21913ab3SNickeau
1915f891b7eSNickeau                $link = new LinkUtility($attributes[LinkUtility::ATTRIBUTE_REF]);
1925f891b7eSNickeau                $linkTag = $link->getHtmlTag();
1935f891b7eSNickeau                return array(
1945f891b7eSNickeau                    PluginUtility::STATE => $state,
1955f891b7eSNickeau                    PluginUtility::ATTRIBUTES => $attributes,
1965f891b7eSNickeau                    PluginUtility::CONTEXT => $parentName,
1975f891b7eSNickeau                    self::LINK_TAG => $linkTag
1985f891b7eSNickeau                );
1995f891b7eSNickeau            case DOKU_LEXER_UNMATCHED:
2005f891b7eSNickeau
20132b85071SNickeau                $data = PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler);
2025f891b7eSNickeau                /**
20332b85071SNickeau                 * Delete the separator `|` between the ref and the description if any
2045f891b7eSNickeau                 */
2055f891b7eSNickeau                $tag = new Tag(self::TAG, array(), $state, $handler);
2065f891b7eSNickeau                $parent = $tag->getParent();
2075f891b7eSNickeau                if ($parent->getName() == self::TAG) {
2085f891b7eSNickeau                    if (strpos($match, '|') === 0) {
20932b85071SNickeau                        $data[PluginUtility::PAYLOAD] = substr($match, 1);
2105f891b7eSNickeau                    }
211007225e5Sgerardnico                }
21232b85071SNickeau                return $data;
213007225e5Sgerardnico
2145f891b7eSNickeau            case DOKU_LEXER_EXIT:
2155f891b7eSNickeau                $tag = new Tag(self::TAG, array(), $state, $handler);
2165f891b7eSNickeau                $openingTag = $tag->getOpeningTag();
2175f891b7eSNickeau                $openingAttributes = $openingTag->getAttributes();
2185f891b7eSNickeau                $linkTag = $openingTag->getData()[self::LINK_TAG];
2195f891b7eSNickeau
220*21913ab3SNickeau                if ($openingTag->getActualPosition() == $tag->getActualPosition() - 1) {
2215f891b7eSNickeau                    // There is no name
2225f891b7eSNickeau                    $link = new LinkUtility($openingAttributes[LinkUtility::ATTRIBUTE_REF]);
2235f891b7eSNickeau                    $linkName = $link->getName();
2245f891b7eSNickeau                } else {
2255f891b7eSNickeau                    $linkName = "";
2265f891b7eSNickeau                }
2275f891b7eSNickeau                return array(
2285f891b7eSNickeau                    PluginUtility::STATE => $state,
2295f891b7eSNickeau                    PluginUtility::ATTRIBUTES => $openingAttributes,
2305f891b7eSNickeau                    PluginUtility::PAYLOAD => $linkName,
2315f891b7eSNickeau                    PluginUtility::CONTEXT => $openingTag->getContext(),
2325f891b7eSNickeau                    self::LINK_TAG => $linkTag
2335f891b7eSNickeau                );
2345f891b7eSNickeau        }
2355f891b7eSNickeau        return true;
2365f891b7eSNickeau
237007225e5Sgerardnico
238007225e5Sgerardnico    }
239007225e5Sgerardnico
240007225e5Sgerardnico    /**
241007225e5Sgerardnico     * Render the output
242007225e5Sgerardnico     * @param string $format
243007225e5Sgerardnico     * @param Doku_Renderer $renderer
244007225e5Sgerardnico     * @param array $data - what the function handle() return'ed
245007225e5Sgerardnico     * @return boolean - rendered correctly? (however, returned value is not used at the moment)
246007225e5Sgerardnico     * @see DokuWiki_Syntax_Plugin::render()
247007225e5Sgerardnico     *
248007225e5Sgerardnico     *
249007225e5Sgerardnico     */
250007225e5Sgerardnico    function render($format, Doku_Renderer $renderer, $data)
251007225e5Sgerardnico    {
252007225e5Sgerardnico        // The data
253007225e5Sgerardnico        switch ($format) {
254007225e5Sgerardnico            case 'xhtml':
255007225e5Sgerardnico
256007225e5Sgerardnico                /** @var Doku_Renderer_xhtml $renderer */
257007225e5Sgerardnico                /**
25819b0880dSgerardnico                 * Cache problem may occurs while releasing
259007225e5Sgerardnico                 */
260007225e5Sgerardnico                if (isset($data[PluginUtility::ATTRIBUTES])) {
261007225e5Sgerardnico                    $attributes = $data[PluginUtility::ATTRIBUTES];
262007225e5Sgerardnico                } else {
263007225e5Sgerardnico                    $attributes = $data;
264007225e5Sgerardnico                }
2655f891b7eSNickeau
266*21913ab3SNickeau                PluginUtility::getSnippetManager()->attachCssSnippetForBar(self::TAG);
2675f891b7eSNickeau
2685f891b7eSNickeau
2695f891b7eSNickeau                $state = $data[PluginUtility::STATE];
2705f891b7eSNickeau                $payload = $data[PluginUtility::PAYLOAD];
2715f891b7eSNickeau                switch ($state) {
2725f891b7eSNickeau                    case DOKU_LEXER_ENTER:
2739f4383e9Sgerardnico                        $ref = $attributes[LinkUtility::ATTRIBUTE_REF];
2745f891b7eSNickeau                        unset($attributes[LinkUtility::ATTRIBUTE_REF]);
275722648eaSgerardnico                        $name = $attributes[LinkUtility::ATTRIBUTE_NAME];
2765f891b7eSNickeau                        unset($attributes[LinkUtility::ATTRIBUTE_NAME]);
2779f4383e9Sgerardnico                        $link = new LinkUtility($ref);
278722648eaSgerardnico                        if ($name != null) {
279722648eaSgerardnico                            $link->setName($name);
28019b0880dSgerardnico                        }
2815f891b7eSNickeau                        $link->setAttributes($attributes);
2825f891b7eSNickeau
283d262537cSgerardnico
28419b0880dSgerardnico                        /**
2855f891b7eSNickeau                         * Extra styling
28619b0880dSgerardnico                         */
2875f891b7eSNickeau                        $parentTag = $data[PluginUtility::CONTEXT];
2889f4383e9Sgerardnico                        switch ($parentTag) {
289*21913ab3SNickeau                            /**
290*21913ab3SNickeau                             * Button link
291*21913ab3SNickeau                             */
2929f4383e9Sgerardnico                            case syntax_plugin_combo_button::TAG:
2935f891b7eSNickeau                                $attributes["role"] = "button";
2945f891b7eSNickeau                                syntax_plugin_combo_button::processButtonAttributesToHtmlAttributes($attributes);
2955f891b7eSNickeau                                $htmlLink = $link->renderOpenTag($renderer);
2969f4383e9Sgerardnico                                break;
2975f891b7eSNickeau                            case syntax_plugin_combo_badge::TAG:
2989f4383e9Sgerardnico                            case syntax_plugin_combo_cite::TAG:
2999f4383e9Sgerardnico                            case syntax_plugin_combo_listitem::TAG:
3009f4383e9Sgerardnico                            case syntax_plugin_combo_preformatted::TAG:
3015f891b7eSNickeau                                $htmlLink = $link->renderOpenTag($renderer);
3029f4383e9Sgerardnico                                break;
3030a517624Sgerardnico                            case syntax_plugin_combo_dropdown::TAG:
3045f891b7eSNickeau                                PluginUtility::addClass2Attributes("dropdown-item", $attributes);
3055f891b7eSNickeau                                $htmlLink = $link->renderOpenTag($renderer);
3060a517624Sgerardnico                                break;
3079f4383e9Sgerardnico                            case syntax_plugin_combo_navbarcollapse::COMPONENT:
3085f891b7eSNickeau                                PluginUtility::addClass2Attributes("navbar-link", $attributes);
3095f891b7eSNickeau                                $htmlLink = '<div class="navbar-nav">' . $link->renderOpenTag($renderer);
3109f4383e9Sgerardnico                                break;
3110a517624Sgerardnico                            case syntax_plugin_combo_navbargroup::COMPONENT:
3125f891b7eSNickeau                                PluginUtility::addClass2Attributes("nav-link", $attributes);
3135f891b7eSNickeau                                $htmlLink = '<li class="nav-item">' . $link->renderOpenTag($renderer);
3140a517624Sgerardnico                                break;
3155f891b7eSNickeau                            default:
3165f891b7eSNickeau
3175f891b7eSNickeau                                $htmlLink = $link->renderOpenTag($renderer);
3180a517624Sgerardnico
3199f4383e9Sgerardnico                        }
3209f4383e9Sgerardnico
32119b0880dSgerardnico
32219b0880dSgerardnico                        /**
32319b0880dSgerardnico                         * Add it to the rendering
32419b0880dSgerardnico                         */
325007225e5Sgerardnico                        $renderer->doc .= $htmlLink;
3265f891b7eSNickeau                        break;
3275f891b7eSNickeau                    case DOKU_LEXER_UNMATCHED:
32832b85071SNickeau                        $renderer->doc .= PluginUtility::renderUnmatched($data);
3295f891b7eSNickeau                        break;
3305f891b7eSNickeau                    case DOKU_LEXER_EXIT:
3315f891b7eSNickeau
3325f891b7eSNickeau                        // if there is no link name defined, we get the name as ref in the payload
3335f891b7eSNickeau                        // otherwise null string
3345f891b7eSNickeau                        $renderer->doc .= $payload;
3355f891b7eSNickeau
336e3d0019cSgerardnico                        // Close the link
337e3d0019cSgerardnico                        $linkTag = $data[self::LINK_TAG];
338e3d0019cSgerardnico                        $renderer->doc .= "</$linkTag>";
339e3d0019cSgerardnico
340e3d0019cSgerardnico                        // Close the html wrapper element
3415f891b7eSNickeau                        $context = $data[PluginUtility::CONTEXT];
3425f891b7eSNickeau                        switch ($context) {
3435f891b7eSNickeau                            case syntax_plugin_combo_navbarcollapse::COMPONENT:
3445f891b7eSNickeau                                $renderer->doc .= '</div>';
3455f891b7eSNickeau                                break;
3465f891b7eSNickeau                            case syntax_plugin_combo_navbargroup::COMPONENT:
3475f891b7eSNickeau                                $renderer->doc .= '</li>';
3485f891b7eSNickeau                                break;
3495f891b7eSNickeau                        }
3505f891b7eSNickeau
351e3d0019cSgerardnico
3525f891b7eSNickeau                }
3535f891b7eSNickeau
354007225e5Sgerardnico
355007225e5Sgerardnico                return true;
356007225e5Sgerardnico                break;
357007225e5Sgerardnico
3585f891b7eSNickeau            case 'metadata':
359007225e5Sgerardnico
3605f891b7eSNickeau                $state = $data[PluginUtility::STATE];
3615f891b7eSNickeau                if ($state == DOKU_LEXER_ENTER) {
362007225e5Sgerardnico                    /**
363007225e5Sgerardnico                     * Keep track of the backlinks ie meta['relation']['references']
364007225e5Sgerardnico                     * @var Doku_Renderer_metadata $renderer
365007225e5Sgerardnico                     */
366007225e5Sgerardnico                    if (isset($data[PluginUtility::ATTRIBUTES])) {
367007225e5Sgerardnico                        $attributes = $data[PluginUtility::ATTRIBUTES];
368007225e5Sgerardnico                    } else {
369007225e5Sgerardnico                        $attributes = $data;
370007225e5Sgerardnico                    }
3719f4383e9Sgerardnico                    $ref = $attributes[LinkUtility::ATTRIBUTE_REF];
3729f4383e9Sgerardnico
3739f4383e9Sgerardnico                    $link = new LinkUtility($ref);
3749f4383e9Sgerardnico                    $name = $attributes[LinkUtility::ATTRIBUTE_NAME];
3759f4383e9Sgerardnico                    if ($name != null) {
3769f4383e9Sgerardnico                        $link->setName($name);
3779f4383e9Sgerardnico                    }
3789f4383e9Sgerardnico                    $link->handleMetadata($renderer);
379007225e5Sgerardnico
380007225e5Sgerardnico                    return true;
3815f891b7eSNickeau                }
382007225e5Sgerardnico                break;
383007225e5Sgerardnico
384007225e5Sgerardnico            case Analytics::RENDERER_FORMAT:
3855f891b7eSNickeau
3865f891b7eSNickeau                $state = $data[PluginUtility::STATE];
3875f891b7eSNickeau                if ($state == DOKU_LEXER_ENTER) {
388007225e5Sgerardnico                    /**
389007225e5Sgerardnico                     *
390007225e5Sgerardnico                     * @var renderer_plugin_combo_analytics $renderer
391007225e5Sgerardnico                     */
3921c5862d3Sgerardnico                    $attributes = $data[PluginUtility::ATTRIBUTES];
3939f4383e9Sgerardnico                    $ref = $attributes[LinkUtility::ATTRIBUTE_REF];
3949f4383e9Sgerardnico                    $link = new LinkUtility($ref);
3959f4383e9Sgerardnico                    $link->processLinkStats($renderer->stats);
396007225e5Sgerardnico                    break;
3975f891b7eSNickeau                }
398007225e5Sgerardnico
399007225e5Sgerardnico        }
400007225e5Sgerardnico        // unsupported $mode
401007225e5Sgerardnico        return false;
402007225e5Sgerardnico    }
403007225e5Sgerardnico
404007225e5Sgerardnico
405007225e5Sgerardnico}
406007225e5Sgerardnico
407