xref: /plugin/combo/syntax/link.php (revision 21913ab3235d516e2fa19c7e3929b555b3a2bda1)
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_open", // italic use // and therefore take over a link as description which is not handy when copying a tweet
99            //"emphasis_close",
100            //"acrnonym"
101        ];
102        if (in_array($mode, $linkModes)) {
103            return false;
104        } else {
105            return true;
106        }
107    }
108
109
110    /**
111     * @see Doku_Parser_Mode::getSort()
112     * The mode with the lowest sort number will win out
113     */
114    function getSort()
115    {
116        return 100;
117    }
118
119
120    function connectTo($mode)
121    {
122
123        if (!$this->getConf(self::CONF_DISABLE_LINK, false)) {
124            $pattern = LinkUtility::ENTRY_PATTERN_SINGLE_LINE;
125            if ($this->getConf(self::CONF_ENABLE_MULTI_LINES_LINK,false)){
126                $pattern = LinkUtility::ENTRY_PATTERN_MULTI_LINE;
127            }
128            $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeForComponent($this->getPluginComponent()));
129        }
130
131    }
132
133    public function postConnect()
134    {
135        if (!$this->getConf(self::CONF_DISABLE_LINK, false)) {
136            $this->Lexer->addExitPattern(LinkUtility::EXIT_PATTERN, PluginUtility::getModeForComponent($this->getPluginComponent()));
137        }
138    }
139
140
141    /**
142     * The handler for an internal link
143     * based on `internallink` in {@link Doku_Handler}
144     * The handler call the good renderer in {@link Doku_Renderer_xhtml} with
145     * the parameters (ie for instance internallink)
146     * @param string $match
147     * @param int $state
148     * @param int $pos
149     * @param Doku_Handler $handler
150     * @return array|bool
151     */
152    function handle($match, $state, $pos, Doku_Handler $handler)
153    {
154
155        /**
156         * Because we use the specialPattern, there is only one state ie DOKU_LEXER_SPECIAL
157         */
158        switch ($state) {
159            case DOKU_LEXER_ENTER:
160                $attributes = LinkUtility::parse($match);
161                $tag = new Tag(self::TAG, $attributes, $state, $handler);
162                $parent = $tag->getParent();
163                $parentName = "";
164                if ($parent != null) {
165                    $parentName = $parent->getName();
166                    switch ($parentName) {
167                        case syntax_plugin_combo_button::TAG:
168                            $attributes = PluginUtility::mergeAttributes($attributes, $parent->getAttributes());
169                            $firstContainingBlock = $parent->getParent();
170                            break;
171                        case syntax_plugin_combo_column::TAG:
172                            // A col is in a row
173                            $firstContainingBlock = $parent->getParent();
174                            break;
175                        case "section":
176                            // When editing, there is a section
177                            $firstContainingBlock = $parent->getParent();
178                            break;
179                        default:
180                            $firstContainingBlock = $parent;
181                    }
182                    if ($firstContainingBlock != null) {
183                        if ($firstContainingBlock->getAttribute("clickable")) {
184                            PluginUtility::addClass2Attributes("stretched-link", $attributes);
185                            $firstContainingBlock->addClass("position-relative");
186                            $firstContainingBlock->unsetAttribute("clickable");
187                        }
188                    }
189                }
190
191                $link = new LinkUtility($attributes[LinkUtility::ATTRIBUTE_REF]);
192                $linkTag = $link->getHtmlTag();
193                return array(
194                    PluginUtility::STATE => $state,
195                    PluginUtility::ATTRIBUTES => $attributes,
196                    PluginUtility::CONTEXT => $parentName,
197                    self::LINK_TAG => $linkTag
198                );
199            case DOKU_LEXER_UNMATCHED:
200
201                $data = PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler);
202                /**
203                 * Delete the separator `|` between the ref and the description if any
204                 */
205                $tag = new Tag(self::TAG, array(), $state, $handler);
206                $parent = $tag->getParent();
207                if ($parent->getName() == self::TAG) {
208                    if (strpos($match, '|') === 0) {
209                        $data[PluginUtility::PAYLOAD] = substr($match, 1);
210                    }
211                }
212                return $data;
213
214            case DOKU_LEXER_EXIT:
215                $tag = new Tag(self::TAG, array(), $state, $handler);
216                $openingTag = $tag->getOpeningTag();
217                $openingAttributes = $openingTag->getAttributes();
218                $linkTag = $openingTag->getData()[self::LINK_TAG];
219
220                if ($openingTag->getActualPosition() == $tag->getActualPosition() - 1) {
221                    // There is no name
222                    $link = new LinkUtility($openingAttributes[LinkUtility::ATTRIBUTE_REF]);
223                    $linkName = $link->getName();
224                } else {
225                    $linkName = "";
226                }
227                return array(
228                    PluginUtility::STATE => $state,
229                    PluginUtility::ATTRIBUTES => $openingAttributes,
230                    PluginUtility::PAYLOAD => $linkName,
231                    PluginUtility::CONTEXT => $openingTag->getContext(),
232                    self::LINK_TAG => $linkTag
233                );
234        }
235        return true;
236
237
238    }
239
240    /**
241     * Render the output
242     * @param string $format
243     * @param Doku_Renderer $renderer
244     * @param array $data - what the function handle() return'ed
245     * @return boolean - rendered correctly? (however, returned value is not used at the moment)
246     * @see DokuWiki_Syntax_Plugin::render()
247     *
248     *
249     */
250    function render($format, Doku_Renderer $renderer, $data)
251    {
252        // The data
253        switch ($format) {
254            case 'xhtml':
255
256                /** @var Doku_Renderer_xhtml $renderer */
257                /**
258                 * Cache problem may occurs while releasing
259                 */
260                if (isset($data[PluginUtility::ATTRIBUTES])) {
261                    $attributes = $data[PluginUtility::ATTRIBUTES];
262                } else {
263                    $attributes = $data;
264                }
265
266                PluginUtility::getSnippetManager()->attachCssSnippetForBar(self::TAG);
267
268
269                $state = $data[PluginUtility::STATE];
270                $payload = $data[PluginUtility::PAYLOAD];
271                switch ($state) {
272                    case DOKU_LEXER_ENTER:
273                        $ref = $attributes[LinkUtility::ATTRIBUTE_REF];
274                        unset($attributes[LinkUtility::ATTRIBUTE_REF]);
275                        $name = $attributes[LinkUtility::ATTRIBUTE_NAME];
276                        unset($attributes[LinkUtility::ATTRIBUTE_NAME]);
277                        $link = new LinkUtility($ref);
278                        if ($name != null) {
279                            $link->setName($name);
280                        }
281                        $link->setAttributes($attributes);
282
283
284                        /**
285                         * Extra styling
286                         */
287                        $parentTag = $data[PluginUtility::CONTEXT];
288                        switch ($parentTag) {
289                            /**
290                             * Button link
291                             */
292                            case syntax_plugin_combo_button::TAG:
293                                $attributes["role"] = "button";
294                                syntax_plugin_combo_button::processButtonAttributesToHtmlAttributes($attributes);
295                                $htmlLink = $link->renderOpenTag($renderer);
296                                break;
297                            case syntax_plugin_combo_badge::TAG:
298                            case syntax_plugin_combo_cite::TAG:
299                            case syntax_plugin_combo_listitem::TAG:
300                            case syntax_plugin_combo_preformatted::TAG:
301                                $htmlLink = $link->renderOpenTag($renderer);
302                                break;
303                            case syntax_plugin_combo_dropdown::TAG:
304                                PluginUtility::addClass2Attributes("dropdown-item", $attributes);
305                                $htmlLink = $link->renderOpenTag($renderer);
306                                break;
307                            case syntax_plugin_combo_navbarcollapse::COMPONENT:
308                                PluginUtility::addClass2Attributes("navbar-link", $attributes);
309                                $htmlLink = '<div class="navbar-nav">' . $link->renderOpenTag($renderer);
310                                break;
311                            case syntax_plugin_combo_navbargroup::COMPONENT:
312                                PluginUtility::addClass2Attributes("nav-link", $attributes);
313                                $htmlLink = '<li class="nav-item">' . $link->renderOpenTag($renderer);
314                                break;
315                            default:
316
317                                $htmlLink = $link->renderOpenTag($renderer);
318
319                        }
320
321
322                        /**
323                         * Add it to the rendering
324                         */
325                        $renderer->doc .= $htmlLink;
326                        break;
327                    case DOKU_LEXER_UNMATCHED:
328                        $renderer->doc .= PluginUtility::renderUnmatched($data);
329                        break;
330                    case DOKU_LEXER_EXIT:
331
332                        // if there is no link name defined, we get the name as ref in the payload
333                        // otherwise null string
334                        $renderer->doc .= $payload;
335
336                        // Close the link
337                        $linkTag = $data[self::LINK_TAG];
338                        $renderer->doc .= "</$linkTag>";
339
340                        // Close the html wrapper element
341                        $context = $data[PluginUtility::CONTEXT];
342                        switch ($context) {
343                            case syntax_plugin_combo_navbarcollapse::COMPONENT:
344                                $renderer->doc .= '</div>';
345                                break;
346                            case syntax_plugin_combo_navbargroup::COMPONENT:
347                                $renderer->doc .= '</li>';
348                                break;
349                        }
350
351
352                }
353
354
355                return true;
356                break;
357
358            case 'metadata':
359
360                $state = $data[PluginUtility::STATE];
361                if ($state == DOKU_LEXER_ENTER) {
362                    /**
363                     * Keep track of the backlinks ie meta['relation']['references']
364                     * @var Doku_Renderer_metadata $renderer
365                     */
366                    if (isset($data[PluginUtility::ATTRIBUTES])) {
367                        $attributes = $data[PluginUtility::ATTRIBUTES];
368                    } else {
369                        $attributes = $data;
370                    }
371                    $ref = $attributes[LinkUtility::ATTRIBUTE_REF];
372
373                    $link = new LinkUtility($ref);
374                    $name = $attributes[LinkUtility::ATTRIBUTE_NAME];
375                    if ($name != null) {
376                        $link->setName($name);
377                    }
378                    $link->handleMetadata($renderer);
379
380                    return true;
381                }
382                break;
383
384            case Analytics::RENDERER_FORMAT:
385
386                $state = $data[PluginUtility::STATE];
387                if ($state == DOKU_LEXER_ENTER) {
388                    /**
389                     *
390                     * @var renderer_plugin_combo_analytics $renderer
391                     */
392                    $attributes = $data[PluginUtility::ATTRIBUTES];
393                    $ref = $attributes[LinkUtility::ATTRIBUTE_REF];
394                    $link = new LinkUtility($ref);
395                    $link->processLinkStats($renderer->stats);
396                    break;
397                }
398
399        }
400        // unsupported $mode
401        return false;
402    }
403
404
405}
406
407