xref: /plugin/combo/ComboStrap/Call.php (revision ad79af66a70046d40e27ff4cc82d28834afaf49b)
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
13namespace ComboStrap;
14
15use dokuwiki\Extension\SyntaxPlugin;
16use 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 */
30class 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                if ($syntaxPlugin === null) {
521                    // not a syntax plugin (ie frontmatter)
522                    return null;
523                }
524                $pType = $syntaxPlugin->getPType();
525                switch ($pType) {
526                    // https://www.dokuwiki.org/devel:syntax_plugins#syntax_types
527                    case "substition":
528                    case "normal":
529                        return Call::INLINE_DISPLAY;
530                    case "block":
531                    case "stack":
532                        return Call::BlOCK_DISPLAY;
533                    default:
534                        LogUtility::msg("The ptype (" . $pType . ") is unknown.");
535                        return null;
536                }
537            } else {
538                if ($mode == "eol") {
539                    /**
540                     * Control character
541                     * We return it as it's used in the
542                     * {@link \syntax_plugin_combo_para::fromEolToParagraphUntilEndOfStack()}
543                     * to create the paragraph
544                     * This is not a block, nor an inline
545                     */
546                    return $mode;
547                }
548
549                if (in_array($mode, self::INLINE_DOKUWIKI_COMPONENTS)) {
550                    return Call::INLINE_DISPLAY;
551                }
552
553                if (in_array($mode, self::BLOCK_MARKUP_DOKUWIKI_COMPONENTS)) {
554                    return Call::BlOCK_DISPLAY;
555                }
556
557                if (in_array($mode, self::TABLE_MARKUP)) {
558                    return Call::TABLE_DISPLAY;
559                }
560
561                LogUtility::warning("The display of the call with the mode (" . $mode . ") is unknown");
562                return null;
563
564
565            }
566        }
567
568    }
569
570    /**
571     * Same as {@link Call::getTagName()}
572     * but fully qualified
573     * @return string
574     */
575    public
576    function getComponentName()
577    {
578        $mode = $this->call[0];
579        if ($mode == "plugin") {
580            $pluginDokuData = $this->call[1];
581            return $pluginDokuData[0];
582        } else {
583            return $mode;
584        }
585    }
586
587    public
588    function updateEolToSpace()
589    {
590        $mode = $this->call[0];
591        if ($mode != "eol") {
592            LogUtility::msg("You can't update a " . $mode . " to a space. It should be a eol", LogUtility::LVL_MSG_WARNING, "support");
593        } else {
594            $this->call[0] = "cdata";
595            $this->call[1] = array(
596                0 => " "
597            );
598        }
599
600    }
601
602    public
603    function &addAttribute($key, $value)
604    {
605        $mode = $this->call[0];
606        if ($mode == "plugin") {
607            $this->call[1][1][PluginUtility::ATTRIBUTES][$key] = $value;
608            // keep the new reference
609            return $this->call[1][1][PluginUtility::ATTRIBUTES][$key];
610        } else {
611            LogUtility::msg("You can't add an attribute to the non plugin call mode (" . $mode . ")", LogUtility::LVL_MSG_WARNING, "support");
612            $whatever = [];
613            return $whatever;
614        }
615    }
616
617    public
618    function getContext()
619    {
620        $mode = $this->call[0];
621        if ($mode === "plugin") {
622            return $this->call[1][1][PluginUtility::CONTEXT] ?? null;
623        } else {
624            LogUtility::msg("You can't ask for a context from a non plugin call mode (" . $mode . ")", LogUtility::LVL_MSG_WARNING, "support");
625            return null;
626        }
627    }
628
629    /**
630     *
631     * @return array
632     */
633    public
634    function toCallArray()
635    {
636        return $this->call;
637    }
638
639    public
640    function __toString()
641    {
642        $name = $this->key;
643        if (!empty($name)) {
644            $name .= " - ";
645        }
646        $name .= $this->getTagName();
647        $name .= " - {$this->getStateName()}";
648        return $name;
649    }
650
651    /**
652     * @return string|null
653     *
654     * If the type returned is a boolean attribute,
655     * it means you need to define the expected types
656     * in the function {@link TagAttributes::createFromTagMatch()}
657     * as third attribute
658     */
659    public
660    function getType(): ?string
661    {
662        if ($this->getState() == DOKU_LEXER_UNMATCHED) {
663            return null;
664        } else {
665            return $this->getAttribute(TagAttributes::TYPE_KEY);
666        }
667    }
668
669    /**
670     * @param $key
671     * @param null $default
672     * @return array|string|null
673     */
674    public
675    function &getAttribute($key, $default = null)
676    {
677        $attributes = &$this->getAttributes();
678        if (isset($attributes[$key])) {
679            return $attributes[$key];
680        }
681        return $default;
682
683    }
684
685    public
686    function getPayload()
687    {
688        $mode = $this->call[0];
689        if ($mode == "plugin") {
690            return $this->call[1][1][PluginUtility::PAYLOAD];
691        } else {
692            LogUtility::msg("You can't ask for a payload from a non plugin call mode (" . $mode . ").", LogUtility::LVL_MSG_WARNING, "support");
693            return null;
694        }
695    }
696
697    public
698    function setContext($value)
699    {
700        $this->call[1][1][PluginUtility::CONTEXT] = $value;
701        return $this;
702    }
703
704    public
705    function hasAttribute($attributeName): bool
706    {
707        $attributes = $this->getAttributes();
708        if (isset($attributes[$attributeName])) {
709            return true;
710        } else {
711            if ($this->getType() == $attributeName) {
712                return true;
713            } else {
714                return false;
715            }
716        }
717    }
718
719    public
720    function isPluginCall()
721    {
722        return $this->call[0] === "plugin";
723    }
724
725    /**
726     * @return mixed|string the position (ie key) in the array
727     */
728    public
729    function getKey()
730    {
731        return $this->key;
732    }
733
734    public
735    function &getInstructionCall()
736    {
737        return $this->call;
738    }
739
740    public
741    function setState($state)
742    {
743        if ($this->call[0] == "plugin") {
744            // for dokuwiki
745            $this->call[1][2] = $state;
746            // for the combo plugin if any
747            if (isset($this->call[1][1][PluginUtility::STATE])) {
748                $this->call[1][1][PluginUtility::STATE] = $state;
749            }
750        } else {
751            LogUtility::msg("This modification of state is not yet supported for a native call");
752        }
753    }
754
755
756    /**
757     * Return the position of the first matched character in the text file
758     * @return mixed
759     */
760    public
761    function getFirstMatchedCharacterPosition()
762    {
763
764        return $this->call[2];
765
766    }
767
768    /**
769     * Return the position of the last matched character in the text file
770     *
771     * This is the {@link Call::getFirstMatchedCharacterPosition()}
772     * plus the length of the {@link Call::getCapturedContent()}
773     * matched content
774     * @return int|mixed
775     */
776    public
777    function getLastMatchedCharacterPosition()
778    {
779        $captureContent = $this->getCapturedContent();
780        $length = 0;
781        if ($captureContent != null) {
782            $length = strlen($captureContent);
783        }
784        return $this->getFirstMatchedCharacterPosition() + $length;
785    }
786
787    /**
788     * @param $value string the class string to add
789     * @return Call
790     */
791    public
792    function addClassName(string $value): Call
793    {
794        $class = $this->getAttribute("class");
795        if ($class != null) {
796            $value = "$class $value";
797        }
798        $this->addAttribute("class", $value);
799        return $this;
800
801    }
802
803    /**
804     * @param $key
805     * @return mixed|null - the delete value of null if not found
806     */
807    public
808    function removeAttribute($key)
809    {
810
811        $data = &$this->getPluginData();
812        if (isset($data[PluginUtility::ATTRIBUTES][$key])) {
813            $value = $data[PluginUtility::ATTRIBUTES][$key];
814            unset($data[PluginUtility::ATTRIBUTES][$key]);
815            return $value;
816        } else {
817            // boolean attribute as first attribute
818            if ($this->getType() == $key) {
819                unset($data[PluginUtility::ATTRIBUTES][TagAttributes::TYPE_KEY]);
820                return true;
821            }
822            return null;
823        }
824
825    }
826
827    public
828    function setPayload($text)
829    {
830        if ($this->isPluginCall()) {
831            $this->call[1][1][PluginUtility::PAYLOAD] = $text;
832        } else {
833            LogUtility::msg("Setting the payload for a non-native call ($this) is not yet implemented");
834        }
835    }
836
837    /**
838     * @return bool true if the call is a text call (same as dom text node)
839     */
840    public
841    function isTextCall()
842    {
843        return (
844            $this->getState() == DOKU_LEXER_UNMATCHED ||
845            $this->getTagName() == "cdata" ||
846            $this->getTagName() == "acronym"
847        );
848    }
849
850    public
851    function setType($type)
852    {
853        if ($this->isPluginCall()) {
854            $this->call[1][1][PluginUtility::ATTRIBUTES][TagAttributes::TYPE_KEY] = $type;
855        } else {
856            LogUtility::msg("This is not a plugin call ($this), you can't set the type");
857        }
858    }
859
860    public
861    function addCssStyle($key, $value)
862    {
863        $style = $this->getAttribute("style");
864        $cssValue = "$key:$value";
865        if ($style !== null) {
866            $cssValue = "$style; $cssValue";
867        }
868        $this->addAttribute("style", $cssValue);
869    }
870
871    public
872    function setSyntaxComponentFromTag($tag)
873    {
874
875        if ($this->isPluginCall()) {
876            $this->call[1][0] = PluginUtility::getComponentName($tag);
877        } else {
878            LogUtility::msg("The call ($this) is a native call and we don't support yet the modification of the component to ($tag)");
879        }
880    }
881
882
883    public
884    function setCapturedContent($content)
885    {
886        $tagName = $this->getTagName();
887        switch ($tagName) {
888            case "cdata":
889                $this->call[1][0] = $content;
890                break;
891            default:
892                LogUtility::msg("Setting the captured content on a call for the tag ($tagName) is not yet implemented", LogUtility::LVL_MSG_ERROR);
893        }
894    }
895
896    /**
897     * Set the display to block or inline
898     * One of `block` or `inline`
899     */
900    public
901    function setDisplay($display): Call
902    {
903        $mode = $this->getMode();
904        if ($mode == "plugin") {
905            $this->call[1][1][PluginUtility::DISPLAY] = $display;
906        } else {
907            LogUtility::msg("You can't set a display on a non plugin call mode (" . $mode . ")", LogUtility::LVL_MSG_WARNING);
908        }
909        return $this;
910
911    }
912
913    /**
914     * The plugin or not
915     * @return mixed
916     */
917    private
918    function getMode()
919    {
920        return $this->call[0];
921    }
922
923    /**
924     * Return if this an unmatched call with space
925     * in captured content
926     * @return bool
927     */
928    public
929    function isUnMatchedEmptyCall(): bool
930    {
931        if ($this->getState() === DOKU_LEXER_UNMATCHED && trim($this->getCapturedContent()) === "") {
932            return true;
933        }
934        return false;
935    }
936
937    public
938    function getExitCode()
939    {
940        $mode = $this->call[0];
941        if ($mode == "plugin") {
942            $value = $this->call[1][1][PluginUtility::EXIT_CODE] ?? null;
943            if ($value === null) {
944                return 0;
945            }
946            return $value;
947        } else {
948            LogUtility::msg("You can't ask for the exit code from a non plugin call mode (" . $mode . ").", LogUtility::LVL_MSG_WARNING, "support");
949            return 0;
950        }
951    }
952
953    public
954    function setAttribute(string $name, $value): Call
955    {
956        $this->getAttributes()[$name] = $value;
957        return $this;
958    }
959
960    public
961    function setPluginData(string $name, $value): Call
962    {
963        $this->getPluginData()[$name] = $value;
964        return $this;
965    }
966
967    public
968    function getIdOrDefault()
969    {
970        $id = $this->getAttribute(TagAttributes::ID_KEY);
971        if ($id !== null) {
972            return $id;
973        }
974        return $this->getAttribute(TagAttributes::GENERATED_ID_KEY);
975    }
976
977    public
978    function getAttributeAndRemove(string $key)
979    {
980        $value = $this->getAttribute($key);
981        $this->removeAttribute($key);
982        return $value;
983    }
984
985    private function getStateName(): string
986    {
987        $state = $this->getState();
988        switch ($state) {
989            case DOKU_LEXER_ENTER:
990                return "enter";
991            case DOKU_LEXER_EXIT:
992                return "exit";
993            case DOKU_LEXER_SPECIAL:
994                return "empty";
995            case DOKU_LEXER_UNMATCHED:
996                return "text";
997            default:
998                return "unknown " . $state;
999        }
1000    }
1001
1002
1003}
1004