1<?php 2 3 4require_once(__DIR__ . "/../class/Analytics.php"); 5require_once(__DIR__ . "/../class/PluginUtility.php"); 6require_once(__DIR__ . "/../class/LinkUtility.php"); 7require_once(__DIR__ . "/../class/HtmlUtility.php"); 8 9use ComboStrap\Analytics; 10use ComboStrap\Call; 11use ComboStrap\LogUtility; 12use ComboStrap\PluginUtility; 13use ComboStrap\Tag; 14use ComboStrap\TagAttributes; 15 16if (!defined('DOKU_INC')) die(); 17 18/** 19 * 20 * A paragraph syntax 21 * 22 * This syntax component is used dynamically while parsing (at the {@link DOKU_LEXER_END} of {@link \dokuwiki\Extension\SyntaxPlugin::handle()} 23 * with the function {@link \syntax_plugin_combo_para::fromEolToParagraphUntilEndOfStack()} 24 * 25 * 26 * !!!!! 27 * 28 * Info: The `eol` call are temporary created with {@link \dokuwiki\Parsing\ParserMode\Eol} 29 * and transformed to `p_open` and `p_close` via {@link \dokuwiki\Parsing\Handler\Block::process()} 30 * 31 * Note: p_open call may appears when the {@link \ComboStrap\Syntax::getPType()} is set to `block` or `stack` 32 * and the next call is not a block or a stack 33 * 34 * !!!!! 35 * 36 * 37 * Note on Typography 38 * TODO: https://github.com/typekit/webfontloader 39 * https://www.dokuwiki.org/plugin:typography 40 * https://stackoverflow.design/email/base/typography/ 41 * http://kyleamathews.github.io/typography.js/ 42 * https://docs.gitbook.com/features/advanced-branding (Roboto, Roboto Slab, Open Sans, Source Sans Pro, Lato, Ubuntu, Raleway, Merriweather) 43 * http://themenectar.com/docs/salient/theme-options/typography/ (Doc) 44 * https://www.modularscale.com/ - see the size of font 45 * 46 * See the fonts on your computer 47 * https://wordmark.it/ 48 * 49 * What's a type ? Type terminology 50 * https://www.supremo.co.uk/typeterms/ 51 * 52 * https://theprotoolbox.com/browse/font-tools/ 53 */ 54class syntax_plugin_combo_para extends DokuWiki_Syntax_Plugin 55{ 56 57 const TAG = 'para'; 58 const COMPONENT = "combo_para"; 59 60 61 /** 62 * The component that have a `normal` {@link \dokuwiki\Extension\SyntaxPlugin::getPType()} 63 * are wrapped in a p element by {@link Block::process()} if they are not in a component with a `block` ptype 64 * 65 * This function makes it easy for the test 66 * to do it and gives a little bit of documentation 67 * on why there is a `p` in the test 68 * @param $html 69 * @return string the html wrapped in p 70 */ 71 public static function wrapInP($html) 72 { 73 return "<p>" . $html . "</p>"; 74 } 75 76 77 /** 78 * Syntax Type. 79 * 80 * Needs to return one of the mode types defined in $PARSER_MODES in parser.php 81 * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types 82 */ 83 function getType() 84 { 85 return 'paragraphs'; 86 } 87 88 /** 89 * How Dokuwiki will add P element 90 * 91 * * 'normal' - The plugin can be used inside paragraphs 92 * * 'block' - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs 93 * * 'stack' - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs 94 * 95 * @see DokuWiki_Syntax_Plugin::getPType() 96 */ 97 function getPType() 98 { 99 /** 100 * !important! 101 * The {@link \dokuwiki\Parsing\Handler\Block::process()} 102 * will then not create an extra paragraph after it encounters a block 103 */ 104 return 'block'; 105 } 106 107 /** 108 * @return array 109 * Allow which kind of plugin inside 110 * 111 * No one of array('container', 'baseonly', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs') 112 * because we manage self the content and we call self the parser 113 */ 114 function getAllowedTypes() 115 { 116 /** 117 * Not needed as we don't have any {@link syntax_plugin_combo_para::connectTo()} 118 */ 119 return array(); 120 } 121 122 123 /** 124 * @see Doku_Parser_Mode::getSort() 125 * The mode with the lowest sort number will win out 126 * 127 */ 128 function getSort() 129 { 130 /** 131 * Not really needed as we don't have any {@link syntax_plugin_combo_para::connectTo()} 132 * 133 * Note: if we start to use it should be less than 370 134 * Ie Less than {@link \dokuwiki\Parsing\ParserMode\Eol::getSort()} 135 */ 136 return 369; 137 } 138 139 140 function connectTo($mode) 141 { 142 143 /** 144 * No need to connect 145 * This syntax plugin is added dynamically with the {@link Tag::processEolToEndStack()} 146 * function 147 */ 148 149 } 150 151 152 /** 153 * The handler for an internal link 154 * based on `internallink` in {@link Doku_Handler} 155 * The handler call the good renderer in {@link Doku_Renderer_xhtml} with 156 * the parameters (ie for instance internallink) 157 * @param string $match 158 * @param int $state 159 * @param int $pos 160 * @param Doku_Handler $handler 161 * @return array|bool 162 */ 163 function handle($match, $state, $pos, Doku_Handler $handler) 164 { 165 166 /** 167 * No need to handle, 168 * there is no {@link syntax_plugin_combo_para::connectTo() connection} 169 */ 170 return true; 171 172 173 } 174 175 /** 176 * Render the output 177 * @param string $format 178 * @param Doku_Renderer $renderer 179 * @param array $data - what the function handle() return'ed 180 * @return boolean - rendered correctly? (however, returned value is not used at the moment) 181 * @see DokuWiki_Syntax_Plugin::render() 182 * 183 * 184 */ 185 function render($format, Doku_Renderer $renderer, $data) 186 { 187 // The data 188 switch ($format) { 189 case 'xhtml': 190 191 /** @var Doku_Renderer_xhtml $renderer */ 192 $state = $data[PluginUtility::STATE]; 193 194 switch ($state) { 195 case DOKU_LEXER_ENTER: 196 $attributes = $data[PluginUtility::ATTRIBUTES]; 197 $tagAttributes = TagAttributes::createFromCallStackArray($attributes); 198 if ($tagAttributes->hasComponentAttribute(TagAttributes::TYPE_KEY)) { 199 $class = $tagAttributes->getType(); 200 $tagAttributes->addClassName($class); 201 } 202 $renderer->doc .= $tagAttributes->toHtmlEnterTag("p"); 203 break; 204 case DOKU_LEXER_SPECIAL: 205 $attributes = $data[PluginUtility::ATTRIBUTES]; 206 $tagAttributes = TagAttributes::createFromCallStackArray($attributes); 207 $renderer->doc .= $tagAttributes->toHtmlEnterTag("p"); 208 $renderer->doc .= "</p>"; 209 break; 210 case DOKU_LEXER_EXIT: 211 $renderer->doc .= "</p>"; 212 break; 213 } 214 return true; 215 216 case 'metadata': 217 218 /** @var Doku_Renderer_metadata $renderer */ 219 220 221 return true; 222 223 224 case renderer_plugin_combo_analytics::RENDERER_FORMAT: 225 226 /** 227 * @var renderer_plugin_combo_analytics $renderer 228 */ 229 return true; 230 231 } 232 // unsupported $mode 233 return false; 234 } 235 236 /** 237 * 238 * Transform EOL into paragraph 239 * between the {@link CallStack::getActualCall()} and the {@link CallStack::isPointerAtEnd()} 240 * of the stack 241 * 242 * Info: Basically, you get an new paragraph with a blank line or `\\` : https://www.dokuwiki.org/faq:newlines 243 * 244 * It replaces the {@link \dokuwiki\Parsing\Handler\Block::process() eol to paragraph Dokuwiki process} 245 * that takes place at the end of parsing process on the whole stack 246 * 247 * Info: The `eol` call are temporary created with {@link \dokuwiki\Parsing\ParserMode\Eol} 248 * and transformed to `p_open` and `p_close` via {@link \dokuwiki\Parsing\Handler\Block::process()} 249 * 250 * @param \ComboStrap\CallStack $callstack 251 * @param array $attributes - the attributes passed to the paragraph 252 */ 253 public static function fromEolToParagraphUntilEndOfStack(&$callstack, $attributes) 254 { 255 256 if (!is_array($attributes)) { 257 LogUtility::msg("The passed attributes array ($attributes) for the creation of the paragraph is not an array", LogUtility::LVL_MSG_ERROR); 258 $attributes = []; 259 } 260 261 /** 262 * The syntax plugin that implements the paragraph 263 * ie {@link \syntax_plugin_combo_para} 264 * We will transform the eol with a call to this syntax plugin 265 * to create the paragraph 266 */ 267 $paragraphComponent = \syntax_plugin_combo_para::COMPONENT; 268 $paragraphTag = \syntax_plugin_combo_para::TAG; 269 270 /** 271 * The running variables 272 */ 273 $paragraphIsOpen = false; // A pointer to see if the paragraph is open 274 while ($actualCall = $callstack->next()) { 275 276 /** 277 * end of line is not always present 278 * because the pattern is eating it 279 * Example (list_open) 280 */ 281 if ($paragraphIsOpen && $actualCall->getTagName() !== "eol") { 282 if ($actualCall->getDisplay() == Call::BlOCK_DISPLAY) { 283 $paragraphIsOpen = false; 284 $callstack->insertBefore( 285 Call::createComboCall( 286 $paragraphTag, 287 DOKU_LEXER_EXIT, 288 $attributes 289 ) 290 ); 291 } 292 } 293 294 if ($actualCall->getTagName() === "eol") { 295 296 /** 297 * Next Call that is not the empty string 298 * Because Empty string would create an empty paragraph 299 * 300 * Start at 1 because we may not do 301 * a loop if we are at the end, the next call 302 * will return false 303 */ 304 $i = 1; 305 while ($nextCall = $callstack->next()) { 306 if (!( 307 trim($nextCall->getCapturedContent()) == "" && 308 $nextCall->isTextCall() 309 )) { 310 break; 311 } 312 $i++; 313 } 314 while ($i > 0) { // go back 315 $i--; 316 $callstack->previous(); 317 } 318 if ($nextCall === false) { 319 $nextDisplay = "last"; 320 $nextCall = null; 321 } else { 322 $nextDisplay = $nextCall->getDisplay(); 323 } 324 325 326 /** 327 * Processing 328 */ 329 if (!$paragraphIsOpen) { 330 331 switch ($nextDisplay) { 332 case Call::BlOCK_DISPLAY: 333 case "last": 334 $callstack->deleteActualCallAndPrevious(); 335 break; 336 case Call::INLINE_DISPLAY: 337 $paragraphIsOpen = true; 338 $actualCall->updateToPluginComponent( 339 $paragraphComponent, 340 DOKU_LEXER_ENTER, 341 $attributes 342 ); 343 break; 344 case "eol": 345 /** 346 * Empty line 347 */ 348 $actualCall->updateToPluginComponent( 349 $paragraphComponent, 350 DOKU_LEXER_ENTER, 351 $attributes 352 ); 353 $nextCall->updateToPluginComponent( 354 $paragraphComponent, 355 DOKU_LEXER_EXIT 356 ); 357 $callstack->next(); 358 break; 359 default: 360 LogUtility::msg("The eol action for the combination enter / (" . $nextDisplay . ") of the call ( $nextCall ) was not implemented", LogUtility::LVL_MSG_ERROR); 361 break; 362 } 363 } else { 364 /** 365 * Paragraph is open 366 */ 367 switch ($nextDisplay) { 368 case "eol": 369 /** 370 * Empty line 371 */ 372 $actualCall->updateToPluginComponent( 373 $paragraphComponent, 374 DOKU_LEXER_EXIT 375 ); 376 $nextCall->updateToPluginComponent( 377 $paragraphComponent, 378 DOKU_LEXER_ENTER, 379 $attributes 380 ); 381 $callstack->next(); 382 break; 383 case Call::INLINE_DISPLAY: 384 // A space 385 $actualCall->updateEolToSpace(); 386 break; 387 case Call::BlOCK_DISPLAY: 388 case "last"; 389 $actualCall->updateToPluginComponent( 390 $paragraphComponent, 391 DOKU_LEXER_EXIT 392 ); 393 $paragraphIsOpen = false; 394 break; 395 default: 396 LogUtility::msg("The display for a open paragraph (" . $nextDisplay . ") is not implemented", LogUtility::LVL_MSG_ERROR); 397 break; 398 } 399 } 400 401 } 402 } 403 404 // if the paragraph is open close it 405 if ($paragraphIsOpen) { 406 $callstack->insertBefore( 407 Call::createComboCall( 408 \syntax_plugin_combo_para::TAG, 409 DOKU_LEXER_EXIT 410 ) 411 ); 412 } 413 } 414 415} 416 417