1*09b43617SGerry Weißbach<?php 2*09b43617SGerry Weißbach/** 3*09b43617SGerry Weißbach * Condition Plugin: render a block if a condition if fullfilled 4*09b43617SGerry Weißbach * 5*09b43617SGerry Weißbach * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6*09b43617SGerry Weißbach * @author Etienne Meleard <etienne.meleard@free.fr> 7*09b43617SGerry Weißbach * 8*09b43617SGerry Weißbach * 2009/06/08 : Creation 9*09b43617SGerry Weißbach * 2009/06/09 : Drop of the multi-value tests / creation of tester class system 10*09b43617SGerry Weißbach * 2009/06/10 : Added tester class override to allow user to define custom tests 11*09b43617SGerry Weißbach * 2010/06/09 : Changed $tester visibility to ensure compatibility with PHP4 12*09b43617SGerry Weißbach */ 13*09b43617SGerry Weißbach 14*09b43617SGerry Weißbachif(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/'); 15*09b43617SGerry Weißbachif(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); 16*09b43617SGerry Weißbachrequire_once(DOKU_PLUGIN.'syntax.php'); 17*09b43617SGerry Weißbach 18*09b43617SGerry Weißbach/** 19*09b43617SGerry Weißbach * All DokuWiki plugins to extend the parser/rendering mechanism 20*09b43617SGerry Weißbach * need to inherit from this class 21*09b43617SGerry Weißbach */ 22*09b43617SGerry Weißbachclass syntax_plugin_condition extends DokuWiki_Syntax_Plugin { 23*09b43617SGerry Weißbach // To be used by _processblocks to mix the test results together 24*09b43617SGerry Weißbach var $allowedoperators = array('\&\&', '\|\|', '\^', 'and', 'or', 'xor'); // plus '!' specific operator 25*09b43617SGerry Weißbach 26*09b43617SGerry Weißbach // Allowed test operators, their behavior is defined in the tester class, they are just defined here for recognition during parsing 27*09b43617SGerry Weißbach var $allowedtests = array(); 28*09b43617SGerry Weißbach 29*09b43617SGerry Weißbach // Allowed test keys 30*09b43617SGerry Weißbach var $allowedkeys = array(); 31*09b43617SGerry Weißbach 32*09b43617SGerry Weißbach // To store the tester object 33*09b43617SGerry Weißbach var $tester = null; 34*09b43617SGerry Weißbach 35*09b43617SGerry Weißbach /*function accepts($mode) { return true; } 36*09b43617SGerry Weißbach function getAllowedTypes() { 37*09b43617SGerry Weißbach return array('container', 'baseonly', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs'); // quick hack 38*09b43617SGerry Weißbach }*/ 39*09b43617SGerry Weißbach 40*09b43617SGerry Weißbach function getType() { return 'container';} 41*09b43617SGerry Weißbach function getPType() { return 'normal';} 42*09b43617SGerry Weißbach function getSort() { return 5; } // condition is top priority 43*09b43617SGerry Weißbach 44*09b43617SGerry Weißbach // Connect pattern to lexer 45*09b43617SGerry Weißbach function connectTo($mode){ 46*09b43617SGerry Weißbach $this->Lexer->addEntryPattern('<if(?=.*?</if>)', $mode, 'plugin_condition'); 47*09b43617SGerry Weißbach } 48*09b43617SGerry Weißbach 49*09b43617SGerry Weißbach function postConnect() { 50*09b43617SGerry Weißbach $this->Lexer->addExitPattern('</if>', 'plugin_condition'); 51*09b43617SGerry Weißbach } 52*09b43617SGerry Weißbach 53*09b43617SGerry Weißbach // Handle the match 54*09b43617SGerry Weißbach function handle($match, $state, $pos, &$handler) { 55*09b43617SGerry Weißbach if($state != DOKU_LEXER_UNMATCHED) return false; 56*09b43617SGerry Weißbach 57*09b43617SGerry Weißbach // Get allowed test operators 58*09b43617SGerry Weißbach $this->_loadtester(); 59*09b43617SGerry Weißbach if(!$this->tester) return array(array(), ''); 60*09b43617SGerry Weißbach $this->allowedtests = $this->tester->getops(); 61*09b43617SGerry Weißbach $this->allowedkeys = $this->tester->getkeys(); 62*09b43617SGerry Weißbach 63*09b43617SGerry Weißbach $blocks = array(); 64*09b43617SGerry Weißbach $content = ''; 65*09b43617SGerry Weißbach $this->_parse($match, $blocks, $content); 66*09b43617SGerry Weißbach 67*09b43617SGerry Weißbach return array($blocks, $content); 68*09b43617SGerry Weißbach } 69*09b43617SGerry Weißbach 70*09b43617SGerry Weißbach // extracts condition / content 71*09b43617SGerry Weißbach function _parse(&$match, &$b, &$ctn) { 72*09b43617SGerry Weißbach $match = preg_replace('`^\s+`', '', $match); // trim heading whitespaces 73*09b43617SGerry Weißbach $b = $this->_fetch_block($match, 0); 74*09b43617SGerry Weißbach if($match != '') $ctn = preg_replace('`\n+$`', '', preg_replace('`^\n+`', '', preg_replace('`^>`', '', $match))); 75*09b43617SGerry Weißbach return true; 76*09b43617SGerry Weißbach } 77*09b43617SGerry Weißbach 78*09b43617SGerry Weißbach // fetch a condition block from buffer 79*09b43617SGerry Weißbach function _fetch_block(&$match, $lvl=0) { 80*09b43617SGerry Weißbach $match = preg_replace('`^\s+`', '', $match); // trim heading whitespaces 81*09b43617SGerry Weißbach $instrs = array(); 82*09b43617SGerry Weißbach $continue = true; 83*09b43617SGerry Weißbach 84*09b43617SGerry Weißbach while(($match{0} != '>') && ($match != '') && (($lvl == 0) || ($match{0} != ')')) && $continue) { 85*09b43617SGerry Weißbach $i = array('type' => null, 'key' => '', 'test' => '', 'value' => '', 'next' => ''); 86*09b43617SGerry Weißbach if($this->_fetch_op($match, true)) { // ! heading equals block descending for first token 87*09b43617SGerry Weißbach $i['type'] = 'nblock'; 88*09b43617SGerry Weißbach $match = substr($match, 1); // remove heading ! 89*09b43617SGerry Weißbach $i['value'] = $this->_fetch_block($match, $lvl+1); 90*09b43617SGerry Weißbach }else if($this->_is_block($match)) { 91*09b43617SGerry Weißbach $i['type'] = 'block'; 92*09b43617SGerry Weißbach $match = substr($match, 1); // remove heading ( 93*09b43617SGerry Weißbach $i['value'] = $this->_fetch_block($match, $lvl+1); 94*09b43617SGerry Weißbach }else if($this->_is_key($match, $key)) { 95*09b43617SGerry Weißbach $i['type'] = 'test'; 96*09b43617SGerry Weißbach $i['key'] = $key; 97*09b43617SGerry Weißbach $match = substr($match, strlen($key)); // remove heading key 98*09b43617SGerry Weißbach if($this->_is_test($match, $test)) { 99*09b43617SGerry Weißbach $i['test'] = $test; 100*09b43617SGerry Weißbach $match = substr($match, strlen($test)); // remove heading test 101*09b43617SGerry Weißbach if(($v = $this->_fetch_value($match)) !== null) $i['value'] = $v; 102*09b43617SGerry Weißbach } 103*09b43617SGerry Weißbach }else $match = preg_replace('`^[^>\s\(]+`', '', $match); // here dummy stuff remains 104*09b43617SGerry Weißbach if($i['type']) { 105*09b43617SGerry Weißbach if(($op = $this->_fetch_op($match, false)) !== null) { 106*09b43617SGerry Weißbach $match = substr($match, strlen($op)); // remove heading op 107*09b43617SGerry Weißbach $i['next'] = $op; 108*09b43617SGerry Weißbach }else $continue = false; 109*09b43617SGerry Weißbach $instrs[] = $i; 110*09b43617SGerry Weißbach } 111*09b43617SGerry Weißbach } 112*09b43617SGerry Weißbach return $instrs; 113*09b43617SGerry Weißbach } 114*09b43617SGerry Weißbach 115*09b43617SGerry Weißbach // test if buffer starts with new sub-block 116*09b43617SGerry Weißbach function _is_block(&$match) { 117*09b43617SGerry Weißbach $match = preg_replace('`^\s+`', '', $match); // trim heading whitespaces 118*09b43617SGerry Weißbach return preg_match('`^\(`', $match); 119*09b43617SGerry Weißbach } 120*09b43617SGerry Weißbach 121*09b43617SGerry Weißbach // test if buffer starts with a key ref 122*09b43617SGerry Weißbach function _is_key(&$match, &$key) { 123*09b43617SGerry Weißbach $match = preg_replace('`^\s+`', '', $match); // trim heading whitespaces 124*09b43617SGerry Weißbach if(preg_match('`^([a-zA-Z0-9_-]+)`', $match, $r)) { 125*09b43617SGerry Weißbach if(preg_match('`^'.$this->_preg_build_alternative($this->allowedkeys).'$`', $r[1])) { 126*09b43617SGerry Weißbach $key = $r[1]; 127*09b43617SGerry Weißbach return true; 128*09b43617SGerry Weißbach } 129*09b43617SGerry Weißbach } 130*09b43617SGerry Weißbach return false; 131*09b43617SGerry Weißbach } 132*09b43617SGerry Weißbach 133*09b43617SGerry Weißbach // build a pcre alternative escaped test from array 134*09b43617SGerry Weißbach function _preg_build_alternative($choices) { 135*09b43617SGerry Weißbach //$choices = array_map(create_function('$e', 'return preg_replace(\'`([^a-zA-Z0-9])`\', \'\\\\\\\\$1\', $e);'), $choices); 136*09b43617SGerry Weißbach return '('.implode('|', $choices).')'; 137*09b43617SGerry Weißbach } 138*09b43617SGerry Weißbach 139*09b43617SGerry Weißbach // tells if buffer starts with a test operator 140*09b43617SGerry Weißbach function _is_test(&$match, &$test) { 141*09b43617SGerry Weißbach $match = preg_replace('`^\s+`', '', $match); // trim heading whitespaces 142*09b43617SGerry Weißbach if(preg_match('`^'.$this->_preg_build_alternative($this->allowedtests).'`', $match, $r)) { $test = $r[1]; return true; } 143*09b43617SGerry Weißbach return false; 144*09b43617SGerry Weißbach } 145*09b43617SGerry Weißbach 146*09b43617SGerry Weißbach // fetch value from buffer, handles value quoting 147*09b43617SGerry Weißbach function _fetch_value(&$match) { 148*09b43617SGerry Weißbach $match = preg_replace('`^\s+`', '', $match); // trim heading whitespaces 149*09b43617SGerry Weißbach if($match{0} == '"') { 150*09b43617SGerry Weißbach $match = substr($match, 1); 151*09b43617SGerry Weißbach $value = substr($match, 0, strpos($match, '"')); 152*09b43617SGerry Weißbach $match = substr($match, strlen($value) + 1); 153*09b43617SGerry Weißbach }else{ 154*09b43617SGerry Weißbach $psp = strpos($match, ')'); 155*09b43617SGerry Weißbach $wsp = strpos($match, ' '); 156*09b43617SGerry Weißbach $esp = strpos($match, '>'); 157*09b43617SGerry Weißbach $sp = 0; 158*09b43617SGerry Weißbach $bug = false; 159*09b43617SGerry Weißbach if(($wsp === false) && ($esp === false) && ($psp === false)) { 160*09b43617SGerry Weißbach return null; // BUG 161*09b43617SGerry Weißbach }else if(($wsp === false) && ($esp === false)) { 162*09b43617SGerry Weißbach $sp = $psp; 163*09b43617SGerry Weißbach }else if(($wsp === false) && ($psp === false)) { 164*09b43617SGerry Weißbach $sp = $esp; 165*09b43617SGerry Weißbach }else if(($psp === false) && ($esp === false)) { 166*09b43617SGerry Weißbach $sp = $wsp; 167*09b43617SGerry Weißbach }else if($wsp === false) { 168*09b43617SGerry Weißbach $sp = min($esp, $psp); 169*09b43617SGerry Weißbach }else if($esp === false) { 170*09b43617SGerry Weißbach $sp = min($wsp, $psp); 171*09b43617SGerry Weißbach }else if($psp === false) { 172*09b43617SGerry Weißbach $sp = min($esp, $wsp); 173*09b43617SGerry Weißbach }else $sp = min($wsp, $esp, $psp); 174*09b43617SGerry Weißbach 175*09b43617SGerry Weißbach $value = substr($match, 0, $sp); 176*09b43617SGerry Weißbach $match = substr($match, strlen($value)); 177*09b43617SGerry Weißbach } 178*09b43617SGerry Weißbach return $value; 179*09b43617SGerry Weißbach } 180*09b43617SGerry Weißbach 181*09b43617SGerry Weißbach // fetch a logic operator from buffer 182*09b43617SGerry Weißbach function _fetch_op(&$match, $head=false) { 183*09b43617SGerry Weißbach $match = preg_replace('`^\s+`', '', $match); // trim heading whitespaces 184*09b43617SGerry Weißbach $ops = $this->allowedoperators; 185*09b43617SGerry Weißbach if($head) $ops = array('!'); 186*09b43617SGerry Weißbach if(preg_match('`^'.$this->_preg_build_alternative($ops).'`', $match, $r)) return $r[1]; 187*09b43617SGerry Weißbach return null; 188*09b43617SGerry Weißbach } 189*09b43617SGerry Weißbach 190*09b43617SGerry Weißbach /** 191*09b43617SGerry Weißbach * Create output 192*09b43617SGerry Weißbach */ 193*09b43617SGerry Weißbach function render($mode, &$renderer, $data) { 194*09b43617SGerry Weißbach global $INFO; 195*09b43617SGerry Weißbach if(count($data) != 2) return false; 196*09b43617SGerry Weißbach if($mode == 'xhtml') { 197*09b43617SGerry Weißbach global $ID; 198*09b43617SGerry Weißbach // prevent caching to ensure good user data detection for tests 199*09b43617SGerry Weißbach $renderer->info['cache'] = false; 200*09b43617SGerry Weißbach 201*09b43617SGerry Weißbach $blocks = $data[0]; 202*09b43617SGerry Weißbach $content = $data[1]; 203*09b43617SGerry Weißbach 204*09b43617SGerry Weißbach // parsing content for a <else> statement 205*09b43617SGerry Weißbach $else = ''; 206*09b43617SGerry Weißbach if(strpos($content, '<else>') !== false) { 207*09b43617SGerry Weißbach $i = explode('<else>', $content); 208*09b43617SGerry Weißbach $content = $i[0]; 209*09b43617SGerry Weißbach $else = implode('', array_slice($i, 1)); 210*09b43617SGerry Weißbach } 211*09b43617SGerry Weißbach 212*09b43617SGerry Weißbach // Process condition blocks 213*09b43617SGerry Weißbach $bug = false; 214*09b43617SGerry Weißbach $this->_loadtester(); 215*09b43617SGerry Weißbach $ok = $this->_processblocks($blocks, $bug); 216*09b43617SGerry Weißbach 217*09b43617SGerry Weißbach // Render content if all went well 218*09b43617SGerry Weißbach $toc = $renderer->toc; 219*09b43617SGerry Weißbach if(!$bug) { 220*09b43617SGerry Weißbach $instr = p_get_instructions($ok ? $content : $else); 221*09b43617SGerry Weißbach foreach($instr as $instruction) { 222*09b43617SGerry Weißbach if ( in_array($instruction[0], array('document_start', 'document_end') ) ) continue; 223*09b43617SGerry Weißbach call_user_func_array(array(&$renderer, $instruction[0]), $instruction[1]); 224*09b43617SGerry Weißbach } 225*09b43617SGerry Weißbach } 226*09b43617SGerry Weißbach $renderer->toc = array_merge($toc, $renderer->toc); 227*09b43617SGerry Weißbach 228*09b43617SGerry Weißbach return true; 229*09b43617SGerry Weißbach } 230*09b43617SGerry Weißbach if($mode == 'metadata') { 231*09b43617SGerry Weißbach global $ID; 232*09b43617SGerry Weißbach // prevent caching to ensure good user data detection for tests 233*09b43617SGerry Weißbach $renderer->info['cache'] = false; 234*09b43617SGerry Weißbach 235*09b43617SGerry Weißbach $blocks = $data[0]; 236*09b43617SGerry Weißbach $content = $data[1]; 237*09b43617SGerry Weißbach 238*09b43617SGerry Weißbach // parsing content for a <else> statement 239*09b43617SGerry Weißbach $else = ''; 240*09b43617SGerry Weißbach if(strpos($content, '<else>') !== false) { 241*09b43617SGerry Weißbach $i = explode('<else>', $content); 242*09b43617SGerry Weißbach $content = $i[0]; 243*09b43617SGerry Weißbach $else = implode('', array_slice($i, 1)); 244*09b43617SGerry Weißbach } 245*09b43617SGerry Weißbach 246*09b43617SGerry Weißbach // Process condition blocks 247*09b43617SGerry Weißbach $bug = false; 248*09b43617SGerry Weißbach $this->_loadtester(); 249*09b43617SGerry Weißbach $ok = $this->_processblocks($blocks, $bug); 250*09b43617SGerry Weißbach // Render content if all went well 251*09b43617SGerry Weißbach $metatoc = $renderer->meta['description']['tableofcontents']; 252*09b43617SGerry Weißbach if(!$bug) { 253*09b43617SGerry Weißbach $instr = p_get_instructions($ok ? $content : $else); 254*09b43617SGerry Weißbach foreach($instr as $instruction) { 255*09b43617SGerry Weißbach if ( in_array($instruction[0], array('document_start', 'document_end') ) ) continue; 256*09b43617SGerry Weißbach call_user_func_array(array(&$renderer, $instruction[0]), $instruction[1]); 257*09b43617SGerry Weißbach } 258*09b43617SGerry Weißbach } 259*09b43617SGerry Weißbach 260*09b43617SGerry Weißbach if ( !is_array($renderer->meta['description']['tableofcontents']) ) { 261*09b43617SGerry Weißbach $renderer->meta['description']['tableofcontents'] = array(); 262*09b43617SGerry Weißbach } 263*09b43617SGerry Weißbach 264*09b43617SGerry Weißbach $renderer->meta['description']['tableofcontents'] = array_merge($metatoc, $renderer->meta['description']['tableofcontents']); 265*09b43617SGerry Weißbach 266*09b43617SGerry Weißbach return true; 267*09b43617SGerry Weißbach } 268*09b43617SGerry Weißbach return false; 269*09b43617SGerry Weißbach } 270*09b43617SGerry Weißbach 271*09b43617SGerry Weißbach // Strips the heading <p> and trailing </p> added by p_render xhtml to acheive inline behavior 272*09b43617SGerry Weißbach function _stripp($data) { 273*09b43617SGerry Weißbach $data = preg_replace('`^\s*<p[^>]*>\s*`', '', $data); 274*09b43617SGerry Weißbach $data = preg_replace('`\s*</p[^>]*>\s*$`', '', $data); 275*09b43617SGerry Weißbach return $data; 276*09b43617SGerry Weißbach } 277*09b43617SGerry Weißbach 278*09b43617SGerry Weißbach // evaluates the logical result from a set of blocks 279*09b43617SGerry Weißbach function _processblocks($b, &$bug) { 280*09b43617SGerry Weißbach for($i=0; $i<count($b); $i++) { 281*09b43617SGerry Weißbach if(($b[$i]['type'] == 'block') || ($b[$i]['type'] == 'nblock')) { 282*09b43617SGerry Weißbach $b[$i]['r'] = $this->_processblocks($b[$i]['value'], $bug); 283*09b43617SGerry Weißbach if($b[$i]['type'] == 'nblock') $b[$i]['r'] = !$b[$i]['r']; 284*09b43617SGerry Weißbach }else{ 285*09b43617SGerry Weißbach $b[$i]['r'] = $this->_evaluate($b[$i], $bug); 286*09b43617SGerry Weißbach } 287*09b43617SGerry Weißbach } 288*09b43617SGerry Weißbach if(!count($b)) $bug = true; // no condition in block 289*09b43617SGerry Weißbach if($bug) return false; 290*09b43617SGerry Weißbach 291*09b43617SGerry Weißbach // assemble conditions 292*09b43617SGerry Weißbach /* CUSTOMISATION : 293*09b43617SGerry Weißbach * You can add custom mixing operators here, don't forget to add them to 294*09b43617SGerry Weißbach * the "allowedoperators" list at the top of this file 295*09b43617SGerry Weißbach */ 296*09b43617SGerry Weißbach $r = $b[0]['r']; 297*09b43617SGerry Weißbach for($i=1; $i<count($b); $i++) { 298*09b43617SGerry Weißbach if($b[$i-1]['next'] == '') { 299*09b43617SGerry Weißbach $bug = true; 300*09b43617SGerry Weißbach return false; 301*09b43617SGerry Weißbach } 302*09b43617SGerry Weißbach switch($b[$i-1]['next']) { 303*09b43617SGerry Weißbach case '&&' : 304*09b43617SGerry Weißbach case 'and' : 305*09b43617SGerry Weißbach $r &= $b[$i]['r']; 306*09b43617SGerry Weißbach break; 307*09b43617SGerry Weißbach case '||' : 308*09b43617SGerry Weißbach case 'or' : 309*09b43617SGerry Weißbach $r |= $b[$i]['r']; 310*09b43617SGerry Weißbach break; 311*09b43617SGerry Weißbach case '^' : 312*09b43617SGerry Weißbach case 'xor' : 313*09b43617SGerry Weißbach $r ^= $b[$i]['r']; 314*09b43617SGerry Weißbach break; 315*09b43617SGerry Weißbach } 316*09b43617SGerry Weißbach } 317*09b43617SGerry Weißbach return $r; 318*09b43617SGerry Weißbach } 319*09b43617SGerry Weißbach 320*09b43617SGerry Weißbach // evaluates a single test, loads custom tests if class exists, default test set otherwise 321*09b43617SGerry Weißbach function _evaluate($b, &$bug) { 322*09b43617SGerry Weißbach if(!$this->tester) { 323*09b43617SGerry Weißbach $bug = true; 324*09b43617SGerry Weißbach return false; 325*09b43617SGerry Weißbach } 326*09b43617SGerry Weißbach return $this->tester->run($b, $bug); 327*09b43617SGerry Weißbach } 328*09b43617SGerry Weißbach 329*09b43617SGerry Weißbach // tries to load user defined tester, then base tester if previous failed 330*09b43617SGerry Weißbach function _loadtester() { 331*09b43617SGerry Weißbach global $conf; 332*09b43617SGerry Weißbach $this->tester = null; 333*09b43617SGerry Weißbach include_once(DOKU_PLUGIN.'condition/base_tester.php'); 334*09b43617SGerry Weißbach if(@file_exists(DOKU_INC.'lib/tpl/'.$conf['template'].'/condition_plugin_custom_tester.php')) { 335*09b43617SGerry Weißbach include_once(DOKU_INC.'lib/tpl/'.$conf['template'].'/condition_plugin_custom_tester.php'); 336*09b43617SGerry Weißbach if(class_exists('condition_plugin_custom_tester')) { 337*09b43617SGerry Weißbach $this->tester = new condition_plugin_custom_tester(); 338*09b43617SGerry Weißbach } 339*09b43617SGerry Weißbach } 340*09b43617SGerry Weißbach if(!$this->tester) { 341*09b43617SGerry Weißbach if(class_exists('condition_plugin_base_tester')) { 342*09b43617SGerry Weißbach $this->tester = new condition_plugin_base_tester(); 343*09b43617SGerry Weißbach } 344*09b43617SGerry Weißbach } 345*09b43617SGerry Weißbach } 346*09b43617SGerry Weißbach} //class 347*09b43617SGerry Weißbach?> 348