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