1 <?php
2 /**
3  * Copyright (c) 2021. ComboStrap, Inc. and its affiliates. All Rights Reserved.
4  *
5  * This source code is licensed under the GPL license found in the
6  * COPYING  file in the root directory of this source tree.
7  *
8  * @license  GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html)
9  * @author   ComboStrap <support@combostrap.com>
10  *
11  */
12 
13 namespace ComboStrap;
14 
15 use dokuwiki\Extension\SyntaxPlugin;
16 use syntax_plugin_combo_media;
17 
18 
19 /**
20  * Class Call
21  * @package ComboStrap
22  *
23  * A wrapper around what's called a call
24  * which is an array of information such
25  * the mode, the data
26  *
27  * The {@link CallStack} is the only syntax representation that
28  * is available in DokuWiki
29  */
30 class Call
31 {
32 
33     const INLINE_DISPLAY = "inline";
34     const BlOCK_DISPLAY = "block";
35     /**
36      * List of inline components
37      * Used to manage white space before an unmatched string.
38      * The syntax tree of Dokuwiki (ie {@link \Doku_Handler::$calls})
39      * has only data and no class, for now, we create this
40      * lists manually because this is a hassle to retrieve this information from {@link \DokuWiki_Syntax_Plugin::getType()}
41      */
42     const INLINE_DOKUWIKI_COMPONENTS = array(
43         /**
44          * Formatting https://www.dokuwiki.org/devel:syntax_plugins#syntax_types
45          * Comes from the {@link \dokuwiki\Parsing\ParserMode\Formatting} class
46          */
47         "cdata",
48         "unformatted", // ie %% or nowiki
49         "doublequoteclosing", // https://www.dokuwiki.org/config:typography / https://www.dokuwiki.org/wiki:syntax#text_to_html_conversions
50         "doublequoteopening",
51         "singlequoteopening",
52         "singlequoteclosing",
53         "quote_open",
54         "quote_close",
55         "interwikilink",
56         "multiplyentity",
57         "apostrophe",
58         "deleted_open",
59         "deleted_close",
60         "emaillink",
61         "strong",
62         "emphasis",
63         "emphasis_open",
64         "emphasis_close",
65         "underline",
66         "underline_close",
67         "underline_open",
68         "monospace",
69         "subscript",
70         "subscript_open",
71         "subscript_close",
72         "superscript_open",
73         "superscript_close",
74         "superscript",
75         "deleted",
76         "footnote",
77         /**
78          * Others
79          */
80         "acronym", // abbr
81         "strong_close",
82         "strong_open",
83         "monospace_open",
84         "monospace_close",
85         "doublequoteopening", // ie the character " in "The"
86         "entity", // for instance `...` are transformed in character
87         "linebreak",
88         "externallink",
89         "internallink",
90         "smiley",
91         MediaMarkup::INTERNAL_MEDIA_CALL_NAME,
92         MediaMarkup::EXTERNAL_MEDIA_CALL_NAME,
93         /**
94          * The inline of combo
95          */
96         \syntax_plugin_combo_link::TAG,
97         IconTag::TAG,
98         NoteTag::TAG_INOTE,
99         ButtonTag::MARKUP_LONG,
100         \syntax_plugin_combo_tooltip::TAG,
101         PipelineTag::TAG,
102         BreadcrumbTag::MARKUP_BLOCK, // only the typo is inline but yeah
103     );
104 
105 
106     const BLOCK_MARKUP_DOKUWIKI_COMPONENTS = array(
107         "listu_open", // ul
108         "listu_close",
109         "listo_open",
110         "listo_close",
111         "listitem_open", //li
112         "listitem_close",
113         "listcontent_open", // after li ???
114         "listcontent_close",
115         "table_open",
116         "table_close",
117         "p_open",
118         "p_close",
119         "nest", // seen as enclosing footnotes
120         "hr",
121         "rss"
122     );
123 
124     /**
125      * Not inline, not block
126      */
127     const TABLE_MARKUP = array(
128         "tablethead_open",
129         "tablethead_close",
130         "tableheader_open",
131         "tableheader_close",
132         "tablerow_open",
133         "tablerow_close",
134         "tablecell_open",
135         "tablecell_close"
136     );
137 
138     /**
139      * A media is not really an image
140      * but it may contains one
141      */
142     const IMAGE_TAGS = [
143         syntax_plugin_combo_media::TAG,
144         PageImageTag::MARKUP
145     ];
146     const CANONICAL = "call";
147     const TABLE_DISPLAY = "table-display";
148 
149     private $call;
150 
151     /**
152      * The key identifier in the {@link CallStack}
153      * @var mixed|string
154      */
155     private $key;
156 
157     /**
158      * Call constructor.
159      * @param $call - the instruction array (ie called a call)
160      */
161     public function __construct(&$call, $key = "")
162     {
163         $this->call = &$call;
164         $this->key = $key;
165     }
166 
167     /**
168      * Insert a tag above
169      * @param $tagName
170      * @param $state
171      * @param $syntaxComponentName - the name of the dokuwiki syntax component (ie plugin_name)
172      * @param array $attribute
173      * @param string|null $rawContext
174      * @param string|null $content - the parsed content
175      * @param string|null $payload - the payload after handler
176      * @param int|null $position
177      * @return Call - a call
178      */
179     public static function createComboCall($tagName, $state, array $attribute = array(), string $rawContext = null, string $content = null, string $payload = null, int $position = null, string $syntaxComponentName = null): Call
180     {
181         $data = array(
182             PluginUtility::ATTRIBUTES => $attribute,
183             PluginUtility::CONTEXT => $rawContext,
184             PluginUtility::STATE => $state,
185             PluginUtility::TAG => $tagName,
186             PluginUtility::POSITION => $position
187         );
188         if ($payload !== null) {
189             $data[PluginUtility::PAYLOAD] = $payload;
190         }
191         $positionInText = $position;
192 
193         if ($syntaxComponentName !== null) {
194             $componentName = PluginUtility::getComponentName($syntaxComponentName);
195         } else {
196             $componentName = PluginUtility::getComponentName($tagName);
197         }
198         $obj = plugin_load('syntax', $componentName);
199         if ($obj === null) {
200             throw new ExceptionRuntimeInternal("The component tag ($componentName) does not exists");
201         }
202 
203         $call = [
204             "plugin",
205             array(
206                 $componentName,
207                 $data,
208                 $state,
209                 $content
210             ),
211             $positionInText
212         ];
213         return new Call($call);
214     }
215 
216     /**
217      * Insert a dokuwiki call
218      * @param $callName
219      * @param $array
220      * @param $positionInText
221      * @return Call
222      */
223     public static function createNativeCall($callName, $array = [], $positionInText = null): Call
224     {
225         $call = [
226             $callName,
227             $array,
228             $positionInText
229         ];
230         return new Call($call);
231     }
232 
233     public static function createFromInstruction($instruction): Call
234     {
235         return new Call($instruction);
236     }
237 
238     /**
239      * @param Call $call
240      * @return Call
241      */
242     public static function createFromCall(Call $call): Call
243     {
244         return self::createFromInstruction($call->toCallArray());
245     }
246 
247 
248     /**
249      *
250      * Return the tag name from a call array
251      *
252      * This is not the logical tag.
253      * This is much more what's called:
254      *   * the component name for a plugin
255      *   * or the handler name for dokuwiki
256      *
257      * For a plugin, this is equivalent
258      * to the {@link SyntaxPlugin::getPluginComponent()}
259      *
260      * This is not the fully qualified component name:
261      *   * with the plugin as prefix such as in {@link Call::getComponentName()}
262      *   * or with the `open` and `close` prefix such as `p_close` ...
263      *
264      * @return mixed|string
265      */
266     public function getTagName()
267     {
268 
269         $mode = $this->call[0];
270         if ($mode != "plugin") {
271 
272             /**
273              * This is a standard dokuwiki node
274              */
275             $dokuWikiNodeName = $this->call[0];
276 
277             /**
278              * The dokwuiki node name has also the open and close notion
279              * We delete this is not in the doc and therefore not logical
280              */
281             $tagName = str_replace("_close", "", $dokuWikiNodeName);
282             return str_replace("_open", "", $tagName);
283         }
284 
285         /**
286          * This is a plugin node
287          */
288         $pluginDokuData = $this->call[1];
289 
290         /**
291          * If the tag is set
292          */
293         $pluginData = $pluginDokuData[1];
294         if (isset($pluginData[PluginUtility::TAG])) {
295             return $pluginData[PluginUtility::TAG];
296         }
297 
298         $component = $pluginDokuData[0];
299         if (!is_array($component)) {
300             /**
301              * Tag name from class
302              */
303             $componentNames = explode("_", $component);
304             /**
305              * To take care of
306              * PHP Warning:  sizeof(): Parameter must be an array or an object that implements Countable
307              * in lib/plugins/combo/class/Tag.php on line 314
308              */
309             if (is_array($componentNames)) {
310                 $tagName = $componentNames[sizeof($componentNames) - 1];
311             } else {
312                 $tagName = $component;
313             }
314             return $tagName;
315         }
316 
317         // To resolve: explode() expects parameter 2 to be string, array given
318         LogUtility::msg("The call (" . print_r($this->call, true) . ") has an array and not a string as component (" . print_r($component, true) . "). Page: " . MarkupPath::createFromRequestedPage(), LogUtility::LVL_MSG_ERROR);
319         return "";
320 
321 
322     }
323 
324 
325     /**
326      * The parser state
327      * @return mixed
328      * May be null (example eol, internallink, ...)
329      */
330     public
331     function getState()
332     {
333         $mode = $this->call[0];
334         if ($mode !== "plugin") {
335 
336             /**
337              * There is no state because this is a standard
338              * dokuwiki syntax found in {@link \Doku_Renderer_xhtml}
339              * check if this is not a `...._close` or `...._open`
340              * to derive the state
341              */
342             $mode = $this->call[0];
343             $lastPositionSepName = strrpos($mode, "_");
344             $closeOrOpen = substr($mode, $lastPositionSepName + 1);
345             switch ($closeOrOpen) {
346                 case "open":
347                     return DOKU_LEXER_ENTER;
348                 case "close":
349                     return DOKU_LEXER_EXIT;
350                 default:
351                     /**
352                      * Let op null, is used
353                      * in {@link CallStack::processEolToEndStack()}
354                      * and things can break
355                      */
356                     return null;
357             }
358 
359         } else {
360             // Plugin
361             $returnedArray = $this->call[1];
362             if (array_key_exists(2, $returnedArray)) {
363                 return $returnedArray[2];
364             } else {
365                 return null;
366             }
367         }
368     }
369 
370     /**
371      * @return mixed the data returned from the {@link DokuWiki_Syntax_Plugin::handle} (ie attributes, payload, ...)
372      * It may be any type. Array, scalar
373      */
374     public
375     function &getPluginData($attribute = null)
376     {
377         $data = &$this->call[1][1];
378         if ($attribute === null) {
379             return $data;
380         }
381         return $data[$attribute];
382 
383     }
384 
385     /**
386      * @return mixed the matched content from the {@link DokuWiki_Syntax_Plugin::handle}
387      */
388     public
389     function getCapturedContent()
390     {
391         $caller = $this->call[0];
392         switch ($caller) {
393             case "plugin":
394                 return $this->call[1][3];
395             case "internallink":
396                 return '[[' . $this->call[1][0] . '|' . $this->call[1][1] . ']]';
397             case "eol":
398                 return DOKU_LF;
399             case "header":
400             case "cdata":
401                 return $this->call[1][0];
402             default:
403                 if (isset($this->call[1][0]) && is_string($this->call[1][0])) {
404                     return $this->call[1][0];
405                 } else {
406                     return "";
407                 }
408         }
409     }
410 
411 
412     /**
413      * Return the attributes of a call
414      */
415     public
416     function &getAttributes(): array
417     {
418 
419         $isPluginCall = $this->isPluginCall();
420         if (!$isPluginCall) {
421             return $this->call[1];
422         }
423 
424         $data = &$this->getPluginData();
425         if (!is_array($data)) {
426             LogUtility::error("The handle data is not an array for the call ($this), correct the returned data from the handle syntax plugin function", self::CANONICAL);
427             // We discard, it may be a third party plugin
428             // The log will throw an error if it's on our hand
429             $data = [];
430             return $data;
431         }
432         if (!isset($data[PluginUtility::ATTRIBUTES])) {
433             $data[PluginUtility::ATTRIBUTES] = [];
434         }
435         $attributes = &$data[PluginUtility::ATTRIBUTES];
436         if (!is_array($attributes)) {
437             $message = "The attributes value are not an array for the call ($this), the value was wrapped in an array";
438             LogUtility::error($message, self::CANONICAL);
439             $attributes = [$attributes];
440         }
441         return $attributes;
442     }
443 
444     public
445     function removeAttributes()
446     {
447 
448         $data = &$this->getPluginData();
449         if (isset($data[PluginUtility::ATTRIBUTES])) {
450             unset($data[PluginUtility::ATTRIBUTES]);
451         }
452 
453     }
454 
455     public
456     function updateToPluginComponent($component, $state, $attributes = array())
457     {
458         if ($this->call[0] == "plugin") {
459             $match = $this->call[1][3];
460         } else {
461             $this->call[0] = "plugin";
462             $match = "";
463         }
464         $this->call[1] = array(
465             0 => $component,
466             1 => array(
467                 PluginUtility::ATTRIBUTES => $attributes,
468                 PluginUtility::STATE => $state,
469             ),
470             2 => $state,
471             3 => $match
472         );
473 
474     }
475 
476     /**
477      * Does the display has been set
478      * to override the dokuwiki default
479      * ({@link Syntax::getPType()}
480      *
481      * because an image is by default a inline component
482      * but can be a block (ie top image of a card)
483      * @return bool
484      */
485     public
486     function isDisplaySet(): bool
487     {
488         return isset($this->call[1][1][PluginUtility::DISPLAY]);
489     }
490 
491     /**
492      * @return string|null
493      * {@link Call::INLINE_DISPLAY} or {@link Call::BlOCK_DISPLAY}
494      */
495     public
496     function getDisplay(): ?string
497     {
498         $mode = $this->getMode();
499         if ($mode == "plugin") {
500             if ($this->isDisplaySet()) {
501                 return $this->call[1][1][PluginUtility::DISPLAY];
502             }
503         }
504 
505         if ($this->getState() == DOKU_LEXER_UNMATCHED) {
506             /**
507              * Unmatched are content (ie text node in XML/HTML) and have
508              * no display
509              */
510             return Call::INLINE_DISPLAY;
511         } else {
512             $mode = $this->call[0];
513             if ($mode == "plugin") {
514                 global $DOKU_PLUGINS;
515                 $component = $this->getComponentName();
516                 /**
517                  * @var SyntaxPlugin $syntaxPlugin
518                  */
519                 $syntaxPlugin = $DOKU_PLUGINS['syntax'][$component];
520                 $pType = $syntaxPlugin->getPType();
521                 switch ($pType) {
522                     case "normal":
523                         return Call::INLINE_DISPLAY;
524                     case "block":
525                     case "stack":
526                         return Call::BlOCK_DISPLAY;
527                     default:
528                         LogUtility::msg("The ptype (" . $pType . ") is unknown.");
529                         return null;
530                 }
531             } else {
532                 if ($mode == "eol") {
533                     /**
534                      * Control character
535                      * We return it as it's used in the
536                      * {@link \syntax_plugin_combo_para::fromEolToParagraphUntilEndOfStack()}
537                      * to create the paragraph
538                      * This is not a block, nor an inline
539                      */
540                     return $mode;
541                 }
542 
543                 if (in_array($mode, self::INLINE_DOKUWIKI_COMPONENTS)) {
544                     return Call::INLINE_DISPLAY;
545                 }
546 
547                 if (in_array($mode, self::BLOCK_MARKUP_DOKUWIKI_COMPONENTS)) {
548                     return Call::BlOCK_DISPLAY;
549                 }
550 
551                 if (in_array($mode, self::TABLE_MARKUP)) {
552                     return Call::TABLE_DISPLAY;
553                 }
554 
555                 LogUtility::warning("The display of the call with the mode (" . $mode . ") is unknown");
556                 return null;
557 
558 
559             }
560         }
561 
562     }
563 
564     /**
565      * Same as {@link Call::getTagName()}
566      * but fully qualified
567      * @return string
568      */
569     public
570     function getComponentName()
571     {
572         $mode = $this->call[0];
573         if ($mode == "plugin") {
574             $pluginDokuData = $this->call[1];
575             return $pluginDokuData[0];
576         } else {
577             return $mode;
578         }
579     }
580 
581     public
582     function updateEolToSpace()
583     {
584         $mode = $this->call[0];
585         if ($mode != "eol") {
586             LogUtility::msg("You can't update a " . $mode . " to a space. It should be a eol", LogUtility::LVL_MSG_WARNING, "support");
587         } else {
588             $this->call[0] = "cdata";
589             $this->call[1] = array(
590                 0 => " "
591             );
592         }
593 
594     }
595 
596     public
597     function &addAttribute($key, $value)
598     {
599         $mode = $this->call[0];
600         if ($mode == "plugin") {
601             $this->call[1][1][PluginUtility::ATTRIBUTES][$key] = $value;
602             // keep the new reference
603             return $this->call[1][1][PluginUtility::ATTRIBUTES][$key];
604         } else {
605             LogUtility::msg("You can't add an attribute to the non plugin call mode (" . $mode . ")", LogUtility::LVL_MSG_WARNING, "support");
606             $whatever = [];
607             return $whatever;
608         }
609     }
610 
611     public
612     function getContext()
613     {
614         $mode = $this->call[0];
615         if ($mode === "plugin") {
616             return $this->call[1][1][PluginUtility::CONTEXT] ?? null;
617         } else {
618             LogUtility::msg("You can't ask for a context from a non plugin call mode (" . $mode . ")", LogUtility::LVL_MSG_WARNING, "support");
619             return null;
620         }
621     }
622 
623     /**
624      *
625      * @return array
626      */
627     public
628     function toCallArray()
629     {
630         return $this->call;
631     }
632 
633     public
634     function __toString()
635     {
636         $name = $this->key;
637         if (!empty($name)) {
638             $name .= " - ";
639         }
640         $name .= $this->getTagName();
641         $name .= " - {$this->getStateName()}";
642         return $name;
643     }
644 
645     /**
646      * @return string|null
647      *
648      * If the type returned is a boolean attribute,
649      * it means you need to define the expected types
650      * in the function {@link TagAttributes::createFromTagMatch()}
651      * as third attribute
652      */
653     public
654     function getType(): ?string
655     {
656         if ($this->getState() == DOKU_LEXER_UNMATCHED) {
657             return null;
658         } else {
659             return $this->getAttribute(TagAttributes::TYPE_KEY);
660         }
661     }
662 
663     /**
664      * @param $key
665      * @param null $default
666      * @return array|string|null
667      */
668     public
669     function &getAttribute($key, $default = null)
670     {
671         $attributes = &$this->getAttributes();
672         if (isset($attributes[$key])) {
673             return $attributes[$key];
674         }
675         return $default;
676 
677     }
678 
679     public
680     function getPayload()
681     {
682         $mode = $this->call[0];
683         if ($mode == "plugin") {
684             return $this->call[1][1][PluginUtility::PAYLOAD];
685         } else {
686             LogUtility::msg("You can't ask for a payload from a non plugin call mode (" . $mode . ").", LogUtility::LVL_MSG_WARNING, "support");
687             return null;
688         }
689     }
690 
691     public
692     function setContext($value)
693     {
694         $this->call[1][1][PluginUtility::CONTEXT] = $value;
695         return $this;
696     }
697 
698     public
699     function hasAttribute($attributeName): bool
700     {
701         $attributes = $this->getAttributes();
702         if (isset($attributes[$attributeName])) {
703             return true;
704         } else {
705             if ($this->getType() == $attributeName) {
706                 return true;
707             } else {
708                 return false;
709             }
710         }
711     }
712 
713     public
714     function isPluginCall()
715     {
716         return $this->call[0] === "plugin";
717     }
718 
719     /**
720      * @return mixed|string the position (ie key) in the array
721      */
722     public
723     function getKey()
724     {
725         return $this->key;
726     }
727 
728     public
729     function &getInstructionCall()
730     {
731         return $this->call;
732     }
733 
734     public
735     function setState($state)
736     {
737         if ($this->call[0] == "plugin") {
738             // for dokuwiki
739             $this->call[1][2] = $state;
740             // for the combo plugin if any
741             if (isset($this->call[1][1][PluginUtility::STATE])) {
742                 $this->call[1][1][PluginUtility::STATE] = $state;
743             }
744         } else {
745             LogUtility::msg("This modification of state is not yet supported for a native call");
746         }
747     }
748 
749 
750     /**
751      * Return the position of the first matched character in the text file
752      * @return mixed
753      */
754     public
755     function getFirstMatchedCharacterPosition()
756     {
757 
758         return $this->call[2];
759 
760     }
761 
762     /**
763      * Return the position of the last matched character in the text file
764      *
765      * This is the {@link Call::getFirstMatchedCharacterPosition()}
766      * plus the length of the {@link Call::getCapturedContent()}
767      * matched content
768      * @return int|mixed
769      */
770     public
771     function getLastMatchedCharacterPosition()
772     {
773         $captureContent = $this->getCapturedContent();
774         $length = 0;
775         if ($captureContent != null) {
776             $length = strlen($captureContent);
777         }
778         return $this->getFirstMatchedCharacterPosition() + $length;
779     }
780 
781     /**
782      * @param $value string the class string to add
783      * @return Call
784      */
785     public
786     function addClassName(string $value): Call
787     {
788         $class = $this->getAttribute("class");
789         if ($class != null) {
790             $value = "$class $value";
791         }
792         $this->addAttribute("class", $value);
793         return $this;
794 
795     }
796 
797     /**
798      * @param $key
799      * @return mixed|null - the delete value of null if not found
800      */
801     public
802     function removeAttribute($key)
803     {
804 
805         $data = &$this->getPluginData();
806         if (isset($data[PluginUtility::ATTRIBUTES][$key])) {
807             $value = $data[PluginUtility::ATTRIBUTES][$key];
808             unset($data[PluginUtility::ATTRIBUTES][$key]);
809             return $value;
810         } else {
811             // boolean attribute as first attribute
812             if ($this->getType() == $key) {
813                 unset($data[PluginUtility::ATTRIBUTES][TagAttributes::TYPE_KEY]);
814                 return true;
815             }
816             return null;
817         }
818 
819     }
820 
821     public
822     function setPayload($text)
823     {
824         if ($this->isPluginCall()) {
825             $this->call[1][1][PluginUtility::PAYLOAD] = $text;
826         } else {
827             LogUtility::msg("Setting the payload for a non-native call ($this) is not yet implemented");
828         }
829     }
830 
831     /**
832      * @return bool true if the call is a text call (same as dom text node)
833      */
834     public
835     function isTextCall()
836     {
837         return (
838             $this->getState() == DOKU_LEXER_UNMATCHED ||
839             $this->getTagName() == "cdata" ||
840             $this->getTagName() == "acronym"
841         );
842     }
843 
844     public
845     function setType($type)
846     {
847         if ($this->isPluginCall()) {
848             $this->call[1][1][PluginUtility::ATTRIBUTES][TagAttributes::TYPE_KEY] = $type;
849         } else {
850             LogUtility::msg("This is not a plugin call ($this), you can't set the type");
851         }
852     }
853 
854     public
855     function addCssStyle($key, $value)
856     {
857         $style = $this->getAttribute("style");
858         $cssValue = "$key:$value";
859         if ($style !== null) {
860             $cssValue = "$style; $cssValue";
861         }
862         $this->addAttribute("style", $cssValue);
863     }
864 
865     public
866     function setSyntaxComponentFromTag($tag)
867     {
868 
869         if ($this->isPluginCall()) {
870             $this->call[1][0] = PluginUtility::getComponentName($tag);
871         } else {
872             LogUtility::msg("The call ($this) is a native call and we don't support yet the modification of the component to ($tag)");
873         }
874     }
875 
876 
877     public
878     function setCapturedContent($content)
879     {
880         $tagName = $this->getTagName();
881         switch ($tagName) {
882             case "cdata":
883                 $this->call[1][0] = $content;
884                 break;
885             default:
886                 LogUtility::msg("Setting the captured content on a call for the tag ($tagName) is not yet implemented", LogUtility::LVL_MSG_ERROR);
887         }
888     }
889 
890     /**
891      * Set the display to block or inline
892      * One of `block` or `inline`
893      */
894     public
895     function setDisplay($display): Call
896     {
897         $mode = $this->getMode();
898         if ($mode == "plugin") {
899             $this->call[1][1][PluginUtility::DISPLAY] = $display;
900         } else {
901             LogUtility::msg("You can't set a display on a non plugin call mode (" . $mode . ")", LogUtility::LVL_MSG_WARNING);
902         }
903         return $this;
904 
905     }
906 
907     /**
908      * The plugin or not
909      * @return mixed
910      */
911     private
912     function getMode()
913     {
914         return $this->call[0];
915     }
916 
917     /**
918      * Return if this an unmatched call with space
919      * in captured content
920      * @return bool
921      */
922     public
923     function isUnMatchedEmptyCall(): bool
924     {
925         if ($this->getState() === DOKU_LEXER_UNMATCHED && trim($this->getCapturedContent()) === "") {
926             return true;
927         }
928         return false;
929     }
930 
931     public
932     function getExitCode()
933     {
934         $mode = $this->call[0];
935         if ($mode == "plugin") {
936             $value = $this->call[1][1][PluginUtility::EXIT_CODE] ?? null;
937             if ($value === null) {
938                 return 0;
939             }
940             return $value;
941         } else {
942             LogUtility::msg("You can't ask for the exit code from a non plugin call mode (" . $mode . ").", LogUtility::LVL_MSG_WARNING, "support");
943             return 0;
944         }
945     }
946 
947     public
948     function setAttribute(string $name, $value): Call
949     {
950         $this->getAttributes()[$name] = $value;
951         return $this;
952     }
953 
954     public
955     function setPluginData(string $name, $value): Call
956     {
957         $this->getPluginData()[$name] = $value;
958         return $this;
959     }
960 
961     public
962     function getIdOrDefault()
963     {
964         $id = $this->getAttribute(TagAttributes::ID_KEY);
965         if ($id !== null) {
966             return $id;
967         }
968         return $this->getAttribute(TagAttributes::GENERATED_ID_KEY);
969     }
970 
971     public
972     function getAttributeAndRemove(string $key)
973     {
974         $value = $this->getAttribute($key);
975         $this->removeAttribute($key);
976         return $value;
977     }
978 
979     private function getStateName(): string
980     {
981         $state = $this->getState();
982         switch ($state) {
983             case DOKU_LEXER_ENTER:
984                 return "enter";
985             case DOKU_LEXER_EXIT:
986                 return "exit";
987             case DOKU_LEXER_SPECIAL:
988                 return "empty";
989             case DOKU_LEXER_UNMATCHED:
990                 return "text";
991             default:
992                 return "unknown " . $state;
993         }
994     }
995 
996 
997 }
998