1<?php 2 3 4// must be run within Dokuwiki 5use ComboStrap\Bootstrap; 6use ComboStrap\ColorRgb; 7use ComboStrap\ExceptionCompile; 8use ComboStrap\LogUtility; 9use ComboStrap\PluginUtility; 10use ComboStrap\Site; 11use ComboStrap\TagAttributes; 12use ComboStrap\XmlTagProcessing; 13 14require_once(__DIR__ . '/../vendor/autoload.php'); 15 16/** 17 * Class syntax_plugin_combo_badge 18 * Implementation of a badge 19 * called an alert in <a href="https://getbootstrap.com/docs/4.0/components/badge/">bootstrap</a> 20 */ 21class syntax_plugin_combo_badge extends DokuWiki_Syntax_Plugin 22{ 23 24 const TAG = "badge"; 25 26 const CONF_DEFAULT_ATTRIBUTES_KEY = 'defaultBadgeAttributes'; 27 28 const ATTRIBUTE_TYPE = "type"; 29 const ATTRIBUTE_ROUNDED = "rounded"; 30 31 /** 32 * Syntax Type. 33 * 34 * Needs to return one of the mode types defined in $PARSER_MODES in parser.php 35 * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types 36 * @see DokuWiki_Syntax_Plugin::getType() 37 */ 38 function getType() 39 { 40 return 'formatting'; 41 } 42 43 /** 44 * How Dokuwiki will add P element 45 * 46 * * 'normal' - Inline 47 * * 'block' - Block (p are not created inside) 48 * * 'stack' - Block (p can be created inside) 49 * 50 * @see DokuWiki_Syntax_Plugin::getPType() 51 * @see https://www.dokuwiki.org/devel:syntax_plugins#ptype 52 */ 53 function getPType(): string 54 { 55 return 'normal'; 56 } 57 58 /** 59 * @return array 60 * Allow which kind of plugin inside 61 * 62 * No one of array('baseonly','container', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs') 63 * because we manage self the content and we call self the parser 64 * 65 * Return an array of one or more of the mode types {@link $PARSER_MODES} in Parser.php 66 */ 67 function getAllowedTypes() 68 { 69 return array('container', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs'); 70 } 71 72 /** 73 * @see Doku_Parser_Mode::getSort() 74 * the mode with the lowest sort number will win out 75 */ 76 function getSort() 77 { 78 return 201; 79 } 80 81 82 function connectTo($mode) 83 { 84 85 $pattern = XmlTagProcessing::getContainerTagPattern(self::TAG); 86 $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeFromTag($this->getPluginComponent())); 87 88 } 89 90 function postConnect() 91 { 92 93 $this->Lexer->addExitPattern('</' . self::TAG . '>', PluginUtility::getModeFromTag($this->getPluginComponent())); 94 95 } 96 97 /** 98 * 99 * The handle function goal is to parse the matched syntax through the pattern function 100 * and to return the result for use in the renderer 101 * This result is always cached until the page is modified. 102 * @param string $match 103 * @param int $state 104 * @param int $pos - byte position in the original source file 105 * @param Doku_Handler $handler 106 * @return array|bool 107 * @see DokuWiki_Syntax_Plugin::handle() 108 * 109 */ 110 function handle($match, $state, $pos, Doku_Handler $handler) 111 { 112 113 switch ($state) { 114 115 case DOKU_LEXER_ENTER : 116 117 $defaultConfValue = PluginUtility::parseAttributes($this->getConf(self::CONF_DEFAULT_ATTRIBUTES_KEY)); 118 119 $knownTypes = ["primary", "secondary", "success", "danger", "warning", "info", "tip", "light", "dark"]; 120 $tagAttributes = TagAttributes::createFromTagMatch($match, $defaultConfValue, $knownTypes); 121 122 123 /** 124 * Brand and tip colors 125 */ 126 $tagAttributes->addClassName("badge"); 127 $type = $tagAttributes->getType(); 128 $color = null; 129 switch ($type) { 130 case "tip": 131 $color = ColorRgb::TIP_COLOR; 132 break; 133 case ColorRgb::PRIMARY_VALUE: 134 $color = Site::getPrimaryColorValue(); 135 break; 136 case ColorRgb::SECONDARY_VALUE: 137 $color = Site::getSecondaryColorValue(); 138 break; 139 default: 140 } 141 $colorObject = null; 142 if ($color !== null) { 143 try { 144 $colorObject = ColorRgb::createFromString($color); 145 } catch (ExceptionCompile $e) { 146 LogUtility::msg("The color value ($color) for the badge type ($type) is not valid. Error: {$e->getMessage()}"); 147 } 148 } 149 if ($colorObject !== null) { 150 /** 151 * https://getbootstrap.com/docs/5.0/components/alerts/ 152 * $alert-bg-scale: -80%; 153 * $alert-border-scale: -70%; 154 * $alert-color-scale: 40%; 155 */ 156 $backgroundColor = $tagAttributes->getValue(ColorRgb::BACKGROUND_COLOR); 157 if ($backgroundColor === null) { 158 try { 159 $backgroundColor = $colorObject 160 ->scale(-80) 161 ->toHsl() 162 ->setLightness(80) 163 ->toRgb() 164 ->toCssValue(); 165 } catch (ExceptionCompile $e) { 166 LogUtility::msg("Error while trying to set the lightness for the badge background color"); 167 $backgroundColor = $colorObject 168 ->scale(-80) 169 ->toCssValue(); 170 } 171 $tagAttributes->addStyleDeclarationIfNotSet(ColorRgb::BACKGROUND_COLOR, $backgroundColor); 172 } 173 if (!$tagAttributes->hasComponentAttribute(ColorRgb::BORDER_COLOR)) { 174 $borderColor = $colorObject->scale(-70)->toCssValue(); 175 $tagAttributes->addStyleDeclarationIfNotSet(ColorRgb::BORDER_COLOR, $borderColor); 176 } 177 if (!$tagAttributes->hasComponentAttribute(ColorRgb::COLOR)) { 178 try { 179 $textColor = $colorObject 180 ->scale(40) 181 ->toMinimumContrastRatio($backgroundColor) 182 ->toCssValue(); 183 } catch (ExceptionCompile $e) { 184 LogUtility::msg("Error while scaling the text color ($color) for the badge type ($type). Error: {$e->getMessage()}"); 185 $textColor = $colorObject 186 ->scale(40) 187 ->toCssValue(); 188 } 189 $tagAttributes->addStyleDeclarationIfNotSet(ColorRgb::COLOR, $textColor); 190 } 191 } else { 192 $tagAttributes->addClassName("alert-" . $type); 193 } 194 195 $rounded = $tagAttributes->getValueAndRemove(self::ATTRIBUTE_ROUNDED); 196 if (!empty($rounded)) { 197 $badgePillClass = "badge-pill"; 198 if (Bootstrap::getBootStrapMajorVersion() == Bootstrap::BootStrapFiveMajorVersion) { 199 // https://getbootstrap.com/docs/5.0/migration/#badges-1 200 $badgePillClass = "rounded-pill"; 201 } 202 $tagAttributes->addClassName($badgePillClass); 203 } 204 205 return array( 206 PluginUtility::STATE => $state, 207 PluginUtility::ATTRIBUTES => $tagAttributes->toCallStackArray() 208 ); 209 210 case DOKU_LEXER_UNMATCHED : 211 return PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler); 212 213 case DOKU_LEXER_EXIT : 214 215 // Important otherwise we don't get an exit in the render 216 return array( 217 PluginUtility::STATE => $state 218 ); 219 220 221 } 222 return array(); 223 224 } 225 226 /** 227 * Render the output 228 * @param string $format 229 * @param Doku_Renderer $renderer 230 * @param array $data - what the function handle() return'ed 231 * @return boolean - rendered correctly? (however, returned value is not used at the moment) 232 * @see DokuWiki_Syntax_Plugin::render() 233 * 234 * 235 */ 236 function render($format, Doku_Renderer $renderer, $data) 237 { 238 if ($format == 'xhtml') { 239 240 /** @var Doku_Renderer_xhtml $renderer */ 241 $state = $data[PluginUtility::STATE]; 242 switch ($state) { 243 244 case DOKU_LEXER_ENTER : 245 246 PluginUtility::getSnippetManager()->attachCssInternalStyleSheet(self::TAG); 247 248 $attributes = $data[PluginUtility::ATTRIBUTES]; 249 $tagAttributes = TagAttributes::createFromCallStackArray($attributes, self::TAG); 250 // badge on boostrap does not allow 251 $tagAttributes->addStyleDeclarationIfNotSet("white-space", "normal"); 252 $renderer->doc .= $tagAttributes->toHtmlEnterTag("span") . DOKU_LF; 253 break; 254 255 case DOKU_LEXER_UNMATCHED : 256 $renderer->doc .= PluginUtility::renderUnmatched($data); 257 break; 258 259 case DOKU_LEXER_EXIT : 260 $renderer->doc .= "</span>"; 261 break; 262 263 } 264 return true; 265 } 266 267 // unsupported $mode 268 return false; 269 } 270 271 272} 273 274