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