1*04fd306cSNickeau<?php 2*04fd306cSNickeau 3*04fd306cSNickeaunamespace ComboStrap; 4*04fd306cSNickeau 5*04fd306cSNickeau 6*04fd306cSNickeauuse ComboStrap\Meta\Field\FeaturedRasterImage; 7*04fd306cSNickeauuse ComboStrap\Meta\Field\FeaturedSvgImage; 8*04fd306cSNickeauuse ComboStrap\Meta\Field\PageH1; 9*04fd306cSNickeauuse ComboStrap\Tag\TableTag; 10*04fd306cSNickeauuse ComboStrap\TagAttribute\StyleAttribute; 11*04fd306cSNickeauuse dokuwiki\Extension\SyntaxPlugin; 12*04fd306cSNickeauuse syntax_plugin_combo_analytics; 13*04fd306cSNickeauuse syntax_plugin_combo_header; 14*04fd306cSNickeauuse syntax_plugin_combo_headingatx; 15*04fd306cSNickeauuse syntax_plugin_combo_headingwiki; 16*04fd306cSNickeauuse syntax_plugin_combo_media; 17*04fd306cSNickeau 18*04fd306cSNickeau/** 19*04fd306cSNickeau * The outline is creating a XML like document 20*04fd306cSNickeau * with section 21*04fd306cSNickeau * 22*04fd306cSNickeau * It's also the post-processing of the instructions 23*04fd306cSNickeau */ 24*04fd306cSNickeauclass Outline 25*04fd306cSNickeau{ 26*04fd306cSNickeau 27*04fd306cSNickeau 28*04fd306cSNickeau const CANONICAL = "outline"; 29*04fd306cSNickeau private const OUTLINE_HEADING_PREFIX = "outline-heading"; 30*04fd306cSNickeau const CONTEXT = self::CANONICAL; 31*04fd306cSNickeau public const OUTLINE_HEADING_NUMBERING = "outline-heading-numbering"; 32*04fd306cSNickeau public const TOC_NUMBERING = "toc-numbering"; 33*04fd306cSNickeau /** 34*04fd306cSNickeau * As seen on 35*04fd306cSNickeau * https://drafts.csswg.org/css-counter-styles-3/#predefined-counters 36*04fd306cSNickeau */ 37*04fd306cSNickeau public const CONF_COUNTER_STYLES_CHOICES = [ 38*04fd306cSNickeau 'arabic-indic', 39*04fd306cSNickeau 'bengali', 40*04fd306cSNickeau 'cambodian/khmer', 41*04fd306cSNickeau 'cjk-decimal', 42*04fd306cSNickeau 'decimal', 43*04fd306cSNickeau 'decimal-leading-zero', 44*04fd306cSNickeau 'devanagari', 45*04fd306cSNickeau 'georgian', 46*04fd306cSNickeau 'gujarati', 47*04fd306cSNickeau 'gurmukhi', 48*04fd306cSNickeau 'hebrew', 49*04fd306cSNickeau 'hiragana', 50*04fd306cSNickeau 'hiragana-iroha', 51*04fd306cSNickeau 'kannada', 52*04fd306cSNickeau 'katakana', 53*04fd306cSNickeau 'katakana-iroha', 54*04fd306cSNickeau 'lao', 55*04fd306cSNickeau 'lower-alpha', 56*04fd306cSNickeau 'lower-armenian', 57*04fd306cSNickeau 'lower-greek', 58*04fd306cSNickeau 'lower-roman', 59*04fd306cSNickeau 'malayalam', 60*04fd306cSNickeau 'mongolian', 61*04fd306cSNickeau 'myanmar', 62*04fd306cSNickeau 'oriya', 63*04fd306cSNickeau 'persian', 64*04fd306cSNickeau 'tamil', 65*04fd306cSNickeau 'telugu', 66*04fd306cSNickeau 'thai', 67*04fd306cSNickeau 'tibetan', 68*04fd306cSNickeau 'upper-alpha', 69*04fd306cSNickeau 'upper-armenian', 70*04fd306cSNickeau 'upper-roman' 71*04fd306cSNickeau ]; 72*04fd306cSNickeau public const CONF_OUTLINE_NUMBERING_COUNTER_STYLE_LEVEL4 = "outlineNumberingCounterStyleLevel4"; 73*04fd306cSNickeau public const CONF_OUTLINE_NUMBERING_COUNTER_STYLE_LEVEL3 = "outlineNumberingCounterStyleLevel3"; 74*04fd306cSNickeau public const CONF_OUTLINE_NUMBERING_SUFFIX = "outlineNumberingSuffix"; 75*04fd306cSNickeau public const CONF_OUTLINE_NUMBERING_COUNTER_STYLE_LEVEL2 = "outlineNumberingCounterStyleLevel2"; 76*04fd306cSNickeau public const CONF_OUTLINE_NUMBERING_COUNTER_SEPARATOR = "outlineNumberingCounterSeparator"; 77*04fd306cSNickeau public const CONF_OUTLINE_NUMBERING_COUNTER_STYLE_LEVEL6 = "outlineNumberingCounterStyleLevel6"; 78*04fd306cSNickeau public const CONF_OUTLINE_NUMBERING_COUNTER_STYLE_LEVEL5 = "outlineNumberingCounterStyleLevel5"; 79*04fd306cSNickeau public const CONF_OUTLINE_NUMBERING_PREFIX = "outlineNumberingPrefix"; 80*04fd306cSNickeau public const CONF_OUTLINE_NUMBERING_ENABLE = "outlineNumberingEnable"; 81*04fd306cSNickeau /** 82*04fd306cSNickeau * To add hash tag to heading 83*04fd306cSNickeau */ 84*04fd306cSNickeau public const OUTLINE_ANCHOR = "outline-anchor"; 85*04fd306cSNickeau const CONF_OUTLINE_NUMBERING_ENABLE_DEFAULT = 1; 86*04fd306cSNickeau private OutlineSection $rootSection; 87*04fd306cSNickeau 88*04fd306cSNickeau private OutlineSection $actualSection; // the actual section that is created 89*04fd306cSNickeau private Call $actualHeadingCall; // the heading that is parsed 90*04fd306cSNickeau private int $actualHeadingParsingState = DOKU_LEXER_EXIT; // the state of the heading parsed (enter, closed), enter if we have entered an heading, exit if not; 91*04fd306cSNickeau private ?MarkupPath $markupPath = null; 92*04fd306cSNickeau private bool $isFragment; 93*04fd306cSNickeau private bool $metaHeaderCapture; 94*04fd306cSNickeau 95*04fd306cSNickeau /** 96*04fd306cSNickeau * @param CallStack $callStack 97*04fd306cSNickeau * @param MarkupPath|null $markup - needed to store the parsed toc, h1, ... (null if the markup is dynamic) 98*04fd306cSNickeau * @param bool $isFragment - needed to control the structure of the outline (if this is a preview, the first heading may be not h1) 99*04fd306cSNickeau * @return void 100*04fd306cSNickeau */ 101*04fd306cSNickeau public function __construct(CallStack $callStack, MarkupPath $markup = null, bool $isFragment = false) 102*04fd306cSNickeau { 103*04fd306cSNickeau if ($markup !== null) { 104*04fd306cSNickeau $this->markupPath = $markup; 105*04fd306cSNickeau } 106*04fd306cSNickeau $this->isFragment = $isFragment; 107*04fd306cSNickeau $this->buildOutline($callStack); 108*04fd306cSNickeau $this->storeH1(); 109*04fd306cSNickeau $this->storeTocForMarkupIfAny(); 110*04fd306cSNickeau } 111*04fd306cSNickeau 112*04fd306cSNickeau /** 113*04fd306cSNickeau * @param CallStack $callStack 114*04fd306cSNickeau * @param MarkupPath|null $markupPath - needed to store the parsed toc, h1, ... (null if the markup is dynamic) 115*04fd306cSNickeau * @param bool $isFragment - needed to control the structure of the outline (if this is a preview, the first heading may be not h1) 116*04fd306cSNickeau * @return Outline 117*04fd306cSNickeau */ 118*04fd306cSNickeau public static function createFromCallStack(CallStack $callStack, MarkupPath $markupPath = null, bool $isFragment = false): Outline 119*04fd306cSNickeau { 120*04fd306cSNickeau return new Outline($callStack, $markupPath, $isFragment); 121*04fd306cSNickeau } 122*04fd306cSNickeau 123*04fd306cSNickeau private function buildOutline(CallStack $callStack) 124*04fd306cSNickeau { 125*04fd306cSNickeau 126*04fd306cSNickeau /** 127*04fd306cSNickeau * {@link syntax_plugin_combo_analytics tag analytics} 128*04fd306cSNickeau * By default, in a test environment 129*04fd306cSNickeau * this is set to 0 130*04fd306cSNickeau * to speed test and to not pollute 131*04fd306cSNickeau */ 132*04fd306cSNickeau $analtyicsEnabled = SiteConfig::getConfValue(syntax_plugin_combo_analytics::CONF_SYNTAX_ANALYTICS_ENABLE, true); 133*04fd306cSNickeau $analyticsTagUsed = []; 134*04fd306cSNickeau 135*04fd306cSNickeau /** 136*04fd306cSNickeau * Processing variable about the context 137*04fd306cSNickeau */ 138*04fd306cSNickeau $this->rootSection = OutlineSection::createOutlineRoot() 139*04fd306cSNickeau ->setStartPosition(0); 140*04fd306cSNickeau $this->actualSection = $this->rootSection; 141*04fd306cSNickeau $actualLastPosition = 0; 142*04fd306cSNickeau $callStack->moveToStart(); 143*04fd306cSNickeau while ($actualCall = $callStack->next()) { 144*04fd306cSNickeau 145*04fd306cSNickeau 146*04fd306cSNickeau $state = $actualCall->getState(); 147*04fd306cSNickeau 148*04fd306cSNickeau /** 149*04fd306cSNickeau * Block Post Processing 150*04fd306cSNickeau * to not get any unwanted p 151*04fd306cSNickeau * to counter {@link Block::process()} 152*04fd306cSNickeau * setting dynamically the {@link SyntaxPlugin::getPType()} 153*04fd306cSNickeau * 154*04fd306cSNickeau * Unfortunately, it can't work because this is called after 155*04fd306cSNickeau * {@link Block::process()} 156*04fd306cSNickeau */ 157*04fd306cSNickeau if ($analtyicsEnabled) { 158*04fd306cSNickeau 159*04fd306cSNickeau if (in_array($state, CallStack::TAG_STATE)) { 160*04fd306cSNickeau $tagName = $actualCall->getComponentName(); 161*04fd306cSNickeau // The dokuwiki component name have open in their name 162*04fd306cSNickeau $tagName = str_replace("_open", "", $tagName); 163*04fd306cSNickeau if (isset($analyticsTagUsed[$tagName])) { 164*04fd306cSNickeau $analyticsTagUsed[$tagName] = $analyticsTagUsed[$tagName] + 1; 165*04fd306cSNickeau } else { 166*04fd306cSNickeau $analyticsTagUsed[$tagName] = 1; 167*04fd306cSNickeau } 168*04fd306cSNickeau } 169*04fd306cSNickeau 170*04fd306cSNickeau } 171*04fd306cSNickeau 172*04fd306cSNickeau /** 173*04fd306cSNickeau * We don't take the outline and document call if any 174*04fd306cSNickeau * This is the case when we build from an actual stored instructions 175*04fd306cSNickeau * (to bundle multiple page for instance) 176*04fd306cSNickeau */ 177*04fd306cSNickeau $tagName = $actualCall->getTagName(); 178*04fd306cSNickeau switch ($tagName) { 179*04fd306cSNickeau case "document_start": 180*04fd306cSNickeau case "document_end": 181*04fd306cSNickeau case SectionTag::TAG: 182*04fd306cSNickeau continue 2; 183*04fd306cSNickeau case syntax_plugin_combo_header::TAG: 184*04fd306cSNickeau if ($actualCall->isPluginCall() && $actualCall->getContext() === self::CONTEXT) { 185*04fd306cSNickeau continue 2; 186*04fd306cSNickeau } 187*04fd306cSNickeau } 188*04fd306cSNickeau 189*04fd306cSNickeau /** 190*04fd306cSNickeau * Wrap the table 191*04fd306cSNickeau */ 192*04fd306cSNickeau $componentName = $actualCall->getComponentName(); 193*04fd306cSNickeau if ($componentName === "table_open") { 194*04fd306cSNickeau $position = $actualCall->getFirstMatchedCharacterPosition(); 195*04fd306cSNickeau $originalInstructionCall = &$actualCall->getInstructionCall(); 196*04fd306cSNickeau $originalInstructionCall = Call::createComboCall( 197*04fd306cSNickeau TableTag::TAG, 198*04fd306cSNickeau DOKU_LEXER_ENTER, 199*04fd306cSNickeau [PluginUtility::POSITION => $position], 200*04fd306cSNickeau null, 201*04fd306cSNickeau null, 202*04fd306cSNickeau null, 203*04fd306cSNickeau $position, 204*04fd306cSNickeau \syntax_plugin_combo_xmlblocktag::TAG 205*04fd306cSNickeau )->toCallArray(); 206*04fd306cSNickeau } 207*04fd306cSNickeau 208*04fd306cSNickeau /** 209*04fd306cSNickeau * Enter new section ? 210*04fd306cSNickeau */ 211*04fd306cSNickeau $shouldWeCreateASection = false; 212*04fd306cSNickeau switch ($tagName) { 213*04fd306cSNickeau case syntax_plugin_combo_headingatx::TAG: 214*04fd306cSNickeau $actualCall->setState(DOKU_LEXER_ENTER); 215*04fd306cSNickeau if ($actualCall->getContext() === HeadingTag::TYPE_OUTLINE) { 216*04fd306cSNickeau $shouldWeCreateASection = true; 217*04fd306cSNickeau } 218*04fd306cSNickeau $this->enterHeading($actualCall); 219*04fd306cSNickeau break; 220*04fd306cSNickeau case HeadingTag::HEADING_TAG: 221*04fd306cSNickeau case syntax_plugin_combo_headingwiki::TAG: 222*04fd306cSNickeau if ($state == DOKU_LEXER_ENTER 223*04fd306cSNickeau && $actualCall->getContext() === HeadingTag::TYPE_OUTLINE) { 224*04fd306cSNickeau $shouldWeCreateASection = true; 225*04fd306cSNickeau $this->enterHeading($actualCall); 226*04fd306cSNickeau } 227*04fd306cSNickeau break; 228*04fd306cSNickeau case "header": 229*04fd306cSNickeau // Should happen only on outline section 230*04fd306cSNickeau // we take over inside a component 231*04fd306cSNickeau if (!$actualCall->isPluginCall()) { 232*04fd306cSNickeau /** 233*04fd306cSNickeau * ie not {@link syntax_plugin_combo_header} 234*04fd306cSNickeau * but the dokuwiki header (ie heading) 235*04fd306cSNickeau */ 236*04fd306cSNickeau $shouldWeCreateASection = true; 237*04fd306cSNickeau $this->enterHeading($actualCall); 238*04fd306cSNickeau } 239*04fd306cSNickeau break; 240*04fd306cSNickeau } 241*04fd306cSNickeau if ($shouldWeCreateASection) { 242*04fd306cSNickeau if ($this->actualSection->hasParent()) { 243*04fd306cSNickeau // -1 because the actual position is the start of the next section 244*04fd306cSNickeau $this->actualSection->setEndPosition($actualCall->getFirstMatchedCharacterPosition() - 1); 245*04fd306cSNickeau } 246*04fd306cSNickeau $actualSectionLevel = $this->actualSection->getLevel(); 247*04fd306cSNickeau 248*04fd306cSNickeau if ($actualCall->isPluginCall()) { 249*04fd306cSNickeau try { 250*04fd306cSNickeau $newSectionLevel = DataType::toInteger($actualCall->getAttribute(HeadingTag::LEVEL)); 251*04fd306cSNickeau } catch (ExceptionBadArgument $e) { 252*04fd306cSNickeau LogUtility::internalError("The level was not present on the heading call", self::CANONICAL); 253*04fd306cSNickeau $newSectionLevel = $actualSectionLevel; 254*04fd306cSNickeau } 255*04fd306cSNickeau } else { 256*04fd306cSNickeau $headerTagName = $tagName; 257*04fd306cSNickeau if ($headerTagName !== "header") { 258*04fd306cSNickeau throw new ExceptionRuntimeInternal("This is not a dokuwiki header call", self::CANONICAL); 259*04fd306cSNickeau } 260*04fd306cSNickeau $newSectionLevel = $actualCall->getInstructionCall()[1][1]; 261*04fd306cSNickeau } 262*04fd306cSNickeau 263*04fd306cSNickeau 264*04fd306cSNickeau $newOutlineSection = OutlineSection::createFromEnterHeadingCall($actualCall); 265*04fd306cSNickeau $sectionDiff = $newSectionLevel - $actualSectionLevel; 266*04fd306cSNickeau if ($sectionDiff > 0) { 267*04fd306cSNickeau 268*04fd306cSNickeau /** 269*04fd306cSNickeau * A child of the actual section 270*04fd306cSNickeau * We append it first before the message check to 271*04fd306cSNickeau * build the {@link TreeNode::getTreeIdentifier()} 272*04fd306cSNickeau */ 273*04fd306cSNickeau try { 274*04fd306cSNickeau $this->actualSection->appendChild($newOutlineSection); 275*04fd306cSNickeau } catch (ExceptionBadState $e) { 276*04fd306cSNickeau throw new ExceptionRuntimeInternal("The node is not added multiple time, this error should not fired. Error:{$e->getMessage()}", self::CANONICAL, 1, $e); 277*04fd306cSNickeau } 278*04fd306cSNickeau 279*04fd306cSNickeau if ($sectionDiff > 1 & !($actualSectionLevel === 0 && $newSectionLevel === 2)) { 280*04fd306cSNickeau $expectedLevel = $actualSectionLevel + 1; 281*04fd306cSNickeau if ($actualSectionLevel === 0) { 282*04fd306cSNickeau /** 283*04fd306cSNickeau * In a fragment run (preview), 284*04fd306cSNickeau * the first heading may not be the first one 285*04fd306cSNickeau */ 286*04fd306cSNickeau if (!$this->isFragment) { 287*04fd306cSNickeau $message = "The first section heading should have the level 1 or 2 (not $newSectionLevel)."; 288*04fd306cSNickeau } 289*04fd306cSNickeau } else { 290*04fd306cSNickeau $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)."; 291*04fd306cSNickeau } 292*04fd306cSNickeau if (isset($message)) { 293*04fd306cSNickeau LogUtility::warning($message, self::CANONICAL); 294*04fd306cSNickeau } 295*04fd306cSNickeau $actualCall->setAttribute(HeadingTag::LEVEL, $newSectionLevel); 296*04fd306cSNickeau } 297*04fd306cSNickeau 298*04fd306cSNickeau } else { 299*04fd306cSNickeau 300*04fd306cSNickeau /** 301*04fd306cSNickeau * A child of the parent section, A sibling of the actual session 302*04fd306cSNickeau */ 303*04fd306cSNickeau try { 304*04fd306cSNickeau $parent = $this->actualSection->getParent(); 305*04fd306cSNickeau for ($i = 0; $i < abs($sectionDiff); $i++) { 306*04fd306cSNickeau $parent = $parent->getParent(); 307*04fd306cSNickeau } 308*04fd306cSNickeau try { 309*04fd306cSNickeau $parent->appendChild($newOutlineSection); 310*04fd306cSNickeau } catch (ExceptionBadState $e) { 311*04fd306cSNickeau throw new ExceptionRuntimeInternal("The node is not added multiple time, this error should not fired. Error:{$e->getMessage()}", self::CANONICAL, 1, $e); 312*04fd306cSNickeau } 313*04fd306cSNickeau } catch (ExceptionNotFound $e) { 314*04fd306cSNickeau /** 315*04fd306cSNickeau * no parent 316*04fd306cSNickeau * May happen in preview (ie fragment) 317*04fd306cSNickeau */ 318*04fd306cSNickeau if (!$this->isFragment) { 319*04fd306cSNickeau LogUtility::internalError("Due to the level logic, the actual section should have a parent"); 320*04fd306cSNickeau } 321*04fd306cSNickeau try { 322*04fd306cSNickeau $this->actualSection->appendChild($newOutlineSection); 323*04fd306cSNickeau } catch (ExceptionBadState $e) { 324*04fd306cSNickeau throw new ExceptionRuntimeInternal("The node is not added multiple time, this error should not fired. Error:{$e->getMessage()}", self::CANONICAL, 1, $e); 325*04fd306cSNickeau } 326*04fd306cSNickeau } 327*04fd306cSNickeau 328*04fd306cSNickeau } 329*04fd306cSNickeau 330*04fd306cSNickeau $this->actualSection = $newOutlineSection; 331*04fd306cSNickeau continue; 332*04fd306cSNickeau } 333*04fd306cSNickeau 334*04fd306cSNickeau /** 335*04fd306cSNickeau * Track the number of lines 336*04fd306cSNickeau * to inject ads 337*04fd306cSNickeau */ 338*04fd306cSNickeau switch ($tagName) { 339*04fd306cSNickeau case "linebreak": 340*04fd306cSNickeau case "tablerow": 341*04fd306cSNickeau // linebreak is an inline component 342*04fd306cSNickeau $this->actualSection->incrementLineNumber(); 343*04fd306cSNickeau break; 344*04fd306cSNickeau default: 345*04fd306cSNickeau $display = $actualCall->getDisplay(); 346*04fd306cSNickeau if ($display === Call::BlOCK_DISPLAY) { 347*04fd306cSNickeau $this->actualSection->incrementLineNumber(); 348*04fd306cSNickeau } 349*04fd306cSNickeau break; 350*04fd306cSNickeau } 351*04fd306cSNickeau 352*04fd306cSNickeau /** 353*04fd306cSNickeau * Track the position in the file 354*04fd306cSNickeau */ 355*04fd306cSNickeau $currentLastPosition = $actualCall->getLastMatchedCharacterPosition(); 356*04fd306cSNickeau if ($currentLastPosition > $actualLastPosition) { 357*04fd306cSNickeau // the position in the stack is not always good 358*04fd306cSNickeau $actualLastPosition = $currentLastPosition; 359*04fd306cSNickeau } 360*04fd306cSNickeau 361*04fd306cSNickeau 362*04fd306cSNickeau switch ($actualCall->getComponentName()) { 363*04fd306cSNickeau case \action_plugin_combo_instructionspostprocessing::EDIT_SECTION_OPEN: 364*04fd306cSNickeau case \action_plugin_combo_instructionspostprocessing::EDIT_SECTION_CLOSE: 365*04fd306cSNickeau // we don't store them 366*04fd306cSNickeau continue 2; 367*04fd306cSNickeau } 368*04fd306cSNickeau 369*04fd306cSNickeau /** 370*04fd306cSNickeau * Close/Process the heading description 371*04fd306cSNickeau */ 372*04fd306cSNickeau if ($this->actualHeadingParsingState === DOKU_LEXER_ENTER) { 373*04fd306cSNickeau switch ($tagName) { 374*04fd306cSNickeau 375*04fd306cSNickeau case HeadingTag::HEADING_TAG: 376*04fd306cSNickeau case syntax_plugin_combo_headingwiki::TAG: 377*04fd306cSNickeau if ($state == DOKU_LEXER_EXIT) { 378*04fd306cSNickeau $this->addCallToSection($actualCall); 379*04fd306cSNickeau $this->exitHeading(); 380*04fd306cSNickeau continue 2; 381*04fd306cSNickeau } 382*04fd306cSNickeau break; 383*04fd306cSNickeau 384*04fd306cSNickeau case "internalmedia": 385*04fd306cSNickeau // no link for media in heading 386*04fd306cSNickeau $actualCall->getInstructionCall()[1][6] = MediaMarkup::LINKING_NOLINK_VALUE; 387*04fd306cSNickeau break; 388*04fd306cSNickeau case syntax_plugin_combo_media::TAG: 389*04fd306cSNickeau // no link for media in heading 390*04fd306cSNickeau $actualCall->addAttribute(MediaMarkup::LINKING_KEY, MediaMarkup::LINKING_NOLINK_VALUE); 391*04fd306cSNickeau break; 392*04fd306cSNickeau 393*04fd306cSNickeau case "header": 394*04fd306cSNickeau if (SiteConfig::getConfValue(syntax_plugin_combo_headingwiki::CONF_WIKI_HEADING_ENABLE, syntax_plugin_combo_headingwiki::CONF_DEFAULT_WIKI_ENABLE_VALUE) == 1) { 395*04fd306cSNickeau LogUtility::msg("The combo heading wiki is enabled, we should not see `header` calls in the call stack"); 396*04fd306cSNickeau } 397*04fd306cSNickeau break; 398*04fd306cSNickeau 399*04fd306cSNickeau case "p": 400*04fd306cSNickeau 401*04fd306cSNickeau if ($this->actualHeadingCall->getTagName() === syntax_plugin_combo_headingatx::TAG) { 402*04fd306cSNickeau // A new p is the end of an atx call 403*04fd306cSNickeau switch ($actualCall->getComponentName()) { 404*04fd306cSNickeau case "p_open": 405*04fd306cSNickeau // We don't take the p tag inside atx heading 406*04fd306cSNickeau // therefore we continue 407*04fd306cSNickeau continue 3; 408*04fd306cSNickeau case "p_close": 409*04fd306cSNickeau $endAtxCall = Call::createComboCall( 410*04fd306cSNickeau syntax_plugin_combo_headingatx::TAG, 411*04fd306cSNickeau DOKU_LEXER_EXIT, 412*04fd306cSNickeau $this->actualHeadingCall->getAttributes(), 413*04fd306cSNickeau $this->actualHeadingCall->getContext(), 414*04fd306cSNickeau ); 415*04fd306cSNickeau $this->addCallToSection($endAtxCall); 416*04fd306cSNickeau $this->exitHeading(); 417*04fd306cSNickeau // We don't take the p tag inside atx heading 418*04fd306cSNickeau // therefore we continue 419*04fd306cSNickeau continue 3; 420*04fd306cSNickeau } 421*04fd306cSNickeau } 422*04fd306cSNickeau break; 423*04fd306cSNickeau 424*04fd306cSNickeau } 425*04fd306cSNickeau } 426*04fd306cSNickeau $this->addCallToSection($actualCall); 427*04fd306cSNickeau } 428*04fd306cSNickeau 429*04fd306cSNickeau // empty text 430*04fd306cSNickeau if (sizeof($analyticsTagUsed) > 0) { 431*04fd306cSNickeau $pluginAnalyticsCall = Call::createComboCall( 432*04fd306cSNickeau syntax_plugin_combo_analytics::TAG, 433*04fd306cSNickeau DOKU_LEXER_SPECIAL, 434*04fd306cSNickeau $analyticsTagUsed 435*04fd306cSNickeau ); 436*04fd306cSNickeau $this->addCallToSection($pluginAnalyticsCall); 437*04fd306cSNickeau } 438*04fd306cSNickeau 439*04fd306cSNickeau // Add label the heading text to the metadata 440*04fd306cSNickeau $this->saveOutlineToMetadata(); 441*04fd306cSNickeau 442*04fd306cSNickeau 443*04fd306cSNickeau } 444*04fd306cSNickeau 445*04fd306cSNickeau public static function getOutlineHeadingClass(): string 446*04fd306cSNickeau { 447*04fd306cSNickeau return StyleAttribute::addComboStrapSuffix(self::OUTLINE_HEADING_PREFIX); 448*04fd306cSNickeau } 449*04fd306cSNickeau 450*04fd306cSNickeau public function getRootOutlineSection(): OutlineSection 451*04fd306cSNickeau { 452*04fd306cSNickeau return $this->rootSection; 453*04fd306cSNickeau 454*04fd306cSNickeau } 455*04fd306cSNickeau 456*04fd306cSNickeau /** 457*04fd306cSNickeau */ 458*04fd306cSNickeau public static function merge(Outline $inner, Outline $outer) 459*04fd306cSNickeau { 460*04fd306cSNickeau /** 461*04fd306cSNickeau * Get the inner section where the outer section will be added 462*04fd306cSNickeau */ 463*04fd306cSNickeau $innerRootOutlineSection = $inner->getRootOutlineSection(); 464*04fd306cSNickeau $innerTopSections = $innerRootOutlineSection->getChildren(); 465*04fd306cSNickeau if (count($innerTopSections) === 0) { 466*04fd306cSNickeau $firstInnerSection = $innerRootOutlineSection; 467*04fd306cSNickeau } else { 468*04fd306cSNickeau $firstInnerSection = $innerTopSections[count($innerTopSections)]; 469*04fd306cSNickeau } 470*04fd306cSNickeau $firstInnerSectionLevel = $firstInnerSection->getLevel(); 471*04fd306cSNickeau 472*04fd306cSNickeau /** 473*04fd306cSNickeau * Add the outer sections 474*04fd306cSNickeau */ 475*04fd306cSNickeau $outerRootOutlineSection = $outer->getRootOutlineSection(); 476*04fd306cSNickeau foreach ($outerRootOutlineSection->getChildren() as $childOuterSection) { 477*04fd306cSNickeau /** 478*04fd306cSNickeau * One level less than where the section is included 479*04fd306cSNickeau */ 480*04fd306cSNickeau $childOuterSection->setLevel($firstInnerSectionLevel + 1); 481*04fd306cSNickeau $childOuterSection->detachBeforeAppend(); 482*04fd306cSNickeau try { 483*04fd306cSNickeau $firstInnerSection->appendChild($childOuterSection); 484*04fd306cSNickeau } catch (ExceptionBadState $e) { 485*04fd306cSNickeau // We add the node only once. This error should not happen 486*04fd306cSNickeau throw new ExceptionRuntimeInternal("Error while adding a section during the outline merge. Error: {$e->getMessage()}", self::CANONICAL, 1, $e); 487*04fd306cSNickeau } 488*04fd306cSNickeau } 489*04fd306cSNickeau 490*04fd306cSNickeau } 491*04fd306cSNickeau 492*04fd306cSNickeau public static function mergeRecurse(Outline $inner, Outline $outer) 493*04fd306cSNickeau { 494*04fd306cSNickeau $innerRootOutlineSection = $inner->getRootOutlineSection(); 495*04fd306cSNickeau $outerRootOutlineSection = $outer->getRootOutlineSection(); 496*04fd306cSNickeau 497*04fd306cSNickeau } 498*04fd306cSNickeau 499*04fd306cSNickeau /** 500*04fd306cSNickeau * Utility class to create a outline from a markup string 501*04fd306cSNickeau * @param string $content 502*04fd306cSNickeau * @return Outline 503*04fd306cSNickeau */ 504*04fd306cSNickeau public static function createFromMarkup(string $content, Path $contentPath, WikiPath $contextPath): Outline 505*04fd306cSNickeau { 506*04fd306cSNickeau $instructions = MarkupRenderer::createFromMarkup($content, $contentPath, $contextPath) 507*04fd306cSNickeau ->setRequestedMimeToInstruction() 508*04fd306cSNickeau ->getOutput(); 509*04fd306cSNickeau $callStack = CallStack::createFromInstructions($instructions); 510*04fd306cSNickeau return Outline::createFromCallStack($callStack); 511*04fd306cSNickeau } 512*04fd306cSNickeau 513*04fd306cSNickeau /** 514*04fd306cSNickeau * Get the heading numbering snippet 515*04fd306cSNickeau * @param string $type heading or toc - for {@link Outline::TOC_NUMBERING} or {@link Outline::OUTLINE_HEADING_NUMBERING} 516*04fd306cSNickeau * @return string - the css internal stylesheet 517*04fd306cSNickeau * @throws ExceptionNotEnabled 518*04fd306cSNickeau * @throws ExceptionBadSyntax 519*04fd306cSNickeau * Page on DokuWiki 520*04fd306cSNickeau * https://www.dokuwiki.org/tips:numbered_headings 521*04fd306cSNickeau */ 522*04fd306cSNickeau public static function getCssNumberingRulesFor(string $type): string 523*04fd306cSNickeau { 524*04fd306cSNickeau 525*04fd306cSNickeau $enable = SiteConfig::getConfValue(self::CONF_OUTLINE_NUMBERING_ENABLE, Outline::CONF_OUTLINE_NUMBERING_ENABLE_DEFAULT); 526*04fd306cSNickeau if (!$enable) { 527*04fd306cSNickeau throw new ExceptionNotEnabled(); 528*04fd306cSNickeau } 529*04fd306cSNickeau 530*04fd306cSNickeau $level2CounterStyle = SiteConfig::getConfValue(self::CONF_OUTLINE_NUMBERING_COUNTER_STYLE_LEVEL2, "decimal"); 531*04fd306cSNickeau $level3CounterStyle = SiteConfig::getConfValue(self::CONF_OUTLINE_NUMBERING_COUNTER_STYLE_LEVEL3, "decimal"); 532*04fd306cSNickeau $level4CounterStyle = SiteConfig::getConfValue(self::CONF_OUTLINE_NUMBERING_COUNTER_STYLE_LEVEL4, "decimal"); 533*04fd306cSNickeau $level5CounterStyle = SiteConfig::getConfValue(self::CONF_OUTLINE_NUMBERING_COUNTER_STYLE_LEVEL5, "decimal"); 534*04fd306cSNickeau $level6CounterStyle = SiteConfig::getConfValue(self::CONF_OUTLINE_NUMBERING_COUNTER_STYLE_LEVEL6, "decimal"); 535*04fd306cSNickeau $counterSeparator = SiteConfig::getConfValue(self::CONF_OUTLINE_NUMBERING_COUNTER_SEPARATOR, "."); 536*04fd306cSNickeau $prefix = SiteConfig::getConfValue(self::CONF_OUTLINE_NUMBERING_PREFIX, ""); 537*04fd306cSNickeau $suffix = SiteConfig::getConfValue(self::CONF_OUTLINE_NUMBERING_SUFFIX, " - "); 538*04fd306cSNickeau 539*04fd306cSNickeau switch ($type) { 540*04fd306cSNickeau 541*04fd306cSNickeau case self::OUTLINE_HEADING_NUMBERING: 542*04fd306cSNickeau global $ACT; 543*04fd306cSNickeau if ($ACT === "preview") { 544*04fd306cSNickeau $mainContainerSelector = ".pad"; 545*04fd306cSNickeau } else { 546*04fd306cSNickeau $mainContainerSelector = "#" . TemplateSlot::MAIN_CONTENT_ID; 547*04fd306cSNickeau } 548*04fd306cSNickeau /** 549*04fd306cSNickeau * Because the HTML file structure is not really fixed 550*04fd306cSNickeau * (we may have section HTML element with a bar, the sectioning heading 551*04fd306cSNickeau * may be not enabled) 552*04fd306cSNickeau * We can't select via html structure 553*04fd306cSNickeau * the outline heading consistently 554*04fd306cSNickeau * We do it then with the class value 555*04fd306cSNickeau */ 556*04fd306cSNickeau $outlineClass = Outline::getOutlineHeadingClass(); 557*04fd306cSNickeau return <<<EOF 558*04fd306cSNickeau$mainContainerSelector { counter-set: h2 0 h3 0 h4 0 h5 0 h6 0; } 559*04fd306cSNickeau$mainContainerSelector h2.$outlineClass::before { counter-increment: h2; counter-set: h3 0 h4 0 h5 0 h6 0; content: "$prefix" counter(h2, $level2CounterStyle) "$suffix\A"; } 560*04fd306cSNickeau$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"; } 561*04fd306cSNickeau$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"; } 562*04fd306cSNickeau$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"; } 563*04fd306cSNickeau$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"; } 564*04fd306cSNickeauEOF; 565*04fd306cSNickeau case self::TOC_NUMBERING: 566*04fd306cSNickeau /** 567*04fd306cSNickeau * The level counter on the toc are based 568*04fd306cSNickeau * on the https://www.dokuwiki.org/config:toptoclevel 569*04fd306cSNickeau * configuration 570*04fd306cSNickeau * if toptoclevel = 2, then level1 = h2 and not h1 571*04fd306cSNickeau * @deprecated 572*04fd306cSNickeau */ 573*04fd306cSNickeau // global $conf; 574*04fd306cSNickeau // $topTocLevel = $conf['toptoclevel']; 575*04fd306cSNickeau 576*04fd306cSNickeau $tocSelector = "." . Toc::getClass() . " ul"; 577*04fd306cSNickeau return <<<EOF 578*04fd306cSNickeau$tocSelector li { counter-increment: toc2; } 579*04fd306cSNickeau$tocSelector li li { counter-increment: toc3; } 580*04fd306cSNickeau$tocSelector li li li { counter-increment: toc4; } 581*04fd306cSNickeau$tocSelector li li li li { counter-increment: toc5; } 582*04fd306cSNickeau$tocSelector li li li li li { counter-increment: toc6; } 583*04fd306cSNickeau$tocSelector li a::before { content: "$prefix" counter(toc2, $level2CounterStyle) "$suffix\A"; } 584*04fd306cSNickeau$tocSelector li li a::before { content: "$prefix" counter(toc2, $level2CounterStyle) "$counterSeparator" counter(toc3,$level3CounterStyle) "$suffix\A"; } 585*04fd306cSNickeau$tocSelector li li li a::before { content: "$prefix" counter(toc2, $level2CounterStyle) "$counterSeparator" counter(toc3,$level3CounterStyle) "$counterSeparator" counter(toc4,$level4CounterStyle) "$suffix\A"; } 586*04fd306cSNickeau$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"; } 587*04fd306cSNickeau$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"; } 588*04fd306cSNickeauEOF; 589*04fd306cSNickeau 590*04fd306cSNickeau default: 591*04fd306cSNickeau throw new ExceptionBadSyntax("The type ($type) is unknown"); 592*04fd306cSNickeau } 593*04fd306cSNickeau 594*04fd306cSNickeau 595*04fd306cSNickeau } 596*04fd306cSNickeau 597*04fd306cSNickeau /** 598*04fd306cSNickeau * @throws ExceptionNotFound 599*04fd306cSNickeau */ 600*04fd306cSNickeau public static function createFromMarkupPath(MarkupPath $markupPath): Outline 601*04fd306cSNickeau { 602*04fd306cSNickeau $path = $markupPath->getPathObject(); 603*04fd306cSNickeau if (!($path instanceof WikiPath)) { 604*04fd306cSNickeau throw new ExceptionRuntimeInternal("The path is not a wiki path"); 605*04fd306cSNickeau } 606*04fd306cSNickeau $markup = FileSystems::getContent($path); 607*04fd306cSNickeau $instructions = MarkupRenderer::createFromMarkup($markup, $path, $path) 608*04fd306cSNickeau ->setRequestedMimeToInstruction() 609*04fd306cSNickeau ->getOutput(); 610*04fd306cSNickeau $callStack = CallStack::createFromInstructions($instructions); 611*04fd306cSNickeau return new Outline($callStack, $markupPath); 612*04fd306cSNickeau } 613*04fd306cSNickeau 614*04fd306cSNickeau public function getInstructionCalls(): array 615*04fd306cSNickeau { 616*04fd306cSNickeau $totalInstructionCalls = []; 617*04fd306cSNickeau $collectCalls = function (OutlineSection $outlineSection) use (&$totalInstructionCalls) { 618*04fd306cSNickeau $instructionCalls = array_map(function (Call $element) { 619*04fd306cSNickeau return $element->getInstructionCall(); 620*04fd306cSNickeau }, $outlineSection->getCalls()); 621*04fd306cSNickeau $totalInstructionCalls = array_merge($totalInstructionCalls, $instructionCalls); 622*04fd306cSNickeau }; 623*04fd306cSNickeau TreeVisit::visit($this->rootSection, $collectCalls); 624*04fd306cSNickeau return $totalInstructionCalls; 625*04fd306cSNickeau } 626*04fd306cSNickeau 627*04fd306cSNickeau public function toDokuWikiTemplateInstructionCalls(): array 628*04fd306cSNickeau { 629*04fd306cSNickeau $totalInstructionCalls = []; 630*04fd306cSNickeau $sectionSequenceId = 0; 631*04fd306cSNickeau $collectCalls = function (OutlineSection $outlineSection) use (&$totalInstructionCalls, &$sectionSequenceId) { 632*04fd306cSNickeau 633*04fd306cSNickeau $wikiSectionOpen = Call::createNativeCall( 634*04fd306cSNickeau \action_plugin_combo_instructionspostprocessing::EDIT_SECTION_OPEN, 635*04fd306cSNickeau array($outlineSection->getLevel()), 636*04fd306cSNickeau $outlineSection->getStartPosition() 637*04fd306cSNickeau ); 638*04fd306cSNickeau $wikiSectionClose = Call::createNativeCall( 639*04fd306cSNickeau \action_plugin_combo_instructionspostprocessing::EDIT_SECTION_CLOSE, 640*04fd306cSNickeau array(), 641*04fd306cSNickeau $outlineSection->getEndPosition() 642*04fd306cSNickeau ); 643*04fd306cSNickeau 644*04fd306cSNickeau 645*04fd306cSNickeau if ($outlineSection->hasParent()) { 646*04fd306cSNickeau 647*04fd306cSNickeau 648*04fd306cSNickeau $sectionCalls = array_merge( 649*04fd306cSNickeau $outlineSection->getHeadingCalls(), 650*04fd306cSNickeau [$wikiSectionOpen], 651*04fd306cSNickeau $outlineSection->getContentCalls(), 652*04fd306cSNickeau [$wikiSectionClose], 653*04fd306cSNickeau ); 654*04fd306cSNickeau 655*04fd306cSNickeau if ($this->isSectionEditingEnabled()) { 656*04fd306cSNickeau 657*04fd306cSNickeau /** 658*04fd306cSNickeau * Adding sectionedit class to be conform 659*04fd306cSNickeau * with the Dokuwiki {@link \Doku_Renderer_xhtml::header()} function 660*04fd306cSNickeau */ 661*04fd306cSNickeau $sectionSequenceId++; 662*04fd306cSNickeau $headingCall = $outlineSection->getEnterHeadingCall(); 663*04fd306cSNickeau if ($headingCall->isPluginCall()) { 664*04fd306cSNickeau $level = DataType::toIntegerOrDefaultIfNull($headingCall->getAttribute(HeadingTag::LEVEL), 0); 665*04fd306cSNickeau if ($level <= $this->getTocMaxLevel()) { 666*04fd306cSNickeau $headingCall->addClassName("sectionedit$sectionSequenceId"); 667*04fd306cSNickeau } 668*04fd306cSNickeau } 669*04fd306cSNickeau 670*04fd306cSNickeau $editButton = EditButton::create($outlineSection->getLabel()) 671*04fd306cSNickeau ->setStartPosition($outlineSection->getStartPosition()) 672*04fd306cSNickeau ->setEndPosition($outlineSection->getEndPosition()) 673*04fd306cSNickeau ->setOutlineHeadingId($outlineSection->getHeadingId()) 674*04fd306cSNickeau ->setOutlineSectionId($sectionSequenceId) 675*04fd306cSNickeau ->toComboCallDokuWikiForm(); 676*04fd306cSNickeau $sectionCalls[] = $editButton; 677*04fd306cSNickeau } 678*04fd306cSNickeau 679*04fd306cSNickeau } else { 680*04fd306cSNickeau // dokuwiki seems to have no section for the content before the first heading 681*04fd306cSNickeau $sectionCalls = $outlineSection->getContentCalls(); 682*04fd306cSNickeau } 683*04fd306cSNickeau 684*04fd306cSNickeau $instructionCalls = array_map(function (Call $element) { 685*04fd306cSNickeau return $element->getInstructionCall(); 686*04fd306cSNickeau }, $sectionCalls); 687*04fd306cSNickeau $totalInstructionCalls = array_merge($totalInstructionCalls, $instructionCalls); 688*04fd306cSNickeau }; 689*04fd306cSNickeau TreeVisit::visit($this->rootSection, $collectCalls); 690*04fd306cSNickeau return $totalInstructionCalls; 691*04fd306cSNickeau } 692*04fd306cSNickeau 693*04fd306cSNickeau private function addCallToSection(Call $actualCall) 694*04fd306cSNickeau { 695*04fd306cSNickeau if ($this->actualHeadingParsingState === DOKU_LEXER_ENTER && !$this->actualSection->hasContentCall()) { 696*04fd306cSNickeau $this->actualSection->addHeaderCall($actualCall); 697*04fd306cSNickeau } else { 698*04fd306cSNickeau // an content heading (not outline) or another call 699*04fd306cSNickeau $this->actualSection->addContentCall($actualCall); 700*04fd306cSNickeau } 701*04fd306cSNickeau } 702*04fd306cSNickeau 703*04fd306cSNickeau private function enterHeading(Call $actualCall) 704*04fd306cSNickeau { 705*04fd306cSNickeau $this->actualHeadingParsingState = DOKU_LEXER_ENTER; 706*04fd306cSNickeau $this->actualHeadingCall = $actualCall; 707*04fd306cSNickeau } 708*04fd306cSNickeau 709*04fd306cSNickeau private function exitHeading() 710*04fd306cSNickeau { 711*04fd306cSNickeau $this->actualHeadingParsingState = DOKU_LEXER_EXIT; 712*04fd306cSNickeau } 713*04fd306cSNickeau 714*04fd306cSNickeau /** 715*04fd306cSNickeau * @return array - Dokuwiki TOC array format 716*04fd306cSNickeau */ 717*04fd306cSNickeau public function toTocDokuwikiFormat(): array 718*04fd306cSNickeau { 719*04fd306cSNickeau 720*04fd306cSNickeau $tableOfContent = []; 721*04fd306cSNickeau $collectTableOfContent = function (OutlineSection $outlineSection) use (&$tableOfContent) { 722*04fd306cSNickeau 723*04fd306cSNickeau if (!$outlineSection->hasParent()) { 724*04fd306cSNickeau // Root Section, no heading 725*04fd306cSNickeau return; 726*04fd306cSNickeau } 727*04fd306cSNickeau $tableOfContent[] = [ 728*04fd306cSNickeau 'link' => '#' . $outlineSection->getHeadingId(), 729*04fd306cSNickeau 'title' => $outlineSection->getLabel(), 730*04fd306cSNickeau 'type' => 'ul', 731*04fd306cSNickeau 'level' => $outlineSection->getLevel() 732*04fd306cSNickeau ]; 733*04fd306cSNickeau 734*04fd306cSNickeau }; 735*04fd306cSNickeau TreeVisit::visit($this->rootSection, $collectTableOfContent); 736*04fd306cSNickeau return $tableOfContent; 737*04fd306cSNickeau 738*04fd306cSNickeau } 739*04fd306cSNickeau 740*04fd306cSNickeau 741*04fd306cSNickeau public 742*04fd306cSNickeau function toHtmlSectionOutlineCalls(): array 743*04fd306cSNickeau { 744*04fd306cSNickeau return OutlineVisitor::create($this)->getCalls(); 745*04fd306cSNickeau } 746*04fd306cSNickeau 747*04fd306cSNickeau 748*04fd306cSNickeau /** 749*04fd306cSNickeau * Fragment Rendering 750*04fd306cSNickeau * * does not have any section/edit button 751*04fd306cSNickeau * * no outline or edit button for dynamic rendering but closing of atx heading 752*04fd306cSNickeau * 753*04fd306cSNickeau * The outline processing ({@link Outline::buildOutline()} just close the atx heading 754*04fd306cSNickeau * 755*04fd306cSNickeau * @return array 756*04fd306cSNickeau */ 757*04fd306cSNickeau public 758*04fd306cSNickeau function toFragmentInstructionCalls(): array 759*04fd306cSNickeau { 760*04fd306cSNickeau $totalInstructionCalls = []; 761*04fd306cSNickeau $collectCalls = function (OutlineSection $outlineSection) use (&$totalInstructionCalls) { 762*04fd306cSNickeau 763*04fd306cSNickeau $sectionCalls = array_merge( 764*04fd306cSNickeau $outlineSection->getHeadingCalls(), 765*04fd306cSNickeau $outlineSection->getContentCalls() 766*04fd306cSNickeau ); 767*04fd306cSNickeau 768*04fd306cSNickeau $instructionCalls = array_map(function (Call $element) { 769*04fd306cSNickeau return $element->getInstructionCall(); 770*04fd306cSNickeau }, $sectionCalls); 771*04fd306cSNickeau $totalInstructionCalls = array_merge($totalInstructionCalls, $instructionCalls); 772*04fd306cSNickeau }; 773*04fd306cSNickeau TreeVisit::visit($this->rootSection, $collectCalls); 774*04fd306cSNickeau return $totalInstructionCalls; 775*04fd306cSNickeau 776*04fd306cSNickeau } 777*04fd306cSNickeau 778*04fd306cSNickeau /** 779*04fd306cSNickeau * Add the label (ie heading text to the cal attribute) 780*04fd306cSNickeau * 781*04fd306cSNickeau * @return void 782*04fd306cSNickeau */ 783*04fd306cSNickeau private 784*04fd306cSNickeau function saveOutlineToMetadata() 785*04fd306cSNickeau { 786*04fd306cSNickeau try { 787*04fd306cSNickeau $firstChild = $this->rootSection->getFirstChild(); 788*04fd306cSNickeau } catch (ExceptionNotFound $e) { 789*04fd306cSNickeau // no child 790*04fd306cSNickeau return; 791*04fd306cSNickeau } 792*04fd306cSNickeau if ($firstChild->getLevel() === 1) { 793*04fd306cSNickeau $headingCall = $firstChild->getEnterHeadingCall(); 794*04fd306cSNickeau // not dokuwiki header ? 795*04fd306cSNickeau if ($headingCall->isPluginCall()) { 796*04fd306cSNickeau $headingCall->setAttribute(HeadingTag::HEADING_TEXT_ATTRIBUTE, $firstChild->getLabel()); 797*04fd306cSNickeau } 798*04fd306cSNickeau } 799*04fd306cSNickeau 800*04fd306cSNickeau } 801*04fd306cSNickeau 802*04fd306cSNickeau 803*04fd306cSNickeau private 804*04fd306cSNickeau function storeH1() 805*04fd306cSNickeau { 806*04fd306cSNickeau try { 807*04fd306cSNickeau $outlineSection = $this->getRootOutlineSection()->getFirstChild(); 808*04fd306cSNickeau } catch (ExceptionNotFound $e) { 809*04fd306cSNickeau // 810*04fd306cSNickeau return; 811*04fd306cSNickeau } 812*04fd306cSNickeau if ($this->markupPath != null && $outlineSection->getLevel() === 1) { 813*04fd306cSNickeau $label = $outlineSection->getLabel(); 814*04fd306cSNickeau $call = $outlineSection->getEnterHeadingCall(); 815*04fd306cSNickeau if ($call->isPluginCall()) { 816*04fd306cSNickeau // we support also the dokwuiki header call that does not need the label 817*04fd306cSNickeau $call->addAttribute(HeadingTag::PARSED_LABEL, $label); 818*04fd306cSNickeau } 819*04fd306cSNickeau PageH1::createForPage($this->markupPath)->setDefaultValue($label); 820*04fd306cSNickeau } 821*04fd306cSNickeau } 822*04fd306cSNickeau 823*04fd306cSNickeau private 824*04fd306cSNickeau function storeTocForMarkupIfAny() 825*04fd306cSNickeau { 826*04fd306cSNickeau 827*04fd306cSNickeau $toc = $this->toTocDokuwikiFormat(); 828*04fd306cSNickeau 829*04fd306cSNickeau try { 830*04fd306cSNickeau $fetcherMarkup = ExecutionContext::getActualOrCreateFromEnv()->getExecutingMarkupHandler(); 831*04fd306cSNickeau $fetcherMarkup->toc = $toc; 832*04fd306cSNickeau if ($fetcherMarkup->isDocument()) { 833*04fd306cSNickeau /** 834*04fd306cSNickeau * We still update the global TOC Dokuwiki variables 835*04fd306cSNickeau */ 836*04fd306cSNickeau global $TOC; 837*04fd306cSNickeau $TOC = $toc; 838*04fd306cSNickeau } 839*04fd306cSNickeau } catch (ExceptionNotFound $e) { 840*04fd306cSNickeau // outline is not runnned from a markup handler 841*04fd306cSNickeau } 842*04fd306cSNickeau 843*04fd306cSNickeau if (!isset($this->markupPath)) { 844*04fd306cSNickeau return; 845*04fd306cSNickeau } 846*04fd306cSNickeau 847*04fd306cSNickeau try { 848*04fd306cSNickeau Toc::createForPage($this->markupPath) 849*04fd306cSNickeau ->setValue($toc) 850*04fd306cSNickeau ->persist(); 851*04fd306cSNickeau } catch (ExceptionBadArgument $e) { 852*04fd306cSNickeau LogUtility::error("The Toc could not be persisted. Error:{$e->getMessage()}"); 853*04fd306cSNickeau } 854*04fd306cSNickeau } 855*04fd306cSNickeau 856*04fd306cSNickeau public 857*04fd306cSNickeau function getMarkupPath(): ?MarkupPath 858*04fd306cSNickeau { 859*04fd306cSNickeau return $this->markupPath; 860*04fd306cSNickeau } 861*04fd306cSNickeau 862*04fd306cSNickeau 863*04fd306cSNickeau private 864*04fd306cSNickeau function getTocMaxLevel(): int 865*04fd306cSNickeau { 866*04fd306cSNickeau return ExecutionContext::getActualOrCreateFromEnv() 867*04fd306cSNickeau ->getConfig()->getTocMaxLevel(); 868*04fd306cSNickeau } 869*04fd306cSNickeau 870*04fd306cSNickeau public function setMetaHeaderCapture(bool $metaHeaderCapture): Outline 871*04fd306cSNickeau { 872*04fd306cSNickeau $this->metaHeaderCapture = $metaHeaderCapture; 873*04fd306cSNickeau return $this; 874*04fd306cSNickeau } 875*04fd306cSNickeau 876*04fd306cSNickeau public function getMetaHeaderCapture(): bool 877*04fd306cSNickeau { 878*04fd306cSNickeau if (isset($this->metaHeaderCapture)) { 879*04fd306cSNickeau return $this->metaHeaderCapture; 880*04fd306cSNickeau } 881*04fd306cSNickeau try { 882*04fd306cSNickeau if ($this->markupPath !== null) { 883*04fd306cSNickeau $contextPath = $this->markupPath->getPathObject()->toWikiPath(); 884*04fd306cSNickeau $hasMainHeaderElement = TemplateForWebPage::create() 885*04fd306cSNickeau ->setRequestedContextPath($contextPath) 886*04fd306cSNickeau ->hasElement(TemplateSlot::MAIN_HEADER_ID); 887*04fd306cSNickeau $isThemeSystemEnabled = ExecutionContext::getActualOrCreateFromEnv() 888*04fd306cSNickeau ->getConfig() 889*04fd306cSNickeau ->isThemeSystemEnabled(); 890*04fd306cSNickeau if ($isThemeSystemEnabled && $hasMainHeaderElement) { 891*04fd306cSNickeau return true; 892*04fd306cSNickeau } 893*04fd306cSNickeau } 894*04fd306cSNickeau } catch (ExceptionCast $e) { 895*04fd306cSNickeau // to Wiki Path should be good 896*04fd306cSNickeau } 897*04fd306cSNickeau return false; 898*04fd306cSNickeau } 899*04fd306cSNickeau 900*04fd306cSNickeau public function isSectionEditingEnabled(): bool 901*04fd306cSNickeau { 902*04fd306cSNickeau 903*04fd306cSNickeau return ExecutionContext::getActualOrCreateFromEnv() 904*04fd306cSNickeau ->getConfig()->isSectionEditingEnabled(); 905*04fd306cSNickeau 906*04fd306cSNickeau } 907*04fd306cSNickeau 908*04fd306cSNickeau 909*04fd306cSNickeau} 910