1 <?php
2 
3 
4 use ComboStrap\Bootstrap;
5 use ComboStrap\CallStack;
6 use ComboStrap\LogUtility;
7 use ComboStrap\PluginUtility;
8 use ComboStrap\TagAttributes;
9 use ComboStrap\Tooltip;
10 use 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  */
33 class 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