1<?php 2/** 3 * Bootstrap Wrapper Plugin 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com> 7 * @copyright (C) 2015-2019, Giuseppe Di Terlizzi 8 */ 9// must be run within Dokuwiki 10if (!defined('DOKU_INC')) { 11 die(); 12} 13 14if (!defined('DOKU_PLUGIN')) { 15 define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/'); 16} 17 18class syntax_plugin_bootswrapper_bootstrap extends DokuWiki_Syntax_Plugin 19{ 20 21 public $p_type = 'stack'; 22 public $pattern_start = '<BOOTSTRAP.+?>'; 23 public $pattern_end = '</BOOTSTRAP>'; 24 public $template_start = '<div class="%s">'; 25 public $template_content = '%s'; 26 public $template_end = '</div>'; 27 public $header_pattern = '[ \t]*={2,}[^\n]+={2,}[ \t]*(?=\n)'; 28 public $tag_attributes = array(); 29 public $tag_name = null; 30 31 // HTML core/global attribute 32 public $core_attributes = array( 33 'id' => array( 34 'type' => 'string', 35 'values' => null, 36 'required' => false, 37 'default' => null), 38 'class' => array( 39 'type' => 'string', 40 'values' => null, 41 'required' => false, 42 'default' => null), 43 'style' => array( 44 'type' => 'string', 45 'values' => null, 46 'required' => false, 47 'default' => null), 48 'title' => array( 49 'type' => 'string', 50 'values' => null, 51 'required' => false, 52 'default' => null), 53 'lang' => array( 54 'type' => 'string', 55 'values' => null, 56 'required' => false, 57 'default' => null), 58 'dir' => array( 59 'type' => 'string', 60 'values' => array('ltr', 'rtl'), 61 'required' => false, 62 'default' => null), 63 ); 64 65 /** 66 * Check default and user attributes 67 * 68 * @param array $attributes 69 */ 70 protected function checkAttributes($attributes = array()) 71 { 72 73 global $ACT; 74 75 $default_attributes = array(); 76 $merged_attributes = array(); 77 $checked_attributes = array(); 78 79 if ($ACT == 'preview') { 80 $msg_title = '<strong>Bootstrap Wrapper Plugin - ' . (ucfirst(str_replace('syntax_plugin_bootswrapper_', 81 '', get_class($this)))) . '</strong>'; 82 } 83 84 $tag_attributes = array_merge($this->core_attributes, $this->tag_attributes); 85 86 // Save the default values of attributes 87 foreach ($tag_attributes as $attribute => $item) { 88 $default_attributes[$attribute] = $item['default']; 89 } 90 91 foreach ($attributes as $name => $value) { 92 93 if (!isset($tag_attributes[$name])) { 94 if ($ACT == 'preview') { 95 msg("$msg_title Unknown attribute <code>$name</code>", -1); 96 } 97 continue; 98 } 99 100 $item = $tag_attributes[$name]; 101 102 $required = isset($item['required']) ? $item['required'] : false; 103 $values = isset($item['values']) ? $item['values'] : null; 104 $default = isset($item['default']) ? $item['default'] : null; 105 106 // Normalize boolean value 107 if ($item['type'] == 'boolean') { 108 switch ($value) { 109 case 'false': 110 case 'FALSE': 111 $value = false; 112 break; 113 case 'true': 114 case 'TRUE': 115 $value = true; 116 break; 117 } 118 } 119 120 if ($name == 'class') { 121 $value = explode(' ', $value); 122 } 123 124 $checked_attributes[$name] = $value; 125 126 // Set the default value when the user-value is empty 127 if ($required && empty($value)) { 128 $checked_attributes[$name] = $default; 129 130 // Check if the user attribute have a valid range values (single value) 131 } elseif ($item['type'] !== 'multiple' && is_array($values) && !in_array($value, $values)) { 132 if ($ACT == 'preview') { 133 msg("$msg_title Invalid value (<code>$value</code>) for <code>$name</code> attribute. It will apply the default value <code>$default</code>", 2); 134 } 135 136 $checked_attributes[$name] = $default; 137 138 // Check if the user attribute have a valid range values (multiple values) 139 } elseif ($item['type'] == 'multiple') { 140 141 $multitple_values = explode(' ', $value); 142 $check = 0; 143 144 foreach ($multitple_values as $single_value) { 145 if (!in_array($single_value, $values)) { 146 $check = 1; 147 } 148 } 149 150 if ($check) { 151 if ($ACT == 'preview') { 152 msg("$msg_title Invalid value (<code>$value</code>) for <code>$name</code> attribute. It will apply the default value <code>$default</code>", 2); 153 } 154 $checked_attributes[$name] = $default; 155 } 156 } 157 } 158 159 // Merge attributes (default + user) 160 $merged_attributes = array_merge($default_attributes, $checked_attributes); 161 162 // Remove empty attributes 163 foreach ($merged_attributes as $attribute => $value) { 164 if (empty($value)) { 165 unset($merged_attributes[$attribute]); 166 } 167 } 168 169 // Uncomment for debug 170 // msg("$msg_title " . print_r($merged_attributes, 1)); 171 172 return $merged_attributes; 173 174 } 175 176 public function getType() 177 { 178 return 'formatting'; 179 } 180 181 public function getAllowedTypes() 182 { 183 return array('container', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs'); 184 } 185 186 public function getPType() 187 { 188 return $this->p_type; 189 } 190 191 public function getSort() 192 { 193 return 195; 194 } 195 196 public function connectTo($mode) 197 { 198 $this->Lexer->addEntryPattern($this->pattern_start, $mode, 'plugin_bootswrapper_' . $this->getPluginComponent()); 199 } 200 201 public function postConnect() 202 { 203 $this->Lexer->addExitPattern($this->pattern_end, 'plugin_bootswrapper_' . $this->getPluginComponent()); 204 $this->Lexer->addPattern($this->header_pattern, 'plugin_bootswrapper_' . $this->getPluginComponent()); 205 } 206 207 public function handle($match, $state, $pos, Doku_Handler $handler) 208 { 209 210 switch ($state) { 211 case DOKU_LEXER_MATCHED: 212 $title = trim($match); 213 $level = 7 - strspn($title, '='); 214 if ($level < 1) { 215 $level = 1; 216 } 217 218 $title = trim($title, '='); 219 $title = trim($title); 220 221 $handler->_addCall('header', array($title, $level, $pos), $pos); 222 223 break; 224 225 case DOKU_LEXER_ENTER: 226 $attributes = array(); 227 $xml = simplexml_load_string(str_replace('>', '/>', $match)); 228 229 if (!is_object($xml)) { 230 $xml = simplexml_load_string('<foo />'); 231 232 global $ACT; 233 234 if ($ACT == 'preview') { 235 msg('<strong>Bootstrap Wrapper</strong> - Malformed tag (<code>' . hsc($match) . '</code>). Please check your code!', -1); 236 } 237 } 238 239 $tag = $xml->getName(); 240 241 foreach ($xml->attributes() as $key => $value) { 242 $attributes[$key] = (string) $value; 243 } 244 245 if ($tag == strtolower($tag)) { 246 $is_block = false; 247 } 248 249 if ($tag == strtoupper($tag)) { 250 $is_block = true; 251 } 252 253 $checked_attributes = $this->checkAttributes($attributes); 254 255 return array($state, $match, $pos, $checked_attributes, $is_block); 256 257 case DOKU_LEXER_UNMATCHED: 258 $handler->_addCall('cdata', array($match), $pos, null); 259 break; 260 261 case DOKU_LEXER_EXIT: 262 return array($state, $match, $pos, null); 263 } 264 265 return array(); 266 } 267 268 public function render($mode, Doku_Renderer $renderer, $data) 269 { 270 271 if (empty($data)) { 272 return false; 273 } 274 275 if ($mode !== 'xhtml') { 276 return false; 277 } 278 279 /** @var Doku_Renderer_xhtml $renderer */ 280 list($state, $match) = $data; 281 282 if ($state == DOKU_LEXER_ENTER) { 283 $markup = $this->template_start; 284 $renderer->doc .= $markup; 285 return true; 286 } 287 288 if ($state == DOKU_LEXER_EXIT) { 289 $renderer->doc .= $this->template_end; 290 return true; 291 } 292 293 return true; 294 } 295 296 protected function mergeCoreAttributes($attributes) 297 { 298 299 $core_attributes = array(); 300 301 foreach (array_keys($this->core_attributes) as $attribute) { 302 if (isset($attributes[$attribute])) { 303 $core_attributes[$attribute] = $attributes[$attribute]; 304 } 305 } 306 307 return $core_attributes; 308 } 309 310 protected function buildAttributes($attributes, $override_attributes = array()) 311 { 312 313 $attributes = array_merge_recursive($attributes, $override_attributes); 314 $html_attributes = array(); 315 316 foreach ($attributes as $attribute => $value) { 317 if ($attribute == 'class') { 318 $value = trim(implode(' ', array_unique($value))); 319 } 320 321 if ($attribute == 'style') { 322 $tmp = ''; 323 foreach ($value as $property => $val) { 324 $tmp .= "$property:$val"; 325 } 326 $value = $tmp; 327 } 328 329 if ($value) { 330 $html_attributes[] = "$attribute=\"$value\""; 331 } 332 } 333 334 return implode(' ', $html_attributes); 335 336 } 337 338} 339