1531e725cSNickeau<?php 2531e725cSNickeau 3531e725cSNickeauuse ComboStrap\CallStack; 4034808a5SNicouse ComboStrap\ExceptionNotFound; 504fd306cSNickeauuse ComboStrap\HeadingTag; 6531e725cSNickeauuse ComboStrap\LogUtility; 7531e725cSNickeauuse ComboStrap\PluginUtility; 804fd306cSNickeauuse ComboStrap\SiteConfig; 9531e725cSNickeauuse ComboStrap\TagAttributes; 10531e725cSNickeau 11531e725cSNickeau 12531e725cSNickeau/** 13531e725cSNickeau * Class headingwiki 14531e725cSNickeau * Taking over {@link \dokuwiki\Parsing\ParserMode\Header} 15531e725cSNickeau */ 16531e725cSNickeauclass syntax_plugin_combo_headingwiki extends DokuWiki_Syntax_Plugin 17531e725cSNickeau{ 18531e725cSNickeau 19531e725cSNickeau /** 20531e725cSNickeau * Header pattern 21531e725cSNickeau * * Dokuwiki does not made a space mandatory after and before the opening an closing `=` character 22531e725cSNickeau * * No line break in the look ahead 23531e725cSNickeau * * The capture of the first spaces should be optional otherwise the {@link \dokuwiki\Parsing\ParserMode\Header} is taking over 24531e725cSNickeau * 25531e725cSNickeau * See also for information, 26531e725cSNickeau * the original heading pattern of Dokuwiki {@link \dokuwiki\Parsing\ParserMode\Header} 27531e725cSNickeau */ 28531e725cSNickeau const ENTRY_PATTERN = '[ \t]*={1,6}(?=[^\n]*={1,6}\s*\r??\n)'; 29531e725cSNickeau const EXIT_PATTERN = '={1,6}\s*(?=\r??\n)'; 30531e725cSNickeau const TAG = "headingwiki"; 31531e725cSNickeau 32531e725cSNickeau const CONF_WIKI_HEADING_ENABLE = "headingWikiEnable"; 33531e725cSNickeau const CONF_DEFAULT_WIKI_ENABLE_VALUE = 1; 34531e725cSNickeau 3504fd306cSNickeau 3604fd306cSNickeau /** 3704fd306cSNickeau * When we takes over the dokuwiki heading 3804fd306cSNickeau * we are also taking over the sectioning 3904fd306cSNickeau * and allows {@link syntax_plugin_combo_section} 4004fd306cSNickeau * @return int - 1 or 0 4104fd306cSNickeau */ 4204fd306cSNickeau public static function isEnabled(): int 4304fd306cSNickeau { 4404fd306cSNickeau return SiteConfig::getConfValue(self::CONF_WIKI_HEADING_ENABLE, self::CONF_DEFAULT_WIKI_ENABLE_VALUE); 4504fd306cSNickeau } 4604fd306cSNickeau 474cadd4f8SNickeau public function getSort(): int 48531e725cSNickeau { 49531e725cSNickeau /** 50531e725cSNickeau * It's 49 (on less than the original heading) 51531e725cSNickeau * {@link \dokuwiki\Parsing\ParserMode\Header::getSort()} 52531e725cSNickeau */ 53531e725cSNickeau return 49; 54531e725cSNickeau } 55531e725cSNickeau 564cadd4f8SNickeau public function getType(): string 57531e725cSNickeau { 5804fd306cSNickeau return HeadingTag::SYNTAX_TYPE; 59531e725cSNickeau } 60531e725cSNickeau 61531e725cSNickeau 62531e725cSNickeau /** 63531e725cSNickeau * 64531e725cSNickeau * How Dokuwiki will add P element 65531e725cSNickeau * 66531e725cSNickeau * * 'normal' - The plugin can be used inside paragraphs (inline) 67531e725cSNickeau * * 'block' - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs 68531e725cSNickeau * * 'stack' - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs 69531e725cSNickeau * 70531e725cSNickeau * @see DokuWiki_Syntax_Plugin::getPType() 71531e725cSNickeau * 72531e725cSNickeau * This is the equivalent of inline or block for css 73531e725cSNickeau */ 744cadd4f8SNickeau public function getPType(): string 75531e725cSNickeau { 7604fd306cSNickeau return HeadingTag::SYNTAX_PTYPE; 77531e725cSNickeau } 78531e725cSNickeau 79531e725cSNickeau /** 80531e725cSNickeau * @return array 81531e725cSNickeau * Allow which kind of plugin inside 82531e725cSNickeau * 83531e725cSNickeau * No one of array('baseonly','container', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs') 84531e725cSNickeau * because we manage self the content and we call self the parser 85531e725cSNickeau * 86531e725cSNickeau * Return an array of one or more of the mode types {@link $PARSER_MODES} in Parser.php 87531e725cSNickeau */ 884cadd4f8SNickeau function getAllowedTypes(): array 89531e725cSNickeau { 90531e725cSNickeau return array('formatting', 'substition', 'protected', 'disabled'); 91531e725cSNickeau } 92531e725cSNickeau 93531e725cSNickeau 94531e725cSNickeau public function connectTo($mode) 95531e725cSNickeau { 96531e725cSNickeau if ($this->enableWikiHeading($mode)) { 979337a630SNickeau $this->Lexer->addEntryPattern(self::ENTRY_PATTERN, $mode, PluginUtility::getModeFromTag($this->getPluginComponent())); 98531e725cSNickeau } 99531e725cSNickeau } 100531e725cSNickeau 101531e725cSNickeau public function postConnect() 102531e725cSNickeau { 103531e725cSNickeau 1049337a630SNickeau $this->Lexer->addExitPattern(self::EXIT_PATTERN, PluginUtility::getModeFromTag($this->getPluginComponent())); 105531e725cSNickeau 106531e725cSNickeau } 107531e725cSNickeau 108531e725cSNickeau 109531e725cSNickeau /** 110531e725cSNickeau * Handle the syntax 111531e725cSNickeau * 112531e725cSNickeau * At the end of the parser, the `section_open` and `section_close` calls 11304fd306cSNickeau * are created in {@link action_plugin_combo_instructionspostprocessing} 114531e725cSNickeau * and the text inside for the toc is captured 115531e725cSNickeau * 116531e725cSNickeau * @param string $match 117531e725cSNickeau * @param int $state 118531e725cSNickeau * @param int $pos 119531e725cSNickeau * @param Doku_Handler $handler 120531e725cSNickeau * @return array 121531e725cSNickeau */ 1224cadd4f8SNickeau public function handle($match, $state, $pos, Doku_Handler $handler): array 123531e725cSNickeau { 124531e725cSNickeau switch ($state) { 125531e725cSNickeau 126531e725cSNickeau case DOKU_LEXER_ENTER: 127531e725cSNickeau /** 128531e725cSNickeau * Title regexp 129531e725cSNickeau */ 13004fd306cSNickeau $level = $this->getLevelFromMatch($match); 131531e725cSNickeau 132034808a5SNico 133*04823ca8SNico $attributes = TagAttributes::createEmpty(self::TAG) 134*04823ca8SNico ->addComponentAttributeValue(HeadingTag::LEVEL, $level) 135*04823ca8SNico ->toCallStackArray(); 13604fd306cSNickeau 13704fd306cSNickeau $callStack = CallStack::createFromHandler($handler); 13804fd306cSNickeau $context = HeadingTag::getContext($callStack); 139531e725cSNickeau 140531e725cSNickeau return array( 141531e725cSNickeau PluginUtility::STATE => $state, 142531e725cSNickeau PluginUtility::ATTRIBUTES => $attributes, 143531e725cSNickeau PluginUtility::CONTEXT => $context, 144531e725cSNickeau PluginUtility::POSITION => $pos 145531e725cSNickeau ); 146531e725cSNickeau case DOKU_LEXER_UNMATCHED : 147531e725cSNickeau 148531e725cSNickeau return PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler); 149531e725cSNickeau 150531e725cSNickeau case DOKU_LEXER_EXIT : 151531e725cSNickeau 15204fd306cSNickeau $returnedData = HeadingTag::handleExit($handler); 153531e725cSNickeau 154531e725cSNickeau /** 155531e725cSNickeau * Control of the Number of `=` before and after 156531e725cSNickeau */ 15704fd306cSNickeau $callStack = CallStack::createFromHandler($handler); 158531e725cSNickeau $callStack->moveToEnd(); 159531e725cSNickeau $openingTag = $callStack->moveToPreviousCorrespondingOpeningCall(); 160531e725cSNickeau $levelFromMatch = $this->getLevelFromMatch($match); 16104fd306cSNickeau $levelFromStartTag = $openingTag->getAttribute(HeadingTag::LEVEL); 162531e725cSNickeau if ($levelFromMatch != $levelFromStartTag) { 163531e725cSNickeau $content = ""; 164531e725cSNickeau while ($actualCall = $callStack->next()) { 165531e725cSNickeau $content .= $actualCall->getCapturedContent(); 166531e725cSNickeau } 16704fd306cSNickeau LogUtility::msg("The number of `=` character for a wiki heading is not the same before ($levelFromStartTag) and after ($levelFromMatch) the content ($content).", LogUtility::LVL_MSG_INFO, HeadingTag::CANONICAL); 168531e725cSNickeau } 169531e725cSNickeau 170531e725cSNickeau return $returnedData; 171531e725cSNickeau 172531e725cSNickeau } 173531e725cSNickeau return array(); 174531e725cSNickeau } 175531e725cSNickeau 176c3437056SNickeau public function render($format, Doku_Renderer $renderer, $data): bool 177531e725cSNickeau { 178531e725cSNickeau 17904fd306cSNickeau switch ($format) { 18004fd306cSNickeau case "xhtml": 181531e725cSNickeau /** 182531e725cSNickeau * @var Doku_Renderer_xhtml $renderer 183531e725cSNickeau */ 184531e725cSNickeau $state = $data[PluginUtility::STATE]; 18504fd306cSNickeau $context = $data[PluginUtility::CONTEXT]; 186531e725cSNickeau switch ($state) { 187531e725cSNickeau 188531e725cSNickeau case DOKU_LEXER_ENTER: 189531e725cSNickeau $callStackArray = $data[PluginUtility::ATTRIBUTES]; 19004fd306cSNickeau $tagAttributes = TagAttributes::createFromCallStackArray($callStackArray, HeadingTag::HEADING_TAG); 191531e725cSNickeau $pos = $data[PluginUtility::POSITION]; 19204fd306cSNickeau HeadingTag::processRenderEnterXhtml($context, $tagAttributes, $renderer, $pos); 193531e725cSNickeau return true; 194531e725cSNickeau case DOKU_LEXER_UNMATCHED: 195531e725cSNickeau $renderer->doc .= PluginUtility::renderUnmatched($data); 196531e725cSNickeau return true; 197531e725cSNickeau case DOKU_LEXER_EXIT: 198531e725cSNickeau $callStackArray = $data[PluginUtility::ATTRIBUTES]; 199531e725cSNickeau $tagAttributes = TagAttributes::createFromCallStackArray($callStackArray); 20004fd306cSNickeau $renderer->doc .= HeadingTag::renderClosingTag($tagAttributes, $context); 201531e725cSNickeau return true; 202531e725cSNickeau 203531e725cSNickeau } 20404fd306cSNickeau return false; 20504fd306cSNickeau case renderer_plugin_combo_analytics::RENDERER_FORMAT: 206531e725cSNickeau 207531e725cSNickeau /** 208531e725cSNickeau * @var renderer_plugin_combo_analytics $renderer 209531e725cSNickeau */ 21004fd306cSNickeau HeadingTag::processMetadataAnalytics($data, $renderer); 21104fd306cSNickeau return true; 212531e725cSNickeau 21304fd306cSNickeau case "metadata": 214531e725cSNickeau 215531e725cSNickeau /** 216531e725cSNickeau * @var Doku_Renderer_metadata $renderer 217531e725cSNickeau */ 21804fd306cSNickeau HeadingTag::processHeadingEnterMetadata($data, $renderer); 21904fd306cSNickeau return true; 220531e725cSNickeau 22104fd306cSNickeau case renderer_plugin_combo_xml::FORMAT: 222c3437056SNickeau $state = $data[PluginUtility::STATE]; 223c3437056SNickeau switch ($state) { 224c3437056SNickeau case DOKU_LEXER_ENTER: 22504fd306cSNickeau $level = $data[PluginUtility::ATTRIBUTES][HeadingTag::LEVEL]; 226c3437056SNickeau $renderer->doc .= "<h$level>"; 22704fd306cSNickeau return true; 228c3437056SNickeau case DOKU_LEXER_UNMATCHED: 229c3437056SNickeau $renderer->doc .= PluginUtility::renderUnmatchedXml($data); 23004fd306cSNickeau return true; 231c3437056SNickeau case DOKU_LEXER_EXIT: 23204fd306cSNickeau $level = $data[PluginUtility::ATTRIBUTES][HeadingTag::LEVEL]; 233c3437056SNickeau $renderer->doc .= "</h$level>"; 23404fd306cSNickeau return true; 235c3437056SNickeau 236c3437056SNickeau } 237531e725cSNickeau return false; 23804fd306cSNickeau default: 23904fd306cSNickeau return false; 24004fd306cSNickeau } 24104fd306cSNickeau 242531e725cSNickeau } 243531e725cSNickeau 244531e725cSNickeau /** 245531e725cSNickeau * @param $match 246531e725cSNickeau * @return int 247531e725cSNickeau */ 248531e725cSNickeau public 24904fd306cSNickeau function getLevelFromMatch($match): int 250531e725cSNickeau { 251531e725cSNickeau return 7 - strlen(trim($match)); 252531e725cSNickeau } 253531e725cSNickeau 254531e725cSNickeau 255531e725cSNickeau private 256531e725cSNickeau function enableWikiHeading($mode) 257531e725cSNickeau { 258531e725cSNickeau 259531e725cSNickeau 260531e725cSNickeau /** 261531e725cSNickeau * Basically all mode that are not `base` 262531e725cSNickeau * To not take the dokuwiki heading 263531e725cSNickeau */ 264531e725cSNickeau if (!(in_array($mode, ['base', 'header', 'table']))) { 265531e725cSNickeau return true; 266531e725cSNickeau } else { 26704fd306cSNickeau return SiteConfig::getConfValue(self::CONF_WIKI_HEADING_ENABLE, self::CONF_DEFAULT_WIKI_ENABLE_VALUE); 268531e725cSNickeau } 269531e725cSNickeau 270531e725cSNickeau 271531e725cSNickeau } 272531e725cSNickeau 273531e725cSNickeau 274531e725cSNickeau} 275