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