xref: /plugin/combo/syntax/link.php (revision 9337a630db122fdba0294f47d72bdf5433c2bf10)
1007225e5Sgerardnico<?php
2007225e5Sgerardnico
3007225e5Sgerardnico
471f916b9Sgerardnicorequire_once(__DIR__ . "/../class/Analytics.php");
5007225e5Sgerardnicorequire_once(__DIR__ . "/../class/PluginUtility.php");
6007225e5Sgerardnicorequire_once(__DIR__ . "/../class/LinkUtility.php");
7a6bf47aaSNickeaurequire_once(__DIR__ . "/../class/XhtmlUtility.php");
8007225e5Sgerardnico
9531e725cSNickeauuse ComboStrap\CallStack;
10007225e5Sgerardnicouse ComboStrap\LinkUtility;
11007225e5Sgerardnicouse ComboStrap\PluginUtility;
12007225e5Sgerardnicouse ComboStrap\Tag;
13531e725cSNickeauuse ComboStrap\TagAttributes;
14007225e5Sgerardnico
15007225e5Sgerardnicoif (!defined('DOKU_INC')) die();
16007225e5Sgerardnico
17007225e5Sgerardnico/**
18007225e5Sgerardnico *
19007225e5Sgerardnico * A link pattern to take over the link of Dokuwiki
20007225e5Sgerardnico * and transform it as a bootstrap link
21007225e5Sgerardnico *
22007225e5Sgerardnico * The handle of the move of link is to be found in the
23007225e5Sgerardnico * admin action {@link action_plugin_combo_linkmove}
24007225e5Sgerardnico *
25007225e5Sgerardnico */
26007225e5Sgerardnicoclass syntax_plugin_combo_link extends DokuWiki_Syntax_Plugin
27007225e5Sgerardnico{
28007225e5Sgerardnico    const TAG = 'link';
29ef295d81Sgerardnico    const COMPONENT = 'combo_link';
30007225e5Sgerardnico
315f891b7eSNickeau    /**
3221913ab3SNickeau     * Disable the link
3321913ab3SNickeau     */
3421913ab3SNickeau    const CONF_DISABLE_LINK = "disableLink";
3521913ab3SNickeau
3621913ab3SNickeau    /**
375f891b7eSNickeau     * The link Tag
38531e725cSNickeau     * a or p
395f891b7eSNickeau     */
405f891b7eSNickeau    const LINK_TAG = "linkTag";
415f891b7eSNickeau
4221913ab3SNickeau    /**
4321913ab3SNickeau     * Do the link component allows to be spawn on multilines
4421913ab3SNickeau     */
45531e725cSNickeau    const CLICKABLE_ATTRIBUTE = "clickable";
4621913ab3SNickeau
475f891b7eSNickeau
48007225e5Sgerardnico    /**
49007225e5Sgerardnico     * Syntax Type.
50007225e5Sgerardnico     *
51007225e5Sgerardnico     * Needs to return one of the mode types defined in $PARSER_MODES in parser.php
52007225e5Sgerardnico     * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types
53007225e5Sgerardnico     */
54007225e5Sgerardnico    function getType()
55007225e5Sgerardnico    {
56007225e5Sgerardnico        return 'substition';
57007225e5Sgerardnico    }
58007225e5Sgerardnico
59007225e5Sgerardnico    /**
60007225e5Sgerardnico     * How Dokuwiki will add P element
61007225e5Sgerardnico     *
62007225e5Sgerardnico     *  * 'normal' - The plugin can be used inside paragraphs
63007225e5Sgerardnico     *  * 'block'  - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs
64007225e5Sgerardnico     *  * 'stack'  - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs
65007225e5Sgerardnico     *
66007225e5Sgerardnico     * @see DokuWiki_Syntax_Plugin::getPType()
67007225e5Sgerardnico     */
68007225e5Sgerardnico    function getPType()
69007225e5Sgerardnico    {
70007225e5Sgerardnico        return 'normal';
71007225e5Sgerardnico    }
72007225e5Sgerardnico
73007225e5Sgerardnico    /**
74007225e5Sgerardnico     * @return array
75007225e5Sgerardnico     * Allow which kind of plugin inside
76007225e5Sgerardnico     *
77007225e5Sgerardnico     * No one of array('container', 'baseonly', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs')
78007225e5Sgerardnico     * because we manage self the content and we call self the parser
79007225e5Sgerardnico     */
80007225e5Sgerardnico    function getAllowedTypes()
81007225e5Sgerardnico    {
82007225e5Sgerardnico        return array('substition', 'formatting', 'disabled');
83007225e5Sgerardnico    }
84007225e5Sgerardnico
855f891b7eSNickeau    public function accepts($mode)
865f891b7eSNickeau    {
875f891b7eSNickeau        /**
885f891b7eSNickeau         * To avoid that the description if it contains a link
895f891b7eSNickeau         * will be taken by the links mode
905f891b7eSNickeau         *
915f891b7eSNickeau         * For instance, [[https://hallo|https://hallo]] will send https://hallo
925f891b7eSNickeau         * to the external link mode
935f891b7eSNickeau         */
945f891b7eSNickeau        $linkModes = [
955f891b7eSNickeau            "externallink",
965f891b7eSNickeau            "locallink",
975f891b7eSNickeau            "internallink",
985f891b7eSNickeau            "interwikilink",
995f891b7eSNickeau            "emaillink",
100fc45fbf7Sgerardnico            "emphasis", // double slash can not be used inside to preserve the possibility to write an URL in the description
1015f891b7eSNickeau            //"emphasis_open", // italic use // and therefore take over a link as description which is not handy when copying a tweet
1025f891b7eSNickeau            //"emphasis_close",
1035f891b7eSNickeau            //"acrnonym"
1045f891b7eSNickeau        ];
1055f891b7eSNickeau        if (in_array($mode, $linkModes)) {
1065f891b7eSNickeau            return false;
1075f891b7eSNickeau        } else {
1085f891b7eSNickeau            return true;
1095f891b7eSNickeau        }
1105f891b7eSNickeau    }
1115f891b7eSNickeau
1125f891b7eSNickeau
113007225e5Sgerardnico    /**
114007225e5Sgerardnico     * @see Doku_Parser_Mode::getSort()
115007225e5Sgerardnico     * The mode with the lowest sort number will win out
116007225e5Sgerardnico     */
117007225e5Sgerardnico    function getSort()
118007225e5Sgerardnico    {
119007225e5Sgerardnico        return 100;
120007225e5Sgerardnico    }
121007225e5Sgerardnico
122007225e5Sgerardnico
123007225e5Sgerardnico    function connectTo($mode)
124007225e5Sgerardnico    {
125d262537cSgerardnico
12621913ab3SNickeau        if (!$this->getConf(self::CONF_DISABLE_LINK, false)) {
12721913ab3SNickeau            $pattern = LinkUtility::ENTRY_PATTERN_SINGLE_LINE;
128*9337a630SNickeau            $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeFromTag($this->getPluginComponent()));
12921913ab3SNickeau        }
130d262537cSgerardnico
131007225e5Sgerardnico    }
132007225e5Sgerardnico
1335f891b7eSNickeau    public function postConnect()
1345f891b7eSNickeau    {
13521913ab3SNickeau        if (!$this->getConf(self::CONF_DISABLE_LINK, false)) {
136*9337a630SNickeau            $this->Lexer->addExitPattern(LinkUtility::EXIT_PATTERN, PluginUtility::getModeFromTag($this->getPluginComponent()));
1375f891b7eSNickeau        }
13821913ab3SNickeau    }
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
155531e725cSNickeau
1565f891b7eSNickeau        switch ($state) {
1575f891b7eSNickeau            case DOKU_LEXER_ENTER:
158531e725cSNickeau                $tagAttributes = TagAttributes::createFromCallStackArray(LinkUtility::parse($match));
159531e725cSNickeau                $callStack = CallStack::createFromHandler($handler);
160531e725cSNickeau
161531e725cSNickeau
162531e725cSNickeau                $parent = $callStack->moveToParent();
163007225e5Sgerardnico                $parentName = "";
164531e725cSNickeau                if ($parent != false) {
165531e725cSNickeau
166531e725cSNickeau                    /**
167531e725cSNickeau                     * Button Link
168531e725cSNickeau                     * Getting the attributes
169531e725cSNickeau                     */
170531e725cSNickeau                    $parentName = $parent->getTagName();
171531e725cSNickeau                    if ($parentName == syntax_plugin_combo_button::TAG) {
172531e725cSNickeau                        $tagAttributes->mergeWithCallStackArray($parent->getAttributes());
17321913ab3SNickeau                    }
174531e725cSNickeau
175531e725cSNickeau                    /**
176531e725cSNickeau                     * Searching Clickable parent
177531e725cSNickeau                     */
178531e725cSNickeau                    $maxLevel = 3;
179531e725cSNickeau                    $level = 0;
180531e725cSNickeau                    while (
181531e725cSNickeau                        $parent != false &&
182531e725cSNickeau                        !$parent->hasAttribute(self::CLICKABLE_ATTRIBUTE) &&
183531e725cSNickeau                        $level < $maxLevel
184531e725cSNickeau                    ) {
185531e725cSNickeau                        $parent = $callStack->moveToParent();
186531e725cSNickeau                        $level++;
1875f891b7eSNickeau                    }
188531e725cSNickeau                    if ($parent != false) {
189531e725cSNickeau                        if ($parent->getAttribute(self::CLICKABLE_ATTRIBUTE)) {
190531e725cSNickeau                            $tagAttributes->addClassName("stretched-link");
191531e725cSNickeau                            $parent->addClassName("position-relative");
192531e725cSNickeau                            $parent->removeAttribute(self::CLICKABLE_ATTRIBUTE);
1935f891b7eSNickeau                        }
19421913ab3SNickeau                    }
19521913ab3SNickeau
196531e725cSNickeau                }
197531e725cSNickeau
198531e725cSNickeau                $link = new LinkUtility($tagAttributes->getValue(LinkUtility::ATTRIBUTE_REF));
1995f891b7eSNickeau                $linkTag = $link->getHtmlTag();
2005f891b7eSNickeau                return array(
2015f891b7eSNickeau                    PluginUtility::STATE => $state,
202531e725cSNickeau                    PluginUtility::ATTRIBUTES => $tagAttributes->toCallStackArray(),
2035f891b7eSNickeau                    PluginUtility::CONTEXT => $parentName,
2045f891b7eSNickeau                    self::LINK_TAG => $linkTag
2055f891b7eSNickeau                );
2065f891b7eSNickeau            case DOKU_LEXER_UNMATCHED:
2075f891b7eSNickeau
20832b85071SNickeau                $data = PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler);
2095f891b7eSNickeau                /**
21032b85071SNickeau                 * Delete the separator `|` between the ref and the description if any
2115f891b7eSNickeau                 */
2125f891b7eSNickeau                $tag = new Tag(self::TAG, array(), $state, $handler);
2135f891b7eSNickeau                $parent = $tag->getParent();
2145f891b7eSNickeau                if ($parent->getName() == self::TAG) {
2155f891b7eSNickeau                    if (strpos($match, '|') === 0) {
21632b85071SNickeau                        $data[PluginUtility::PAYLOAD] = substr($match, 1);
2175f891b7eSNickeau                    }
218007225e5Sgerardnico                }
21932b85071SNickeau                return $data;
220007225e5Sgerardnico
2215f891b7eSNickeau            case DOKU_LEXER_EXIT:
222531e725cSNickeau                $callStack = CallStack::createFromHandler($handler);
223531e725cSNickeau                $openingTag = $callStack->moveToPreviousCorrespondingOpeningCall();
2245f891b7eSNickeau                $openingAttributes = $openingTag->getAttributes();
225531e725cSNickeau                $linkTag = $openingTag->getPluginData()[self::LINK_TAG];
226531e725cSNickeau                $openingPosition = $openingTag->getKey();
2275f891b7eSNickeau
228531e725cSNickeau                $callStack->moveToEnd();
229531e725cSNickeau                $previousCall = $callStack->previous();
230531e725cSNickeau                $previousCallPosition = $previousCall->getKey();
231531e725cSNickeau                $previousCallContent = $previousCall->getCapturedContent();
232531e725cSNickeau
233531e725cSNickeau                if (
234531e725cSNickeau                    $openingPosition == $previousCallPosition // ie [[id]]
235531e725cSNickeau                    ||
236531e725cSNickeau                    ($openingPosition == $previousCallPosition - 1 && $previousCallContent == "|") // ie [[id|]]
237531e725cSNickeau                ) {
2385f891b7eSNickeau                    // There is no name
2395f891b7eSNickeau                    $link = new LinkUtility($openingAttributes[LinkUtility::ATTRIBUTE_REF]);
2405f891b7eSNickeau                    $linkName = $link->getName();
2415f891b7eSNickeau                } else {
2425f891b7eSNickeau                    $linkName = "";
2435f891b7eSNickeau                }
2445f891b7eSNickeau                return array(
2455f891b7eSNickeau                    PluginUtility::STATE => $state,
2465f891b7eSNickeau                    PluginUtility::ATTRIBUTES => $openingAttributes,
2475f891b7eSNickeau                    PluginUtility::PAYLOAD => $linkName,
2485f891b7eSNickeau                    PluginUtility::CONTEXT => $openingTag->getContext(),
2495f891b7eSNickeau                    self::LINK_TAG => $linkTag
2505f891b7eSNickeau                );
2515f891b7eSNickeau        }
2525f891b7eSNickeau        return true;
2535f891b7eSNickeau
254007225e5Sgerardnico
255007225e5Sgerardnico    }
256007225e5Sgerardnico
257007225e5Sgerardnico    /**
258007225e5Sgerardnico     * Render the output
259007225e5Sgerardnico     * @param string $format
260007225e5Sgerardnico     * @param Doku_Renderer $renderer
261007225e5Sgerardnico     * @param array $data - what the function handle() return'ed
262007225e5Sgerardnico     * @return boolean - rendered correctly? (however, returned value is not used at the moment)
263007225e5Sgerardnico     * @see DokuWiki_Syntax_Plugin::render()
264007225e5Sgerardnico     *
265007225e5Sgerardnico     *
266007225e5Sgerardnico     */
267007225e5Sgerardnico    function render($format, Doku_Renderer $renderer, $data)
268007225e5Sgerardnico    {
269007225e5Sgerardnico        // The data
270007225e5Sgerardnico        switch ($format) {
271007225e5Sgerardnico            case 'xhtml':
272007225e5Sgerardnico
273007225e5Sgerardnico                /** @var Doku_Renderer_xhtml $renderer */
274007225e5Sgerardnico                /**
27519b0880dSgerardnico                 * Cache problem may occurs while releasing
276007225e5Sgerardnico                 */
277007225e5Sgerardnico                if (isset($data[PluginUtility::ATTRIBUTES])) {
278531e725cSNickeau                    $callStackAttributes = $data[PluginUtility::ATTRIBUTES];
279007225e5Sgerardnico                } else {
280531e725cSNickeau                    $callStackAttributes = $data;
281007225e5Sgerardnico                }
2825f891b7eSNickeau
28321913ab3SNickeau                PluginUtility::getSnippetManager()->attachCssSnippetForBar(self::TAG);
2845f891b7eSNickeau
2855f891b7eSNickeau                $state = $data[PluginUtility::STATE];
2865f891b7eSNickeau                switch ($state) {
2875f891b7eSNickeau                    case DOKU_LEXER_ENTER:
288531e725cSNickeau                        $tagAttributes = TagAttributes::createFromCallStackArray($callStackAttributes, self::TAG);
289531e725cSNickeau                        $ref = $tagAttributes->getValueAndRemove(LinkUtility::ATTRIBUTE_REF);
290531e725cSNickeau                        $link = new LinkUtility($ref, $tagAttributes);
291d262537cSgerardnico
29219b0880dSgerardnico                        /**
2935f891b7eSNickeau                         * Extra styling
29419b0880dSgerardnico                         */
2955f891b7eSNickeau                        $parentTag = $data[PluginUtility::CONTEXT];
2969f4383e9Sgerardnico                        switch ($parentTag) {
29721913ab3SNickeau                            /**
29821913ab3SNickeau                             * Button link
29921913ab3SNickeau                             */
3009f4383e9Sgerardnico                            case syntax_plugin_combo_button::TAG:
301531e725cSNickeau                                $tagAttributes->addHtmlAttributeValue("role", "button");
302531e725cSNickeau                                syntax_plugin_combo_button::processButtonAttributesToHtmlAttributes($tagAttributes);
3035f891b7eSNickeau                                $htmlLink = $link->renderOpenTag($renderer);
3049f4383e9Sgerardnico                                break;
3055f891b7eSNickeau                            case syntax_plugin_combo_badge::TAG:
3069f4383e9Sgerardnico                            case syntax_plugin_combo_cite::TAG:
307*9337a630SNickeau                            case syntax_plugin_combo_contentlistitem::DOKU_TAG:
3089f4383e9Sgerardnico                            case syntax_plugin_combo_preformatted::TAG:
3095f891b7eSNickeau                                $htmlLink = $link->renderOpenTag($renderer);
3109f4383e9Sgerardnico                                break;
3110a517624Sgerardnico                            case syntax_plugin_combo_dropdown::TAG:
312531e725cSNickeau                                $tagAttributes->addClassName("dropdown-item");
3135f891b7eSNickeau                                $htmlLink = $link->renderOpenTag($renderer);
3140a517624Sgerardnico                                break;
3159f4383e9Sgerardnico                            case syntax_plugin_combo_navbarcollapse::COMPONENT:
316531e725cSNickeau                                $tagAttributes->addClassName("navbar-link");
3175f891b7eSNickeau                                $htmlLink = '<div class="navbar-nav">' . $link->renderOpenTag($renderer);
3189f4383e9Sgerardnico                                break;
3190a517624Sgerardnico                            case syntax_plugin_combo_navbargroup::COMPONENT:
320531e725cSNickeau                                $tagAttributes->addClassName("nav-link");
3215f891b7eSNickeau                                $htmlLink = '<li class="nav-item">' . $link->renderOpenTag($renderer);
3220a517624Sgerardnico                                break;
3235f891b7eSNickeau                            default:
3245f891b7eSNickeau                                $htmlLink = $link->renderOpenTag($renderer);
3250a517624Sgerardnico
3269f4383e9Sgerardnico                        }
3279f4383e9Sgerardnico
32819b0880dSgerardnico
32919b0880dSgerardnico                        /**
33019b0880dSgerardnico                         * Add it to the rendering
33119b0880dSgerardnico                         */
332007225e5Sgerardnico                        $renderer->doc .= $htmlLink;
3335f891b7eSNickeau                        break;
3345f891b7eSNickeau                    case DOKU_LEXER_UNMATCHED:
33532b85071SNickeau                        $renderer->doc .= PluginUtility::renderUnmatched($data);
3365f891b7eSNickeau                        break;
3375f891b7eSNickeau                    case DOKU_LEXER_EXIT:
3385f891b7eSNickeau
3395f891b7eSNickeau                        // if there is no link name defined, we get the name as ref in the payload
3405f891b7eSNickeau                        // otherwise null string
341531e725cSNickeau                        $renderer->doc .= $data[PluginUtility::PAYLOAD];;
3425f891b7eSNickeau
343e3d0019cSgerardnico                        // Close the link
344e3d0019cSgerardnico                        $linkTag = $data[self::LINK_TAG];
345e3d0019cSgerardnico                        $renderer->doc .= "</$linkTag>";
346e3d0019cSgerardnico
347e3d0019cSgerardnico                        // Close the html wrapper element
3485f891b7eSNickeau                        $context = $data[PluginUtility::CONTEXT];
3495f891b7eSNickeau                        switch ($context) {
3505f891b7eSNickeau                            case syntax_plugin_combo_navbarcollapse::COMPONENT:
3515f891b7eSNickeau                                $renderer->doc .= '</div>';
3525f891b7eSNickeau                                break;
3535f891b7eSNickeau                            case syntax_plugin_combo_navbargroup::COMPONENT:
3545f891b7eSNickeau                                $renderer->doc .= '</li>';
3555f891b7eSNickeau                                break;
3565f891b7eSNickeau                        }
3575f891b7eSNickeau
358e3d0019cSgerardnico
3595f891b7eSNickeau                }
3605f891b7eSNickeau
361007225e5Sgerardnico
362007225e5Sgerardnico                return true;
363007225e5Sgerardnico
3645f891b7eSNickeau            case 'metadata':
365007225e5Sgerardnico
3665f891b7eSNickeau                $state = $data[PluginUtility::STATE];
3675f891b7eSNickeau                if ($state == DOKU_LEXER_ENTER) {
368007225e5Sgerardnico                    /**
369007225e5Sgerardnico                     * Keep track of the backlinks ie meta['relation']['references']
370007225e5Sgerardnico                     * @var Doku_Renderer_metadata $renderer
371007225e5Sgerardnico                     */
372007225e5Sgerardnico                    if (isset($data[PluginUtility::ATTRIBUTES])) {
373531e725cSNickeau                        $tagAttributes = $data[PluginUtility::ATTRIBUTES];
374007225e5Sgerardnico                    } else {
375531e725cSNickeau                        $tagAttributes = $data;
376007225e5Sgerardnico                    }
377531e725cSNickeau                    $ref = $tagAttributes[LinkUtility::ATTRIBUTE_REF];
3789f4383e9Sgerardnico
3799f4383e9Sgerardnico                    $link = new LinkUtility($ref);
380531e725cSNickeau                    $name = $tagAttributes[LinkUtility::ATTRIBUTE_NAME];
3819f4383e9Sgerardnico                    if ($name != null) {
3829f4383e9Sgerardnico                        $link->setName($name);
3839f4383e9Sgerardnico                    }
3849f4383e9Sgerardnico                    $link->handleMetadata($renderer);
385007225e5Sgerardnico
386007225e5Sgerardnico                    return true;
3875f891b7eSNickeau                }
388007225e5Sgerardnico                break;
389007225e5Sgerardnico
390531e725cSNickeau            case renderer_plugin_combo_analytics::RENDERER_FORMAT:
3915f891b7eSNickeau
3925f891b7eSNickeau                $state = $data[PluginUtility::STATE];
3935f891b7eSNickeau                if ($state == DOKU_LEXER_ENTER) {
394007225e5Sgerardnico                    /**
395007225e5Sgerardnico                     *
396007225e5Sgerardnico                     * @var renderer_plugin_combo_analytics $renderer
397007225e5Sgerardnico                     */
398531e725cSNickeau                    $tagAttributes = $data[PluginUtility::ATTRIBUTES];
399531e725cSNickeau                    $ref = $tagAttributes[LinkUtility::ATTRIBUTE_REF];
4009f4383e9Sgerardnico                    $link = new LinkUtility($ref);
4019f4383e9Sgerardnico                    $link->processLinkStats($renderer->stats);
402007225e5Sgerardnico                    break;
4035f891b7eSNickeau                }
404007225e5Sgerardnico
405007225e5Sgerardnico        }
406007225e5Sgerardnico        // unsupported $mode
407007225e5Sgerardnico        return false;
408007225e5Sgerardnico    }
409007225e5Sgerardnico
410007225e5Sgerardnico
411007225e5Sgerardnico}
412007225e5Sgerardnico
413