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