12186d66dSSatoshi Sahara<?php 22186d66dSSatoshi Sahara/** 3*ec44bd34SLeonard Heyman * DokuWiki Plugin Extab4 (Syntax component) 492d5c8a1SSatoshi Sahara * 592d5c8a1SSatoshi Sahara * Allows extended (MediaWiki-style) tables inside DokuWiki 62186d66dSSatoshi Sahara * 72186d66dSSatoshi Sahara * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 892d5c8a1SSatoshi Sahara * @author Satoshi Sahara <sahara.satoshi@gmail.com> 92186d66dSSatoshi Sahara */ 10*ec44bd34SLeonard Heymanclass syntax_plugin_extab4 extends DokuWiki_Syntax_Plugin 111e984f26SSatoshi Sahara{ 121e984f26SSatoshi Sahara public function getType() 131e984f26SSatoshi Sahara { // Syntax Type 141e984f26SSatoshi Sahara return 'container'; 151e984f26SSatoshi Sahara } 162186d66dSSatoshi Sahara 171e984f26SSatoshi Sahara public function getAllowedTypes() 181e984f26SSatoshi Sahara { // Allowed Mode Types 191e984f26SSatoshi Sahara return array( 201e984f26SSatoshi Sahara 'container', 211e984f26SSatoshi Sahara 'formatting', 221e984f26SSatoshi Sahara 'substition', 231e984f26SSatoshi Sahara 'disabled', 241e984f26SSatoshi Sahara 'protected', 251e984f26SSatoshi Sahara 'paragraphs', 261e984f26SSatoshi Sahara ); 271e984f26SSatoshi Sahara } 281e984f26SSatoshi Sahara 291e984f26SSatoshi Sahara public function getPType() 301e984f26SSatoshi Sahara { // Paragraph Type 311e984f26SSatoshi Sahara return 'block'; 321e984f26SSatoshi Sahara } 331e984f26SSatoshi Sahara 341e984f26SSatoshi Sahara 353fb7eb41SSatoshi Sahara protected $stack = array(); // stack of current open tag - used by handle() method 36bb581870SSatoshi Sahara protected $tagsmap; 37bb581870SSatoshi Sahara protected $attrsmap; 382186d66dSSatoshi Sahara 391e984f26SSatoshi Sahara /** 401e984f26SSatoshi Sahara * Connect pattern to lexer 411e984f26SSatoshi Sahara */ 421e984f26SSatoshi Sahara protected $mode; 431e984f26SSatoshi Sahara 441e984f26SSatoshi Sahara public function preConnect() 451e984f26SSatoshi Sahara { 461e984f26SSatoshi Sahara // drop 'syntax_' from class name 471e984f26SSatoshi Sahara $this->mode = substr(get_class($this), 7); 48e43fcd23SSatoshi Sahara } 492186d66dSSatoshi Sahara 501e984f26SSatoshi Sahara public function connectTo($mode) 511e984f26SSatoshi Sahara { 52e43fcd23SSatoshi Sahara // table start: {| attrs 539f716539SSatoshi Sahara $this->Lexer->addEntryPattern('\n\{\|[^\n]*',$mode, $this->mode); 542186d66dSSatoshi Sahara } 551e984f26SSatoshi Sahara 561e984f26SSatoshi Sahara public function postConnect() 571e984f26SSatoshi Sahara { 58e43fcd23SSatoshi Sahara // table end: |} 599f716539SSatoshi Sahara $this->Lexer->addExitPattern('[ \t]*\n\|\}', $this->mode); 602186d66dSSatoshi Sahara 61cc63ba86SSatoshi Sahara // match pattern for attributes 62cc63ba86SSatoshi Sahara $attrs = '[^\n\{\|\!\[]+'; 634e16aaf0SSatoshi Sahara 6492d5c8a1SSatoshi Sahara // caption: |+ attrs | caption 659f716539SSatoshi Sahara $this->Lexer->addPattern("\n\|\+ *(?:$attrs\|(?!\|))?", $this->mode); 6692d5c8a1SSatoshi Sahara // table row: |- attrs 679f716539SSatoshi Sahara $this->Lexer->addPattern(' *?\n\|\-+[^\n]*', $this->mode); 6892d5c8a1SSatoshi Sahara // table header: ! attrs | 699f716539SSatoshi Sahara $this->Lexer->addPattern("(?: *?\n|\!)\!(?:$attrs\|(?!\|))?", $this->mode); 7092d5c8a1SSatoshi Sahara // table data: | attrs | 719f716539SSatoshi Sahara $this->Lexer->addPattern("(?: *?\n|\|)\|(?:$attrs\|(?!\|))?", $this->mode); 722186d66dSSatoshi Sahara } 732186d66dSSatoshi Sahara 741e984f26SSatoshi Sahara public function accepts($mode) 751e984f26SSatoshi Sahara { // plugin accepts its own entry syntax 761e984f26SSatoshi Sahara if ($mode == $this->mode) return true; 771e984f26SSatoshi Sahara return parent::accepts($mode); 781e984f26SSatoshi Sahara } 791e984f26SSatoshi Sahara 801e984f26SSatoshi Sahara public function getSort() 811e984f26SSatoshi Sahara { // sort number used to determine priority of this mode 821e984f26SSatoshi Sahara return 59; // = Doku_Parser_Mode_table-1 831e984f26SSatoshi Sahara } 841e984f26SSatoshi Sahara 8592d5c8a1SSatoshi Sahara 8692d5c8a1SSatoshi Sahara /** 8792d5c8a1SSatoshi Sahara * helper function to simplify writing plugin calls to the instruction list 8892d5c8a1SSatoshi Sahara * first three arguments are passed to function render as $data 8992d5c8a1SSatoshi Sahara */ 902235f4feSSatoshi Sahara protected function writeCall($tag, $attr, $state, $pos, $match, $handler) 911e984f26SSatoshi Sahara { 922235f4feSSatoshi Sahara $data = array($state, $tag, $attr); 932235f4feSSatoshi Sahara $handler->addPluginCall($this->getPluginName(), $data, $state, $pos, $match); 942235f4feSSatoshi Sahara } 952235f4feSSatoshi Sahara 962235f4feSSatoshi Sahara protected function open($tag, $attr, $pos, $match, $handler) 972235f4feSSatoshi Sahara { 982235f4feSSatoshi Sahara //$this->writeCall($tag,$attr,DOKU_LEXER_ENTER, $pos,$match,$handler); 99166bfb80SSatoshi Sahara $match = array(DOKU_LEXER_ENTER, $tag, $attr); 100166bfb80SSatoshi Sahara $handler->plugin($match, 'addPluginCall', $pos, $this->getPluginName()); 101cc61c42eSSatoshi Sahara } 102cc61c42eSSatoshi Sahara 1032235f4feSSatoshi Sahara protected function close($tag, $pos, $match, $handler) 1041e984f26SSatoshi Sahara { 1052235f4feSSatoshi Sahara //$this->writeCall($tag,'',DOKU_LEXER_EXIT, $pos,$match,$handler); 106*ec44bd34SLeonard Heyman $match = array(DOKU_LEXER_EXIT, $tag, ''); 107166bfb80SSatoshi Sahara $handler->plugin($match, 'addPluginCall', $pos, $this->getPluginName()); 108cc61c42eSSatoshi Sahara } 109cc61c42eSSatoshi Sahara 11092d5c8a1SSatoshi Sahara /** 11192d5c8a1SSatoshi Sahara * helper function for exttab syntax translation to html 11292d5c8a1SSatoshi Sahara * 11392d5c8a1SSatoshi Sahara * @param string $match matched string 11492bf75d3SSatoshi Sahara * @return array tag name and attributes 11592d5c8a1SSatoshi Sahara */ 1161e984f26SSatoshi Sahara protected function interpret($match = '') 1171e984f26SSatoshi Sahara { 11862cacab2SSatoshi Sahara $markup = ltrim($match); 11962cacab2SSatoshi Sahara $len = 2; 120194fb8c1SSatoshi Sahara switch (substr($markup, 0, $len)) { 12162cacab2SSatoshi Sahara case '{|': $tag = 'table'; break; 12262cacab2SSatoshi Sahara case '|}': $tag = '/table'; break; 12362cacab2SSatoshi Sahara case '|+': $tag = 'caption'; break; 12462cacab2SSatoshi Sahara case '|-': $tag = 'tr'; break; 125194fb8c1SSatoshi Sahara case '||': $tag = 'td'; break; 126194fb8c1SSatoshi Sahara case '!!': $tag = 'th'; break; 12762cacab2SSatoshi Sahara default: 12862cacab2SSatoshi Sahara $len = 1; 129194fb8c1SSatoshi Sahara switch (substr($markup, 0, $len)) { 130194fb8c1SSatoshi Sahara case '!': $tag = 'th'; break; 131194fb8c1SSatoshi Sahara case '|': $tag = 'td'; break; 132194fb8c1SSatoshi Sahara } 13392d5c8a1SSatoshi Sahara } 13462cacab2SSatoshi Sahara if (isset($tag)) { 13562cacab2SSatoshi Sahara $attrs = substr($markup, $len); 13662cacab2SSatoshi Sahara return array($tag, $attrs); 13792d5c8a1SSatoshi Sahara } else { 13862cacab2SSatoshi Sahara msg($this->getPluginName().' ERROR: unknown syntax: '.hsc($markup) ,-1); 13992d5c8a1SSatoshi Sahara return false; 14092d5c8a1SSatoshi Sahara } 14192d5c8a1SSatoshi Sahara } 14292d5c8a1SSatoshi Sahara 1435a49dd0fSSatoshi Sahara /** 1445a49dd0fSSatoshi Sahara * append specified class name to attributes 1455a49dd0fSSatoshi Sahara * 1465a49dd0fSSatoshi Sahara * @param string $class class name 1475a49dd0fSSatoshi Sahara * @param string $attr attributes of html tag 1485a49dd0fSSatoshi Sahara * @return string modified $attr 1495a49dd0fSSatoshi Sahara */ 1501e984f26SSatoshi Sahara private function appendClass($class, $attr) 1511e984f26SSatoshi Sahara { 152*ec44bd34SLeonard Heyman $regex = '/\bclass\s*=\s*(["\'])(.*?)\1/i'; 153*ec44bd34SLeonard Heyman if (preg_match($regex, $attr, $matches) !== 1) { 154194fb8c1SSatoshi Sahara // class attribute is not specified 155*ec44bd34SLeonard Heyman return rtrim($attr).' class="'.$class.'"'; 1565a49dd0fSSatoshi Sahara } 157*ec44bd34SLeonard Heyman 158*ec44bd34SLeonard Heyman $items = preg_split('/\s+/', trim($matches[2]), -1, PREG_SPLIT_NO_EMPTY); 159*ec44bd34SLeonard Heyman if (in_array($class, $items, true)) { 160*ec44bd34SLeonard Heyman // class already exists in the class attribute 161*ec44bd34SLeonard Heyman return $attr; 162*ec44bd34SLeonard Heyman } 163*ec44bd34SLeonard Heyman 164*ec44bd34SLeonard Heyman $items[] = $class; 165*ec44bd34SLeonard Heyman $replace = 'class='.$matches[1].implode(' ', $items).$matches[1]; 166*ec44bd34SLeonard Heyman return preg_replace($regex, $replace, $attr, 1); 1675a49dd0fSSatoshi Sahara } 1685a49dd0fSSatoshi Sahara 16992d5c8a1SSatoshi Sahara 1702186d66dSSatoshi Sahara /** 1712186d66dSSatoshi Sahara * Handle the match 1722186d66dSSatoshi Sahara */ 1731e984f26SSatoshi Sahara public function handle($match, $state, $pos, Doku_Handler $handler) 1741e984f26SSatoshi Sahara { 17592d5c8a1SSatoshi Sahara switch ($state) { 176166bfb80SSatoshi Sahara case 'addPluginCall': 177bb581870SSatoshi Sahara // write plugin instruction to call list of the handler 178bb581870SSatoshi Sahara // Note: $match is array, not matched text 179166bfb80SSatoshi Sahara return $data = $match; 180166bfb80SSatoshi Sahara 18192d5c8a1SSatoshi Sahara case DOKU_LEXER_ENTER: 18292d5c8a1SSatoshi Sahara // table start 1831e984f26SSatoshi Sahara list($tag, $attr) = $this->interpret($match); 1845a49dd0fSSatoshi Sahara // ensure that class attribute cotains "exttable" 1851e984f26SSatoshi Sahara $attr = $this->appendClass('exttable', $attr); 18692d5c8a1SSatoshi Sahara array_push($this->stack, $tag); 1872235f4feSSatoshi Sahara $this->open($tag, $attr, $pos, $match, $handler); 18892d5c8a1SSatoshi Sahara break; 18992bf75d3SSatoshi Sahara case DOKU_LEXER_EXIT: 19092bf75d3SSatoshi Sahara do { // rewind table 19192bf75d3SSatoshi Sahara $oldtag = array_pop($this->stack); 1922235f4feSSatoshi Sahara $this->close($oldtag, $pos, $match, $handler); 19392bf75d3SSatoshi Sahara } while ($oldtag != 'table'); 19492bf75d3SSatoshi Sahara break; 19592d5c8a1SSatoshi Sahara case DOKU_LEXER_MATCHED: 19692d5c8a1SSatoshi Sahara $tag_prev = end($this->stack); 1971e984f26SSatoshi Sahara list($tag, $attr) = $this->interpret($match); 19892d5c8a1SSatoshi Sahara switch ($tag_prev) { 19992d5c8a1SSatoshi Sahara case 'caption': 200358471aaSSatoshi Sahara $oldtag = array_pop($this->stack); 2012235f4feSSatoshi Sahara $this->close($oldtag, $pos, $match, $handler); 20292d5c8a1SSatoshi Sahara case 'table': 20392d5c8a1SSatoshi Sahara switch ($tag) { 20492d5c8a1SSatoshi Sahara case 'caption': 20592d5c8a1SSatoshi Sahara case 'tr': 20692d5c8a1SSatoshi Sahara array_push($this->stack, $tag); 2072235f4feSSatoshi Sahara $this->open($tag, $attr, $pos, $match, $handler); 20892d5c8a1SSatoshi Sahara break; 20992d5c8a1SSatoshi Sahara case 'th': 21092d5c8a1SSatoshi Sahara case 'td': 21192d5c8a1SSatoshi Sahara array_push($this->stack, 'tr'); 2122235f4feSSatoshi Sahara $this->open('tr', '', $pos, $match, $handler); 21392d5c8a1SSatoshi Sahara array_push($this->stack, $tag); 2142235f4feSSatoshi Sahara $this->open($tag, $attr, $pos, $match, $handler); 21592d5c8a1SSatoshi Sahara break; 21692d5c8a1SSatoshi Sahara } 21792d5c8a1SSatoshi Sahara break; 21892d5c8a1SSatoshi Sahara case 'tr': 21992d5c8a1SSatoshi Sahara switch ($tag) { 22092d5c8a1SSatoshi Sahara case 'caption': 22162cacab2SSatoshi Sahara msg($this->getPluginName().' Syntax ERROR: match='.hsc(trim($match)) ,-1); 22292d5c8a1SSatoshi Sahara break; 22392d5c8a1SSatoshi Sahara case 'tr': 22492d5c8a1SSatoshi Sahara $oldtag = array_pop($this->stack); 2252235f4feSSatoshi Sahara $this->close($oldtag, $pos, $match, $handler); 22692d5c8a1SSatoshi Sahara array_push($this->stack, $tag); 2272235f4feSSatoshi Sahara $this->open($tag, $attr, $pos, $match, $handler); 22892d5c8a1SSatoshi Sahara break; 22992d5c8a1SSatoshi Sahara case 'th': 23092d5c8a1SSatoshi Sahara case 'td': 23192d5c8a1SSatoshi Sahara array_push($this->stack, $tag); 2322235f4feSSatoshi Sahara $this->open($tag, $attr, $pos, $match, $handler); 23392d5c8a1SSatoshi Sahara break; 23492d5c8a1SSatoshi Sahara } 23592d5c8a1SSatoshi Sahara break; 23692d5c8a1SSatoshi Sahara case 'th': 23792d5c8a1SSatoshi Sahara case 'td': 23892d5c8a1SSatoshi Sahara switch ($tag) { 23992d5c8a1SSatoshi Sahara case 'caption': 24062cacab2SSatoshi Sahara msg($this->getPluginName().' Syntax ERROR: match='.hsc(trim($match)) ,-1); 24192d5c8a1SSatoshi Sahara break; 24292d5c8a1SSatoshi Sahara case 'tr': 24392d5c8a1SSatoshi Sahara do { // rewind old row prior to start new row 24492d5c8a1SSatoshi Sahara $oldtag = array_pop($this->stack); 2452235f4feSSatoshi Sahara $this->close($oldtag, $pos, $match, $handler); 24692d5c8a1SSatoshi Sahara } while ($oldtag != 'tr'); 24792d5c8a1SSatoshi Sahara array_push($this->stack, $tag); 2482235f4feSSatoshi Sahara $this->open($tag, $attr, $pos, $match, $handler); 24992d5c8a1SSatoshi Sahara break; 25092d5c8a1SSatoshi Sahara case 'th': 25192d5c8a1SSatoshi Sahara case 'td': 25292d5c8a1SSatoshi Sahara $oldtag = array_pop($this->stack); 2532235f4feSSatoshi Sahara $this->close($oldtag, $pos, $match, $handler); 25492d5c8a1SSatoshi Sahara array_push($this->stack, $tag); 2552235f4feSSatoshi Sahara $this->open($tag, $attr, $pos, $match, $handler); 25692d5c8a1SSatoshi Sahara break; 25792d5c8a1SSatoshi Sahara } 25892d5c8a1SSatoshi Sahara break; 25992d5c8a1SSatoshi Sahara } 26092d5c8a1SSatoshi Sahara break; 26192d5c8a1SSatoshi Sahara case DOKU_LEXER_UNMATCHED: 26292d5c8a1SSatoshi Sahara $tag_prev = end($this->stack); 26392d5c8a1SSatoshi Sahara switch ($tag_prev) { 26492d5c8a1SSatoshi Sahara case 'caption': 265e6934c5dSSatoshi Sahara // cdata --- use base() instead of $this->_writeCall() 26692d5c8a1SSatoshi Sahara $handler->base($match, $state, $pos); 26792d5c8a1SSatoshi Sahara break; 26892d5c8a1SSatoshi Sahara case 'table': 26992d5c8a1SSatoshi Sahara array_push($this->stack, 'tr'); 2702235f4feSSatoshi Sahara $this->open('tr', '', $pos, $match, $handler); 27192d5c8a1SSatoshi Sahara case 'tr': 27292d5c8a1SSatoshi Sahara array_push($this->stack, 'td'); 2732235f4feSSatoshi Sahara $this->open('td', '', $pos, $match, $handler); 27492d5c8a1SSatoshi Sahara case 'th': 27592d5c8a1SSatoshi Sahara case 'td': 276e6934c5dSSatoshi Sahara // cdata --- use base() instead of $this->_writeCall() 277ed8db0b0SSatoshi Sahara $handler->base($match, $state, $pos); 27892d5c8a1SSatoshi Sahara break; 27992d5c8a1SSatoshi Sahara } 28092d5c8a1SSatoshi Sahara break; 2812186d66dSSatoshi Sahara } 2825c7d1ba6SSatoshi Sahara return false; 2832186d66dSSatoshi Sahara } 28492d5c8a1SSatoshi Sahara 2852186d66dSSatoshi Sahara 2862186d66dSSatoshi Sahara /** 2872186d66dSSatoshi Sahara * Create output 2882186d66dSSatoshi Sahara */ 2891e984f26SSatoshi Sahara public function render($format, Doku_Renderer $renderer, $data) 2901e984f26SSatoshi Sahara { 29192d5c8a1SSatoshi Sahara if (empty($data)) return false; 2922186d66dSSatoshi Sahara 29392d5c8a1SSatoshi Sahara switch ($format) { 294c448a253SSatoshi Sahara case 'xhtml' : 295c448a253SSatoshi Sahara return $this->render_xhtml($renderer, $data); 296c448a253SSatoshi Sahara case 'odt' : 2979d09e2e1SAndreas Ferber case 'odt_pdf': 298*ec44bd34SLeonard Heyman $odt = $this->loadHelper('extab4_odt'); 299c448a253SSatoshi Sahara return $odt->render($renderer, $data); 30092d5c8a1SSatoshi Sahara default: 3012186d66dSSatoshi Sahara return false; 3022186d66dSSatoshi Sahara } 303194fb8c1SSatoshi Sahara } 3042186d66dSSatoshi Sahara 3051e984f26SSatoshi Sahara protected function render_xhtml(Doku_Renderer $renderer, $data) 3061e984f26SSatoshi Sahara { 307bb581870SSatoshi Sahara // prepare class properties 308bb581870SSatoshi Sahara isset($this->tagsmap) || $this->setTagsmap(); 309*ec44bd34SLeonard Heyman isset($this->attrsmap) || $this->setAllowedAttributes(); 310bb581870SSatoshi Sahara 31192d5c8a1SSatoshi Sahara list($state, $tag, $attr) = $data; 31292d5c8a1SSatoshi Sahara 31392d5c8a1SSatoshi Sahara switch ($state) { 31492d5c8a1SSatoshi Sahara case DOKU_LEXER_ENTER: // open tag 3151e984f26SSatoshi Sahara $renderer->doc.= $this->tag_open($tag, $attr); 31692d5c8a1SSatoshi Sahara break; 31792d5c8a1SSatoshi Sahara case DOKU_LEXER_MATCHED: // defensive, shouldn't occur 31892d5c8a1SSatoshi Sahara case DOKU_LEXER_UNMATCHED: 31992d5c8a1SSatoshi Sahara $renderer->cdata($tag); 32092d5c8a1SSatoshi Sahara break; 32192d5c8a1SSatoshi Sahara case DOKU_LEXER_EXIT: // close tag 3221e984f26SSatoshi Sahara $renderer->doc.= $this->tag_close($tag); 32392d5c8a1SSatoshi Sahara break; 32492d5c8a1SSatoshi Sahara } 3255c7d1ba6SSatoshi Sahara return true; 32692d5c8a1SSatoshi Sahara } 32792d5c8a1SSatoshi Sahara 32892d5c8a1SSatoshi Sahara /** 329bb581870SSatoshi Sahara * open a exttab tag, used in render_xhtml() 33092d5c8a1SSatoshi Sahara * 33192d5c8a1SSatoshi Sahara * @param string $tag 'table','caption','tr','th' or 'td' 33292d5c8a1SSatoshi Sahara * @param string $attr attibutes of tag element 33392d5c8a1SSatoshi Sahara * @return string html used to open the tag 33492d5c8a1SSatoshi Sahara */ 3351e984f26SSatoshi Sahara protected function tag_open($tag, $attr = null) 3361e984f26SSatoshi Sahara { 33792d5c8a1SSatoshi Sahara $before = $this->tagsmap[$tag][0]; 33892d5c8a1SSatoshi Sahara $after = $this->tagsmap[$tag][1]; 3391e984f26SSatoshi Sahara $attr = $this->cleanAttrString($attr, $this->attrsmap); 34092d5c8a1SSatoshi Sahara return $before.'<'.$tag.$attr.'>'.$after; 34192d5c8a1SSatoshi Sahara } 34292d5c8a1SSatoshi Sahara 34392d5c8a1SSatoshi Sahara /** 344bb581870SSatoshi Sahara * close a exttab tag, used in render_xhtml() 34592d5c8a1SSatoshi Sahara * 34692d5c8a1SSatoshi Sahara * @param string $tag 'table','caption','tr','th' or 'td' 34792d5c8a1SSatoshi Sahara * @return string html used to close the tag 34892d5c8a1SSatoshi Sahara */ 3491e984f26SSatoshi Sahara protected function tag_close($tag) 3501e984f26SSatoshi Sahara { 3514bd4a7d9SSatoshi Sahara $before = $this->tagsmap['/'.$tag][0]; 3524bd4a7d9SSatoshi Sahara $after = $this->tagsmap['/'.$tag][1]; 35392d5c8a1SSatoshi Sahara return $before.'</'.$tag.'>'.$after; 35492d5c8a1SSatoshi Sahara } 35592d5c8a1SSatoshi Sahara 356bb581870SSatoshi Sahara /** 357bb581870SSatoshi Sahara * prepare tagsmap used in tag_open() and tag_close() 358bb581870SSatoshi Sahara */ 359bb581870SSatoshi Sahara protected function setTagsmap() 360bb581870SSatoshi Sahara { 361bb581870SSatoshi Sahara // define name, prefix and postfix of tags 362bb581870SSatoshi Sahara $this->tagsmap = array( 363bb581870SSatoshi Sahara 'table' => array("", "\n" ), // table start : {| 364bb581870SSatoshi Sahara '/table' => array("", "\n"), // table end : |} 365bb581870SSatoshi Sahara 'caption' => array("", ""), // caption : |+ 366bb581870SSatoshi Sahara '/caption' => array("", "\n"), 367bb581870SSatoshi Sahara 'tr' => array("", "\n"), // table row : |- 368bb581870SSatoshi Sahara '/tr' => array("", "\n"), 369bb581870SSatoshi Sahara 'th' => array("", ""), // table header : ! 370bb581870SSatoshi Sahara '/th' => array("", "\n"), 371bb581870SSatoshi Sahara 'td' => array("", ""), // table data : | 372bb581870SSatoshi Sahara '/td' => array("", "\n"), 373bb581870SSatoshi Sahara ); 374bb581870SSatoshi Sahara } 375bb581870SSatoshi Sahara 376bb581870SSatoshi Sahara /** 377bb581870SSatoshi Sahara * prepare attrsmap used in cleanAttrString() 378bb581870SSatoshi Sahara */ 379bb581870SSatoshi Sahara protected function setAllowedAttributes() 380bb581870SSatoshi Sahara { 381bb581870SSatoshi Sahara // define allowable attibutes for table tags 382bb581870SSatoshi Sahara $this->attrsmap = array( 383bb581870SSatoshi Sahara // html5 HTML Global Attributes 384bb581870SSatoshi Sahara 'accesskey', 'class', 'contenteditable', 'contextmenu', 385bb581870SSatoshi Sahara 'dir', 'draggable', 'dropzone', 'hidden', 'id', 'lang', 386bb581870SSatoshi Sahara 'spellcheck', 'style', 'tabindex', 'title', 'translate', 387bb581870SSatoshi Sahara 'xml:lang', 388bb581870SSatoshi Sahara // html5 table tag 389bb581870SSatoshi Sahara 'border', 'sortable', 390bb581870SSatoshi Sahara // html5 th and td tag 391bb581870SSatoshi Sahara 'abbr', 'colspan', 'headers', 'rowspan', 'scope', 'sorted', 392bb581870SSatoshi Sahara // deprecated in html5 393bb581870SSatoshi Sahara 'align', 'valign', 'width', 'height', 'bgcolor', 'nowrap', 394bb581870SSatoshi Sahara ); 395bb581870SSatoshi Sahara } 39692d5c8a1SSatoshi Sahara 3972186d66dSSatoshi Sahara 3982186d66dSSatoshi Sahara /** 3992186d66dSSatoshi Sahara * Make the attribute string safe to avoid XSS attacks. 4003b558e78SSatoshi Sahara * 4013b558e78SSatoshi Sahara * @author Ashish Myles <marcianx@gmail.com> 4023b558e78SSatoshi Sahara * 40392d5c8a1SSatoshi Sahara * @param string $attr attibutes to be checked 40492d5c8a1SSatoshi Sahara * @param array $allowed_keys allowed attribute name map 40592d5c8a1SSatoshi Sahara * ex: array('border','bgcolor'); 40692d5c8a1SSatoshi Sahara * @return string cleaned attibutes 40792d5c8a1SSatoshi Sahara * 4082186d66dSSatoshi Sahara * WATCH OUT FOR 4092186d66dSSatoshi Sahara * - event handlers (e.g. onclick="javascript:...", etc) 4102186d66dSSatoshi Sahara * - CSS (e.g. background: url(javascript:...)) 4112186d66dSSatoshi Sahara * - closing the tag and opening a new one 4122186d66dSSatoshi Sahara * WHAT IS DONE 4132186d66dSSatoshi Sahara * - turn all whitespace into ' ' (to protect from removal) 4142186d66dSSatoshi Sahara * - remove all non-printable characters and < and > 4152186d66dSSatoshi Sahara * - parse and filter attributes using a whitelist 4162186d66dSSatoshi Sahara * - styles with 'url' in them are altogether removed 4172186d66dSSatoshi Sahara * (I know this is brutally aggressive and doesn't allow 4182186d66dSSatoshi Sahara * some safe stuff, but better safe than sorry.) 4192186d66dSSatoshi Sahara * NOTE: Attribute values MUST be in quotes now. 4202186d66dSSatoshi Sahara */ 421*ec44bd34SLeonard Heyman protected function cleanAttrString($attr, $allowed_keys) 4221e984f26SSatoshi Sahara { 4231e984f26SSatoshi Sahara if (is_null($attr)) return null; 4241e984f26SSatoshi Sahara 4251e984f26SSatoshi Sahara // Keep spaces simple 4262186d66dSSatoshi Sahara $attr = trim(preg_replace('/\s+/', ' ', $attr)); 4271e984f26SSatoshi Sahara // Remove non-printable characters and angle brackets 4282186d66dSSatoshi Sahara $attr = preg_replace('/[<>[:^print:]]+/', '', $attr); 4291e984f26SSatoshi Sahara // This regular expression parses the value of an attribute and 4301e984f26SSatoshi Sahara // the quotation marks surrounding it. 4311e984f26SSatoshi Sahara // It assumes that all quotes within the value itself must be escaped, 4321e984f26SSatoshi Sahara // which is not technically true. 4331e984f26SSatoshi Sahara // To keep the parsing simple (no look-ahead), the value must be in 4341e984f26SSatoshi Sahara // quotes. 4352186d66dSSatoshi Sahara $val = "([\"'`])(?:[^\\\\\"'`]|\\\\.)*\g{-1}"; 4362186d66dSSatoshi Sahara 4372186d66dSSatoshi Sahara $nattr = preg_match_all("/(\w+)\s*=\s*($val)/", $attr, $matches, PREG_SET_ORDER); 4381e984f26SSatoshi Sahara if (!$nattr) return null; 4392186d66dSSatoshi Sahara 4402186d66dSSatoshi Sahara $clean_attr = ''; 4412186d66dSSatoshi Sahara for ($i = 0; $i < $nattr; ++$i) { 4422186d66dSSatoshi Sahara $m = $matches[$i]; 4432186d66dSSatoshi Sahara $attrname = strtolower($m[1]); 4442186d66dSSatoshi Sahara $attrval = $m[2]; 4451e984f26SSatoshi Sahara // allow only recognized attributes 44692d5c8a1SSatoshi Sahara if (in_array($attrname, $allowed_keys, true)) { 4471e984f26SSatoshi Sahara // make sure that style attributes do not have a url in them 4482186d66dSSatoshi Sahara if ($attrname != 'style' || 4491e984f26SSatoshi Sahara (stristr($attrval, 'url') === false && 4501e984f26SSatoshi Sahara stristr($attrval, 'import') === false)) { 4512186d66dSSatoshi Sahara $clean_attr.= " $attrname=$attrval"; 4522186d66dSSatoshi Sahara } 4532186d66dSSatoshi Sahara } 4542186d66dSSatoshi Sahara } 4552186d66dSSatoshi Sahara return $clean_attr; 4562186d66dSSatoshi Sahara } 4572186d66dSSatoshi Sahara 4582186d66dSSatoshi Sahara} 459