1<?php
2
3
4use ComboStrap\Bootstrap;
5use ComboStrap\CallStack;
6use ComboStrap\LogUtility;
7use ComboStrap\PluginUtility;
8use ComboStrap\TagAttributes;
9use ComboStrap\Tooltip;
10use ComboStrap\XmlTagProcessing;
11
12
13/**
14 * Class syntax_plugin_combo_tooltip
15 * Implementation of a tooltip
16 *
17 * A tooltip is implemented as a super title attribute
18 * on a HTML element such as a link or a button
19 *
20 * The implementation pass the information that there is
21 * a tooltip on the container which makes the output of {@link TagAttributes::toHtmlEnterTag()}
22 * to print all attributes until the title and not closing.
23 *
24 * Bootstrap generate the <a href="https://getbootstrap.com/docs/5.0/components/tooltips/#markup">markup tooltip</a>
25 * on the fly. It's possible to generate a bootstrap markup like and use popper directly
26 * but this is far more difficult
27 *
28 *
29 * https://material.io/components/tooltips
30 * [[https://getbootstrap.com/docs/4.0/components/tooltips/|Tooltip Boostrap version 4]]
31 * [[https://getbootstrap.com/docs/5.0/components/tooltips/|Tooltip Boostrap version 5]]
32 */
33class syntax_plugin_combo_tooltip extends DokuWiki_Syntax_Plugin
34{
35
36    const TAG = "tooltip";
37
38    /**
39     * Class added to the parent
40     */
41    const CANONICAL = "tooltip";
42    public const TEXT_ATTRIBUTE = "text";
43
44    /**
45     * To see the tooltip immediately when hovering the class d-inline-block
46     *
47     * The inline block is to make the element (span) take the whole space
48     * of the image (ie dimension) otherwise it has no dimension and
49     * you can't click on it
50     *
51     * TODO: Add this to the {@link Tooltip} ???
52     */
53    const TOOLTIP_CLASS_INLINE_BLOCK = "d-inline-block";
54
55    /**
56     * Syntax Type.
57     *
58     * Needs to return one of the mode types defined in $PARSER_MODES in parser.php
59     * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types
60     * @see DokuWiki_Syntax_Plugin::getType()
61     */
62    function getType(): string
63    {
64        /**
65         * You could add a tooltip to a {@link syntax_plugin_combo_itext}
66         */
67        return 'formatting';
68    }
69
70    /**
71     * How Dokuwiki will add P element
72     *
73     *  * 'normal' - The plugin can be used inside paragraphs (inline)
74     *  * 'block'  - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs
75     *  * 'stack'  - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs
76     *
77     * @see DokuWiki_Syntax_Plugin::getPType()
78     * @see https://www.dokuwiki.org/devel:syntax_plugins#ptype
79     */
80    function getPType(): string
81    {
82        return 'normal';
83    }
84
85    /**
86     * @return array
87     * Allow which kind of plugin inside
88     *
89     * No one of array('baseonly','container', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs')
90     * because we manage self the content and we call self the parser
91     *
92     * Return an array of one or more of the mode types {@link $PARSER_MODES} in Parser.php
93     */
94    function getAllowedTypes(): array
95    {
96        return array('baseonly', 'container', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs');
97    }
98
99    public function accepts($mode): bool
100    {
101        return syntax_plugin_combo_preformatted::disablePreformatted($mode);
102    }
103
104    function getSort(): int
105    {
106        return 201;
107    }
108
109
110    function connectTo($mode)
111    {
112
113        $pattern = XmlTagProcessing::getContainerTagPattern(self::TAG);
114        $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeFromTag($this->getPluginComponent()));
115
116    }
117
118    function postConnect()
119    {
120
121        $this->Lexer->addExitPattern('</' . self::TAG . '>', PluginUtility::getModeFromTag($this->getPluginComponent()));
122
123    }
124
125    /**
126     *
127     * The handle function goal is to parse the matched syntax through the pattern function
128     * and to return the result for use in the renderer
129     * This result is always cached until the page is modified.
130     * @param string $match
131     * @param int $state
132     * @param int $pos - byte position in the original source file
133     * @param Doku_Handler $handler
134     * @return array
135     * @see DokuWiki_Syntax_Plugin::handle()
136     *
137     */
138    function handle($match, $state, $pos, Doku_Handler $handler): array
139    {
140
141        switch ($state) {
142
143            case DOKU_LEXER_ENTER :
144                $tagAttributes = TagAttributes::createFromTagMatch($match);
145                return array(
146                    PluginUtility::STATE => $state,
147                    PluginUtility::ATTRIBUTES => $tagAttributes->toCallStackArray()
148                );
149
150
151            case DOKU_LEXER_UNMATCHED :
152                return PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler);
153
154            case DOKU_LEXER_EXIT :
155
156                $callStack = CallStack::createFromHandler($handler);
157                $openingTag = $callStack->moveToPreviousCorrespondingOpeningCall();
158                if ($openingTag->hasAttribute(self::TEXT_ATTRIBUTE)) {
159                    /**
160                     * Old syntax where the tooltip was the wrapper
161                     */
162                    return array(
163                        PluginUtility::STATE => $state,
164                        PluginUtility::ATTRIBUTES => $openingTag->getAttributes()
165                    );
166                }
167                $parent = $callStack->moveToParent();
168                if ($parent === false) {
169                    return array(
170                        PluginUtility::STATE => $state,
171                        PluginUtility::EXIT_MESSAGE => "A parent is mandatory for a tooltip",
172                        PluginUtility::EXIT_CODE => 1
173                    );
174                }
175
176                /**
177                 * Capture the callstack
178                 */
179                $callStack->moveToCall($openingTag);
180                $toolTipCallStack = null;
181                while ($actualCall = $callStack->next()) {
182                    $toolTipCallStack[] = $actualCall->toCallArray();
183                }
184                $callStack->deleteAllCallsAfter($openingTag);
185
186                /**
187                 * Set on the parent the tooltip attributes
188                 * It will be processed by the {@link Tooltip}
189                 * class at the end of {@link TagAttributes::toHtmlEnterTag()}
190                 */
191                $attributes = $openingTag->getAttributes();
192                $attributes[Tooltip::CALLSTACK] = $toolTipCallStack;
193                $parent->addAttribute(Tooltip::TOOLTIP_ATTRIBUTE, $attributes);
194
195                return array(
196                    PluginUtility::STATE => $state
197                );
198
199
200        }
201        return array();
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): bool
216    {
217        if ($format == 'xhtml') {
218
219            /** @var Doku_Renderer_xhtml $renderer */
220            $state = $data[PluginUtility::STATE];
221            switch ($state) {
222
223                case DOKU_LEXER_ENTER :
224                    /**
225                     * Old syntax
226                     * where tooltip was enclosing the text with the tooltip
227                     */
228                    $callStackArray = $data[PluginUtility::ATTRIBUTES];
229                    $tagAttributes = TagAttributes::createFromCallStackArray($callStackArray);
230                    $text = $tagAttributes->getValue(self::TEXT_ATTRIBUTE);
231                    if ($text !== null) {
232                        /**
233                         * Old syntax where the tooltip was the wrapper
234                         */
235                        $renderer->doc .= TagAttributes::createFromCallStackArray([Tooltip::TOOLTIP_ATTRIBUTE => $callStackArray])
236                            ->addClassName(self::TOOLTIP_CLASS_INLINE_BLOCK)
237                            ->toHtmlEnterTag("span");
238                    }
239                    break;
240
241                case DOKU_LEXER_UNMATCHED:
242                    $renderer->doc .= PluginUtility::renderUnmatched($data);
243                    break;
244
245                case DOKU_LEXER_EXIT:
246                    $message = $data[PluginUtility::EXIT_MESSAGE] ?? null;
247                    if ($message !== null) {
248                        $renderer->doc .= LogUtility::wrapInRedForHtml($message);
249                        return false;
250                    }
251
252                    $callStackArray = $data[PluginUtility::ATTRIBUTES] ?? null;
253                    $tagAttributes = TagAttributes::createFromCallStackArray($callStackArray);
254                    $text = $tagAttributes->getValue(self::TEXT_ATTRIBUTE);
255                    if ($text !== null) {
256                        /**
257                         * Old syntax where the tooltip was the wrapper
258                         */
259                        $renderer->doc .= "</span>";
260                    }
261
262                    break;
263
264
265            }
266            return true;
267        }
268
269        // unsupported $mode
270        return false;
271    }
272
273
274}
275
276