1<?php 2 3 4use ComboStrap\CallStack; 5use ComboStrap\Html; 6use ComboStrap\PluginUtility; 7use ComboStrap\Prism; 8use ComboStrap\TagAttributes; 9 10require_once(__DIR__ . '/../ComboStrap/PluginUtility.php'); 11 12/** 13 * Overwrite {@link \dokuwiki\Parsing\ParserMode\Preformatted} 14 */ 15if (!defined('DOKU_INC')) die(); 16 17/** 18 * 19 * Preformatted shows a block of text as code via space at the beginning of the line 20 * 21 * It is the same as <a href="https://github.github.com/gfm/#indented-code-blocks">indented-code-blocks</a> 22 * but with 2 characters in place of 4 23 * 24 * This component is used to: 25 * * showcase preformatted as {@link \ComboStrap\Prism} component 26 * * disable preformatted mode via the function {@link syntax_plugin_combo_preformatted::disablePreformatted()} 27 * used in other HTML super set syntax component to disable this behavior 28 * 29 * It's also the original markdown specification 30 * 31 */ 32class syntax_plugin_combo_preformatted extends DokuWiki_Syntax_Plugin 33{ 34 35 const TAG = 'preformatted'; 36 37 38 const CONF_PREFORMATTED_ENABLE = "preformattedEnable"; 39 /** 40 * The content is not printed when the content of a preformatted block is empty 41 */ 42 const CONF_PREFORMATTED_EMPTY_CONTENT_NOT_PRINTED_ENABLE = "preformattedEmptyContentNotPrintedEnable"; 43 44 const HAS_EMPTY_CONTENT = "hasEmptyContent"; 45 46 /** 47 * Syntax Type. 48 * 49 * Needs to return one of the mode types defined in $PARSER_MODES in parser.php 50 * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types 51 * @see DokuWiki_Syntax_Plugin::getType() 52 */ 53 function getType() 54 { 55 return 'formatting'; 56 } 57 58 /** 59 * How DokuWiki will add P element 60 * 61 * * 'normal' - The plugin can be used inside paragraphs 62 * * 'block' - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs 63 * * 'stack' - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs 64 * 65 * @see DokuWiki_Syntax_Plugin::getPType() 66 */ 67 function getPType() 68 { 69 return 'block'; 70 } 71 72 73 /** 74 * @return array 75 * Allow which kind of plugin inside 76 * 77 * No one of array('baseonly','container', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs') 78 * because we manage self the content and we call self the parser 79 * 80 * Return an array of one or more of the mode types {@link $PARSER_MODES} in Parser.php 81 */ 82 function getAllowedTypes() 83 { 84 return array(); 85 } 86 87 function getSort() 88 { 89 /** 90 * Should be less than the preformatted mode 91 * which is 20 92 * From {@link \dokuwiki\Parsing\ParserMode\Preformatted::getSort()} 93 **/ 94 return 19; 95 } 96 97 98 function connectTo($mode) 99 { 100 101 if ($this->getConf(self::CONF_PREFORMATTED_ENABLE, 1)) { 102 103 /** 104 * From {@link \dokuwiki\Parsing\ParserMode\Preformatted} 105 */ 106 $patterns = array('\n (?![\*\-])', '\n\t(?![\*\-])'); 107 foreach ($patterns as $pattern) { 108 $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeFromTag($this->getPluginComponent())); 109 } 110 $this->Lexer->addPattern('\n ', PluginUtility::getModeFromTag($this->getPluginComponent())); 111 $this->Lexer->addPattern('\n\t', PluginUtility::getModeFromTag($this->getPluginComponent())); 112 113 } 114 115 } 116 117 118 function postConnect() 119 { 120 /** 121 * From {@link \dokuwiki\Parsing\ParserMode\Preformatted} 122 */ 123 $this->Lexer->addExitPattern('\n', PluginUtility::getModeFromTag($this->getPluginComponent())); 124 125 } 126 127 /** 128 * 129 * The handle function goal is to parse the matched syntax through the pattern function 130 * and to return the result for use in the renderer 131 * This result is always cached until the page is modified. 132 * @param string $match 133 * @param int $state 134 * @param int $pos - byte position in the original source file 135 * @param Doku_Handler $handler 136 * @return array|bool 137 * @see DokuWiki_Syntax_Plugin::handle() 138 * 139 */ 140 function handle($match, $state, $pos, Doku_Handler $handler) 141 { 142 143 switch ($state) { 144 case DOKU_LEXER_ENTER: 145 /** 146 * used at the {@link DOKU_LEXER_EXIT} state 147 * to add the {@link syntax_plugin_combo_preformatted::HAS_EMPTY_CONTENT} 148 * flag 149 */ 150 $attributes = []; 151 return array( 152 PluginUtility::STATE => $state, 153 PluginUtility::ATTRIBUTES => $attributes 154 ); 155 case DOKU_LEXER_MATCHED: 156 return array( 157 PluginUtility::STATE => $state 158 ); 159 case DOKU_LEXER_UNMATCHED: 160 return array( 161 PluginUtility::STATE => $state, 162 PluginUtility::PAYLOAD => $match 163 ); 164 case DOKU_LEXER_EXIT: 165 $callStack = CallStack::createFromHandler($handler); 166 $openingCall = $callStack->moveToPreviousCorrespondingOpeningCall(); 167 $text = ""; 168 while ($callStack->next()) { 169 $actualCall = $callStack->getActualCall(); 170 if ($actualCall->getState() == DOKU_LEXER_UNMATCHED && $actualCall->getTagName() == self::TAG) { 171 $text .= $actualCall->getPayload() . "\n"; 172 $callStack->deleteActualCallAndPrevious(); 173 } 174 } 175 if (trim($text) == "") { 176 $openingCall->addAttribute(self::HAS_EMPTY_CONTENT, true); 177 } 178 return array( 179 PluginUtility::STATE => $state, 180 PluginUtility::PAYLOAD => $text 181 ); 182 } 183 return array(); 184 185 } 186 187 /** 188 * Render the output 189 * @param string $format 190 * @param Doku_Renderer $renderer 191 * @param array $data - what the function handle() return'ed 192 * @return boolean - rendered correctly? (however, returned value is not used at the moment) 193 * @see DokuWiki_Syntax_Plugin::render() 194 * 195 * 196 */ 197 function render($format, Doku_Renderer $renderer, $data) 198 { 199 if ($format == "xhtml") { 200 /** 201 * @var Doku_Renderer_xhtml $renderer 202 */ 203 $state = $data[PluginUtility::STATE]; 204 $emptyContentShouldBeDeleted = $this->getConf(self::CONF_PREFORMATTED_EMPTY_CONTENT_NOT_PRINTED_ENABLE, 1); 205 switch ($state) { 206 case DOKU_LEXER_ENTER: 207 $tagAttributes = TagAttributes::createFromCallStackArray($data[PluginUtility::ATTRIBUTES], self::TAG); 208 $hasEmptyContent = $tagAttributes->getValueAndRemove(self::HAS_EMPTY_CONTENT,0); 209 if (!($hasEmptyContent && $emptyContentShouldBeDeleted)) { 210 Prism::htmlEnter($renderer, $this, $tagAttributes); 211 } 212 break; 213 case DOKU_LEXER_EXIT: 214 // Delete the eol at the beginning and end 215 // otherwise we get a big block 216 $text = trim($data[PluginUtility::PAYLOAD], "\n\r"); 217 if (!(trim($text) == "" && $emptyContentShouldBeDeleted)) { 218 219 $renderer->doc .= Html::encode($text); 220 Prism::htmlExit($renderer); 221 } 222 break; 223 } 224 } 225 return false; 226 } 227 228 /** 229 * Utility function to disable preformatted formatting 230 * in a HTML super set element 231 * 232 * @param $mode 233 * @return bool 234 */ 235 public 236 static function disablePreformatted($mode): bool 237 { 238 /** 239 * Disable {@link \dokuwiki\Parsing\ParserMode\Preformatted} 240 * and this syntax 241 */ 242 if ( 243 $mode == 'preformatted' 244 || 245 $mode == PluginUtility::getModeFromTag(syntax_plugin_combo_preformatted::TAG) 246 ) { 247 return false; 248 } else { 249 return true; 250 } 251 } 252 253} 254 255