104fd306cSNickeau<?php 204fd306cSNickeau 304fd306cSNickeaunamespace ComboStrap; 404fd306cSNickeau 504fd306cSNickeau 604fd306cSNickeauuse ComboStrap\Meta\Field\PageH1; 704fd306cSNickeauuse Doku_Renderer_metadata; 804fd306cSNickeauuse Doku_Renderer_xhtml; 904fd306cSNickeauuse renderer_plugin_combo_analytics; 1004fd306cSNickeauuse syntax_plugin_combo_webcode; 1104fd306cSNickeau 1204fd306cSNickeauclass HeadingTag 1304fd306cSNickeau{ 1404fd306cSNickeau 1504fd306cSNickeau /** 1604fd306cSNickeau * The type of the heading tag 1704fd306cSNickeau */ 1804fd306cSNickeau public const DISPLAY_TYPES = ["d1", "d2", "d3", "d4", "d5", "d6"]; 1904fd306cSNickeau public const HEADING_TYPES = ["h1", "h2", "h3", "h4", "h5", "h6"]; 2004fd306cSNickeau public const SHORT_TYPES = ["1", "2", "3", "4", "5", "6"]; 2104fd306cSNickeau 2204fd306cSNickeau /** 2304fd306cSNickeau * The type of the title tag 2404fd306cSNickeau * @deprecated 2504fd306cSNickeau */ 2604fd306cSNickeau public const TITLE_DISPLAY_TYPES = ["0", "1", "2", "3", "4", "5", "6"]; 2704fd306cSNickeau 2804fd306cSNickeau 2904fd306cSNickeau /** 3004fd306cSNickeau * An heading may be printed 3104fd306cSNickeau * as outline and should be in the toc 3204fd306cSNickeau */ 3304fd306cSNickeau public const TYPE_OUTLINE = "outline"; 3404fd306cSNickeau public const CANONICAL = "heading"; 3504fd306cSNickeau 3604fd306cSNickeau public const SYNTAX_TYPE = 'baseonly'; 3704fd306cSNickeau public const SYNTAX_PTYPE = 'block'; 3804fd306cSNickeau /** 3904fd306cSNickeau * The section generation: 4004fd306cSNickeau * - Dokuwiki section (ie div just after the heading) 4104fd306cSNickeau * - or Combo section (ie section just before the heading) 4204fd306cSNickeau */ 4304fd306cSNickeau public const CONF_SECTION_LAYOUT = 'section_layout'; 4404fd306cSNickeau public const CONF_SECTION_LAYOUT_VALUES = [HeadingTag::CONF_SECTION_LAYOUT_COMBO, HeadingTag::CONF_SECTION_LAYOUT_DOKUWIKI]; 4504fd306cSNickeau public const CONF_SECTION_LAYOUT_DEFAULT = HeadingTag::CONF_SECTION_LAYOUT_COMBO; 4604fd306cSNickeau public const LEVEL = 'level'; 4704fd306cSNickeau public const CONF_SECTION_LAYOUT_DOKUWIKI = "dokuwiki"; 4804fd306cSNickeau /** 4904fd306cSNickeau * old tag 5004fd306cSNickeau */ 5104fd306cSNickeau public const TITLE_TAG = "title"; 5204fd306cSNickeau /** 5304fd306cSNickeau * New tag 5404fd306cSNickeau */ 5504fd306cSNickeau public const HEADING_TAG = "heading"; 5604fd306cSNickeau public const LOGICAL_TAG = self::HEADING_TAG; 5704fd306cSNickeau 5804fd306cSNickeau 5904fd306cSNickeau /** 6004fd306cSNickeau * The default level if not set 6104fd306cSNickeau * Not level 1 because this is the top level heading 6204fd306cSNickeau * Not level 2 because this is the most used level and we can confound with it 6304fd306cSNickeau */ 6404fd306cSNickeau public const DEFAULT_LEVEL_TITLE_CONTEXT = "3"; 6504fd306cSNickeau public const DISPLAY_BS_4_RESPONSIVE_SNIPPET_ID = "display-bs-4"; 6604fd306cSNickeau /** 6704fd306cSNickeau * The attribute that holds only the text of the heading 6804fd306cSNickeau * (used to create the id and the text in the toc) 6904fd306cSNickeau */ 7004fd306cSNickeau public const HEADING_TEXT_ATTRIBUTE = "heading_text"; 7104fd306cSNickeau public const CONF_SECTION_LAYOUT_COMBO = "combo"; 7204fd306cSNickeau 7304fd306cSNickeau 7404fd306cSNickeau /** 7504fd306cSNickeau * 1 because in test if used without any, this is 7604fd306cSNickeau * the first expected one in a outline 7704fd306cSNickeau */ 7804fd306cSNickeau public const DEFAULT_LEVEL_OUTLINE_CONTEXT = "1"; 7904fd306cSNickeau public const TYPE_TITLE = "title"; 8004fd306cSNickeau public const TAGS = [HeadingTag::HEADING_TAG, HeadingTag::TITLE_TAG]; 8104fd306cSNickeau /** 8204fd306cSNickeau * only available in 5 8304fd306cSNickeau */ 8404fd306cSNickeau public const DISPLAY_TYPES_ONLY_BS_5 = ["d5", "d6"]; 8504fd306cSNickeau 8604fd306cSNickeau /** 8704fd306cSNickeau * The label is the text that is generally used 8804fd306cSNickeau * in a TOC but also as default title for the page 8904fd306cSNickeau */ 9004fd306cSNickeau public const PARSED_LABEL = "label"; 9104fd306cSNickeau 9204fd306cSNickeau 9304fd306cSNickeau /** 9404fd306cSNickeau * A common function used to handle exit of headings 9504fd306cSNickeau * @param \Doku_Handler $handler 9604fd306cSNickeau * @return array 9704fd306cSNickeau */ 9804fd306cSNickeau public static function handleExit(\Doku_Handler $handler): array 9904fd306cSNickeau { 10004fd306cSNickeau 10104fd306cSNickeau $callStack = CallStack::createFromHandler($handler); 10204fd306cSNickeau 10304fd306cSNickeau /** 10404fd306cSNickeau * Delete the last space if any 10504fd306cSNickeau */ 10604fd306cSNickeau $callStack->moveToEnd(); 10704fd306cSNickeau $previous = $callStack->previous(); 10804fd306cSNickeau if ($previous->getState() == DOKU_LEXER_UNMATCHED) { 10904fd306cSNickeau $previous->setPayload(rtrim($previous->getCapturedContent())); 11004fd306cSNickeau } 11104fd306cSNickeau $callStack->next(); 11204fd306cSNickeau 11304fd306cSNickeau /** 11404fd306cSNickeau * Get context data 11504fd306cSNickeau */ 11604fd306cSNickeau $openingTag = $callStack->moveToPreviousCorrespondingOpeningCall(); 11704fd306cSNickeau $openingAttributes = $openingTag->getAttributes(); // for level 11804fd306cSNickeau $context = $openingTag->getContext(); // for sectioning 11904fd306cSNickeau 12004fd306cSNickeau return array( 12104fd306cSNickeau PluginUtility::STATE => DOKU_LEXER_EXIT, 12204fd306cSNickeau PluginUtility::ATTRIBUTES => $openingAttributes, 12304fd306cSNickeau PluginUtility::CONTEXT => $context 12404fd306cSNickeau ); 12504fd306cSNickeau } 12604fd306cSNickeau 12704fd306cSNickeau /** 12804fd306cSNickeau * @param CallStack $callStack 12904fd306cSNickeau * @return string 13004fd306cSNickeau */ 13104fd306cSNickeau public static function getContext(CallStack $callStack): string 13204fd306cSNickeau { 13304fd306cSNickeau 13404fd306cSNickeau /** 13504fd306cSNickeau * If the heading is inside a component, 13604fd306cSNickeau * it's a title heading, otherwise it's a outline heading 13704fd306cSNickeau * 13804fd306cSNickeau * (Except for {@link syntax_plugin_combo_webcode} that can wrap several outline heading) 13904fd306cSNickeau * 14004fd306cSNickeau * When the parent is empty, a section_open (ie another outline heading) 14104fd306cSNickeau * this is a outline 14204fd306cSNickeau */ 14304fd306cSNickeau $parent = $callStack->moveToParent(); 14404fd306cSNickeau if ($parent && $parent->getTagName() === Tag\WebCodeTag::TAG) { 14504fd306cSNickeau $parent = $callStack->moveToParent(); 14604fd306cSNickeau } 14704fd306cSNickeau if ($parent && $parent->getComponentName() !== "section_open") { 14804fd306cSNickeau $headingType = self::TYPE_TITLE; 14904fd306cSNickeau } else { 15004fd306cSNickeau $headingType = self::TYPE_OUTLINE; 15104fd306cSNickeau } 15204fd306cSNickeau 15304fd306cSNickeau switch ($headingType) { 15404fd306cSNickeau case HeadingTag::TYPE_TITLE: 15504fd306cSNickeau 15604fd306cSNickeau $context = $parent->getTagName(); 15704fd306cSNickeau break; 15804fd306cSNickeau 15904fd306cSNickeau case HeadingTag::TYPE_OUTLINE: 16004fd306cSNickeau 16104fd306cSNickeau $context = HeadingTag::TYPE_OUTLINE; 16204fd306cSNickeau break; 16304fd306cSNickeau 16404fd306cSNickeau default: 16504fd306cSNickeau LogUtility::msg("The heading type ($headingType) is unknown"); 16604fd306cSNickeau $context = ""; 16704fd306cSNickeau break; 16804fd306cSNickeau } 16904fd306cSNickeau return $context; 17004fd306cSNickeau } 17104fd306cSNickeau 17204fd306cSNickeau /** 17304fd306cSNickeau * @param $data 17404fd306cSNickeau * @param Doku_Renderer_metadata $renderer 17504fd306cSNickeau */ 17604fd306cSNickeau public static function processHeadingEnterMetadata($data, Doku_Renderer_metadata $renderer) 17704fd306cSNickeau { 17804fd306cSNickeau 17904fd306cSNickeau $state = $data[PluginUtility::STATE]; 18004fd306cSNickeau if (!in_array($state, [DOKU_LEXER_ENTER, DOKU_LEXER_SPECIAL])) { 18104fd306cSNickeau return; 18204fd306cSNickeau } 18304fd306cSNickeau /** 18404fd306cSNickeau * Only outline heading metadata 18504fd306cSNickeau * Not component heading 18604fd306cSNickeau */ 18704fd306cSNickeau $context = $data[PluginUtility::CONTEXT]; 18804fd306cSNickeau if ($context === self::TYPE_OUTLINE) { 18904fd306cSNickeau 19004fd306cSNickeau $callStackArray = $data[PluginUtility::ATTRIBUTES]; 19104fd306cSNickeau $tagAttributes = TagAttributes::createFromCallStackArray($callStackArray); 19270bbd7f1Sgerardnico $text = $tagAttributes->getValue(HeadingTag::HEADING_TEXT_ATTRIBUTE); 19370bbd7f1Sgerardnico if ($text !== null) { 19470bbd7f1Sgerardnico $text = trim($text); 19570bbd7f1Sgerardnico } 19604fd306cSNickeau $level = $tagAttributes->getValue(HeadingTag::LEVEL); 19704fd306cSNickeau $pos = 0; // mandatory for header but not for metadata, we set 0 to make the code analyser happy 19804fd306cSNickeau $renderer->header($text, $level, $pos); 19904fd306cSNickeau 20004fd306cSNickeau if ($level === 1) { 20104fd306cSNickeau $parsedLabel = $tagAttributes->getValue(self::PARSED_LABEL); 20204fd306cSNickeau $renderer->meta[PageH1::H1_PARSED] = $parsedLabel; 20304fd306cSNickeau } 20404fd306cSNickeau 20504fd306cSNickeau } 20604fd306cSNickeau 20704fd306cSNickeau 20804fd306cSNickeau } 20904fd306cSNickeau 21004fd306cSNickeau public static function processMetadataAnalytics(array $data, renderer_plugin_combo_analytics $renderer) 21104fd306cSNickeau { 21204fd306cSNickeau 21304fd306cSNickeau $state = $data[PluginUtility::STATE]; 21404fd306cSNickeau if ($state !== DOKU_LEXER_ENTER) { 21504fd306cSNickeau return; 21604fd306cSNickeau } 21704fd306cSNickeau /** 21804fd306cSNickeau * Only outline heading metadata 21904fd306cSNickeau * Not component heading 22004fd306cSNickeau */ 22170bbd7f1Sgerardnico $context = $data[PluginUtility::CONTEXT] ?? null; 22204fd306cSNickeau if ($context === self::TYPE_OUTLINE) { 22304fd306cSNickeau $callStackArray = $data[PluginUtility::ATTRIBUTES]; 22404fd306cSNickeau $tagAttributes = TagAttributes::createFromCallStackArray($callStackArray); 22504fd306cSNickeau $text = $tagAttributes->getValue(HeadingTag::HEADING_TEXT_ATTRIBUTE); 22604fd306cSNickeau $level = $tagAttributes->getValue(HeadingTag::LEVEL); 22704fd306cSNickeau $renderer->header($text, $level, 0); 22804fd306cSNickeau } 22904fd306cSNickeau 23004fd306cSNickeau } 23104fd306cSNickeau 23204fd306cSNickeau /** 23304fd306cSNickeau * @param string $context 23404fd306cSNickeau * @param TagAttributes $tagAttributes 23504fd306cSNickeau * @param Doku_Renderer_xhtml $renderer 23604fd306cSNickeau * @param int|null $pos - null if the call was generated 23704fd306cSNickeau * @return void 23804fd306cSNickeau */ 23904fd306cSNickeau public static function processRenderEnterXhtml(string $context, TagAttributes $tagAttributes, Doku_Renderer_xhtml &$renderer, ?int $pos) 24004fd306cSNickeau { 24104fd306cSNickeau 24204fd306cSNickeau /** 24304fd306cSNickeau * All correction that are dependent 24404fd306cSNickeau * on the markup (ie title or heading) 24504fd306cSNickeau * are done in the {@link self::processRenderEnterXhtml()} 24604fd306cSNickeau */ 24704fd306cSNickeau 24804fd306cSNickeau /** 24904fd306cSNickeau * Variable 25004fd306cSNickeau */ 25104fd306cSNickeau $type = $tagAttributes->getType(); 25204fd306cSNickeau 25304fd306cSNickeau /** 25404fd306cSNickeau * Old syntax deprecated 25504fd306cSNickeau */ 25604fd306cSNickeau if ($type === "0") { 25704fd306cSNickeau if ($context === self::TYPE_OUTLINE) { 25804fd306cSNickeau $type = 'h' . self::DEFAULT_LEVEL_OUTLINE_CONTEXT; 25904fd306cSNickeau } else { 26004fd306cSNickeau $type = 'h' . self::DEFAULT_LEVEL_TITLE_CONTEXT; 26104fd306cSNickeau } 26204fd306cSNickeau } 26304fd306cSNickeau /** 26404fd306cSNickeau * Label is for the TOC 26504fd306cSNickeau */ 26604fd306cSNickeau $tagAttributes->removeAttributeIfPresent(self::PARSED_LABEL); 26704fd306cSNickeau 26804fd306cSNickeau 26904fd306cSNickeau /** 27004fd306cSNickeau * Level 27104fd306cSNickeau */ 27204fd306cSNickeau $level = $tagAttributes->getValueAndRemove(HeadingTag::LEVEL); 27304fd306cSNickeau 27404fd306cSNickeau /** 27504fd306cSNickeau * Display Heading 27604fd306cSNickeau * https://getbootstrap.com/docs/5.0/content/typography/#display-headings 27704fd306cSNickeau */ 27804fd306cSNickeau if ($context !== self::TYPE_OUTLINE && $type === null) { 27904fd306cSNickeau /** 28004fd306cSNickeau * if not an outline, a display 28104fd306cSNickeau */ 28204fd306cSNickeau $type = "h$level"; 28304fd306cSNickeau } 28404fd306cSNickeau if (in_array($type, self::DISPLAY_TYPES)) { 28504fd306cSNickeau 28604fd306cSNickeau $displayClass = "display-$level"; 28704fd306cSNickeau 28804fd306cSNickeau if (Bootstrap::getBootStrapMajorVersion() == Bootstrap::BootStrapFourMajorVersion) { 28904fd306cSNickeau /** 29004fd306cSNickeau * Make Bootstrap display responsive 29104fd306cSNickeau */ 29204fd306cSNickeau PluginUtility::getSnippetManager()->attachCssInternalStyleSheet(HeadingTag::DISPLAY_BS_4_RESPONSIVE_SNIPPET_ID); 29304fd306cSNickeau 29404fd306cSNickeau if (in_array($type, self::DISPLAY_TYPES_ONLY_BS_5)) { 29504fd306cSNickeau $displayClass = "display-4"; 29604fd306cSNickeau LogUtility::msg("Bootstrap 4 does not support the type ($type). Switch to " . PluginUtility::getDocumentationHyperLink(Bootstrap::CANONICAL, "bootstrap 5") . " if you want to use it. The display type was set to `d4`", LogUtility::LVL_MSG_WARNING, self::CANONICAL); 29704fd306cSNickeau } 29804fd306cSNickeau 29904fd306cSNickeau } 30004fd306cSNickeau $tagAttributes->addClassName($displayClass); 30104fd306cSNickeau } 30204fd306cSNickeau 30304fd306cSNickeau /** 30404fd306cSNickeau * Heading class 30504fd306cSNickeau * https://getbootstrap.com/docs/5.0/content/typography/#headings 30604fd306cSNickeau * Works on 4 and 5 30704fd306cSNickeau */ 30804fd306cSNickeau if (in_array($type, self::HEADING_TYPES)) { 30904fd306cSNickeau $tagAttributes->addClassName($type); 31004fd306cSNickeau } 31104fd306cSNickeau 31204fd306cSNickeau /** 31304fd306cSNickeau * Card title Context class 31404fd306cSNickeau * TODO: should move to card 31504fd306cSNickeau */ 31604fd306cSNickeau if (in_array($context, [BlockquoteTag::TAG, CardTag::CARD_TAG])) { 31704fd306cSNickeau $tagAttributes->addClassName("card-title"); 31804fd306cSNickeau } 31904fd306cSNickeau 32004fd306cSNickeau $executionContext = ExecutionContext::getActualOrCreateFromEnv(); 32104fd306cSNickeau 32204fd306cSNickeau /** 32304fd306cSNickeau * Add an outline class to be able to style them at once 32404fd306cSNickeau * 32504fd306cSNickeau * The context is by default the parent name or outline. 32604fd306cSNickeau */ 32704fd306cSNickeau $snippetManager = SnippetSystem::getFromContext(); 32804fd306cSNickeau if ($context === self::TYPE_OUTLINE) { 32904fd306cSNickeau 33004fd306cSNickeau $tagAttributes->addClassName(Outline::getOutlineHeadingClass()); 33104fd306cSNickeau 33204fd306cSNickeau $snippetManager->attachCssInternalStyleSheet(self::TYPE_OUTLINE); 33304fd306cSNickeau 33404fd306cSNickeau // numbering 33504fd306cSNickeau try { 33604fd306cSNickeau 33704fd306cSNickeau $enable = $executionContext 33804fd306cSNickeau ->getConfig() 33904fd306cSNickeau ->getValue(Outline::CONF_OUTLINE_NUMBERING_ENABLE, Outline::CONF_OUTLINE_NUMBERING_ENABLE_DEFAULT); 34004fd306cSNickeau if ($enable) { 34104fd306cSNickeau $snippet = $snippetManager->attachCssInternalStyleSheet(Outline::OUTLINE_HEADING_NUMBERING); 34204fd306cSNickeau if (!$snippet->hasInlineContent()) { 34304fd306cSNickeau $css = Outline::getCssNumberingRulesFor(Outline::OUTLINE_HEADING_NUMBERING); 34404fd306cSNickeau $snippet->setInlineContent($css); 34504fd306cSNickeau } 34604fd306cSNickeau } 34704fd306cSNickeau } catch (ExceptionBadSyntax $e) { 34804fd306cSNickeau LogUtility::internalError("An error has occurred while trying to add the outline heading numbering stylesheet.", self::CANONICAL, $e); 34904fd306cSNickeau } catch (ExceptionNotEnabled $e) { 35004fd306cSNickeau // ok 35104fd306cSNickeau } 35204fd306cSNickeau 35304fd306cSNickeau /** 35404fd306cSNickeau * Anchor on id 35504fd306cSNickeau */ 35604fd306cSNickeau $snippetManager = PluginUtility::getSnippetManager(); 35704fd306cSNickeau try { 35804fd306cSNickeau $snippetManager->attachRemoteJavascriptLibrary( 35904fd306cSNickeau Outline::OUTLINE_ANCHOR, 36004fd306cSNickeau "https://cdn.jsdelivr.net/npm/anchor-js@4.3.0/anchor.min.js", 36104fd306cSNickeau "sha256-LGOWMG4g6/zc0chji4hZP1d8RxR2bPvXMzl/7oPZqjs=" 36204fd306cSNickeau ); 36304fd306cSNickeau } catch (ExceptionBadArgument|ExceptionBadSyntax $e) { 36404fd306cSNickeau // The url has a file name. this error should not happen 36504fd306cSNickeau LogUtility::internalError("Unable to add anchor. Error:{$e->getMessage()}", Outline::OUTLINE_ANCHOR); 36604fd306cSNickeau } 36704fd306cSNickeau $snippetManager->attachJavascriptFromComponentId(Outline::OUTLINE_ANCHOR); 36804fd306cSNickeau 36904fd306cSNickeau } 37004fd306cSNickeau $snippetManager->attachCssInternalStyleSheet(HeadingTag::HEADING_TAG); 37104fd306cSNickeau 37204fd306cSNickeau /** 37304fd306cSNickeau * Not a HTML attribute 37404fd306cSNickeau */ 37504fd306cSNickeau $tagAttributes->removeComponentAttributeIfPresent(self::HEADING_TEXT_ATTRIBUTE); 37604fd306cSNickeau 37704fd306cSNickeau /** 378783d9857Sgerardnico * Two headings 1 are shown 379783d9857Sgerardnico * 380783d9857Sgerardnico * We delete the heading 1 in the instructions 381783d9857Sgerardnico * if the template has a content header 382783d9857Sgerardnico * 383783d9857Sgerardnico * The instructions may not be reprocessed after upgrade for instance 384783d9857Sgerardnico * when the installation is done manually 385783d9857Sgerardnico * 386783d9857Sgerardnico * To avoid to have two headings, we set a display none if this is the case 387783d9857Sgerardnico * 388783d9857Sgerardnico * Note that this should only apply on the document and not on a partial but yeah 389783d9857Sgerardnico * We go that h1 is not used in partials. 390783d9857Sgerardnico */ 391783d9857Sgerardnico if ($level === 1) { 392783d9857Sgerardnico 393783d9857Sgerardnico $hasMainHeaderElement = TemplateForWebPage::create() 394783d9857Sgerardnico ->setRequestedContextPath(ExecutionContext::getActualOrCreateFromEnv()->getContextPath()) 395783d9857Sgerardnico ->hasElement(TemplateSlot::MAIN_HEADER_ID); 396*dff3a8c8SNico 397*dff3a8c8SNico // fuck: template should be a runtime parameters and is not 398*dff3a8c8SNico $executingAction = ExecutionContext::getActualOrCreateFromEnv()->getExecutingAction(); 399*dff3a8c8SNico if ($executingAction === "combo_" . FetcherPageBundler::NAME) { 400*dff3a8c8SNico $hasMainHeaderElement = false; 401*dff3a8c8SNico } 402*dff3a8c8SNico 403783d9857Sgerardnico if ($hasMainHeaderElement) { 404783d9857Sgerardnico $tagAttributes->addClassName("d-none"); 405783d9857Sgerardnico } 406783d9857Sgerardnico 407783d9857Sgerardnico } 408783d9857Sgerardnico 409783d9857Sgerardnico /** 41004fd306cSNickeau * Printing 41104fd306cSNickeau */ 41204fd306cSNickeau $tag = self::getTagFromContext($context, $level); 41304fd306cSNickeau $renderer->doc .= $tagAttributes->toHtmlEnterTag($tag); 41404fd306cSNickeau 41504fd306cSNickeau } 41604fd306cSNickeau 41704fd306cSNickeau /** 41804fd306cSNickeau * @param TagAttributes $tagAttributes 41904fd306cSNickeau * @param string $context 42004fd306cSNickeau * @return string 42104fd306cSNickeau */ 42204fd306cSNickeau public 42304fd306cSNickeau static function renderClosingTag(TagAttributes $tagAttributes, string $context): string 42404fd306cSNickeau { 42504fd306cSNickeau $level = $tagAttributes->getValueAndRemove(HeadingTag::LEVEL); 42604fd306cSNickeau if ($level == null) { 42704fd306cSNickeau LogUtility::msg("The level is mandatory when closing a heading", self::CANONICAL); 42804fd306cSNickeau } 42904fd306cSNickeau $tag = self::getTagFromContext($context, $level); 43004fd306cSNickeau 43104fd306cSNickeau return "</$tag>"; 43204fd306cSNickeau } 43304fd306cSNickeau 43404fd306cSNickeau /** 43504fd306cSNickeau * Reduce the end of the input string 43604fd306cSNickeau * to the first opening tag without the ">" 43704fd306cSNickeau * and returns the closing tag 43804fd306cSNickeau * 43904fd306cSNickeau * @param $input 44004fd306cSNickeau * @return array - the heading attributes as a string 44104fd306cSNickeau */ 44204fd306cSNickeau public 44304fd306cSNickeau static function reduceToFirstOpeningTagAndReturnAttributes(&$input) 44404fd306cSNickeau { 44504fd306cSNickeau // the variable that will capture the attribute string 44604fd306cSNickeau $headingStartTagString = ""; 44704fd306cSNickeau // Set to true when the heading tag has completed 44804fd306cSNickeau $endHeadingParsed = false; 44904fd306cSNickeau // The closing character `>` indicator of the start and end tag 45004fd306cSNickeau // true when found 45104fd306cSNickeau $endTagClosingCharacterParsed = false; 45204fd306cSNickeau $startTagClosingCharacterParsed = false; 45304fd306cSNickeau // We start from the edn 45404fd306cSNickeau $position = strlen($input) - 1; 45504fd306cSNickeau while ($position > 0) { 45604fd306cSNickeau $character = $input[$position]; 45704fd306cSNickeau 45804fd306cSNickeau if ($character == "<") { 45904fd306cSNickeau if (!$endHeadingParsed) { 46004fd306cSNickeau // We are at the beginning of the ending tag 46104fd306cSNickeau $endHeadingParsed = true; 46204fd306cSNickeau } else { 46304fd306cSNickeau // We have delete all character until the heading start tag 46404fd306cSNickeau // add the last one and exit 46504fd306cSNickeau $headingStartTagString = $character . $headingStartTagString; 46604fd306cSNickeau break; 46704fd306cSNickeau } 46804fd306cSNickeau } 46904fd306cSNickeau 47004fd306cSNickeau if ($character == ">") { 47104fd306cSNickeau if (!$endTagClosingCharacterParsed) { 47204fd306cSNickeau // We are at the beginning of the ending tag 47304fd306cSNickeau $endTagClosingCharacterParsed = true; 47404fd306cSNickeau } else { 47504fd306cSNickeau // We have delete all character until the heading start tag 47604fd306cSNickeau $startTagClosingCharacterParsed = true; 47704fd306cSNickeau } 47804fd306cSNickeau } 47904fd306cSNickeau 48004fd306cSNickeau if ($startTagClosingCharacterParsed) { 48104fd306cSNickeau $headingStartTagString = $character . $headingStartTagString; 48204fd306cSNickeau } 48304fd306cSNickeau 48404fd306cSNickeau 48504fd306cSNickeau // position -- 48604fd306cSNickeau $position--; 48704fd306cSNickeau 48804fd306cSNickeau } 48904fd306cSNickeau $input = substr($input, 0, $position); 49004fd306cSNickeau 49104fd306cSNickeau if (!empty($headingStartTagString)) { 49204fd306cSNickeau return PluginUtility::getTagAttributes($headingStartTagString); 49304fd306cSNickeau } else { 49404fd306cSNickeau LogUtility::msg("The attributes of the heading are empty and this should not be possible"); 49504fd306cSNickeau return []; 49604fd306cSNickeau } 49704fd306cSNickeau 49804fd306cSNickeau 49904fd306cSNickeau } 50004fd306cSNickeau 50104fd306cSNickeau public 50204fd306cSNickeau static function handleEnter(\Doku_Handler $handler, TagAttributes $tagAttributes, string $markupTag): array 50304fd306cSNickeau { 50404fd306cSNickeau /** 50504fd306cSNickeau * Context determination 50604fd306cSNickeau */ 50704fd306cSNickeau $callStack = CallStack::createFromHandler($handler); 50804fd306cSNickeau $context = HeadingTag::getContext($callStack); 50904fd306cSNickeau 51004fd306cSNickeau /** 51104fd306cSNickeau * Level is mandatory (for the closing tag) 51204fd306cSNickeau */ 51304fd306cSNickeau $level = $tagAttributes->getValue(HeadingTag::LEVEL); 51404fd306cSNickeau if ($level === null) { 51504fd306cSNickeau 51604fd306cSNickeau /** 51704fd306cSNickeau * Old title type 51804fd306cSNickeau * from 1 to 4 to set the display heading 51904fd306cSNickeau */ 52004fd306cSNickeau $type = $tagAttributes->getType(); 52104fd306cSNickeau if (is_numeric($type) && $type != 0) { 52204fd306cSNickeau $level = $type; 52304fd306cSNickeau if ($markupTag === self::TITLE_TAG) { 52404fd306cSNickeau $type = "d$level"; 52504fd306cSNickeau } else { 52604fd306cSNickeau $type = "h$level"; 52704fd306cSNickeau } 52804fd306cSNickeau $tagAttributes->setType($type); 52904fd306cSNickeau } 53004fd306cSNickeau /** 53104fd306cSNickeau * Still null, check the type 53204fd306cSNickeau */ 53304fd306cSNickeau if ($level == null) { 53404fd306cSNickeau if (in_array($type, HeadingTag::getAllTypes())) { 53504fd306cSNickeau $level = substr($type, 1); 53604fd306cSNickeau } 53704fd306cSNickeau } 53804fd306cSNickeau /** 53904fd306cSNickeau * Still null, default level 54004fd306cSNickeau */ 54104fd306cSNickeau if ($level == null) { 54204fd306cSNickeau if ($context === HeadingTag::TYPE_OUTLINE) { 54304fd306cSNickeau $level = HeadingTag::DEFAULT_LEVEL_OUTLINE_CONTEXT; 54404fd306cSNickeau } else { 54504fd306cSNickeau $level = HeadingTag::DEFAULT_LEVEL_TITLE_CONTEXT; 54604fd306cSNickeau } 54704fd306cSNickeau } 54804fd306cSNickeau /** 54904fd306cSNickeau * Set the level 55004fd306cSNickeau */ 55104fd306cSNickeau $tagAttributes->addComponentAttributeValue(HeadingTag::LEVEL, $level); 55204fd306cSNickeau } 55304fd306cSNickeau return [PluginUtility::CONTEXT => $context]; 55404fd306cSNickeau } 55504fd306cSNickeau 55604fd306cSNickeau public 55704fd306cSNickeau static function getAllTypes(): array 55804fd306cSNickeau { 55904fd306cSNickeau return array_merge( 56004fd306cSNickeau self::DISPLAY_TYPES, 56104fd306cSNickeau self::HEADING_TYPES, 56204fd306cSNickeau self::SHORT_TYPES, 56304fd306cSNickeau self::TITLE_DISPLAY_TYPES 56404fd306cSNickeau ); 56504fd306cSNickeau } 56604fd306cSNickeau 56704fd306cSNickeau /** 56804fd306cSNickeau * @param string $context 56904fd306cSNickeau * @param int $level 57004fd306cSNickeau * @return string 57104fd306cSNickeau */ 57204fd306cSNickeau private static function getTagFromContext(string $context, int $level): string 57304fd306cSNickeau { 57404fd306cSNickeau if ($context === self::TYPE_OUTLINE) { 57504fd306cSNickeau return "h$level"; 57604fd306cSNickeau } else { 57704fd306cSNickeau return "div"; 57804fd306cSNickeau } 57904fd306cSNickeau } 58004fd306cSNickeau 58104fd306cSNickeau 58204fd306cSNickeau} 583