1<?php 2 3/** 4 * Box Plugin: Draw highlighting boxes around wiki markup 5 * 6 * Syntax: <box width% classes|title> 7 * width% width of the box, must use % unit 8 * classes one or more classes used to style the box, several predefined styles included in style.css 9 * title (optional) all text after '|' will be rendered above the main code text with a 10 * different style. 11 * 12 * Acknowledgements: 13 * Rounded corners based on snazzy borders by Stu Nicholls (http://www.cssplay.co.uk/boxes/snazzy) 14 * which is in turn based on nifty corners by Alessandro Fulciniti (http://pro.html.it/esempio/nifty/) 15 * 16 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 17 * @author Christopher Smith <chris@jalakai.co.uk> 18 */ 19class syntax_plugin_box extends DokuWiki_Syntax_Plugin 20{ 21 public $title_mode = false; 22 23 // the following are used in rendering and are set by xhtmlBoxOpen() 24 protected $xb_colours = ''; 25 protected $content_colours = ''; 26 protected $title_colours = ''; 27 28 public function getType() 29 { 30 return 'protected'; 31 } 32 33 public function getAllowedTypes() 34 { 35 return array('container','substition','protected','disabled','formatting','paragraphs'); 36 } 37 38 public function getPType() 39 { 40 return 'block'; 41 } 42 43 // must return a number lower than returned by native 'code' mode (200) 44 public function getSort() 45 { 46 return 195; 47 } 48 49 // override default accepts() method to allow nesting 50 // - ie, to get the plugin accepts its own entry syntax 51 public function accepts($mode) 52 { 53 if ($mode == substr(get_class($this), 7)) { 54 return true; 55 } 56 57 return parent::accepts($mode); 58 } 59 60 /** 61 * Connect pattern to lexer 62 */ 63 public function connectTo($mode) 64 { 65 $this->Lexer->addEntryPattern('<box>(?=.*?</box.*?>)', $mode, 'plugin_box'); 66 $this->Lexer->addEntryPattern('<box\s[^\r\n\|]*?>(?=.*?</box.*?>)', $mode, 'plugin_box'); 67 $this->Lexer->addEntryPattern('<box\|(?=[^\r\n]*?\>.*?</box.*?\>)', $mode, 'plugin_box'); 68 $this->Lexer->addEntryPattern('<box\s[^\r\n\|]*?\|(?=[^\r\n]*?>.*?</box.*?>)', $mode, 'plugin_box'); 69 } 70 71 public function postConnect() 72 { 73 $this->Lexer->addPattern('>', 'plugin_box'); 74 $this->Lexer->addExitPattern('</box.*?>', 'plugin_box'); 75 } 76 77 /** 78 * Handle the match 79 */ 80 public function handle($match, $state, $pos, Doku_Handler $handler) 81 { 82 83 switch ($state) { 84 case DOKU_LEXER_ENTER: 85 $data = $this->boxstyle(trim(substr($match, 4, -1))); 86 if (substr($match, -1) == '|') { 87 $this->title_mode = true; 88 return array('title_open',$data); 89 } else { 90 return array('box_open',$data); 91 } 92 93 case DOKU_LEXER_MATCHED: 94 if ($this->title_mode) { 95 $this->title_mode = false; 96 return array('box_open',''); 97 } else { 98 return array('data', $match); 99 } 100 101 case DOKU_LEXER_UNMATCHED: 102 $handler->_addCall('cdata', array($match), $pos); 103 return false; 104 105 case DOKU_LEXER_EXIT: 106 $data = trim(substr($match, 5, -1)); 107 $title = ($data && $data[0] == "|") ? substr($data, 1) : ''; 108 109 return array('box_close', $title); 110 } 111 return false; 112 } 113 114 /** 115 * Create output 116 */ 117 public function render($mode, Doku_Renderer $renderer, $indata) 118 { 119 120 if (empty($indata)) { 121 return false; 122 } 123 list($instr, $data) = $indata; 124 125 if ($mode == 'xhtml') { 126 switch ($instr) { 127 case 'title_open': 128 $this->title_mode = true; 129 $renderer->doc .= $this->xhtmlBoxOpen($data) . "<p class='box_title' {$this->title_colours}>"; 130 break; 131 132 case 'box_open': 133 if ($this->title_mode) { 134 $this->title_mode = false; 135 $renderer->doc .= "</p>\n<div class='box_content' {$this->content_colours}>"; 136 } else { 137 $renderer->doc .= $this->xhtmlBoxOpen($data) . 138 "<div class='box_content' {$this->content_colours}>"; 139 } 140 break; 141 142 case 'data': 143 $renderer->doc .= $renderer->_xmlEntities($data); 144 break; 145 146 case 'box_close': 147 $renderer->doc .= "</div>\n"; 148 149 if ($data) { 150 $renderer->doc .= "<p class='box_caption' {$this->title_colours}>" . 151 $renderer->_xmlEntities($data) . "</p>\n"; 152 } 153 $renderer->doc .= $this->xhtmlBoxClose(); 154 break; 155 } 156 157 return true; 158 } 159 return false; 160 } 161 162 protected function boxstyle($str) 163 { 164 if (!strlen($str)) { 165 return array(); 166 } 167 168 $styles = array(); 169 170 $tokens = preg_split('/\s+/', $str, 9); // limit is defensive 171 foreach ($tokens as $token) { 172 if (preg_match('/^\d*\.?\d+(%|px|em|ex|pt|cm|mm|pi|in)$/', $token)) { 173 $styles['width'] = $token; 174 continue; 175 } 176 177 if ( 178 preg_match('/^( 179 (\#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}))| #colorvalue 180 (rgb\(([0-9]{1,3}%?,){2}[0-9]{1,3}%?\)) #rgb triplet 181 )$/x', $token) 182 ) { 183 $styles['colour'][] = $token; 184 continue; 185 } 186 187 // restrict token (class names) characters to prevent any malicious data 188 if (preg_match('/[^A-Za-z0-9_-]/', $token)) { 189 continue; 190 } 191 $styles['class'] = (isset($styles['class']) ? $styles['class'] . ' ' : '') . $token; 192 } 193 if (!empty($styles['colour'])) { 194 $styles['colour'] = $this->boxColours($styles['colour']); 195 } 196 197 return $styles; 198 } 199 200 protected function boxColours($colours) 201 { 202 $triplets = array(); 203 204 // only need the first four colours 205 if (count($colours) > 4) { 206 $colours = array_slice($colours, 0, 4); 207 } 208 foreach ($colours as $colour) { 209 $triplet[] = $this->colourToTriplet($colour); 210 } 211 212 // there must be one colour to get here - the primary background 213 // calculate title background colour if not present 214 if (empty($triplet[1])) { 215 $triplet[1] = $triplet[0]; 216 } 217 218 // calculate outer background colour if not present 219 if (empty($triplet[2])) { 220 $triplet[2] = $triplet[0]; 221 } 222 223 // calculate border colour if not present 224 if (empty($triplet[3])) { 225 $triplet[3] = $triplet[0]; 226 } 227 228 // convert triplets back to style sheet colours 229 $style_colours['content_background'] = 'rgb(' . join(',', $triplet[0]) . ')'; 230 $style_colours['title_background'] = 'rgb(' . join(',', $triplet[1]) . ')'; 231 $style_colours['outer_background'] = 'rgb(' . join(',', $triplet[2]) . ')'; 232 $style_colours['borders'] = 'rgb(' . join(',', $triplet[3]) . ')'; 233 234 return $style_colours; 235 } 236 237 protected function colourToTriplet($colour) 238 { 239 if ($colour[0] == '#') { 240 if (strlen($colour) == 4) { 241 // format #FFF 242 return array( 243 hexdec($colour[1] . $colour[1]), 244 hexdec($colour[2] . $colour[2]), 245 hexdec($colour[3] . $colour[3]) 246 ); 247 } else { 248 // format #FFFFFF 249 return array( 250 hexdec(substr($colour, 1, 2)), 251 hexdec(substr($colour, 3, 2)), 252 hexdec(substr($colour, 5, 2)) 253 ); 254 } 255 } else { 256 // format rgb(x,y,z) 257 return explode(',', substr($colour, 4, -1)); 258 } 259 } 260 261 protected function xhtmlBoxOpen($styles) 262 { 263 $class = 'class="box' . (isset($styles['class']) ? ' ' . $styles['class'] : '') . '"'; 264 $style = isset($styles['width']) ? "width: {$styles['width']};" : ''; 265 266 if (isset($styles['colour'])) { 267 $colours = 'background-color: ' . $styles['colour']['outer_background'] . '; '; 268 $colours .= 'border-color: ' . $styles['colour']['borders'] . ';'; 269 270 $this->content_colours = 'style="background-color: ' . $styles['colour']['content_background'] . 271 '; border-color: ' . $styles['colour']['borders'] . '"'; 272 $this->title_colours = 'style="background-color: ' . $styles['colour']['title_background'] . ';"'; 273 } else { 274 $colours = ''; 275 276 $this->content_colours = ''; 277 $this->title_colours = ''; 278 } 279 280 if ($style || $colours) { 281 $style = ' style="' . $style . ' ' . $colours . '"'; 282 } 283 if ($colours) { 284 $colours = ' style="' . $colours . '"'; 285 } 286 287 $this->xb_colours = $colours; 288 289 $html = "<div $class$style>\n"; 290 $html .= " <b class='xtop'>" . 291 "<b class='xb1'$colours></b>" . 292 "<b class='xb2'$colours></b>" . 293 "<b class='xb3'$colours></b>" . 294 "<b class='xb4'$colours></b>" . 295 "</b>\n"; 296 $html .= " <div class='xbox'$colours>\n"; 297 298 return $html; 299 } 300 301 protected function xhtmlBoxClose() 302 { 303 304 $colours = $this->xb_colours; 305 306 $html = " </div>\n"; 307 $html .= " <b class='xbottom'>" . 308 "<b class='xb4'$colours></b>" . 309 "<b class='xb3'$colours></b>" . 310 "<b class='xb2'$colours></b>" . 311 "<b class='xb1'$colours></b>" . 312 "</b>\n"; 313 $html .= "</div>\n"; 314 315 return $html; 316 } 317} 318 319//Setup VIM: ex: et ts=4 enc=utf-8 : 320