xref: /template/strap/ComboStrap/Outline.php (revision aea52b497ecf7ce8a15fd6f9dad705aee722f51f)
104fd306cSNickeau<?php
204fd306cSNickeau
304fd306cSNickeaunamespace ComboStrap;
404fd306cSNickeau
504fd306cSNickeau
604fd306cSNickeauuse ComboStrap\Meta\Field\FeaturedRasterImage;
704fd306cSNickeauuse ComboStrap\Meta\Field\FeaturedSvgImage;
804fd306cSNickeauuse ComboStrap\Meta\Field\PageH1;
904fd306cSNickeauuse ComboStrap\Tag\TableTag;
1004fd306cSNickeauuse ComboStrap\TagAttribute\StyleAttribute;
1104fd306cSNickeauuse dokuwiki\Extension\SyntaxPlugin;
1204fd306cSNickeauuse syntax_plugin_combo_analytics;
1304fd306cSNickeauuse syntax_plugin_combo_header;
1404fd306cSNickeauuse syntax_plugin_combo_headingatx;
1504fd306cSNickeauuse syntax_plugin_combo_headingwiki;
1604fd306cSNickeauuse syntax_plugin_combo_media;
1704fd306cSNickeau
1804fd306cSNickeau/**
1904fd306cSNickeau * The outline is creating a XML like document
2004fd306cSNickeau * with section
2104fd306cSNickeau *
2204fd306cSNickeau * It's also the post-processing of the instructions
2304fd306cSNickeau */
2404fd306cSNickeauclass Outline
2504fd306cSNickeau{
2604fd306cSNickeau
2704fd306cSNickeau
2804fd306cSNickeau    const CANONICAL = "outline";
2904fd306cSNickeau    private const OUTLINE_HEADING_PREFIX = "outline-heading";
3004fd306cSNickeau    const CONTEXT = self::CANONICAL;
3104fd306cSNickeau    public const OUTLINE_HEADING_NUMBERING = "outline-heading-numbering";
3204fd306cSNickeau    public const TOC_NUMBERING = "toc-numbering";
3304fd306cSNickeau    /**
3404fd306cSNickeau     * As seen on
3504fd306cSNickeau     * https://drafts.csswg.org/css-counter-styles-3/#predefined-counters
3604fd306cSNickeau     */
3704fd306cSNickeau    public const CONF_COUNTER_STYLES_CHOICES = [
3804fd306cSNickeau        'arabic-indic',
3904fd306cSNickeau        'bengali',
4004fd306cSNickeau        'cambodian/khmer',
4104fd306cSNickeau        'cjk-decimal',
4204fd306cSNickeau        'decimal',
4304fd306cSNickeau        'decimal-leading-zero',
4404fd306cSNickeau        'devanagari',
4504fd306cSNickeau        'georgian',
4604fd306cSNickeau        'gujarati',
4704fd306cSNickeau        'gurmukhi',
4804fd306cSNickeau        'hebrew',
4904fd306cSNickeau        'hiragana',
5004fd306cSNickeau        'hiragana-iroha',
5104fd306cSNickeau        'kannada',
5204fd306cSNickeau        'katakana',
5304fd306cSNickeau        'katakana-iroha',
5404fd306cSNickeau        'lao',
5504fd306cSNickeau        'lower-alpha',
5604fd306cSNickeau        'lower-armenian',
5704fd306cSNickeau        'lower-greek',
5804fd306cSNickeau        'lower-roman',
5904fd306cSNickeau        'malayalam',
6004fd306cSNickeau        'mongolian',
6104fd306cSNickeau        'myanmar',
6204fd306cSNickeau        'oriya',
6304fd306cSNickeau        'persian',
6404fd306cSNickeau        'tamil',
6504fd306cSNickeau        'telugu',
6604fd306cSNickeau        'thai',
6704fd306cSNickeau        'tibetan',
6804fd306cSNickeau        'upper-alpha',
6904fd306cSNickeau        'upper-armenian',
7004fd306cSNickeau        'upper-roman'
7104fd306cSNickeau    ];
7204fd306cSNickeau    public const CONF_OUTLINE_NUMBERING_COUNTER_STYLE_LEVEL4 = "outlineNumberingCounterStyleLevel4";
7304fd306cSNickeau    public const CONF_OUTLINE_NUMBERING_COUNTER_STYLE_LEVEL3 = "outlineNumberingCounterStyleLevel3";
7404fd306cSNickeau    public const CONF_OUTLINE_NUMBERING_SUFFIX = "outlineNumberingSuffix";
7504fd306cSNickeau    public const CONF_OUTLINE_NUMBERING_COUNTER_STYLE_LEVEL2 = "outlineNumberingCounterStyleLevel2";
7604fd306cSNickeau    public const CONF_OUTLINE_NUMBERING_COUNTER_SEPARATOR = "outlineNumberingCounterSeparator";
7704fd306cSNickeau    public const CONF_OUTLINE_NUMBERING_COUNTER_STYLE_LEVEL6 = "outlineNumberingCounterStyleLevel6";
7804fd306cSNickeau    public const CONF_OUTLINE_NUMBERING_COUNTER_STYLE_LEVEL5 = "outlineNumberingCounterStyleLevel5";
7904fd306cSNickeau    public const CONF_OUTLINE_NUMBERING_PREFIX = "outlineNumberingPrefix";
8004fd306cSNickeau    public const CONF_OUTLINE_NUMBERING_ENABLE = "outlineNumberingEnable";
8104fd306cSNickeau    /**
8204fd306cSNickeau     * To add hash tag to heading
8304fd306cSNickeau     */
8404fd306cSNickeau    public const OUTLINE_ANCHOR = "outline-anchor";
8504fd306cSNickeau    const CONF_OUTLINE_NUMBERING_ENABLE_DEFAULT = 1;
8604fd306cSNickeau    private OutlineSection $rootSection;
8704fd306cSNickeau
8804fd306cSNickeau    private OutlineSection $actualSection; // the actual section that is created
8904fd306cSNickeau    private Call $actualHeadingCall; // the heading that is parsed
9004fd306cSNickeau    private int $actualHeadingParsingState = DOKU_LEXER_EXIT;  // the state of the heading parsed (enter, closed), enter if we have entered an heading, exit if not;
9104fd306cSNickeau    private ?MarkupPath $markupPath = null;
9204fd306cSNickeau    private bool $isFragment;
9304fd306cSNickeau    private bool $metaHeaderCapture;
9404fd306cSNickeau
9504fd306cSNickeau    /**
9604fd306cSNickeau     * @param CallStack $callStack
9704fd306cSNickeau     * @param MarkupPath|null $markup - needed to store the parsed toc, h1, ... (null if the markup is dynamic)
9804fd306cSNickeau     * @param bool $isFragment - needed to control the structure of the outline (if this is a preview, the first heading may be not h1)
9904fd306cSNickeau     * @return void
10004fd306cSNickeau     */
10104fd306cSNickeau    public function __construct(CallStack $callStack, MarkupPath $markup = null, bool $isFragment = false)
10204fd306cSNickeau    {
10304fd306cSNickeau        if ($markup !== null) {
10404fd306cSNickeau            $this->markupPath = $markup;
10504fd306cSNickeau        }
10604fd306cSNickeau        $this->isFragment = $isFragment;
10704fd306cSNickeau        $this->buildOutline($callStack);
10804fd306cSNickeau        $this->storeH1();
10904fd306cSNickeau        $this->storeTocForMarkupIfAny();
11004fd306cSNickeau    }
11104fd306cSNickeau
11204fd306cSNickeau    /**
11304fd306cSNickeau     * @param CallStack $callStack
11404fd306cSNickeau     * @param MarkupPath|null $markupPath - needed to store the parsed toc, h1, ... (null if the markup is dynamic)
11504fd306cSNickeau     * @param bool $isFragment - needed to control the structure of the outline (if this is a preview, the first heading may be not h1)
11604fd306cSNickeau     * @return Outline
11704fd306cSNickeau     */
11804fd306cSNickeau    public static function createFromCallStack(CallStack $callStack, MarkupPath $markupPath = null, bool $isFragment = false): Outline
11904fd306cSNickeau    {
12004fd306cSNickeau        return new Outline($callStack, $markupPath, $isFragment);
12104fd306cSNickeau    }
12204fd306cSNickeau
12304fd306cSNickeau    private function buildOutline(CallStack $callStack)
12404fd306cSNickeau    {
12504fd306cSNickeau
12604fd306cSNickeau        /**
12704fd306cSNickeau         * {@link syntax_plugin_combo_analytics tag analytics}
12804fd306cSNickeau         * By default, in a test environment
12904fd306cSNickeau         * this is set to 0
13004fd306cSNickeau         * to speed test and to not pollute
13104fd306cSNickeau         */
13204fd306cSNickeau        $analtyicsEnabled = SiteConfig::getConfValue(syntax_plugin_combo_analytics::CONF_SYNTAX_ANALYTICS_ENABLE, true);
13304fd306cSNickeau        $analyticsTagUsed = [];
13404fd306cSNickeau
13504fd306cSNickeau        /**
13604fd306cSNickeau         * Processing variable about the context
13704fd306cSNickeau         */
13804fd306cSNickeau        $this->rootSection = OutlineSection::createOutlineRoot()
13904fd306cSNickeau            ->setStartPosition(0);
14004fd306cSNickeau        $this->actualSection = $this->rootSection;
14104fd306cSNickeau        $actualLastPosition = 0;
14204fd306cSNickeau        $callStack->moveToStart();
14304fd306cSNickeau        while ($actualCall = $callStack->next()) {
14404fd306cSNickeau
14504fd306cSNickeau
14604fd306cSNickeau            $state = $actualCall->getState();
14704fd306cSNickeau
14804fd306cSNickeau            /**
14904fd306cSNickeau             * Block Post Processing
15004fd306cSNickeau             * to not get any unwanted p
15104fd306cSNickeau             * to counter {@link Block::process()}
15204fd306cSNickeau             * setting dynamically the {@link SyntaxPlugin::getPType()}
15304fd306cSNickeau             *
15404fd306cSNickeau             * Unfortunately, it can't work because this is called after
15504fd306cSNickeau             * {@link Block::process()}
15604fd306cSNickeau             */
15704fd306cSNickeau            if ($analtyicsEnabled) {
15804fd306cSNickeau
15904fd306cSNickeau                if (in_array($state, CallStack::TAG_STATE)) {
160*aea52b49Sgerardnico                    $tagName = $actualCall->getTagName();
16104fd306cSNickeau                    // The dokuwiki component name have open in their name
16204fd306cSNickeau                    $tagName = str_replace("_open", "", $tagName);
163*aea52b49Sgerardnico                    $actual = $analyticsTagUsed[$tagName] ?? 0;
164*aea52b49Sgerardnico                    $analyticsTagUsed[$tagName] = $actual + 1;
16504fd306cSNickeau                }
16604fd306cSNickeau
16704fd306cSNickeau            }
16804fd306cSNickeau
16904fd306cSNickeau            /**
17004fd306cSNickeau             * We don't take the outline and document call if any
17104fd306cSNickeau             * This is the case when we build from an actual stored instructions
17204fd306cSNickeau             * (to bundle multiple page for instance)
17304fd306cSNickeau             */
17404fd306cSNickeau            $tagName = $actualCall->getTagName();
17504fd306cSNickeau            switch ($tagName) {
17604fd306cSNickeau                case "document_start":
17704fd306cSNickeau                case "document_end":
17804fd306cSNickeau                case SectionTag::TAG:
17904fd306cSNickeau                    continue 2;
18004fd306cSNickeau                case syntax_plugin_combo_header::TAG:
18104fd306cSNickeau                    if ($actualCall->isPluginCall() && $actualCall->getContext() === self::CONTEXT) {
18204fd306cSNickeau                        continue 2;
18304fd306cSNickeau                    }
18404fd306cSNickeau            }
18504fd306cSNickeau
18604fd306cSNickeau            /**
18704fd306cSNickeau             * Wrap the table
18804fd306cSNickeau             */
18904fd306cSNickeau            $componentName = $actualCall->getComponentName();
19004fd306cSNickeau            if ($componentName === "table_open") {
19104fd306cSNickeau                $position = $actualCall->getFirstMatchedCharacterPosition();
19204fd306cSNickeau                $originalInstructionCall = &$actualCall->getInstructionCall();
19304fd306cSNickeau                $originalInstructionCall = Call::createComboCall(
19404fd306cSNickeau                    TableTag::TAG,
19504fd306cSNickeau                    DOKU_LEXER_ENTER,
19604fd306cSNickeau                    [PluginUtility::POSITION => $position],
19704fd306cSNickeau                    null,
19804fd306cSNickeau                    null,
19904fd306cSNickeau                    null,
20004fd306cSNickeau                    $position,
20104fd306cSNickeau                    \syntax_plugin_combo_xmlblocktag::TAG
20204fd306cSNickeau                )->toCallArray();
20304fd306cSNickeau            }
20404fd306cSNickeau
20504fd306cSNickeau            /**
20604fd306cSNickeau             * Enter new section ?
20704fd306cSNickeau             */
20804fd306cSNickeau            $shouldWeCreateASection = false;
20904fd306cSNickeau            switch ($tagName) {
21004fd306cSNickeau                case syntax_plugin_combo_headingatx::TAG:
21104fd306cSNickeau                    $actualCall->setState(DOKU_LEXER_ENTER);
21204fd306cSNickeau                    if ($actualCall->getContext() === HeadingTag::TYPE_OUTLINE) {
21304fd306cSNickeau                        $shouldWeCreateASection = true;
21404fd306cSNickeau                    }
21504fd306cSNickeau                    $this->enterHeading($actualCall);
21604fd306cSNickeau                    break;
21704fd306cSNickeau                case HeadingTag::HEADING_TAG:
21804fd306cSNickeau                case syntax_plugin_combo_headingwiki::TAG:
21904fd306cSNickeau                    if ($state == DOKU_LEXER_ENTER
22004fd306cSNickeau                        && $actualCall->getContext() === HeadingTag::TYPE_OUTLINE) {
22104fd306cSNickeau                        $shouldWeCreateASection = true;
22204fd306cSNickeau                        $this->enterHeading($actualCall);
22304fd306cSNickeau                    }
22404fd306cSNickeau                    break;
22504fd306cSNickeau                case "header":
22604fd306cSNickeau                    // Should happen only on outline section
22704fd306cSNickeau                    // we take over inside a component
22804fd306cSNickeau                    if (!$actualCall->isPluginCall()) {
22904fd306cSNickeau                        /**
23004fd306cSNickeau                         * ie not {@link syntax_plugin_combo_header}
23104fd306cSNickeau                         * but the dokuwiki header (ie heading)
23204fd306cSNickeau                         */
23304fd306cSNickeau                        $shouldWeCreateASection = true;
23404fd306cSNickeau                        $this->enterHeading($actualCall);
23504fd306cSNickeau                    }
23604fd306cSNickeau                    break;
23704fd306cSNickeau            }
23804fd306cSNickeau            if ($shouldWeCreateASection) {
23904fd306cSNickeau                if ($this->actualSection->hasParent()) {
24004fd306cSNickeau                    // -1 because the actual position is the start of the next section
24104fd306cSNickeau                    $this->actualSection->setEndPosition($actualCall->getFirstMatchedCharacterPosition() - 1);
24204fd306cSNickeau                }
24304fd306cSNickeau                $actualSectionLevel = $this->actualSection->getLevel();
24404fd306cSNickeau
24504fd306cSNickeau                if ($actualCall->isPluginCall()) {
24604fd306cSNickeau                    try {
24704fd306cSNickeau                        $newSectionLevel = DataType::toInteger($actualCall->getAttribute(HeadingTag::LEVEL));
24804fd306cSNickeau                    } catch (ExceptionBadArgument $e) {
24904fd306cSNickeau                        LogUtility::internalError("The level was not present on the heading call", self::CANONICAL);
25004fd306cSNickeau                        $newSectionLevel = $actualSectionLevel;
25104fd306cSNickeau                    }
25204fd306cSNickeau                } else {
25304fd306cSNickeau                    $headerTagName = $tagName;
25404fd306cSNickeau                    if ($headerTagName !== "header") {
25504fd306cSNickeau                        throw new ExceptionRuntimeInternal("This is not a dokuwiki header call", self::CANONICAL);
25604fd306cSNickeau                    }
25704fd306cSNickeau                    $newSectionLevel = $actualCall->getInstructionCall()[1][1];
25804fd306cSNickeau                }
25904fd306cSNickeau
26004fd306cSNickeau
26104fd306cSNickeau                $newOutlineSection = OutlineSection::createFromEnterHeadingCall($actualCall);
26204fd306cSNickeau                $sectionDiff = $newSectionLevel - $actualSectionLevel;
26304fd306cSNickeau                if ($sectionDiff > 0) {
26404fd306cSNickeau
26504fd306cSNickeau                    /**
26604fd306cSNickeau                     * A child of the actual section
26704fd306cSNickeau                     * We append it first before the message check to
26804fd306cSNickeau                     * build the {@link TreeNode::getTreeIdentifier()}
26904fd306cSNickeau                     */
27004fd306cSNickeau                    try {
27104fd306cSNickeau                        $this->actualSection->appendChild($newOutlineSection);
27204fd306cSNickeau                    } catch (ExceptionBadState $e) {
27304fd306cSNickeau                        throw new ExceptionRuntimeInternal("The node is not added multiple time, this error should not fired. Error:{$e->getMessage()}", self::CANONICAL, 1, $e);
27404fd306cSNickeau                    }
27504fd306cSNickeau
27604fd306cSNickeau                    if ($sectionDiff > 1 & !($actualSectionLevel === 0 && $newSectionLevel === 2)) {
27704fd306cSNickeau                        $expectedLevel = $actualSectionLevel + 1;
27804fd306cSNickeau                        if ($actualSectionLevel === 0) {
27904fd306cSNickeau                            /**
28004fd306cSNickeau                             * In a fragment run (preview),
28104fd306cSNickeau                             * the first heading may not be the first one
28204fd306cSNickeau                             */
28304fd306cSNickeau                            if (!$this->isFragment) {
28404fd306cSNickeau                                $message = "The first section heading should have the level 1 or 2 (not $newSectionLevel).";
28504fd306cSNickeau                            }
28604fd306cSNickeau                        } else {
28704fd306cSNickeau                            $message = "The child section heading ($actualSectionLevel) has the level ($newSectionLevel) but is parent ({$this->actualSection->getLabel()}) has the level ($actualSectionLevel). The expected level is ($expectedLevel).";
28804fd306cSNickeau                        }
28904fd306cSNickeau                        if (isset($message)) {
29004fd306cSNickeau                            LogUtility::warning($message, self::CANONICAL);
29104fd306cSNickeau                        }
29204fd306cSNickeau                        $actualCall->setAttribute(HeadingTag::LEVEL, $newSectionLevel);
29304fd306cSNickeau                    }
29404fd306cSNickeau
29504fd306cSNickeau                } else {
29604fd306cSNickeau
29704fd306cSNickeau                    /**
29804fd306cSNickeau                     * A child of the parent section, A sibling of the actual session
29904fd306cSNickeau                     */
30004fd306cSNickeau                    try {
30104fd306cSNickeau                        $parent = $this->actualSection->getParent();
30204fd306cSNickeau                        for ($i = 0; $i < abs($sectionDiff); $i++) {
30304fd306cSNickeau                            $parent = $parent->getParent();
30404fd306cSNickeau                        }
30504fd306cSNickeau                        try {
30604fd306cSNickeau                            $parent->appendChild($newOutlineSection);
30704fd306cSNickeau                        } catch (ExceptionBadState $e) {
30804fd306cSNickeau                            throw new ExceptionRuntimeInternal("The node is not added multiple time, this error should not fired. Error:{$e->getMessage()}", self::CANONICAL, 1, $e);
30904fd306cSNickeau                        }
31004fd306cSNickeau                    } catch (ExceptionNotFound $e) {
31104fd306cSNickeau                        /**
31204fd306cSNickeau                         * no parent
31304fd306cSNickeau                         * May happen in preview (ie fragment)
31404fd306cSNickeau                         */
31504fd306cSNickeau                        if (!$this->isFragment) {
31604fd306cSNickeau                            LogUtility::internalError("Due to the level logic, the actual section should have a parent");
31704fd306cSNickeau                        }
31804fd306cSNickeau                        try {
31904fd306cSNickeau                            $this->actualSection->appendChild($newOutlineSection);
32004fd306cSNickeau                        } catch (ExceptionBadState $e) {
32104fd306cSNickeau                            throw new ExceptionRuntimeInternal("The node is not added multiple time, this error should not fired. Error:{$e->getMessage()}", self::CANONICAL, 1, $e);
32204fd306cSNickeau                        }
32304fd306cSNickeau                    }
32404fd306cSNickeau
32504fd306cSNickeau                }
32604fd306cSNickeau
32704fd306cSNickeau                $this->actualSection = $newOutlineSection;
32804fd306cSNickeau                continue;
32904fd306cSNickeau            }
33004fd306cSNickeau
33104fd306cSNickeau            /**
33204fd306cSNickeau             * Track the number of lines
33304fd306cSNickeau             * to inject ads
33404fd306cSNickeau             */
33504fd306cSNickeau            switch ($tagName) {
33604fd306cSNickeau                case "linebreak":
33704fd306cSNickeau                case "tablerow":
33804fd306cSNickeau                    // linebreak is an inline component
33904fd306cSNickeau                    $this->actualSection->incrementLineNumber();
34004fd306cSNickeau                    break;
34104fd306cSNickeau                default:
34204fd306cSNickeau                    $display = $actualCall->getDisplay();
34304fd306cSNickeau                    if ($display === Call::BlOCK_DISPLAY) {
34404fd306cSNickeau                        $this->actualSection->incrementLineNumber();
34504fd306cSNickeau                    }
34604fd306cSNickeau                    break;
34704fd306cSNickeau            }
34804fd306cSNickeau
34904fd306cSNickeau            /**
35004fd306cSNickeau             * Track the position in the file
35104fd306cSNickeau             */
35204fd306cSNickeau            $currentLastPosition = $actualCall->getLastMatchedCharacterPosition();
35304fd306cSNickeau            if ($currentLastPosition > $actualLastPosition) {
35404fd306cSNickeau                // the position in the stack is not always good
35504fd306cSNickeau                $actualLastPosition = $currentLastPosition;
35604fd306cSNickeau            }
35704fd306cSNickeau
35804fd306cSNickeau
35904fd306cSNickeau            switch ($actualCall->getComponentName()) {
36004fd306cSNickeau                case \action_plugin_combo_instructionspostprocessing::EDIT_SECTION_OPEN:
36104fd306cSNickeau                case \action_plugin_combo_instructionspostprocessing::EDIT_SECTION_CLOSE:
36204fd306cSNickeau                    // we don't store them
36304fd306cSNickeau                    continue 2;
36404fd306cSNickeau            }
36504fd306cSNickeau
36604fd306cSNickeau            /**
36704fd306cSNickeau             * Close/Process the heading description
36804fd306cSNickeau             */
36904fd306cSNickeau            if ($this->actualHeadingParsingState === DOKU_LEXER_ENTER) {
37004fd306cSNickeau                switch ($tagName) {
37104fd306cSNickeau
37204fd306cSNickeau                    case HeadingTag::HEADING_TAG:
37304fd306cSNickeau                    case syntax_plugin_combo_headingwiki::TAG:
37404fd306cSNickeau                        if ($state == DOKU_LEXER_EXIT) {
37504fd306cSNickeau                            $this->addCallToSection($actualCall);
37604fd306cSNickeau                            $this->exitHeading();
37704fd306cSNickeau                            continue 2;
37804fd306cSNickeau                        }
37904fd306cSNickeau                        break;
38004fd306cSNickeau
38104fd306cSNickeau                    case "internalmedia":
38204fd306cSNickeau                        // no link for media in heading
38304fd306cSNickeau                        $actualCall->getInstructionCall()[1][6] = MediaMarkup::LINKING_NOLINK_VALUE;
38404fd306cSNickeau                        break;
38504fd306cSNickeau                    case syntax_plugin_combo_media::TAG:
38604fd306cSNickeau                        // no link for media in heading
38704fd306cSNickeau                        $actualCall->addAttribute(MediaMarkup::LINKING_KEY, MediaMarkup::LINKING_NOLINK_VALUE);
38804fd306cSNickeau                        break;
38904fd306cSNickeau
39004fd306cSNickeau                    case "header":
39104fd306cSNickeau                        if (SiteConfig::getConfValue(syntax_plugin_combo_headingwiki::CONF_WIKI_HEADING_ENABLE, syntax_plugin_combo_headingwiki::CONF_DEFAULT_WIKI_ENABLE_VALUE) == 1) {
39204fd306cSNickeau                            LogUtility::msg("The combo heading wiki is enabled, we should not see `header` calls in the call stack");
39304fd306cSNickeau                        }
39404fd306cSNickeau                        break;
39504fd306cSNickeau
39604fd306cSNickeau                    case "p":
39704fd306cSNickeau
39804fd306cSNickeau                        if ($this->actualHeadingCall->getTagName() === syntax_plugin_combo_headingatx::TAG) {
39904fd306cSNickeau                            // A new p is the end of an atx call
40004fd306cSNickeau                            switch ($actualCall->getComponentName()) {
40104fd306cSNickeau                                case "p_open":
40204fd306cSNickeau                                    // We don't take the p tag inside atx heading
40304fd306cSNickeau                                    // therefore we continue
40404fd306cSNickeau                                    continue 3;
40504fd306cSNickeau                                case "p_close":
40604fd306cSNickeau                                    $endAtxCall = Call::createComboCall(
40704fd306cSNickeau                                        syntax_plugin_combo_headingatx::TAG,
40804fd306cSNickeau                                        DOKU_LEXER_EXIT,
40904fd306cSNickeau                                        $this->actualHeadingCall->getAttributes(),
41004fd306cSNickeau                                        $this->actualHeadingCall->getContext(),
41104fd306cSNickeau                                    );
41204fd306cSNickeau                                    $this->addCallToSection($endAtxCall);
41304fd306cSNickeau                                    $this->exitHeading();
41404fd306cSNickeau                                    // We don't take the p tag inside atx heading
41504fd306cSNickeau                                    // therefore we continue
41604fd306cSNickeau                                    continue 3;
41704fd306cSNickeau                            }
41804fd306cSNickeau                        }
41904fd306cSNickeau                        break;
42004fd306cSNickeau
42104fd306cSNickeau                }
42204fd306cSNickeau            }
42304fd306cSNickeau            $this->addCallToSection($actualCall);
42404fd306cSNickeau        }
42504fd306cSNickeau
42604fd306cSNickeau        // empty text
42704fd306cSNickeau        if (sizeof($analyticsTagUsed) > 0) {
42804fd306cSNickeau            $pluginAnalyticsCall = Call::createComboCall(
42904fd306cSNickeau                syntax_plugin_combo_analytics::TAG,
43004fd306cSNickeau                DOKU_LEXER_SPECIAL,
43104fd306cSNickeau                $analyticsTagUsed
43204fd306cSNickeau            );
43304fd306cSNickeau            $this->addCallToSection($pluginAnalyticsCall);
43404fd306cSNickeau        }
43504fd306cSNickeau
43604fd306cSNickeau        // Add label the heading text to the metadata
43704fd306cSNickeau        $this->saveOutlineToMetadata();
43804fd306cSNickeau
43904fd306cSNickeau
44004fd306cSNickeau    }
44104fd306cSNickeau
44204fd306cSNickeau    public static function getOutlineHeadingClass(): string
44304fd306cSNickeau    {
44404fd306cSNickeau        return StyleAttribute::addComboStrapSuffix(self::OUTLINE_HEADING_PREFIX);
44504fd306cSNickeau    }
44604fd306cSNickeau
44704fd306cSNickeau    public function getRootOutlineSection(): OutlineSection
44804fd306cSNickeau    {
44904fd306cSNickeau        return $this->rootSection;
45004fd306cSNickeau
45104fd306cSNickeau    }
45204fd306cSNickeau
45304fd306cSNickeau    /**
45404fd306cSNickeau     */
45504fd306cSNickeau    public static function merge(Outline $inner, Outline $outer)
45604fd306cSNickeau    {
45704fd306cSNickeau        /**
45804fd306cSNickeau         * Get the inner section where the outer section will be added
45904fd306cSNickeau         */
46004fd306cSNickeau        $innerRootOutlineSection = $inner->getRootOutlineSection();
46104fd306cSNickeau        $innerTopSections = $innerRootOutlineSection->getChildren();
46204fd306cSNickeau        if (count($innerTopSections) === 0) {
46304fd306cSNickeau            $firstInnerSection = $innerRootOutlineSection;
46404fd306cSNickeau        } else {
46504fd306cSNickeau            $firstInnerSection = $innerTopSections[count($innerTopSections)];
46604fd306cSNickeau        }
46704fd306cSNickeau        $firstInnerSectionLevel = $firstInnerSection->getLevel();
46804fd306cSNickeau
46904fd306cSNickeau        /**
47004fd306cSNickeau         * Add the outer sections
47104fd306cSNickeau         */
47204fd306cSNickeau        $outerRootOutlineSection = $outer->getRootOutlineSection();
47304fd306cSNickeau        foreach ($outerRootOutlineSection->getChildren() as $childOuterSection) {
47404fd306cSNickeau            /**
47504fd306cSNickeau             * One level less than where the section is included
47604fd306cSNickeau             */
47704fd306cSNickeau            $childOuterSection->setLevel($firstInnerSectionLevel + 1);
47804fd306cSNickeau            $childOuterSection->detachBeforeAppend();
47904fd306cSNickeau            try {
48004fd306cSNickeau                $firstInnerSection->appendChild($childOuterSection);
48104fd306cSNickeau            } catch (ExceptionBadState $e) {
48204fd306cSNickeau                // We add the node only once. This error should not happen
48304fd306cSNickeau                throw new ExceptionRuntimeInternal("Error while adding a section during the outline merge. Error: {$e->getMessage()}", self::CANONICAL, 1, $e);
48404fd306cSNickeau            }
48504fd306cSNickeau        }
48604fd306cSNickeau
48704fd306cSNickeau    }
48804fd306cSNickeau
48904fd306cSNickeau    public static function mergeRecurse(Outline $inner, Outline $outer)
49004fd306cSNickeau    {
49104fd306cSNickeau        $innerRootOutlineSection = $inner->getRootOutlineSection();
49204fd306cSNickeau        $outerRootOutlineSection = $outer->getRootOutlineSection();
49304fd306cSNickeau
49404fd306cSNickeau    }
49504fd306cSNickeau
49604fd306cSNickeau    /**
49704fd306cSNickeau     * Utility class to create a outline from a markup string
49804fd306cSNickeau     * @param string $content
49904fd306cSNickeau     * @return Outline
50004fd306cSNickeau     */
50104fd306cSNickeau    public static function createFromMarkup(string $content, Path $contentPath, WikiPath $contextPath): Outline
50204fd306cSNickeau    {
50304fd306cSNickeau        $instructions = MarkupRenderer::createFromMarkup($content, $contentPath, $contextPath)
50404fd306cSNickeau            ->setRequestedMimeToInstruction()
50504fd306cSNickeau            ->getOutput();
50604fd306cSNickeau        $callStack = CallStack::createFromInstructions($instructions);
50704fd306cSNickeau        return Outline::createFromCallStack($callStack);
50804fd306cSNickeau    }
50904fd306cSNickeau
51004fd306cSNickeau    /**
51104fd306cSNickeau     * Get the heading numbering snippet
51204fd306cSNickeau     * @param string $type heading or toc - for {@link Outline::TOC_NUMBERING} or {@link Outline::OUTLINE_HEADING_NUMBERING}
51304fd306cSNickeau     * @return string - the css internal stylesheet
51404fd306cSNickeau     * @throws ExceptionNotEnabled
51504fd306cSNickeau     * @throws ExceptionBadSyntax
51604fd306cSNickeau     * Page on DokuWiki
51704fd306cSNickeau     * https://www.dokuwiki.org/tips:numbered_headings
51804fd306cSNickeau     */
51904fd306cSNickeau    public static function getCssNumberingRulesFor(string $type): string
52004fd306cSNickeau    {
52104fd306cSNickeau
52204fd306cSNickeau        $enable = SiteConfig::getConfValue(self::CONF_OUTLINE_NUMBERING_ENABLE, Outline::CONF_OUTLINE_NUMBERING_ENABLE_DEFAULT);
52304fd306cSNickeau        if (!$enable) {
52404fd306cSNickeau            throw new ExceptionNotEnabled();
52504fd306cSNickeau        }
52604fd306cSNickeau
52704fd306cSNickeau        $level2CounterStyle = SiteConfig::getConfValue(self::CONF_OUTLINE_NUMBERING_COUNTER_STYLE_LEVEL2, "decimal");
52804fd306cSNickeau        $level3CounterStyle = SiteConfig::getConfValue(self::CONF_OUTLINE_NUMBERING_COUNTER_STYLE_LEVEL3, "decimal");
52904fd306cSNickeau        $level4CounterStyle = SiteConfig::getConfValue(self::CONF_OUTLINE_NUMBERING_COUNTER_STYLE_LEVEL4, "decimal");
53004fd306cSNickeau        $level5CounterStyle = SiteConfig::getConfValue(self::CONF_OUTLINE_NUMBERING_COUNTER_STYLE_LEVEL5, "decimal");
53104fd306cSNickeau        $level6CounterStyle = SiteConfig::getConfValue(self::CONF_OUTLINE_NUMBERING_COUNTER_STYLE_LEVEL6, "decimal");
53204fd306cSNickeau        $counterSeparator = SiteConfig::getConfValue(self::CONF_OUTLINE_NUMBERING_COUNTER_SEPARATOR, ".");
53304fd306cSNickeau        $prefix = SiteConfig::getConfValue(self::CONF_OUTLINE_NUMBERING_PREFIX, "");
53404fd306cSNickeau        $suffix = SiteConfig::getConfValue(self::CONF_OUTLINE_NUMBERING_SUFFIX, " - ");
53504fd306cSNickeau
53604fd306cSNickeau        switch ($type) {
53704fd306cSNickeau
53804fd306cSNickeau            case self::OUTLINE_HEADING_NUMBERING:
53904fd306cSNickeau                global $ACT;
54004fd306cSNickeau                if ($ACT === "preview") {
54104fd306cSNickeau                    $mainContainerSelector = ".pad";
54204fd306cSNickeau                } else {
54304fd306cSNickeau                    $mainContainerSelector = "#" . TemplateSlot::MAIN_CONTENT_ID;
54404fd306cSNickeau                }
54504fd306cSNickeau                /**
54604fd306cSNickeau                 * Because the HTML file structure is not really fixed
54704fd306cSNickeau                 * (we may have section HTML element with a bar, the sectioning heading
54804fd306cSNickeau                 * may be not enabled)
54904fd306cSNickeau                 * We can't select via html structure
55004fd306cSNickeau                 * the outline heading consistently
55104fd306cSNickeau                 * We do it then with the class value
55204fd306cSNickeau                 */
55304fd306cSNickeau                $outlineClass = Outline::getOutlineHeadingClass();
55404fd306cSNickeau                return <<<EOF
55504fd306cSNickeau$mainContainerSelector { counter-set: h2 0 h3 0 h4 0 h5 0 h6 0; }
55604fd306cSNickeau$mainContainerSelector h2.$outlineClass::before { counter-increment: h2; counter-set: h3 0 h4 0 h5 0 h6 0; content: "$prefix" counter(h2, $level2CounterStyle) "$suffix\A"; }
55704fd306cSNickeau$mainContainerSelector h3.$outlineClass::before { counter-increment: h3; counter-set: h4 0 h5 0 h6 0; content: "$prefix" counter(h2, $level2CounterStyle) "$counterSeparator" counter(h3,$level3CounterStyle) "$suffix\A"; }
55804fd306cSNickeau$mainContainerSelector h4.$outlineClass::before { counter-increment: h4; counter-set: h5 0 h6 0; content: "$prefix" counter(h2, $level2CounterStyle) "$counterSeparator" counter(h3,$level3CounterStyle) "$counterSeparator" counter(h4,$level4CounterStyle) "$suffix\A"; }
55904fd306cSNickeau$mainContainerSelector h5.$outlineClass::before { counter-increment: h5; counter-set: h6 0; content: "$prefix" counter(h2, $level2CounterStyle) "$counterSeparator" counter(h3,$level3CounterStyle) "$counterSeparator" counter(h4,$level4CounterStyle) "$counterSeparator" counter(h5,$level5CounterStyle) "$suffix\A"; }
56004fd306cSNickeau$mainContainerSelector h6.$outlineClass::before { counter-increment: h6; content: "$prefix" counter(h2, $level2CounterStyle) "$counterSeparator" counter(h3,$level3CounterStyle) "$counterSeparator" counter(h4,$level4CounterStyle) "$counterSeparator" counter(h5,$level5CounterStyle) "$counterSeparator" counter(h6,$level6CounterStyle) "$suffix\A"; }
56104fd306cSNickeauEOF;
56204fd306cSNickeau            case self::TOC_NUMBERING:
56304fd306cSNickeau                /**
56404fd306cSNickeau                 * The level counter on the toc are based
56504fd306cSNickeau                 * on the https://www.dokuwiki.org/config:toptoclevel
56604fd306cSNickeau                 * configuration
56704fd306cSNickeau                 * if toptoclevel = 2, then level1 = h2 and not h1
56804fd306cSNickeau                 * @deprecated
56904fd306cSNickeau                 */
57004fd306cSNickeau                // global $conf;
57104fd306cSNickeau                // $topTocLevel = $conf['toptoclevel'];
57204fd306cSNickeau
57304fd306cSNickeau                $tocSelector = "." . Toc::getClass() . " ul";
57404fd306cSNickeau                return <<<EOF
57504fd306cSNickeau$tocSelector li { counter-increment: toc2; }
57604fd306cSNickeau$tocSelector li li { counter-increment: toc3; }
57704fd306cSNickeau$tocSelector li li li { counter-increment: toc4; }
57804fd306cSNickeau$tocSelector li li li li { counter-increment: toc5; }
57904fd306cSNickeau$tocSelector li li li li li { counter-increment: toc6; }
58004fd306cSNickeau$tocSelector li a::before { content: "$prefix" counter(toc2, $level2CounterStyle) "$suffix\A"; }
58104fd306cSNickeau$tocSelector li li a::before { content: "$prefix" counter(toc2, $level2CounterStyle) "$counterSeparator" counter(toc3,$level3CounterStyle) "$suffix\A"; }
58204fd306cSNickeau$tocSelector li li li a::before { content: "$prefix" counter(toc2, $level2CounterStyle) "$counterSeparator" counter(toc3,$level3CounterStyle) "$counterSeparator" counter(toc4,$level4CounterStyle) "$suffix\A"; }
58304fd306cSNickeau$tocSelector li li li li a::before { content: "$prefix" counter(toc2, $level2CounterStyle) "$counterSeparator" counter(toc3,$level3CounterStyle) "$counterSeparator" counter(toc4,$level4CounterStyle) "$counterSeparator" counter(toc5,$level5CounterStyle) "$suffix\A"; }
58404fd306cSNickeau$tocSelector li li li li li a::before { content: "$prefix" counter(toc2, $level2CounterStyle) "$counterSeparator" counter(toc3,$level3CounterStyle) "$counterSeparator" counter(toc4,$level4CounterStyle) "$counterSeparator" counter(toc5,$level5CounterStyle) "$counterSeparator" counter(toc6,$level6CounterStyle) "$suffix\A"; }
58504fd306cSNickeauEOF;
58604fd306cSNickeau
58704fd306cSNickeau            default:
58804fd306cSNickeau                throw new ExceptionBadSyntax("The type ($type) is unknown");
58904fd306cSNickeau        }
59004fd306cSNickeau
59104fd306cSNickeau
59204fd306cSNickeau    }
59304fd306cSNickeau
59404fd306cSNickeau    /**
59504fd306cSNickeau     * @throws ExceptionNotFound
59604fd306cSNickeau     */
59704fd306cSNickeau    public static function createFromMarkupPath(MarkupPath $markupPath): Outline
59804fd306cSNickeau    {
59904fd306cSNickeau        $path = $markupPath->getPathObject();
60004fd306cSNickeau        if (!($path instanceof WikiPath)) {
60104fd306cSNickeau            throw new ExceptionRuntimeInternal("The path is not a wiki path");
60204fd306cSNickeau        }
60304fd306cSNickeau        $markup = FileSystems::getContent($path);
60404fd306cSNickeau        $instructions = MarkupRenderer::createFromMarkup($markup, $path, $path)
60504fd306cSNickeau            ->setRequestedMimeToInstruction()
60604fd306cSNickeau            ->getOutput();
60704fd306cSNickeau        $callStack = CallStack::createFromInstructions($instructions);
60804fd306cSNickeau        return new Outline($callStack, $markupPath);
60904fd306cSNickeau    }
61004fd306cSNickeau
61104fd306cSNickeau    public function getInstructionCalls(): array
61204fd306cSNickeau    {
61304fd306cSNickeau        $totalInstructionCalls = [];
61404fd306cSNickeau        $collectCalls = function (OutlineSection $outlineSection) use (&$totalInstructionCalls) {
61504fd306cSNickeau            $instructionCalls = array_map(function (Call $element) {
61604fd306cSNickeau                return $element->getInstructionCall();
61704fd306cSNickeau            }, $outlineSection->getCalls());
61804fd306cSNickeau            $totalInstructionCalls = array_merge($totalInstructionCalls, $instructionCalls);
61904fd306cSNickeau        };
62004fd306cSNickeau        TreeVisit::visit($this->rootSection, $collectCalls);
62104fd306cSNickeau        return $totalInstructionCalls;
62204fd306cSNickeau    }
62304fd306cSNickeau
62404fd306cSNickeau    public function toDokuWikiTemplateInstructionCalls(): array
62504fd306cSNickeau    {
62604fd306cSNickeau        $totalInstructionCalls = [];
62704fd306cSNickeau        $sectionSequenceId = 0;
62804fd306cSNickeau        $collectCalls = function (OutlineSection $outlineSection) use (&$totalInstructionCalls, &$sectionSequenceId) {
62904fd306cSNickeau
63004fd306cSNickeau            $wikiSectionOpen = Call::createNativeCall(
63104fd306cSNickeau                \action_plugin_combo_instructionspostprocessing::EDIT_SECTION_OPEN,
63204fd306cSNickeau                array($outlineSection->getLevel()),
63304fd306cSNickeau                $outlineSection->getStartPosition()
63404fd306cSNickeau            );
63504fd306cSNickeau            $wikiSectionClose = Call::createNativeCall(
63604fd306cSNickeau                \action_plugin_combo_instructionspostprocessing::EDIT_SECTION_CLOSE,
63704fd306cSNickeau                array(),
63804fd306cSNickeau                $outlineSection->getEndPosition()
63904fd306cSNickeau            );
64004fd306cSNickeau
64104fd306cSNickeau
64204fd306cSNickeau            if ($outlineSection->hasParent()) {
64304fd306cSNickeau
64404fd306cSNickeau
64504fd306cSNickeau                $sectionCalls = array_merge(
64604fd306cSNickeau                    $outlineSection->getHeadingCalls(),
64704fd306cSNickeau                    [$wikiSectionOpen],
64804fd306cSNickeau                    $outlineSection->getContentCalls(),
64904fd306cSNickeau                    [$wikiSectionClose],
65004fd306cSNickeau                );
65104fd306cSNickeau
65204fd306cSNickeau                if ($this->isSectionEditingEnabled()) {
65304fd306cSNickeau
65404fd306cSNickeau                    /**
65504fd306cSNickeau                     * Adding sectionedit class to be conform
65604fd306cSNickeau                     * with the Dokuwiki {@link \Doku_Renderer_xhtml::header()} function
65704fd306cSNickeau                     */
65804fd306cSNickeau                    $sectionSequenceId++;
65904fd306cSNickeau                    $headingCall = $outlineSection->getEnterHeadingCall();
66004fd306cSNickeau                    if ($headingCall->isPluginCall()) {
66104fd306cSNickeau                        $level = DataType::toIntegerOrDefaultIfNull($headingCall->getAttribute(HeadingTag::LEVEL), 0);
66204fd306cSNickeau                        if ($level <= $this->getTocMaxLevel()) {
66304fd306cSNickeau                            $headingCall->addClassName("sectionedit$sectionSequenceId");
66404fd306cSNickeau                        }
66504fd306cSNickeau                    }
66604fd306cSNickeau
66704fd306cSNickeau                    $editButton = EditButton::create($outlineSection->getLabel())
66804fd306cSNickeau                        ->setStartPosition($outlineSection->getStartPosition())
66904fd306cSNickeau                        ->setEndPosition($outlineSection->getEndPosition())
67004fd306cSNickeau                        ->setOutlineHeadingId($outlineSection->getHeadingId())
67104fd306cSNickeau                        ->setOutlineSectionId($sectionSequenceId)
67204fd306cSNickeau                        ->toComboCallDokuWikiForm();
67304fd306cSNickeau                    $sectionCalls[] = $editButton;
67404fd306cSNickeau                }
67504fd306cSNickeau
67604fd306cSNickeau            } else {
67704fd306cSNickeau                // dokuwiki seems to have no section for the content before the first heading
67804fd306cSNickeau                $sectionCalls = $outlineSection->getContentCalls();
67904fd306cSNickeau            }
68004fd306cSNickeau
68104fd306cSNickeau            $instructionCalls = array_map(function (Call $element) {
68204fd306cSNickeau                return $element->getInstructionCall();
68304fd306cSNickeau            }, $sectionCalls);
68404fd306cSNickeau            $totalInstructionCalls = array_merge($totalInstructionCalls, $instructionCalls);
68504fd306cSNickeau        };
68604fd306cSNickeau        TreeVisit::visit($this->rootSection, $collectCalls);
68704fd306cSNickeau        return $totalInstructionCalls;
68804fd306cSNickeau    }
68904fd306cSNickeau
69004fd306cSNickeau    private function addCallToSection(Call $actualCall)
69104fd306cSNickeau    {
69204fd306cSNickeau        if ($this->actualHeadingParsingState === DOKU_LEXER_ENTER && !$this->actualSection->hasContentCall()) {
69304fd306cSNickeau            $this->actualSection->addHeaderCall($actualCall);
69404fd306cSNickeau        } else {
69504fd306cSNickeau            // an content heading (not outline) or another call
69604fd306cSNickeau            $this->actualSection->addContentCall($actualCall);
69704fd306cSNickeau        }
69804fd306cSNickeau    }
69904fd306cSNickeau
70004fd306cSNickeau    private function enterHeading(Call $actualCall)
70104fd306cSNickeau    {
70204fd306cSNickeau        $this->actualHeadingParsingState = DOKU_LEXER_ENTER;
70304fd306cSNickeau        $this->actualHeadingCall = $actualCall;
70404fd306cSNickeau    }
70504fd306cSNickeau
70604fd306cSNickeau    private function exitHeading()
70704fd306cSNickeau    {
70804fd306cSNickeau        $this->actualHeadingParsingState = DOKU_LEXER_EXIT;
70904fd306cSNickeau    }
71004fd306cSNickeau
71104fd306cSNickeau    /**
71204fd306cSNickeau     * @return array - Dokuwiki TOC array format
71304fd306cSNickeau     */
71404fd306cSNickeau    public function toTocDokuwikiFormat(): array
71504fd306cSNickeau    {
71604fd306cSNickeau
71704fd306cSNickeau        $tableOfContent = [];
71804fd306cSNickeau        $collectTableOfContent = function (OutlineSection $outlineSection) use (&$tableOfContent) {
71904fd306cSNickeau
72004fd306cSNickeau            if (!$outlineSection->hasParent()) {
72104fd306cSNickeau                // Root Section, no heading
72204fd306cSNickeau                return;
72304fd306cSNickeau            }
72404fd306cSNickeau            $tableOfContent[] = [
72504fd306cSNickeau                'link' => '#' . $outlineSection->getHeadingId(),
72604fd306cSNickeau                'title' => $outlineSection->getLabel(),
72704fd306cSNickeau                'type' => 'ul',
72804fd306cSNickeau                'level' => $outlineSection->getLevel()
72904fd306cSNickeau            ];
73004fd306cSNickeau
73104fd306cSNickeau        };
73204fd306cSNickeau        TreeVisit::visit($this->rootSection, $collectTableOfContent);
73304fd306cSNickeau        return $tableOfContent;
73404fd306cSNickeau
73504fd306cSNickeau    }
73604fd306cSNickeau
73704fd306cSNickeau
73804fd306cSNickeau    public
73904fd306cSNickeau    function toHtmlSectionOutlineCalls(): array
74004fd306cSNickeau    {
74104fd306cSNickeau        return OutlineVisitor::create($this)->getCalls();
74204fd306cSNickeau    }
74304fd306cSNickeau
74404fd306cSNickeau
74504fd306cSNickeau    /**
74604fd306cSNickeau     * Fragment Rendering
74704fd306cSNickeau     * * does not have any section/edit button
74804fd306cSNickeau     * * no outline or edit button for dynamic rendering but closing of atx heading
74904fd306cSNickeau     *
75004fd306cSNickeau     * The outline processing ({@link Outline::buildOutline()} just close the atx heading
75104fd306cSNickeau     *
75204fd306cSNickeau     * @return array
75304fd306cSNickeau     */
75404fd306cSNickeau    public
75504fd306cSNickeau    function toFragmentInstructionCalls(): array
75604fd306cSNickeau    {
75704fd306cSNickeau        $totalInstructionCalls = [];
75804fd306cSNickeau        $collectCalls = function (OutlineSection $outlineSection) use (&$totalInstructionCalls) {
75904fd306cSNickeau
76004fd306cSNickeau            $sectionCalls = array_merge(
76104fd306cSNickeau                $outlineSection->getHeadingCalls(),
76204fd306cSNickeau                $outlineSection->getContentCalls()
76304fd306cSNickeau            );
76404fd306cSNickeau
76504fd306cSNickeau            $instructionCalls = array_map(function (Call $element) {
76604fd306cSNickeau                return $element->getInstructionCall();
76704fd306cSNickeau            }, $sectionCalls);
76804fd306cSNickeau            $totalInstructionCalls = array_merge($totalInstructionCalls, $instructionCalls);
76904fd306cSNickeau        };
77004fd306cSNickeau        TreeVisit::visit($this->rootSection, $collectCalls);
77104fd306cSNickeau        return $totalInstructionCalls;
77204fd306cSNickeau
77304fd306cSNickeau    }
77404fd306cSNickeau
77504fd306cSNickeau    /**
77604fd306cSNickeau     * Add the label (ie heading text to the cal attribute)
77704fd306cSNickeau     *
77804fd306cSNickeau     * @return void
77904fd306cSNickeau     */
78004fd306cSNickeau    private
78104fd306cSNickeau    function saveOutlineToMetadata()
78204fd306cSNickeau    {
78304fd306cSNickeau        try {
78404fd306cSNickeau            $firstChild = $this->rootSection->getFirstChild();
78504fd306cSNickeau        } catch (ExceptionNotFound $e) {
78604fd306cSNickeau            // no child
78704fd306cSNickeau            return;
78804fd306cSNickeau        }
78904fd306cSNickeau        if ($firstChild->getLevel() === 1) {
79004fd306cSNickeau            $headingCall = $firstChild->getEnterHeadingCall();
79104fd306cSNickeau            // not dokuwiki header ?
79204fd306cSNickeau            if ($headingCall->isPluginCall()) {
79304fd306cSNickeau                $headingCall->setAttribute(HeadingTag::HEADING_TEXT_ATTRIBUTE, $firstChild->getLabel());
79404fd306cSNickeau            }
79504fd306cSNickeau        }
79604fd306cSNickeau
79704fd306cSNickeau    }
79804fd306cSNickeau
79904fd306cSNickeau
80004fd306cSNickeau    private
80104fd306cSNickeau    function storeH1()
80204fd306cSNickeau    {
80304fd306cSNickeau        try {
80404fd306cSNickeau            $outlineSection = $this->getRootOutlineSection()->getFirstChild();
80504fd306cSNickeau        } catch (ExceptionNotFound $e) {
80604fd306cSNickeau            //
80704fd306cSNickeau            return;
80804fd306cSNickeau        }
80904fd306cSNickeau        if ($this->markupPath != null && $outlineSection->getLevel() === 1) {
81004fd306cSNickeau            $label = $outlineSection->getLabel();
81104fd306cSNickeau            $call = $outlineSection->getEnterHeadingCall();
81204fd306cSNickeau            if ($call->isPluginCall()) {
81304fd306cSNickeau                // we support also the dokwuiki header call that does not need the label
81404fd306cSNickeau                $call->addAttribute(HeadingTag::PARSED_LABEL, $label);
81504fd306cSNickeau            }
81604fd306cSNickeau            PageH1::createForPage($this->markupPath)->setDefaultValue($label);
81704fd306cSNickeau        }
81804fd306cSNickeau    }
81904fd306cSNickeau
82004fd306cSNickeau    private
82104fd306cSNickeau    function storeTocForMarkupIfAny()
82204fd306cSNickeau    {
82304fd306cSNickeau
82404fd306cSNickeau        $toc = $this->toTocDokuwikiFormat();
82504fd306cSNickeau
82604fd306cSNickeau        try {
82704fd306cSNickeau            $fetcherMarkup = ExecutionContext::getActualOrCreateFromEnv()->getExecutingMarkupHandler();
82804fd306cSNickeau            $fetcherMarkup->toc = $toc;
82904fd306cSNickeau            if ($fetcherMarkup->isDocument()) {
83004fd306cSNickeau                /**
83104fd306cSNickeau                 * We still update the global TOC Dokuwiki variables
83204fd306cSNickeau                 */
83304fd306cSNickeau                global $TOC;
83404fd306cSNickeau                $TOC = $toc;
83504fd306cSNickeau            }
83604fd306cSNickeau        } catch (ExceptionNotFound $e) {
83704fd306cSNickeau            // outline is not runnned from a markup handler
83804fd306cSNickeau        }
83904fd306cSNickeau
84004fd306cSNickeau        if (!isset($this->markupPath)) {
84104fd306cSNickeau            return;
84204fd306cSNickeau        }
84304fd306cSNickeau
84404fd306cSNickeau        try {
84504fd306cSNickeau            Toc::createForPage($this->markupPath)
84604fd306cSNickeau                ->setValue($toc)
84704fd306cSNickeau                ->persist();
84804fd306cSNickeau        } catch (ExceptionBadArgument $e) {
84904fd306cSNickeau            LogUtility::error("The Toc could not be persisted. Error:{$e->getMessage()}");
85004fd306cSNickeau        }
85104fd306cSNickeau    }
85204fd306cSNickeau
85304fd306cSNickeau    public
85404fd306cSNickeau    function getMarkupPath(): ?MarkupPath
85504fd306cSNickeau    {
85604fd306cSNickeau        return $this->markupPath;
85704fd306cSNickeau    }
85804fd306cSNickeau
85904fd306cSNickeau
86004fd306cSNickeau    private
86104fd306cSNickeau    function getTocMaxLevel(): int
86204fd306cSNickeau    {
86304fd306cSNickeau        return ExecutionContext::getActualOrCreateFromEnv()
86404fd306cSNickeau            ->getConfig()->getTocMaxLevel();
86504fd306cSNickeau    }
86604fd306cSNickeau
86704fd306cSNickeau    public function setMetaHeaderCapture(bool $metaHeaderCapture): Outline
86804fd306cSNickeau    {
86904fd306cSNickeau        $this->metaHeaderCapture = $metaHeaderCapture;
87004fd306cSNickeau        return $this;
87104fd306cSNickeau    }
87204fd306cSNickeau
87304fd306cSNickeau    public function getMetaHeaderCapture(): bool
87404fd306cSNickeau    {
87504fd306cSNickeau        if (isset($this->metaHeaderCapture)) {
87604fd306cSNickeau            return $this->metaHeaderCapture;
87704fd306cSNickeau        }
87804fd306cSNickeau        try {
87904fd306cSNickeau            if ($this->markupPath !== null) {
88004fd306cSNickeau                $contextPath = $this->markupPath->getPathObject()->toWikiPath();
88104fd306cSNickeau                $hasMainHeaderElement = TemplateForWebPage::create()
88204fd306cSNickeau                    ->setRequestedContextPath($contextPath)
88304fd306cSNickeau                    ->hasElement(TemplateSlot::MAIN_HEADER_ID);
88404fd306cSNickeau                $isThemeSystemEnabled = ExecutionContext::getActualOrCreateFromEnv()
88504fd306cSNickeau                    ->getConfig()
88604fd306cSNickeau                    ->isThemeSystemEnabled();
88704fd306cSNickeau                if ($isThemeSystemEnabled && $hasMainHeaderElement) {
88804fd306cSNickeau                    return true;
88904fd306cSNickeau                }
89004fd306cSNickeau            }
89104fd306cSNickeau        } catch (ExceptionCast $e) {
89204fd306cSNickeau            // to Wiki Path should be good
89304fd306cSNickeau        }
89404fd306cSNickeau        return false;
89504fd306cSNickeau    }
89604fd306cSNickeau
89704fd306cSNickeau    public function isSectionEditingEnabled(): bool
89804fd306cSNickeau    {
89904fd306cSNickeau
90004fd306cSNickeau        return ExecutionContext::getActualOrCreateFromEnv()
90104fd306cSNickeau            ->getConfig()->isSectionEditingEnabled();
90204fd306cSNickeau
90304fd306cSNickeau    }
90404fd306cSNickeau
90504fd306cSNickeau
90604fd306cSNickeau}
907