xref: /plugin/combo/syntax/link.php (revision fc45fbf7e90508e12ecaa85045d857ffe566578d)
1<?php
2
3
4require_once(__DIR__ . "/../class/Analytics.php");
5require_once(__DIR__ . "/../class/PluginUtility.php");
6require_once(__DIR__ . "/../class/LinkUtility.php");
7require_once(__DIR__ . "/../class/HtmlUtility.php");
8
9use ComboStrap\Analytics;
10use ComboStrap\LinkUtility;
11use ComboStrap\PluginUtility;
12use ComboStrap\Tag;
13
14if (!defined('DOKU_INC')) die();
15
16/**
17 *
18 * A link pattern to take over the link of Dokuwiki
19 * and transform it as a bootstrap link
20 *
21 * The handle of the move of link is to be found in the
22 * admin action {@link action_plugin_combo_linkmove}
23 *
24 */
25class syntax_plugin_combo_link extends DokuWiki_Syntax_Plugin
26{
27    const TAG = 'link';
28    const COMPONENT = 'combo_link';
29
30    /**
31     * Disable the link
32     */
33    const CONF_DISABLE_LINK = "disableLink";
34
35    /**
36     * The link Tag
37     */
38    const LINK_TAG = "linkTag";
39
40    /**
41     * Do the link component allows to be spawn on multilines
42     */
43    const CONF_ENABLE_MULTI_LINES_LINK = "enableMultiLinesLink";
44
45
46    /**
47     * Syntax Type.
48     *
49     * Needs to return one of the mode types defined in $PARSER_MODES in parser.php
50     * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types
51     */
52    function getType()
53    {
54        return 'substition';
55    }
56
57    /**
58     * How Dokuwiki will add P element
59     *
60     *  * 'normal' - The plugin can be used inside paragraphs
61     *  * 'block'  - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs
62     *  * 'stack'  - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs
63     *
64     * @see DokuWiki_Syntax_Plugin::getPType()
65     */
66    function getPType()
67    {
68        return 'normal';
69    }
70
71    /**
72     * @return array
73     * Allow which kind of plugin inside
74     *
75     * No one of array('container', 'baseonly', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs')
76     * because we manage self the content and we call self the parser
77     */
78    function getAllowedTypes()
79    {
80        return array('substition', 'formatting', 'disabled');
81    }
82
83    public function accepts($mode)
84    {
85        /**
86         * To avoid that the description if it contains a link
87         * will be taken by the links mode
88         *
89         * For instance, [[https://hallo|https://hallo]] will send https://hallo
90         * to the external link mode
91         */
92        $linkModes = [
93            "externallink",
94            "locallink",
95            "internallink",
96            "interwikilink",
97            "emaillink",
98            "emphasis", // double slash can not be used inside to preserve the possibility to write an URL in the description
99            //"emphasis_open", // italic use // and therefore take over a link as description which is not handy when copying a tweet
100            //"emphasis_close",
101            //"acrnonym"
102        ];
103        if (in_array($mode, $linkModes)) {
104            return false;
105        } else {
106            return true;
107        }
108    }
109
110
111    /**
112     * @see Doku_Parser_Mode::getSort()
113     * The mode with the lowest sort number will win out
114     */
115    function getSort()
116    {
117        return 100;
118    }
119
120
121    function connectTo($mode)
122    {
123
124        if (!$this->getConf(self::CONF_DISABLE_LINK, false)) {
125            $pattern = LinkUtility::ENTRY_PATTERN_SINGLE_LINE;
126            if ($this->getConf(self::CONF_ENABLE_MULTI_LINES_LINK,false)){
127                $pattern = LinkUtility::ENTRY_PATTERN_MULTI_LINE;
128            }
129            $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeForComponent($this->getPluginComponent()));
130        }
131
132    }
133
134    public function postConnect()
135    {
136        if (!$this->getConf(self::CONF_DISABLE_LINK, false)) {
137            $this->Lexer->addExitPattern(LinkUtility::EXIT_PATTERN, PluginUtility::getModeForComponent($this->getPluginComponent()));
138        }
139    }
140
141
142    /**
143     * The handler for an internal link
144     * based on `internallink` in {@link Doku_Handler}
145     * The handler call the good renderer in {@link Doku_Renderer_xhtml} with
146     * the parameters (ie for instance internallink)
147     * @param string $match
148     * @param int $state
149     * @param int $pos
150     * @param Doku_Handler $handler
151     * @return array|bool
152     */
153    function handle($match, $state, $pos, Doku_Handler $handler)
154    {
155
156        /**
157         * Because we use the specialPattern, there is only one state ie DOKU_LEXER_SPECIAL
158         */
159        switch ($state) {
160            case DOKU_LEXER_ENTER:
161                $attributes = LinkUtility::parse($match);
162                $tag = new Tag(self::TAG, $attributes, $state, $handler);
163                $parent = $tag->getParent();
164                $parentName = "";
165                if ($parent != null) {
166                    $parentName = $parent->getName();
167                    switch ($parentName) {
168                        case syntax_plugin_combo_button::TAG:
169                            $attributes = PluginUtility::mergeAttributes($attributes, $parent->getAttributes());
170                            $firstContainingBlock = $parent->getParent();
171                            break;
172                        case syntax_plugin_combo_column::TAG:
173                            // A col is in a row
174                            $firstContainingBlock = $parent->getParent();
175                            break;
176                        case "section":
177                            // When editing, there is a section
178                            $firstContainingBlock = $parent->getParent();
179                            break;
180                        default:
181                            $firstContainingBlock = $parent;
182                    }
183                    if ($firstContainingBlock != null) {
184                        if ($firstContainingBlock->getAttribute("clickable")) {
185                            PluginUtility::addClass2Attributes("stretched-link", $attributes);
186                            $firstContainingBlock->addClass("position-relative");
187                            $firstContainingBlock->unsetAttribute("clickable");
188                        }
189                    }
190                }
191
192                $link = new LinkUtility($attributes[LinkUtility::ATTRIBUTE_REF]);
193                $linkTag = $link->getHtmlTag();
194                return array(
195                    PluginUtility::STATE => $state,
196                    PluginUtility::ATTRIBUTES => $attributes,
197                    PluginUtility::CONTEXT => $parentName,
198                    self::LINK_TAG => $linkTag
199                );
200            case DOKU_LEXER_UNMATCHED:
201
202                $data = PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler);
203                /**
204                 * Delete the separator `|` between the ref and the description if any
205                 */
206                $tag = new Tag(self::TAG, array(), $state, $handler);
207                $parent = $tag->getParent();
208                if ($parent->getName() == self::TAG) {
209                    if (strpos($match, '|') === 0) {
210                        $data[PluginUtility::PAYLOAD] = substr($match, 1);
211                    }
212                }
213                return $data;
214
215            case DOKU_LEXER_EXIT:
216                $tag = new Tag(self::TAG, array(), $state, $handler);
217                $openingTag = $tag->getOpeningTag();
218                $openingAttributes = $openingTag->getAttributes();
219                $linkTag = $openingTag->getData()[self::LINK_TAG];
220
221                if ($openingTag->getActualPosition() == $tag->getActualPosition() - 1) {
222                    // There is no name
223                    $link = new LinkUtility($openingAttributes[LinkUtility::ATTRIBUTE_REF]);
224                    $linkName = $link->getName();
225                } else {
226                    $linkName = "";
227                }
228                return array(
229                    PluginUtility::STATE => $state,
230                    PluginUtility::ATTRIBUTES => $openingAttributes,
231                    PluginUtility::PAYLOAD => $linkName,
232                    PluginUtility::CONTEXT => $openingTag->getContext(),
233                    self::LINK_TAG => $linkTag
234                );
235        }
236        return true;
237
238
239    }
240
241    /**
242     * Render the output
243     * @param string $format
244     * @param Doku_Renderer $renderer
245     * @param array $data - what the function handle() return'ed
246     * @return boolean - rendered correctly? (however, returned value is not used at the moment)
247     * @see DokuWiki_Syntax_Plugin::render()
248     *
249     *
250     */
251    function render($format, Doku_Renderer $renderer, $data)
252    {
253        // The data
254        switch ($format) {
255            case 'xhtml':
256
257                /** @var Doku_Renderer_xhtml $renderer */
258                /**
259                 * Cache problem may occurs while releasing
260                 */
261                if (isset($data[PluginUtility::ATTRIBUTES])) {
262                    $attributes = $data[PluginUtility::ATTRIBUTES];
263                } else {
264                    $attributes = $data;
265                }
266
267                PluginUtility::getSnippetManager()->attachCssSnippetForBar(self::TAG);
268
269
270                $state = $data[PluginUtility::STATE];
271                $payload = $data[PluginUtility::PAYLOAD];
272                switch ($state) {
273                    case DOKU_LEXER_ENTER:
274                        $ref = $attributes[LinkUtility::ATTRIBUTE_REF];
275                        unset($attributes[LinkUtility::ATTRIBUTE_REF]);
276                        $name = $attributes[LinkUtility::ATTRIBUTE_NAME];
277                        unset($attributes[LinkUtility::ATTRIBUTE_NAME]);
278                        $link = new LinkUtility($ref);
279                        if ($name != null) {
280                            $link->setName($name);
281                        }
282                        $link->setAttributes($attributes);
283
284
285                        /**
286                         * Extra styling
287                         */
288                        $parentTag = $data[PluginUtility::CONTEXT];
289                        switch ($parentTag) {
290                            /**
291                             * Button link
292                             */
293                            case syntax_plugin_combo_button::TAG:
294                                $attributes["role"] = "button";
295                                syntax_plugin_combo_button::processButtonAttributesToHtmlAttributes($attributes);
296                                $htmlLink = $link->renderOpenTag($renderer);
297                                break;
298                            case syntax_plugin_combo_badge::TAG:
299                            case syntax_plugin_combo_cite::TAG:
300                            case syntax_plugin_combo_listitem::TAG:
301                            case syntax_plugin_combo_preformatted::TAG:
302                                $htmlLink = $link->renderOpenTag($renderer);
303                                break;
304                            case syntax_plugin_combo_dropdown::TAG:
305                                PluginUtility::addClass2Attributes("dropdown-item", $attributes);
306                                $htmlLink = $link->renderOpenTag($renderer);
307                                break;
308                            case syntax_plugin_combo_navbarcollapse::COMPONENT:
309                                PluginUtility::addClass2Attributes("navbar-link", $attributes);
310                                $htmlLink = '<div class="navbar-nav">' . $link->renderOpenTag($renderer);
311                                break;
312                            case syntax_plugin_combo_navbargroup::COMPONENT:
313                                PluginUtility::addClass2Attributes("nav-link", $attributes);
314                                $htmlLink = '<li class="nav-item">' . $link->renderOpenTag($renderer);
315                                break;
316                            default:
317
318                                $htmlLink = $link->renderOpenTag($renderer);
319
320                        }
321
322
323                        /**
324                         * Add it to the rendering
325                         */
326                        $renderer->doc .= $htmlLink;
327                        break;
328                    case DOKU_LEXER_UNMATCHED:
329                        $renderer->doc .= PluginUtility::renderUnmatched($data);
330                        break;
331                    case DOKU_LEXER_EXIT:
332
333                        // if there is no link name defined, we get the name as ref in the payload
334                        // otherwise null string
335                        $renderer->doc .= $payload;
336
337                        // Close the link
338                        $linkTag = $data[self::LINK_TAG];
339                        $renderer->doc .= "</$linkTag>";
340
341                        // Close the html wrapper element
342                        $context = $data[PluginUtility::CONTEXT];
343                        switch ($context) {
344                            case syntax_plugin_combo_navbarcollapse::COMPONENT:
345                                $renderer->doc .= '</div>';
346                                break;
347                            case syntax_plugin_combo_navbargroup::COMPONENT:
348                                $renderer->doc .= '</li>';
349                                break;
350                        }
351
352
353                }
354
355
356                return true;
357                break;
358
359            case 'metadata':
360
361                $state = $data[PluginUtility::STATE];
362                if ($state == DOKU_LEXER_ENTER) {
363                    /**
364                     * Keep track of the backlinks ie meta['relation']['references']
365                     * @var Doku_Renderer_metadata $renderer
366                     */
367                    if (isset($data[PluginUtility::ATTRIBUTES])) {
368                        $attributes = $data[PluginUtility::ATTRIBUTES];
369                    } else {
370                        $attributes = $data;
371                    }
372                    $ref = $attributes[LinkUtility::ATTRIBUTE_REF];
373
374                    $link = new LinkUtility($ref);
375                    $name = $attributes[LinkUtility::ATTRIBUTE_NAME];
376                    if ($name != null) {
377                        $link->setName($name);
378                    }
379                    $link->handleMetadata($renderer);
380
381                    return true;
382                }
383                break;
384
385            case Analytics::RENDERER_FORMAT:
386
387                $state = $data[PluginUtility::STATE];
388                if ($state == DOKU_LEXER_ENTER) {
389                    /**
390                     *
391                     * @var renderer_plugin_combo_analytics $renderer
392                     */
393                    $attributes = $data[PluginUtility::ATTRIBUTES];
394                    $ref = $attributes[LinkUtility::ATTRIBUTE_REF];
395                    $link = new LinkUtility($ref);
396                    $link->processLinkStats($renderer->stats);
397                    break;
398                }
399
400        }
401        // unsupported $mode
402        return false;
403    }
404
405
406}
407
408