xref: /plugin/box2/syntax.php (revision bd01c8ec14d1117f4ace103a8b4b80a4101bb9e1)
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
802b64928SChristopher Smith *   title     (optional) all text after '|' will be rendered above the main code text with a
902b64928SChristopher Smith *             different style.
1002b64928SChristopher Smith *
1102b64928SChristopher Smith * Acknowledgements:
1202b64928SChristopher Smith *  Rounded corners based on snazzy borders by Stu Nicholls (http://www.cssplay.co.uk/boxes/snazzy)
1302b64928SChristopher Smith *  which is in turn based on nifty corners by Alessandro Fulciniti (http://pro.html.it/esempio/nifty/)
1402b64928SChristopher Smith *
1502b64928SChristopher Smith * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
1602b64928SChristopher Smith * @author     Christopher Smith <chris@jalakai.co.uk>
17*bd01c8ecSGerry Weißbach * @author     i-net software <tools@inetsoftware.de>
1802b64928SChristopher Smith */
1902b64928SChristopher Smith
2002b64928SChristopher Smithif(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
2102b64928SChristopher Smithif(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
2202b64928SChristopher Smithrequire_once(DOKU_PLUGIN.'syntax.php');
2302b64928SChristopher Smith
2402b64928SChristopher Smith/**
2502b64928SChristopher Smith * All DokuWiki plugins to extend the parser/rendering mechanism
2602b64928SChristopher Smith * need to inherit from this class
2702b64928SChristopher Smith */
2802b64928SChristopher Smithclass syntax_plugin_box extends DokuWiki_Syntax_Plugin {
2902b64928SChristopher Smith
3002b64928SChristopher Smith	var $title_mode = false;
31*bd01c8ecSGerry Weißbach	var $title_pos = array();
32*bd01c8ecSGerry Weißbach	var $title_name = array();
3302b64928SChristopher Smith
3402b64928SChristopher Smith	// the following are used in rendering and are set by _xhtml_boxopen()
35*bd01c8ecSGerry Weißbach	var $_xb_colours      = array();
3602b64928SChristopher Smith	var $_content_colours = '';
3702b64928SChristopher Smith	var $_title_colours   = '';
3802b64928SChristopher Smith
39*bd01c8ecSGerry Weißbach	/**
40*bd01c8ecSGerry Weißbach	 * return some info
41*bd01c8ecSGerry Weißbach	 */
42*bd01c8ecSGerry Weißbach	function getInfo(){
43*bd01c8ecSGerry Weißbach		return array(
44*bd01c8ecSGerry Weißbach        'author' => 'Christopher Smith / i-net software',
45*bd01c8ecSGerry Weißbach        'email'  => 'tools@inetsoftware.de',
46*bd01c8ecSGerry Weißbach        'date'   => '2010-04-21 (original 2008-11-11)',
47*bd01c8ecSGerry Weißbach        'name'   => 'Box Plugin',
48*bd01c8ecSGerry Weißbach        'desc'   => 'Boxes with titles, colour and rounded corners.
49*bd01c8ecSGerry Weißbach                     Syntax: <box width class colours|title> ... </box|caption>
50*bd01c8ecSGerry Weißbach                     width, class, colours title & caption are optional.
51*bd01c8ecSGerry Weißbach                     The title can include some wiki markup, the box
52*bd01c8ecSGerry Weißbach                     contents can include almost any wiki markup.',
53*bd01c8ecSGerry Weißbach        'url'    => 'http://www.inetsoftware.de/other-products/dokuwiki-plugins/boxes',
54*bd01c8ecSGerry Weißbach		);
55*bd01c8ecSGerry Weißbach	}
56*bd01c8ecSGerry Weißbach
5702b64928SChristopher Smith	function getType(){ return 'protected';}
5802b64928SChristopher Smith	function getAllowedTypes() { return array('container','substition','protected','disabled','formatting','paragraphs'); }
5902b64928SChristopher Smith	function getPType(){ return 'block';}
6002b64928SChristopher Smith
6102b64928SChristopher Smith	// must return a number lower than returned by native 'code' mode (200)
6202b64928SChristopher Smith	function getSort(){ return 195; }
6302b64928SChristopher Smith
6402b64928SChristopher Smith	// override default accepts() method to allow nesting
6502b64928SChristopher Smith	// - ie, to get the plugin accepts its own entry syntax
6602b64928SChristopher Smith	function accepts($mode) {
6702b64928SChristopher Smith		if ($mode == substr(get_class($this), 7)) return true;
6802b64928SChristopher Smith
6902b64928SChristopher Smith		return parent::accepts($mode);
7002b64928SChristopher Smith	}
7102b64928SChristopher Smith
7202b64928SChristopher Smith	/**
7302b64928SChristopher Smith	 * Connect pattern to lexer
7402b64928SChristopher Smith	 */
7502b64928SChristopher Smith	function connectTo($mode) {
7602b64928SChristopher Smith		$this->Lexer->addEntryPattern('<box>(?=.*?</box.*?>)',$mode,'plugin_box');
7702b64928SChristopher Smith		$this->Lexer->addEntryPattern('<box\s[^\r\n\|]*?>(?=.*?</box.*?>)',$mode,'plugin_box');
7802b64928SChristopher Smith		$this->Lexer->addEntryPattern('<box\|(?=[^\r\n]*?\>.*?</box.*?\>)',$mode,'plugin_box');
7902b64928SChristopher Smith		$this->Lexer->addEntryPattern('<box\s[^\r\n\|]*?\|(?=[^\r\n]*?>.*?</box.*?>)',$mode,'plugin_box');
8002b64928SChristopher Smith	}
8102b64928SChristopher Smith
8202b64928SChristopher Smith	function postConnect() {
8302b64928SChristopher Smith		$this->Lexer->addPattern('>', 'plugin_box');
8402b64928SChristopher Smith		$this->Lexer->addExitPattern('</box.*?>', 'plugin_box');
8502b64928SChristopher Smith	}
8602b64928SChristopher Smith
8702b64928SChristopher Smith	/**
8802b64928SChristopher Smith	 * Handle the match
8902b64928SChristopher Smith	 */
9002b64928SChristopher Smith	function handle($match, $state, $pos, &$handler){
9102b64928SChristopher Smith
9202b64928SChristopher Smith		switch ($state) {
9302b64928SChristopher Smith			case DOKU_LEXER_ENTER:
9402b64928SChristopher Smith				$data = $this->_boxstyle(trim(substr($match, 4, -1)));
9502b64928SChristopher Smith				if (substr($match, -1) == '|') {
9602b64928SChristopher Smith					$this->title_mode = true;
97*bd01c8ecSGerry Weißbach					return array('title_open',$data, $pos);
9802b64928SChristopher Smith				} else {
99*bd01c8ecSGerry Weißbach					return array('box_open',$data, $pos);
10002b64928SChristopher Smith				}
10102b64928SChristopher Smith
10202b64928SChristopher Smith			case DOKU_LEXER_MATCHED:
10302b64928SChristopher Smith				if ($this->title_mode) {
10402b64928SChristopher Smith					$this->title_mode = false;
105*bd01c8ecSGerry Weißbach					return array('box_open','', $pos);
10602b64928SChristopher Smith				} else {
107*bd01c8ecSGerry Weißbach					return array('data', $match, $pos);
10802b64928SChristopher Smith				}
10902b64928SChristopher Smith
11002b64928SChristopher Smith			case DOKU_LEXER_UNMATCHED:
111*bd01c8ecSGerry Weißbach				if ($this->title_mode) {
112*bd01c8ecSGerry Weißbach					return array('data', $match, $pos);
113*bd01c8ecSGerry Weißbach				}
114*bd01c8ecSGerry Weißbach
11502b64928SChristopher Smith				$handler->_addCall('cdata',array($match), $pos);
11602b64928SChristopher Smith				return false;
11702b64928SChristopher Smith			case DOKU_LEXER_EXIT:
118*bd01c8ecSGerry Weißbach				$pos += strlen($match); // has to be done becvause the ending tag comes after $pos
11902b64928SChristopher Smith				$data = trim(substr($match, 5, -1));
12002b64928SChristopher Smith				$title =  ($data && $data{0} == "|") ? substr($data,1) : '';
12102b64928SChristopher Smith
122*bd01c8ecSGerry Weißbach				return array('box_close', $title, $pos);
12302b64928SChristopher Smith
12402b64928SChristopher Smith		}
12502b64928SChristopher Smith		return false;
12602b64928SChristopher Smith	}
12702b64928SChristopher Smith
12802b64928SChristopher Smith	/**
12902b64928SChristopher Smith	 * Create output
13002b64928SChristopher Smith	 */
13102b64928SChristopher Smith	function render($mode, &$renderer, $indata) {
132*bd01c8ecSGerry Weißbach		global $ID, $ACT;
13302b64928SChristopher Smith
134*bd01c8ecSGerry Weißbach		// $pos is for the current position in the wiki page
13502b64928SChristopher Smith		if (empty($indata)) return false;
136*bd01c8ecSGerry Weißbach		list($instr, $data, $pos) = $indata;
13702b64928SChristopher Smith
13802b64928SChristopher Smith		if($mode == 'xhtml'){
13902b64928SChristopher Smith			switch ($instr) {
14002b64928SChristopher Smith				case 'title_open' :
14102b64928SChristopher Smith					$this->title_mode = true;
142*bd01c8ecSGerry Weißbach					$this->title_pos[] = $pos; // Start Position for Section Editing
143*bd01c8ecSGerry Weißbach					$renderer->doc .= $this->_xhtml_boxopen($data);
144*bd01c8ecSGerry Weißbach					$renderer->doc .= "<h2 class='box_title " . (method_exists($renderer, "finishSectionEdit") ? $renderer->startSectionEdit($pos, 'section', 'box') : "") . " '{$this->_title_colours}>";
14502b64928SChristopher Smith					break;
14602b64928SChristopher Smith
14702b64928SChristopher Smith				case 'box_open' :
14802b64928SChristopher Smith					if ($this->title_mode) {
14902b64928SChristopher Smith						$this->title_mode = false;
150*bd01c8ecSGerry Weißbach						$renderer->doc .= "</h2>\n<div class='box_content'{$this->_content_colours}>";
15102b64928SChristopher Smith					} else {
152*bd01c8ecSGerry Weißbach						$this->title_pos[] = $pos; // Start Position for Section Editing
153*bd01c8ecSGerry Weißbach						$this->title_name[] = 'box_' . 'no-title' . '_' . md5(time());
15402b64928SChristopher Smith						$renderer->doc .= $this->_xhtml_boxopen($data)."<div class='box_content'{$this->_content_colours}>";
15502b64928SChristopher Smith					}
15602b64928SChristopher Smith					break;
15702b64928SChristopher Smith
15802b64928SChristopher Smith				case 'data' :
159*bd01c8ecSGerry Weißbach					$output = $renderer->_xmlEntities($data);
160*bd01c8ecSGerry Weißbach
161*bd01c8ecSGerry Weißbach					if ( $this->title_mode ) {
162*bd01c8ecSGerry Weißbach						$this->title_name[] = 'box_' . cleanID($output) . '_' . md5($output);
163*bd01c8ecSGerry Weißbach						$hid = $renderer->_headerToLink($output,true);
164*bd01c8ecSGerry Weißbach						$renderer->doc .= '<a id="' . $hid . '" name="' . $hid . '">' . $output . '</a>';
165*bd01c8ecSGerry Weißbach						break;
166*bd01c8ecSGerry Weißbach					}
167*bd01c8ecSGerry Weißbach
168*bd01c8ecSGerry Weißbach					$renderer->doc .= $output;
16902b64928SChristopher Smith					break;
17002b64928SChristopher Smith
17102b64928SChristopher Smith				case 'box_close' :
17202b64928SChristopher Smith					$renderer->doc .= "</div>\n";
17302b64928SChristopher Smith
17402b64928SChristopher Smith					if ($data) {
17502b64928SChristopher Smith						$renderer->doc .= "<p class='box_caption'{$this->_title_colours}>".$renderer->_xmlEntities($data)."</p>\n";
17602b64928SChristopher Smith					}
177*bd01c8ecSGerry Weißbach
178*bd01c8ecSGerry Weißbach					// insert the section edit button befor the box is closed - array_pop makes sure we take the last box
179*bd01c8ecSGerry Weißbach					if ( $this->getConf('allowSectionEdit') && $ACT != 'preview' ) {
180*bd01c8ecSGerry Weißbach						$renderer->nocache();
181*bd01c8ecSGerry Weißbach
182*bd01c8ecSGerry Weißbach
183*bd01c8ecSGerry Weißbach						if ( auth_quickaclcheck($ID) > AUTH_READ ) {
184*bd01c8ecSGerry Weißbach							$title = array_pop($this->title_name); // Clean up
185*bd01c8ecSGerry Weißbach							if ( method_exists($renderer, "finishSectionEdit") ) {
186*bd01c8ecSGerry Weißbach								$renderer->finishSectionEdit($pos);
187*bd01c8ecSGerry Weißbach							}
188*bd01c8ecSGerry Weißbach						}
189*bd01c8ecSGerry Weißbach					}
190*bd01c8ecSGerry Weißbach
19102b64928SChristopher Smith					$renderer->doc .= $this->_xhtml_boxclose();
192*bd01c8ecSGerry Weißbach
19302b64928SChristopher Smith					break;
19402b64928SChristopher Smith			}
19502b64928SChristopher Smith
19602b64928SChristopher Smith			return true;
19702b64928SChristopher Smith		}
19802b64928SChristopher Smith		return false;
19902b64928SChristopher Smith	}
20002b64928SChristopher Smith
20102b64928SChristopher Smith	function _boxstyle($str) {
20202b64928SChristopher Smith		if (!strlen($str)) return array();
20302b64928SChristopher Smith
20402b64928SChristopher Smith		$styles = array();
20502b64928SChristopher Smith
20602b64928SChristopher Smith		$tokens = preg_split('/\s+/', $str, 9);                      // limit is defensive
20702b64928SChristopher Smith		foreach ($tokens as $token) {
20802b64928SChristopher Smith			if (preg_match('/^\d*\.?\d+(%|px|em|ex|pt|cm|mm|pi|in)$/', $token)) {
20902b64928SChristopher Smith				$styles['width'] = $token;
21002b64928SChristopher Smith				continue;
21102b64928SChristopher Smith			}
21202b64928SChristopher Smith
21302b64928SChristopher Smith			if (preg_match('/^(
21402b64928SChristopher Smith              (\#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}))|        #colorvalue
21502b64928SChristopher Smith              (rgb\(([0-9]{1,3}%?,){2}[0-9]{1,3}%?\))     #rgb triplet
21602b64928SChristopher Smith              )$/x', $token)) {
21702b64928SChristopher Smith			$styles['colour'][] = $token;
21802b64928SChristopher Smith			continue;
21902b64928SChristopher Smith              }
22002b64928SChristopher Smith
22102b64928SChristopher Smith              // restrict token (class names) characters to prevent any malicious data
22202b64928SChristopher Smith              if (preg_match('/[^A-Za-z0-9_-]/',$token)) continue;
22302b64928SChristopher Smith              $styles['class'] = (isset($styles['class']) ? $styles['class'].' ' : '').$token;
22402b64928SChristopher Smith		}
22502b64928SChristopher Smith		if (!empty($styles['colour'])) {
22602b64928SChristopher Smith			$styles['colour'] = $this->_box_colours($styles['colour']);
22702b64928SChristopher Smith		}
22802b64928SChristopher Smith
22902b64928SChristopher Smith		return $styles;
23002b64928SChristopher Smith	}
23102b64928SChristopher Smith
23202b64928SChristopher Smith	function _box_colours($colours) {
23302b64928SChristopher Smith		$triplets = array();
23402b64928SChristopher Smith
23502b64928SChristopher Smith		// only need the first four colours
23602b64928SChristopher Smith		if (count($colours) > 4) $colours = array_slice($colours,0,4);
23702b64928SChristopher Smith		foreach ($colours as $colour) {
23802b64928SChristopher Smith			$triplet[] = $this->_colourToTriplet($colour);
23902b64928SChristopher Smith		}
24002b64928SChristopher Smith
24102b64928SChristopher Smith		// there must be one colour to get here - the primary background
24202b64928SChristopher Smith		// calculate title background colour if not present
24302b64928SChristopher Smith		if (empty($triplet[1])) {
24402b64928SChristopher Smith			$triplet[1] = $triplet[0];
24502b64928SChristopher Smith		}
24602b64928SChristopher Smith
24702b64928SChristopher Smith		// calculate outer background colour if not present
24802b64928SChristopher Smith		if (empty($triplet[2])) {
24902b64928SChristopher Smith			$triplet[2] = $triplet[0];
25002b64928SChristopher Smith		}
25102b64928SChristopher Smith
25202b64928SChristopher Smith		// calculate border colour if not present
25302b64928SChristopher Smith		if (empty($triplet[3])) {
25402b64928SChristopher Smith			$triplet[3] = $triplet[0];
25502b64928SChristopher Smith		}
25602b64928SChristopher Smith
25702b64928SChristopher Smith		// convert triplets back to style sheet colours
25802b64928SChristopher Smith		$style_colours['content_background'] = 'rgb('.join(',',$triplet[0]).')';
25902b64928SChristopher Smith		$style_colours['title_background'] = 'rgb('.join(',',$triplet[1]).')';
26002b64928SChristopher Smith		$style_colours['outer_background'] = 'rgb('.join(',',$triplet[2]).')';
26102b64928SChristopher Smith		$style_colours['borders'] = 'rgb('.join(',',$triplet[3]).')';
26202b64928SChristopher Smith
26302b64928SChristopher Smith		return $style_colours;
26402b64928SChristopher Smith	}
26502b64928SChristopher Smith
26602b64928SChristopher Smith	function _colourToTriplet($colour) {
26702b64928SChristopher Smith		if ($colour{0} == '#') {
26802b64928SChristopher Smith			if (strlen($colour) == 4) {
26902b64928SChristopher Smith				// format #FFF
27002b64928SChristopher Smith				return array(hexdec($colour{1}.$colour{1}),hexdec($colour{2}.$colour{2}),hexdec($colour{3}.$colour{3}));
27102b64928SChristopher Smith			} else {
27202b64928SChristopher Smith				// format #FFFFFF
27302b64928SChristopher Smith				return array(hexdec(substr($colour,1,2)),hexdec(substr($colour,3,2)), hexdec(substr($colour,5,2)));
27402b64928SChristopher Smith			}
27502b64928SChristopher Smith		} else {
27602b64928SChristopher Smith			// format rgb(x,y,z)
27702b64928SChristopher Smith			return explode(',',substr($colour,4,-1));
27802b64928SChristopher Smith		}
27902b64928SChristopher Smith	}
28002b64928SChristopher Smith
28102b64928SChristopher Smith	function _xhtml_boxopen($styles) {
28202b64928SChristopher Smith		$class = 'class="box' . (isset($styles['class']) ? ' '.$styles['class'] : '') . '"';
28302b64928SChristopher Smith		$style = isset($styles['width']) ? "width: {$styles['width']};" : '';
28402b64928SChristopher Smith
28502b64928SChristopher Smith		if (isset($styles['colour'])) {
28602b64928SChristopher Smith			$colours = 'background-color: '.$styles['colour']['outer_background'].'; ';
28702b64928SChristopher Smith			$colours .= '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			$colours = '';
29402b64928SChristopher Smith
29502b64928SChristopher Smith			$this->_content_colours = '';
29602b64928SChristopher Smith			$this->_title_colours = '';
29702b64928SChristopher Smith		}
29802b64928SChristopher Smith
29902b64928SChristopher Smith		if ($style || $colours) $style = ' style="'.$style.' '.$colours.'"';
30002b64928SChristopher Smith		if ($colours) $colours = ' style="'.$colours.'"';
30102b64928SChristopher Smith
302*bd01c8ecSGerry Weißbach		$this->_xb_colours[] = $colours;
30302b64928SChristopher Smith
30402b64928SChristopher Smith		$html = "<div $class$style>\n";
305*bd01c8ecSGerry Weißbach
306*bd01c8ecSGerry Weißbach		// Don't do box extras if there is no style for them
307*bd01c8ecSGerry Weißbach		if ( !empty($colours) ) {
308*bd01c8ecSGerry 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";
30902b64928SChristopher Smith			$html .="  <div class='xbox'$colours>\n";
310*bd01c8ecSGerry Weißbach		}
31102b64928SChristopher Smith
31202b64928SChristopher Smith		return $html;
31302b64928SChristopher Smith	}
31402b64928SChristopher Smith
31502b64928SChristopher Smith	function _xhtml_boxclose() {
31602b64928SChristopher Smith
317*bd01c8ecSGerry Weißbach		$colours = array_pop($this->_xb_colours);
31802b64928SChristopher Smith
319*bd01c8ecSGerry Weißbach		// Don't do box extras if there is no style for them
320*bd01c8ecSGerry Weißbach		if ( !empty($colours) ) {
32102b64928SChristopher Smith			$html = "  </div>\n";
322*bd01c8ecSGerry 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";
323*bd01c8ecSGerry Weißbach		}
324*bd01c8ecSGerry 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 :