xref: /plugin/box2/syntax.php (revision b9928f9b45571ee744da728c91725916f545d371)
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
32*b9928f9bSGerry Weißbach    var $title_mode = array();
33*b9928f9bSGerry 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
70*b9928f9bSGerry Weißbach    private function isTitleMode() {
71*b9928f9bSGerry Weißbach        return $this->title_mode[count($this->title_mode)-1];
72*b9928f9bSGerry Weißbach    }
73*b9928f9bSGerry Weißbach
74*b9928f9bSGerry Weißbach    private function isBoxContent() {
75*b9928f9bSGerry Weißbach        return $this->box_content[count($this->box_content)-1];
76*b9928f9bSGerry Weißbach    }
77*b9928f9bSGerry 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) == '|') {
87*b9928f9bSGerry 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:
94*b9928f9bSGerry Weißbach                if ($this->isTitleMode()) {
95*b9928f9bSGerry 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:
102*b9928f9bSGerry 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));
11102b64928SChristopher Smith                $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' :
132*b9928f9bSGerry 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' :
138*b9928f9bSGerry Weißbach                    if ($this->isTitleMode()) {
139*b9928f9bSGerry Weißbach                        array_pop( $this->title_mode );
140*b9928f9bSGerry 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 ) {
146*b9928f9bSGerry 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
156*b9928f9bSGerry 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' :
166*b9928f9bSGerry Weißbach					if ( $this->isBoxContent() ) {
167*b9928f9bSGerry 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) {
26602b64928SChristopher Smith        if ($colour{0} == '#') {
26702b64928SChristopher Smith            if (strlen($colour) == 4) {
26802b64928SChristopher Smith                // format #FFF
26902b64928SChristopher Smith                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 . '>&nbsp;</b><b class="xb2"' . $colours . '">&nbsp;</b><b class="xb3"' . $colours . '>&nbsp;</b><b class="xb4"' . $colours . '>&nbsp;</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 .  '>&nbsp;</b><b class="xb3"' . $colours . '>&nbsp;</b><b class="xb2"' . $colours . '>&nbsp;</b><b class="xb1"' . $colours . '>&nbsp;</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 :