102b64928SChristopher Smith<?php 202b64928SChristopher Smith/** 302b64928SChristopher Smith * Box Plugin: Draw highlighting boxes around wiki markup 402b64928SChristopher Smith * 502b64928SChristopher Smith * Syntax: <box width% classes|title> 602b64928SChristopher Smith * width% width of the box, must use % unit 702b64928SChristopher Smith * classes one or more classes used to style the box, several predefined styles included in style.css 8c5083383SGerry Weißbach * padding can be defined with each direction or as composite 9c5083383SGerry Weißbach * margin can be defined with each direction or as composite 1002b64928SChristopher Smith * title (optional) all text after '|' will be rendered above the main code text with a 1102b64928SChristopher Smith * different style. 1202b64928SChristopher Smith * 1302b64928SChristopher Smith * Acknowledgements: 1402b64928SChristopher Smith * Rounded corners based on snazzy borders by Stu Nicholls (http://www.cssplay.co.uk/boxes/snazzy) 1502b64928SChristopher Smith * which is in turn based on nifty corners by Alessandro Fulciniti (http://pro.html.it/esempio/nifty/) 1602b64928SChristopher Smith * 1702b64928SChristopher Smith * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 1802b64928SChristopher Smith * @author Christopher Smith <chris@jalakai.co.uk> 19bd01c8ecSGerry Weißbach * @author i-net software <tools@inetsoftware.de> 2002b64928SChristopher Smith */ 2102b64928SChristopher Smith 2202b64928SChristopher Smithif(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/'); 2302b64928SChristopher Smithif(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); 2402b64928SChristopher Smithrequire_once(DOKU_PLUGIN.'syntax.php'); 2502b64928SChristopher Smith 2602b64928SChristopher Smith/** 2702b64928SChristopher Smith * All DokuWiki plugins to extend the parser/rendering mechanism 2802b64928SChristopher Smith * need to inherit from this class 2902b64928SChristopher Smith */ 3060c0f4d7SGerry Weißbachclass syntax_plugin_box2 extends DokuWiki_Syntax_Plugin { 3102b64928SChristopher Smith 32b9928f9bSGerry Weißbach var $title_mode = array(); 33b9928f9bSGerry Weißbach var $box_content = array(); 3402b64928SChristopher Smith 3502b64928SChristopher Smith // the following are used in rendering and are set by _xhtml_boxopen() 36bd01c8ecSGerry Weißbach var $_xb_colours = array(); 3702b64928SChristopher Smith var $_content_colours = ''; 3802b64928SChristopher Smith var $_title_colours = ''; 3902b64928SChristopher Smith 4002b64928SChristopher Smith function getType(){ return 'protected';} 4102b64928SChristopher Smith function getAllowedTypes() { return array('container','substition','protected','disabled','formatting','paragraphs'); } 4202b64928SChristopher Smith function getPType(){ return 'block';} 4302b64928SChristopher Smith 4402b64928SChristopher Smith // must return a number lower than returned by native 'code' mode (200) 4502b64928SChristopher Smith function getSort(){ return 195; } 4602b64928SChristopher Smith 4702b64928SChristopher Smith // override default accepts() method to allow nesting 4802b64928SChristopher Smith // - ie, to get the plugin accepts its own entry syntax 4902b64928SChristopher Smith function accepts($mode) { 5002b64928SChristopher Smith if ($mode == substr(get_class($this), 7)) return true; 5102b64928SChristopher Smith 5202b64928SChristopher Smith return parent::accepts($mode); 5302b64928SChristopher Smith } 5402b64928SChristopher Smith 5502b64928SChristopher Smith /** 5602b64928SChristopher Smith * Connect pattern to lexer 5702b64928SChristopher Smith */ 5802b64928SChristopher Smith function connectTo($mode) { 59271e2c1eSGerry Weißbach $this->Lexer->addEntryPattern('<box>(?=.*?</box.*?>)',$mode,'plugin_box2'); 60271e2c1eSGerry Weißbach $this->Lexer->addEntryPattern('<box\s[^\r\n\|]*?>(?=.*?</box.*?>)',$mode,'plugin_box2'); 61271e2c1eSGerry Weißbach $this->Lexer->addEntryPattern('<box\|(?=[^\r\n]*?\>.*?</box.*?\>)',$mode,'plugin_box2'); 62271e2c1eSGerry Weißbach $this->Lexer->addEntryPattern('<box\s[^\r\n\|]*?\|(?=[^\r\n]*?>.*?</box.*?>)',$mode,'plugin_box2'); 6302b64928SChristopher Smith } 6402b64928SChristopher Smith 6502b64928SChristopher Smith function postConnect() { 66271e2c1eSGerry Weißbach $this->Lexer->addPattern('>', 'plugin_box2'); 67271e2c1eSGerry Weißbach $this->Lexer->addExitPattern('</box.*?>', 'plugin_box2'); 6802b64928SChristopher Smith } 6902b64928SChristopher Smith 70b9928f9bSGerry Weißbach private function isTitleMode() { 71b9928f9bSGerry Weißbach return $this->title_mode[count($this->title_mode)-1]; 72b9928f9bSGerry Weißbach } 73b9928f9bSGerry Weißbach 74b9928f9bSGerry Weißbach private function isBoxContent() { 75b9928f9bSGerry Weißbach return $this->box_content[count($this->box_content)-1]; 76b9928f9bSGerry Weißbach } 77b9928f9bSGerry Weißbach 7802b64928SChristopher Smith /** 7902b64928SChristopher Smith * Handle the match 8002b64928SChristopher Smith */ 8112700fb9SGerry Weißbach function handle($match, $state, $pos, Doku_Handler $handler){ 8202b64928SChristopher Smith 8302b64928SChristopher Smith switch ($state) { 8402b64928SChristopher Smith case DOKU_LEXER_ENTER: 8502b64928SChristopher Smith $data = $this->_boxstyle(trim(substr($match, 4, -1))); 8602b64928SChristopher Smith if (substr($match, -1) == '|') { 87b9928f9bSGerry Weißbach $this->title_mode[] = true; 88bd01c8ecSGerry Weißbach return array('title_open',$data, $pos); 8902b64928SChristopher Smith } else { 90bd01c8ecSGerry Weißbach return array('box_open',$data, $pos); 9102b64928SChristopher Smith } 9202b64928SChristopher Smith 9302b64928SChristopher Smith case DOKU_LEXER_MATCHED: 94b9928f9bSGerry Weißbach if ($this->isTitleMode()) { 95b9928f9bSGerry Weißbach array_pop( $this->title_mode ); 96bd01c8ecSGerry Weißbach return array('box_open','', $pos); 9702b64928SChristopher Smith } else { 98bd01c8ecSGerry Weißbach return array('data', $match, $pos); 9902b64928SChristopher Smith } 10002b64928SChristopher Smith 10102b64928SChristopher Smith case DOKU_LEXER_UNMATCHED: 102b9928f9bSGerry Weißbach if ($this->isTitleMode()) { 1035e7625eaSGerry Weißbach return array('title', $match, $pos); 104bd01c8ecSGerry Weißbach } 105bd01c8ecSGerry Weißbach 10602b64928SChristopher Smith $handler->_addCall('cdata',array($match), $pos); 10702b64928SChristopher Smith return false; 10802b64928SChristopher Smith case DOKU_LEXER_EXIT: 109bd01c8ecSGerry Weißbach $pos += strlen($match); // has to be done becvause the ending tag comes after $pos 11002b64928SChristopher Smith $data = trim(substr($match, 5, -1)); 111*e5de5d69SGerry Weißbach $title = ($data && $data[0] == "|") ? substr($data,1) : ''; 11202b64928SChristopher Smith 113bd01c8ecSGerry Weißbach return array('box_close', $title, $pos); 11402b64928SChristopher Smith 11502b64928SChristopher Smith } 11602b64928SChristopher Smith return false; 11702b64928SChristopher Smith } 11802b64928SChristopher Smith 11902b64928SChristopher Smith /** 12002b64928SChristopher Smith * Create output 12102b64928SChristopher Smith */ 12212700fb9SGerry Weißbach function render($mode, Doku_Renderer $renderer, $indata) { 123bd01c8ecSGerry Weißbach global $ID, $ACT; 12402b64928SChristopher Smith 125bd01c8ecSGerry Weißbach // $pos is for the current position in the wiki page 12602b64928SChristopher Smith if (empty($indata)) return false; 127bd01c8ecSGerry Weißbach list($instr, $data, $pos) = $indata; 12802b64928SChristopher Smith 12902b64928SChristopher Smith if($mode == 'xhtml'){ 13002b64928SChristopher Smith switch ($instr) { 13102b64928SChristopher Smith case 'title_open' : 132b9928f9bSGerry Weißbach $this->title_mode[] = true; 13308550d87SGerry Weißbach $renderer->doc .= $this->_xhtml_boxopen($renderer, $pos, $data); 13408550d87SGerry Weißbach $renderer->doc .= '<h2 class="box_title"' . $this->_title_colours . '>'; 13502b64928SChristopher Smith break; 13602b64928SChristopher Smith 13702b64928SChristopher Smith case 'box_open' : 138b9928f9bSGerry Weißbach if ($this->isTitleMode()) { 139b9928f9bSGerry Weißbach array_pop( $this->title_mode ); 140b9928f9bSGerry Weißbach $this->box_content[] = true; 14108550d87SGerry Weißbach $renderer->doc .= "</h2>\n<div class=\"box_content\"" . $this->_content_colours . '>'; 14202b64928SChristopher Smith } else { 1433a0afb65SGerry Weißbach $renderer->doc .= $this->_xhtml_boxopen($renderer, $pos, $data); 1443a0afb65SGerry Weißbach 1453a0afb65SGerry Weißbach if ( strlen( $this->_content_colours ) > 0 ) { 146b9928f9bSGerry Weißbach $this->box_content[] = true; 1473a0afb65SGerry Weißbach $renderer->doc .= '<div class="box_content"' . $this->_content_colours . '>'; 1483a0afb65SGerry Weißbach } 14902b64928SChristopher Smith } 15002b64928SChristopher Smith break; 15102b64928SChristopher Smith 1525e7625eaSGerry Weißbach case 'title': 15302b64928SChristopher Smith case 'data' : 154bd01c8ecSGerry Weißbach $output = $renderer->_xmlEntities($data); 155bd01c8ecSGerry Weißbach 156b9928f9bSGerry Weißbach if ( $this->isTitleMode() ) { 157bd01c8ecSGerry Weißbach $hid = $renderer->_headerToLink($output,true); 158bd01c8ecSGerry Weißbach $renderer->doc .= '<a id="' . $hid . '" name="' . $hid . '">' . $output . '</a>'; 159bd01c8ecSGerry Weißbach break; 160bd01c8ecSGerry Weißbach } 161bd01c8ecSGerry Weißbach 162bd01c8ecSGerry Weißbach $renderer->doc .= $output; 16302b64928SChristopher Smith break; 16402b64928SChristopher Smith 16502b64928SChristopher Smith case 'box_close' : 166b9928f9bSGerry Weißbach if ( $this->isBoxContent() ) { 167b9928f9bSGerry Weißbach array_pop( $this->box_content ); 16802b64928SChristopher Smith $renderer->doc .= "</div>\n"; 1693a0afb65SGerry Weißbach } 17002b64928SChristopher Smith 17102b64928SChristopher Smith if ($data) { 17208550d87SGerry Weißbach $renderer->doc .= '<p class="box_caption"' . $this->_title_colours . '>' . $renderer->_xmlEntities($data) . "</p>\n"; 17302b64928SChristopher Smith } 174bd01c8ecSGerry Weißbach 175bd01c8ecSGerry Weißbach // insert the section edit button befor the box is closed - array_pop makes sure we take the last box 176bd01c8ecSGerry Weißbach if ( method_exists($renderer, "finishSectionEdit") ) { 17708550d87SGerry Weißbach $renderer->nocache(); 178bd01c8ecSGerry Weißbach $renderer->finishSectionEdit($pos); 179bd01c8ecSGerry Weißbach } 180bd01c8ecSGerry Weißbach 18108550d87SGerry Weißbach $renderer->doc .= "\n" . $this->_xhtml_boxclose(); 182bd01c8ecSGerry Weißbach 18302b64928SChristopher Smith break; 18402b64928SChristopher Smith } 18502b64928SChristopher Smith 18602b64928SChristopher Smith return true; 18702b64928SChristopher Smith } 18802b64928SChristopher Smith return false; 18902b64928SChristopher Smith } 19002b64928SChristopher Smith 19102b64928SChristopher Smith function _boxstyle($str) { 19202b64928SChristopher Smith if (!strlen($str)) return array(); 19302b64928SChristopher Smith 19402b64928SChristopher Smith $styles = array(); 19502b64928SChristopher Smith 19602b64928SChristopher Smith $tokens = preg_split('/\s+/', $str, 9); // limit is defensive 19702b64928SChristopher Smith foreach ($tokens as $token) { 19802b64928SChristopher Smith if (preg_match('/^\d*\.?\d+(%|px|em|ex|pt|cm|mm|pi|in)$/', $token)) { 19902b64928SChristopher Smith $styles['width'] = $token; 20002b64928SChristopher Smith continue; 20102b64928SChristopher Smith } 20202b64928SChristopher Smith 20302b64928SChristopher Smith if (preg_match('/^( 20402b64928SChristopher Smith (\#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}))| #colorvalue 20502b64928SChristopher Smith (rgb\(([0-9]{1,3}%?,){2}[0-9]{1,3}%?\)) #rgb triplet 20602b64928SChristopher Smith )$/x', $token)) { 207b1a82c0eSGerry Weißbach if (preg_match('/^#[A-Za-z0-9_-]+$/', $token)) { 208b1a82c0eSGerry Weißbach $styles['id'] = substr($token, 1); 20902b64928SChristopher Smith continue; 21002b64928SChristopher Smith } 21102b64928SChristopher Smith 212b1a82c0eSGerry Weißbach $styles['colour'][] = $token; 2133174f200SGerry Weißbach continue; 2143174f200SGerry Weißbach } 2153174f200SGerry Weißbach 216fdb53e9eSGerry Weißbach if ( preg_match('/^(margin|padding)(-(left|right|top|bottom))?:\d+(%|px|em|ex|pt|cm|mm|pi|in)$/', $token)) { 217fdb53e9eSGerry Weißbach $styles['spacing'][] = $token; 218fdb53e9eSGerry Weißbach } 219fdb53e9eSGerry Weißbach 22002b64928SChristopher Smith // restrict token (class names) characters to prevent any malicious data 22102b64928SChristopher Smith if (preg_match('/[^A-Za-z0-9_-]/',$token)) continue; 22202b64928SChristopher Smith $styles['class'] = (isset($styles['class']) ? $styles['class'].' ' : '').$token; 22302b64928SChristopher Smith } 22402b64928SChristopher Smith if (!empty($styles['colour'])) { 22502b64928SChristopher Smith $styles['colour'] = $this->_box_colours($styles['colour']); 22602b64928SChristopher Smith } 22702b64928SChristopher Smith 22802b64928SChristopher Smith return $styles; 22902b64928SChristopher Smith } 23002b64928SChristopher Smith 23102b64928SChristopher Smith function _box_colours($colours) { 23202b64928SChristopher Smith $triplets = array(); 23302b64928SChristopher Smith 23402b64928SChristopher Smith // only need the first four colours 23502b64928SChristopher Smith if (count($colours) > 4) $colours = array_slice($colours,0,4); 23602b64928SChristopher Smith foreach ($colours as $colour) { 23702b64928SChristopher Smith $triplet[] = $this->_colourToTriplet($colour); 23802b64928SChristopher Smith } 23902b64928SChristopher Smith 24002b64928SChristopher Smith // there must be one colour to get here - the primary background 24102b64928SChristopher Smith // calculate title background colour if not present 24202b64928SChristopher Smith if (empty($triplet[1])) { 24302b64928SChristopher Smith $triplet[1] = $triplet[0]; 24402b64928SChristopher Smith } 24502b64928SChristopher Smith 24602b64928SChristopher Smith // calculate outer background colour if not present 24702b64928SChristopher Smith if (empty($triplet[2])) { 24802b64928SChristopher Smith $triplet[2] = $triplet[0]; 24902b64928SChristopher Smith } 25002b64928SChristopher Smith 25102b64928SChristopher Smith // calculate border colour if not present 25202b64928SChristopher Smith if (empty($triplet[3])) { 25302b64928SChristopher Smith $triplet[3] = $triplet[0]; 25402b64928SChristopher Smith } 25502b64928SChristopher Smith 25602b64928SChristopher Smith // convert triplets back to style sheet colours 25702b64928SChristopher Smith $style_colours['content_background'] = 'rgb('.join(',',$triplet[0]).')'; 25802b64928SChristopher Smith $style_colours['title_background'] = 'rgb('.join(',',$triplet[1]).')'; 25902b64928SChristopher Smith $style_colours['outer_background'] = 'rgb('.join(',',$triplet[2]).')'; 26002b64928SChristopher Smith $style_colours['borders'] = 'rgb('.join(',',$triplet[3]).')'; 26102b64928SChristopher Smith 26202b64928SChristopher Smith return $style_colours; 26302b64928SChristopher Smith } 26402b64928SChristopher Smith 26502b64928SChristopher Smith function _colourToTriplet($colour) { 266*e5de5d69SGerry Weißbach if ($colour[0] == '#') { 26702b64928SChristopher Smith if (strlen($colour) == 4) { 26802b64928SChristopher Smith // format #FFF 269*e5de5d69SGerry Weißbach return array(hexdec($colour[1].$colour[1]),hexdec($colour[2].$colour[2]),hexdec($colour[3].$colour[3])); 27002b64928SChristopher Smith } else { 27102b64928SChristopher Smith // format #FFFFFF 27202b64928SChristopher Smith return array(hexdec(substr($colour,1,2)),hexdec(substr($colour,3,2)), hexdec(substr($colour,5,2))); 27302b64928SChristopher Smith } 27402b64928SChristopher Smith } else { 27502b64928SChristopher Smith // format rgb(x,y,z) 27602b64928SChristopher Smith return explode(',',substr($colour,4,-1)); 27702b64928SChristopher Smith } 27802b64928SChristopher Smith } 27902b64928SChristopher Smith 28008550d87SGerry Weißbach function _xhtml_boxopen($renderer, $pos, $styles) { 281cc066234SGerry Weißbach $class = 'class="box' . (isset($styles['class']) ? ' '.$styles['class'] : '') . (method_exists($renderer, "startSectionEdit") ? " " . $renderer->startSectionEdit($pos, array( 'target' => 'section', 'name' => 'box-' . $pos)) : "") . '"'; 28202b64928SChristopher Smith $style = isset($styles['width']) ? "width: {$styles['width']};" : ''; 283fdb53e9eSGerry Weißbach $style .= isset($styles['spacing']) ? implode(';', $styles['spacing']) : ''; 28402b64928SChristopher Smith 28502b64928SChristopher Smith if (isset($styles['colour'])) { 286fdb53e9eSGerry Weißbach $style .= 'background-color:'.$styles['colour']['outer_background'].';'; 287fdb53e9eSGerry Weißbach $style .= 'border-color: '.$styles['colour']['borders'].';'; 28802b64928SChristopher Smith 28902b64928SChristopher Smith $this->_content_colours = 'style="background-color: '.$styles['colour']['content_background'].'; border-color: '.$styles['colour']['borders'].'"'; 29002b64928SChristopher Smith $this->_title_colours = 'style="background-color: '.$styles['colour']['title_background'].';"'; 29102b64928SChristopher Smith 29202b64928SChristopher Smith } else { 29302b64928SChristopher Smith $this->_content_colours = ''; 29402b64928SChristopher Smith $this->_title_colours = ''; 29502b64928SChristopher Smith } 29602b64928SChristopher Smith 297fdb53e9eSGerry Weißbach if (strlen($style)) $style = ' style="'.$style.'"'; 2983174f200SGerry Weißbach if (array_key_exists('id', $styles)) { 2993174f200SGerry Weißbach $class = 'id="' . $styles['id'] . '" ' . $class; 3003174f200SGerry Weißbach } 30102b64928SChristopher Smith 302bd01c8ecSGerry Weißbach $this->_xb_colours[] = $colours; 30302b64928SChristopher Smith 30402b64928SChristopher Smith $html = "<div $class$style>\n"; 305bd01c8ecSGerry Weißbach 306bd01c8ecSGerry Weißbach // Don't do box extras if there is no style for them 307bd01c8ecSGerry Weißbach if ( !empty($colours) ) { 30808550d87SGerry Weißbach $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"; 30908550d87SGerry Weißbach $html .= '<div class="xbox"' . $colours . ">\n"; 310bd01c8ecSGerry Weißbach } 31102b64928SChristopher Smith 31202b64928SChristopher Smith return $html; 31302b64928SChristopher Smith } 31402b64928SChristopher Smith 31502b64928SChristopher Smith function _xhtml_boxclose() { 31602b64928SChristopher Smith 317bd01c8ecSGerry Weißbach $colours = array_pop($this->_xb_colours); 31802b64928SChristopher Smith 319bd01c8ecSGerry Weißbach // Don't do box extras if there is no style for them 320bd01c8ecSGerry Weißbach if ( !empty($colours) ) { 32102b64928SChristopher Smith $html = "</div>\n"; 32208550d87SGerry Weißbach $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"; 323bd01c8ecSGerry Weißbach } 32408550d87SGerry Weißbach $html .= '</div> <!-- Extras -->' . "\n"; 32502b64928SChristopher Smith 32602b64928SChristopher Smith return $html; 32702b64928SChristopher Smith } 32802b64928SChristopher Smith 32902b64928SChristopher Smith} 33002b64928SChristopher Smith 33102b64928SChristopher Smith//Setup VIM: ex: et ts=4 enc=utf-8 :