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 * padding can be defined with each direction or as composite 9 * margin can be defined with each direction or as composite 10 * title (optional) all text after '|' will be rendered above the main code text with a 11 * different style. 12 * 13 * Acknowledgements: 14 * Rounded corners based on snazzy borders by Stu Nicholls (http://www.cssplay.co.uk/boxes/snazzy) 15 * which is in turn based on nifty corners by Alessandro Fulciniti (http://pro.html.it/esempio/nifty/) 16 * 17 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 18 * @author Christopher Smith <chris@jalakai.co.uk> 19 * @author i-net software <tools@inetsoftware.de> 20 */ 21 22if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/'); 23if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); 24require_once(DOKU_PLUGIN.'syntax.php'); 25 26/** 27 * All DokuWiki plugins to extend the parser/rendering mechanism 28 * need to inherit from this class 29 */ 30class syntax_plugin_box2 extends DokuWiki_Syntax_Plugin { 31 32 var $title_mode = array(); 33 var $box_content = array(); 34 35 // the following are used in rendering and are set by _xhtml_boxopen() 36 var $_xb_colours = array(); 37 var $_content_colours = ''; 38 var $_title_colours = ''; 39 40 function getType(){ return 'protected';} 41 function getAllowedTypes() { return array('container','substition','protected','disabled','formatting','paragraphs'); } 42 function getPType(){ return 'block';} 43 44 // must return a number lower than returned by native 'code' mode (200) 45 function getSort(){ return 195; } 46 47 // override default accepts() method to allow nesting 48 // - ie, to get the plugin accepts its own entry syntax 49 function accepts($mode) { 50 if ($mode == substr(get_class($this), 7)) return true; 51 52 return parent::accepts($mode); 53 } 54 55 /** 56 * Connect pattern to lexer 57 */ 58 function connectTo($mode) { 59 $this->Lexer->addEntryPattern('<box>(?=.*?</box.*?>)',$mode,'plugin_box2'); 60 $this->Lexer->addEntryPattern('<box\s[^\r\n\|]*?>(?=.*?</box.*?>)',$mode,'plugin_box2'); 61 $this->Lexer->addEntryPattern('<box\|(?=[^\r\n]*?\>.*?</box.*?\>)',$mode,'plugin_box2'); 62 $this->Lexer->addEntryPattern('<box\s[^\r\n\|]*?\|(?=[^\r\n]*?>.*?</box.*?>)',$mode,'plugin_box2'); 63 } 64 65 function postConnect() { 66 $this->Lexer->addPattern('>', 'plugin_box2'); 67 $this->Lexer->addExitPattern('</box.*?>', 'plugin_box2'); 68 } 69 70 private function isTitleMode() { 71 return $this->title_mode[count($this->title_mode)-1]; 72 } 73 74 private function isBoxContent() { 75 return $this->box_content[count($this->box_content)-1]; 76 } 77 78 /** 79 * Handle the match 80 */ 81 function handle($match, $state, $pos, Doku_Handler $handler){ 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, $pos); 89 } else { 90 return array('box_open',$data, $pos); 91 } 92 93 case DOKU_LEXER_MATCHED: 94 if ($this->isTitleMode()) { 95 array_pop( $this->title_mode ); 96 return array('box_open','', $pos); 97 } else { 98 return array('data', $match, $pos); 99 } 100 101 case DOKU_LEXER_UNMATCHED: 102 if ($this->isTitleMode()) { 103 return array('title', $match, $pos); 104 } 105 106 $handler->_addCall('cdata',array($match), $pos); 107 return false; 108 case DOKU_LEXER_EXIT: 109 $pos += strlen($match); // has to be done becvause the ending tag comes after $pos 110 $data = trim(substr($match, 5, -1)); 111 $title = ($data && $data[0] == "|") ? substr($data,1) : ''; 112 113 return array('box_close', $title, $pos); 114 115 } 116 return false; 117 } 118 119 /** 120 * Create output 121 */ 122 function render($mode, Doku_Renderer $renderer, $indata) { 123 global $ID, $ACT; 124 125 // $pos is for the current position in the wiki page 126 if (empty($indata)) return false; 127 list($instr, $data, $pos) = $indata; 128 129 if($mode == 'xhtml'){ 130 switch ($instr) { 131 case 'title_open' : 132 $this->title_mode[] = true; 133 $renderer->doc .= $this->_xhtml_boxopen($renderer, $pos, $data); 134 $renderer->doc .= '<h2 class="box_title"' . $this->_title_colours . '>'; 135 break; 136 137 case 'box_open' : 138 if ($this->isTitleMode()) { 139 array_pop( $this->title_mode ); 140 $this->box_content[] = true; 141 $renderer->doc .= "</h2>\n<div class=\"box_content\"" . $this->_content_colours . '>'; 142 } else { 143 $renderer->doc .= $this->_xhtml_boxopen($renderer, $pos, $data); 144 145 if ( strlen( $this->_content_colours ) > 0 ) { 146 $this->box_content[] = true; 147 $renderer->doc .= '<div class="box_content"' . $this->_content_colours . '>'; 148 } 149 } 150 break; 151 152 case 'title': 153 case 'data' : 154 $output = $renderer->_xmlEntities($data); 155 156 if ( $this->isTitleMode() ) { 157 $hid = $renderer->_headerToLink($output,true); 158 $renderer->doc .= '<a id="' . $hid . '" name="' . $hid . '">' . $output . '</a>'; 159 break; 160 } 161 162 $renderer->doc .= $output; 163 break; 164 165 case 'box_close' : 166 if ( $this->isBoxContent() ) { 167 array_pop( $this->box_content ); 168 $renderer->doc .= "</div>\n"; 169 } 170 171 if ($data) { 172 $renderer->doc .= '<p class="box_caption"' . $this->_title_colours . '>' . $renderer->_xmlEntities($data) . "</p>\n"; 173 } 174 175 // insert the section edit button befor the box is closed - array_pop makes sure we take the last box 176 if ( method_exists($renderer, "finishSectionEdit") ) { 177 $renderer->nocache(); 178 $renderer->finishSectionEdit($pos); 179 } 180 181 $renderer->doc .= "\n" . $this->_xhtml_boxclose(); 182 183 break; 184 } 185 186 return true; 187 } 188 return false; 189 } 190 191 function _boxstyle($str) { 192 if (!strlen($str)) return array(); 193 194 $styles = array(); 195 196 $tokens = preg_split('/\s+/', $str, 9); // limit is defensive 197 foreach ($tokens as $token) { 198 if (preg_match('/^\d*\.?\d+(%|px|em|ex|pt|cm|mm|pi|in)$/', $token)) { 199 $styles['width'] = $token; 200 continue; 201 } 202 203 if (preg_match('/^( 204 (\#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}))| #colorvalue 205 (rgb\(([0-9]{1,3}%?,){2}[0-9]{1,3}%?\)) #rgb triplet 206 )$/x', $token)) { 207 if (preg_match('/^#[A-Za-z0-9_-]+$/', $token)) { 208 $styles['id'] = substr($token, 1); 209 continue; 210 } 211 212 $styles['colour'][] = $token; 213 continue; 214 } 215 216 if ( preg_match('/^(margin|padding)(-(left|right|top|bottom))?:\d+(%|px|em|ex|pt|cm|mm|pi|in)$/', $token)) { 217 $styles['spacing'][] = $token; 218 } 219 220 // restrict token (class names) characters to prevent any malicious data 221 if (preg_match('/[^A-Za-z0-9_-]/',$token)) continue; 222 $styles['class'] = (isset($styles['class']) ? $styles['class'].' ' : '').$token; 223 } 224 if (!empty($styles['colour'])) { 225 $styles['colour'] = $this->_box_colours($styles['colour']); 226 } 227 228 return $styles; 229 } 230 231 function _box_colours($colours) { 232 $triplets = array(); 233 234 // only need the first four colours 235 if (count($colours) > 4) $colours = array_slice($colours,0,4); 236 foreach ($colours as $colour) { 237 $triplet[] = $this->_colourToTriplet($colour); 238 } 239 240 // there must be one colour to get here - the primary background 241 // calculate title background colour if not present 242 if (empty($triplet[1])) { 243 $triplet[1] = $triplet[0]; 244 } 245 246 // calculate outer background colour if not present 247 if (empty($triplet[2])) { 248 $triplet[2] = $triplet[0]; 249 } 250 251 // calculate border colour if not present 252 if (empty($triplet[3])) { 253 $triplet[3] = $triplet[0]; 254 } 255 256 // convert triplets back to style sheet colours 257 $style_colours['content_background'] = 'rgb('.join(',',$triplet[0]).')'; 258 $style_colours['title_background'] = 'rgb('.join(',',$triplet[1]).')'; 259 $style_colours['outer_background'] = 'rgb('.join(',',$triplet[2]).')'; 260 $style_colours['borders'] = 'rgb('.join(',',$triplet[3]).')'; 261 262 return $style_colours; 263 } 264 265 function _colourToTriplet($colour) { 266 if ($colour[0] == '#') { 267 if (strlen($colour) == 4) { 268 // format #FFF 269 return array(hexdec($colour[1].$colour[1]),hexdec($colour[2].$colour[2]),hexdec($colour[3].$colour[3])); 270 } else { 271 // format #FFFFFF 272 return array(hexdec(substr($colour,1,2)),hexdec(substr($colour,3,2)), hexdec(substr($colour,5,2))); 273 } 274 } else { 275 // format rgb(x,y,z) 276 return explode(',',substr($colour,4,-1)); 277 } 278 } 279 280 function _xhtml_boxopen($renderer, $pos, $styles) { 281 $class = 'class="box' . (isset($styles['class']) ? ' '.$styles['class'] : '') . (method_exists($renderer, "startSectionEdit") ? " " . $renderer->startSectionEdit($pos, array( 'target' => 'section', 'name' => 'box-' . $pos)) : "") . '"'; 282 $style = isset($styles['width']) ? "width: {$styles['width']};" : ''; 283 $style .= isset($styles['spacing']) ? implode(';', $styles['spacing']) : ''; 284 285 if (isset($styles['colour'])) { 286 $style .= 'background-color:'.$styles['colour']['outer_background'].';'; 287 $style .= '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 $this->_content_colours = ''; 294 $this->_title_colours = ''; 295 } 296 297 if (strlen($style)) $style = ' style="'.$style.'"'; 298 if (array_key_exists('id', $styles)) { 299 $class = 'id="' . $styles['id'] . '" ' . $class; 300 } 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 :