1<?php 2/** 3 * DokuWiki Syntax Plugin Combostrap. 4 * 5 */ 6 7use ComboStrap\LogUtility; 8use ComboStrap\PluginUtility; 9use ComboStrap\Tag; 10use ComboStrap\TagAttributes; 11 12if (!defined('DOKU_INC')) { 13 die(); 14} 15 16require_once(__DIR__ . '/../ComboStrap/PluginUtility.php'); 17 18/** 19 * 20 * The name of the class must follow a pattern (don't change it) 21 * ie: 22 * syntax_plugin_PluginName_ComponentName 23 */ 24class syntax_plugin_combo_panel extends DokuWiki_Syntax_Plugin 25{ 26 27 const TAG = 'panel'; 28 const OLD_TAB_PANEL_TAG = 'tabpanel'; 29 const STATE = 'state'; 30 const SELECTED = 'selected'; 31 32 /** 33 * When the panel is alone in the edit due to the sectioning 34 */ 35 const CONTEXT_PREVIEW_ALONE = "preview_alone"; 36 const CONTEXT_PREVIEW_ALONE_ATTRIBUTES = array( 37 self::SELECTED => true, 38 TagAttributes::ID_KEY => "alone", 39 TagAttributes::TYPE_KEY => syntax_plugin_combo_tabs::ENCLOSED_TABS_TYPE 40 ); 41 42 const CONF_ENABLE_SECTION_EDITING = "panelEnableSectionEditing"; 43 44 /** 45 * @var int a counter to give an id to the accordion panel 46 */ 47 private $accordionCounter = 0; 48 private $tabCounter = 0; 49 50 private $sectionCounter = 0; 51 52 static function getSelectedValue(&$attributes) 53 { 54 55 if (isset($attributes[syntax_plugin_combo_panel::SELECTED])) { 56 /** 57 * Value may be false/true 58 */ 59 $value = $attributes[syntax_plugin_combo_panel::SELECTED]; 60 unset($attributes[syntax_plugin_combo_panel::SELECTED]); 61 return filter_var($value, FILTER_VALIDATE_BOOLEAN); 62 63 } 64 if (isset($attributes[TagAttributes::TYPE_KEY])) { 65 if (strtolower($attributes[TagAttributes::TYPE_KEY]) === "selected") { 66 return true; 67 } 68 } 69 return false; 70 71 } 72 73 private static function getTags() 74 { 75 return [self::TAG, self::OLD_TAB_PANEL_TAG]; 76 } 77 78 79 /** 80 * Syntax Type. 81 * 82 * Needs to return one of the mode types defined in $PARSER_MODES in parser.php 83 * @see DokuWiki_Syntax_Plugin::getType() 84 */ 85 function getType() 86 { 87 return 'container'; 88 } 89 90 /** 91 * @return array 92 * Allow which kind of plugin inside 93 * ************************ 94 * This function has no effect because {@link SyntaxPlugin::accepts()} is used 95 * ************************ 96 */ 97 public function getAllowedTypes() 98 { 99 return array('container', 'base', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs'); 100 } 101 102 public function accepts($mode) 103 { 104 /** 105 * header mode is disable to take over 106 * and replace it with {@link syntax_plugin_combo_heading} 107 */ 108 if ($mode == "header") { 109 return false; 110 } 111 return syntax_plugin_combo_preformatted::disablePreformatted($mode); 112 113 } 114 115 /** 116 * How Dokuwiki will add P element 117 * 118 * * 'normal' - The plugin can be used inside paragraphs 119 * * 'block' - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs 120 * * 'stack' - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs 121 * 122 * @see DokuWiki_Syntax_Plugin::getPType() 123 */ 124 function getPType() 125 { 126 return 'block'; 127 } 128 129 /** 130 * @see Doku_Parser_Mode::getSort() 131 * 132 * the mode with the lowest sort number will win out 133 * the container (parent) must then have a lower number than the child 134 */ 135 function getSort() 136 { 137 return 100; 138 } 139 140 /** 141 * Create a pattern that will called this plugin 142 * 143 * @param string $mode 144 * @see Doku_Parser_Mode::connectTo() 145 */ 146 function connectTo($mode) 147 { 148 149 /** 150 * Only inside tabs and accordion 151 * and tabpanels for history 152 */ 153 $show = in_array($mode, 154 [ 155 PluginUtility::getModeFromTag(syntax_plugin_combo_tabs::TAG), 156 PluginUtility::getModeFromTag(syntax_plugin_combo_accordion::TAG), 157 PluginUtility::getModeFromTag(syntax_plugin_combo_tabpanels::TAG) 158 ]); 159 160 /** 161 * In preview, the panel may be alone 162 * due to the section edit button 163 */ 164 if (!$show) { 165 global $ACT; 166 if ($ACT === "preview") { 167 $show = true; 168 } 169 } 170 171 /** 172 * Let's connect 173 */ 174 if ($show) { 175 foreach (self::getTags() as $tag) { 176 $pattern = PluginUtility::getContainerTagPattern($tag); 177 $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeFromTag($this->getPluginComponent())); 178 } 179 } 180 181 } 182 183 public function postConnect() 184 { 185 186 foreach (self::getTags() as $tag) { 187 $this->Lexer->addExitPattern('</' . $tag . '>', PluginUtility::getModeFromTag($this->getPluginComponent())); 188 } 189 190 } 191 192 /** 193 * 194 * The handle function goal is to parse the matched syntax through the pattern function 195 * and to return the result for use in the renderer 196 * This result is always cached until the page is modified. 197 * @param string $match 198 * @param int $state 199 * @param int $pos 200 * @param Doku_Handler $handler 201 * @return array|bool 202 * @throws Exception 203 * @see DokuWiki_Syntax_Plugin::handle() 204 * 205 */ 206 function handle($match, $state, $pos, Doku_Handler $handler) 207 { 208 209 switch ($state) { 210 211 case DOKU_LEXER_ENTER: 212 213 // tagname to check if this is the old tag name one 214 $tagName = PluginUtility::getTag($match); 215 216 // Context 217 $tagAttributes = PluginUtility::getTagAttributes($match); 218 $tag = new Tag(self::TAG, $tagAttributes, $state, $handler); 219 $parent = $tag->getParent(); 220 if ($parent != null) { 221 $context = $parent->getName(); 222 } else { 223 /** 224 * The panel may be alone in preview 225 * due to the section edit button 226 */ 227 global $ACT; 228 if ($ACT == "preview") { 229 $context = self::CONTEXT_PREVIEW_ALONE; 230 } else { 231 $context = $tagName; 232 } 233 } 234 235 236 if (!isset($tagAttributes["id"])) { 237 switch ($context) { 238 case syntax_plugin_combo_accordion::TAG: 239 $this->accordionCounter++; 240 $id = $context . $this->accordionCounter; 241 $tagAttributes["id"] = $id; 242 break; 243 case syntax_plugin_combo_tabs::TAG: 244 $this->tabCounter++; 245 $id = $context . $this->tabCounter; 246 $tagAttributes["id"] = $id; 247 break; 248 case self::CONTEXT_PREVIEW_ALONE: 249 $tagAttributes["id"] = "alone"; 250 break; 251 default: 252 LogUtility::msg("An id should be given for the context ($context)", LogUtility::LVL_MSG_ERROR, self::TAG); 253 254 } 255 } else { 256 257 $id = $tagAttributes["id"]; 258 } 259 260 /** 261 * Old deprecated syntax 262 */ 263 if ($tagName == self::OLD_TAB_PANEL_TAG) { 264 265 $context = self::OLD_TAB_PANEL_TAG; 266 267 $siblingTag = $parent->getPreviousSibling(); 268 if ($siblingTag != null) { 269 if ($siblingTag->getName() === syntax_plugin_combo_tabs::TAG) { 270 $descendants = $siblingTag->getDescendants(); 271 $tagAttributes[self::SELECTED] = false; 272 foreach ($descendants as $descendant) { 273 $descendantName = $descendant->getName(); 274 $descendantPanel = $descendant->getAttribute("panel"); 275 $descendantSelected = $descendant->getAttribute(self::SELECTED); 276 if ( 277 $descendantName == syntax_plugin_combo_tab::TAG 278 && $descendantPanel === $id 279 && $descendantSelected === "true") { 280 $tagAttributes[self::SELECTED] = true; 281 break; 282 } 283 } 284 } else { 285 LogUtility::msg("The direct element above a " . self::OLD_TAB_PANEL_TAG . " should be a `tabs` and not a `" . $siblingTag->getName() . "`", LogUtility::LVL_MSG_ERROR, "tabs"); 286 } 287 } 288 } 289 290 291 return array( 292 PluginUtility::STATE => $state, 293 PluginUtility::ATTRIBUTES => $tagAttributes, 294 PluginUtility::CONTEXT => $context, 295 PluginUtility::POSITION => $pos 296 ); 297 298 case DOKU_LEXER_UNMATCHED: 299 300 return PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler); 301 302 303 case DOKU_LEXER_EXIT : 304 305 $tag = new Tag(self::TAG, array(), $state, $handler); 306 $openingTag = $tag->getOpeningTag(); 307 308 /** 309 * Label Mandatory check 310 * (Only the presence of at minimum 1 and not the presence in each panel) 311 */ 312 if ($match != "</" . self::OLD_TAB_PANEL_TAG . ">") { 313 $labelTag = $openingTag->getDescendant(syntax_plugin_combo_label::TAG); 314 if (empty($labelTag)) { 315 LogUtility::msg("No label was found in the panel (number " . $this->tabCounter . "). They are mandatory to create tabs or accordion", LogUtility::LVL_MSG_ERROR, self::TAG); 316 } 317 } 318 319 /** 320 * Section 321 * +1 to go at the line 322 */ 323 $endPosition = $pos + strlen($match) + 1; 324 325 return 326 array( 327 PluginUtility::STATE => $state, 328 PluginUtility::CONTEXT => $openingTag->getContext(), 329 PluginUtility::POSITION => $endPosition 330 ); 331 332 333 } 334 335 return array(); 336 337 } 338 339 /** 340 * Render the output 341 * @param string $format 342 * @param Doku_Renderer $renderer 343 * @param array $data - what the function handle() return'ed 344 * @return boolean - rendered correctly? (however, returned value is not used at the moment) 345 * @see DokuWiki_Syntax_Plugin::render() 346 * 347 * 348 */ 349 function render($format, Doku_Renderer $renderer, $data) 350 { 351 352 if ($format == 'xhtml') { 353 354 /** @var Doku_Renderer_xhtml $renderer */ 355 $state = $data[PluginUtility::STATE]; 356 switch ($state) { 357 358 case DOKU_LEXER_ENTER : 359 360 /** 361 * Section (Edit button) 362 */ 363 if (PluginUtility::getConfValue(self::CONF_ENABLE_SECTION_EDITING, 1)) { 364 $position = $data[PluginUtility::POSITION]; 365 $this->sectionCounter++; 366 $name = "section" . self::TAG . $this->sectionCounter; 367 PluginUtility::startSection($renderer, $position, $name); 368 } 369 370 $context = $data[PluginUtility::CONTEXT]; 371 switch ($context) { 372 case syntax_plugin_combo_accordion::TAG: 373 // A panel in a accordion 374 $renderer->doc .= "<div class=\"card\">"; 375 break; 376 case self::OLD_TAB_PANEL_TAG: // Old deprecated syntax 377 case syntax_plugin_combo_tabs::TAG: // new syntax 378 379 $attributes = $data[PluginUtility::ATTRIBUTES]; 380 381 PluginUtility::addClass2Attributes("tab-pane fade", $attributes); 382 383 $selected = self::getSelectedValue($attributes); 384 if ($selected) { 385 PluginUtility::addClass2Attributes("show active", $attributes); 386 } 387 unset($attributes[self::SELECTED]); 388 389 $attributes["role"] = "tabpanel"; 390 $attributes["aria-labelledby"] = $attributes["id"] . "-tab"; 391 392 $renderer->doc .= '<div ' . PluginUtility::array2HTMLAttributesAsString($attributes) . '>'; 393 break; 394 case self::CONTEXT_PREVIEW_ALONE: 395 $aloneAttributes = syntax_plugin_combo_panel::CONTEXT_PREVIEW_ALONE_ATTRIBUTES; 396 $renderer->doc .= syntax_plugin_combo_tabs::openTabPanelsElement($aloneAttributes); 397 break; 398 default: 399 LogUtility::log2FrontEnd("The context ($context) is unknown in enter rendering", LogUtility::LVL_MSG_ERROR, self::TAG); 400 break; 401 } 402 403 break; 404 case DOKU_LEXER_EXIT : 405 $context = $data[PluginUtility::CONTEXT]; 406 switch ($context) { 407 case syntax_plugin_combo_accordion::TAG: 408 $renderer->doc .= '</div>' . DOKU_LF . "</div>" . DOKU_LF; 409 break; 410 case self::CONTEXT_PREVIEW_ALONE: 411 $aloneVariable = syntax_plugin_combo_panel::CONTEXT_PREVIEW_ALONE_ATTRIBUTES; 412 $renderer->doc .= syntax_plugin_combo_tabs::closeTabPanelsElement($aloneVariable); 413 break; 414 415 } 416 417 /** 418 * End section 419 */ 420 if (PluginUtility::getConfValue(self::CONF_ENABLE_SECTION_EDITING, 1)) { 421 $renderer->finishSectionEdit($data[PluginUtility::POSITION]); 422 } 423 424 /** 425 * End panel 426 */ 427 $renderer->doc .= "</div>" . DOKU_LF; 428 break; 429 case DOKU_LEXER_UNMATCHED: 430 $renderer->doc .= PluginUtility::renderUnmatched($data); 431 break; 432 } 433 return true; 434 } 435 return false; 436 } 437 438 439} 440