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