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