109b43617SGerry Weißbach<?php 209b43617SGerry Weißbach/** 309b43617SGerry Weißbach * Condition Plugin: render a block if a condition if fullfilled 409b43617SGerry Weißbach * 509b43617SGerry Weißbach * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 609b43617SGerry Weißbach * @author Etienne Meleard <etienne.meleard@free.fr> 709b43617SGerry Weißbach * 809b43617SGerry Weißbach * 2009/06/08 : Creation 909b43617SGerry Weißbach * 2009/06/09 : Drop of the multi-value tests / creation of tester class system 1009b43617SGerry Weißbach * 2009/06/10 : Added tester class override to allow user to define custom tests 1109b43617SGerry Weißbach * 2010/06/09 : Changed $tester visibility to ensure compatibility with PHP4 12*1821ce0fSdwightmulcahy * 2025/08/08 : Fix deprecated syntax for PHP7.4+ compatibility 1309b43617SGerry Weißbach */ 1409b43617SGerry Weißbach 1509b43617SGerry Weißbachif(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/'); 1609b43617SGerry Weißbachif(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); 1709b43617SGerry Weißbach 1809b43617SGerry Weißbach/** 1909b43617SGerry Weißbach * All DokuWiki plugins to extend the parser/rendering mechanism 2009b43617SGerry Weißbach * need to inherit from this class 2109b43617SGerry Weißbach */ 2209b43617SGerry Weißbachclass syntax_plugin_condition extends DokuWiki_Syntax_Plugin { 2309b43617SGerry Weißbach // To be used by _processblocks to mix the test results together 24*1821ce0fSdwightmulcahy public $allowedoperators = array('\&\&', '\|\|', '\^', 'and', 'or', 'xor'); // plus '!' specific operator 2509b43617SGerry Weißbach 2609b43617SGerry Weißbach // Allowed test operators, their behavior is defined in the tester class, they are just defined here for recognition during parsing 27*1821ce0fSdwightmulcahy public $allowedtests = array(); 2809b43617SGerry Weißbach 2909b43617SGerry Weißbach // Allowed test keys 30*1821ce0fSdwightmulcahy public $allowedkeys = array(); 3109b43617SGerry Weißbach 3209b43617SGerry Weißbach // To store the tester object 33*1821ce0fSdwightmulcahy public $tester = null; 3409b43617SGerry Weißbach 3509b43617SGerry Weißbach /*function accepts($mode) { return true; } 3609b43617SGerry Weißbach function getAllowedTypes() { 3709b43617SGerry Weißbach return array('container', 'baseonly', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs'); // quick hack 3809b43617SGerry Weißbach }*/ 3909b43617SGerry Weißbach 4009b43617SGerry Weißbach function getType() { return 'container';} 4109b43617SGerry Weißbach function getPType() { return 'normal';} 4209b43617SGerry Weißbach function getSort() { return 5; } // condition is top priority 4309b43617SGerry Weißbach 4409b43617SGerry Weißbach // Connect pattern to lexer 4509b43617SGerry Weißbach function connectTo($mode){ 4609b43617SGerry Weißbach $this->Lexer->addEntryPattern('<if(?=.*?</if>)', $mode, 'plugin_condition'); 4709b43617SGerry Weißbach } 4809b43617SGerry Weißbach 4909b43617SGerry Weißbach function postConnect() { 5009b43617SGerry Weißbach $this->Lexer->addExitPattern('</if>', 'plugin_condition'); 5109b43617SGerry Weißbach } 5209b43617SGerry Weißbach 5309b43617SGerry Weißbach // Handle the match 54a9797ad3SAndreas Gohr function handle($match, $state, $pos, Doku_Handler $handler) { 5509b43617SGerry Weißbach if($state != DOKU_LEXER_UNMATCHED) return false; 5609b43617SGerry Weißbach 5709b43617SGerry Weißbach // Get allowed test operators 5809b43617SGerry Weißbach $this->_loadtester(); 5909b43617SGerry Weißbach if(!$this->tester) return array(array(), ''); 6009b43617SGerry Weißbach $this->allowedtests = $this->tester->getops(); 6109b43617SGerry Weißbach $this->allowedkeys = $this->tester->getkeys(); 6209b43617SGerry Weißbach 6309b43617SGerry Weißbach $blocks = array(); 6409b43617SGerry Weißbach $content = ''; 6509b43617SGerry Weißbach $this->_parse($match, $blocks, $content); 6609b43617SGerry Weißbach 6709b43617SGerry Weißbach return array($blocks, $content); 6809b43617SGerry Weißbach } 6909b43617SGerry Weißbach 7009b43617SGerry Weißbach // extracts condition / content 7109b43617SGerry Weißbach function _parse(&$match, &$b, &$ctn) { 7209b43617SGerry Weißbach $match = preg_replace('`^\s+`', '', $match); // trim heading whitespaces 7309b43617SGerry Weißbach $b = $this->_fetch_block($match, 0); 7409b43617SGerry Weißbach if($match != '') $ctn = preg_replace('`\n+$`', '', preg_replace('`^\n+`', '', preg_replace('`^>`', '', $match))); 7509b43617SGerry Weißbach return true; 7609b43617SGerry Weißbach } 7709b43617SGerry Weißbach 7809b43617SGerry Weißbach // fetch a condition block from buffer 7909b43617SGerry Weißbach function _fetch_block(&$match, $lvl=0) { 8009b43617SGerry Weißbach $match = preg_replace('`^\s+`', '', $match); // trim heading whitespaces 8109b43617SGerry Weißbach $instrs = array(); 8209b43617SGerry Weißbach $continue = true; 8309b43617SGerry Weißbach 84*1821ce0fSdwightmulcahy while(($match[0] != '>') && ($match != '') && (($lvl == 0) || ($match[0] != ')')) && $continue) { 8509b43617SGerry Weißbach $i = array('type' => null, 'key' => '', 'test' => '', 'value' => '', 'next' => ''); 8609b43617SGerry Weißbach if($this->_fetch_op($match, true)) { // ! heading equals block descending for first token 8709b43617SGerry Weißbach $i['type'] = 'nblock'; 8809b43617SGerry Weißbach $match = substr($match, 1); // remove heading ! 8909b43617SGerry Weißbach $i['value'] = $this->_fetch_block($match, $lvl+1); 9009b43617SGerry Weißbach }else if($this->_is_block($match)) { 9109b43617SGerry Weißbach $i['type'] = 'block'; 9209b43617SGerry Weißbach $match = substr($match, 1); // remove heading ( 9309b43617SGerry Weißbach $i['value'] = $this->_fetch_block($match, $lvl+1); 9409b43617SGerry Weißbach }else if($this->_is_key($match, $key)) { 9509b43617SGerry Weißbach $i['type'] = 'test'; 9609b43617SGerry Weißbach $i['key'] = $key; 9709b43617SGerry Weißbach $match = substr($match, strlen($key)); // remove heading key 9809b43617SGerry Weißbach if($this->_is_test($match, $test)) { 9909b43617SGerry Weißbach $i['test'] = $test; 10009b43617SGerry Weißbach $match = substr($match, strlen($test)); // remove heading test 10109b43617SGerry Weißbach if(($v = $this->_fetch_value($match)) !== null) $i['value'] = $v; 10209b43617SGerry Weißbach } 10309b43617SGerry Weißbach }else $match = preg_replace('`^[^>\s\(]+`', '', $match); // here dummy stuff remains 10409b43617SGerry Weißbach if($i['type']) { 10509b43617SGerry Weißbach if(($op = $this->_fetch_op($match, false)) !== null) { 10609b43617SGerry Weißbach $match = substr($match, strlen($op)); // remove heading op 10709b43617SGerry Weißbach $i['next'] = $op; 10809b43617SGerry Weißbach }else $continue = false; 10909b43617SGerry Weißbach $instrs[] = $i; 11009b43617SGerry Weißbach } 11109b43617SGerry Weißbach } 11209b43617SGerry Weißbach return $instrs; 11309b43617SGerry Weißbach } 11409b43617SGerry Weißbach 11509b43617SGerry Weißbach // test if buffer starts with new sub-block 11609b43617SGerry Weißbach function _is_block(&$match) { 11709b43617SGerry Weißbach $match = preg_replace('`^\s+`', '', $match); // trim heading whitespaces 11809b43617SGerry Weißbach return preg_match('`^\(`', $match); 11909b43617SGerry Weißbach } 12009b43617SGerry Weißbach 12109b43617SGerry Weißbach // test if buffer starts with a key ref 12209b43617SGerry Weißbach function _is_key(&$match, &$key) { 12309b43617SGerry Weißbach $match = preg_replace('`^\s+`', '', $match); // trim heading whitespaces 12409b43617SGerry Weißbach if(preg_match('`^([a-zA-Z0-9_-]+)`', $match, $r)) { 12509b43617SGerry Weißbach if(preg_match('`^'.$this->_preg_build_alternative($this->allowedkeys).'$`', $r[1])) { 12609b43617SGerry Weißbach $key = $r[1]; 12709b43617SGerry Weißbach return true; 12809b43617SGerry Weißbach } 12909b43617SGerry Weißbach } 13009b43617SGerry Weißbach return false; 13109b43617SGerry Weißbach } 13209b43617SGerry Weißbach 13309b43617SGerry Weißbach // build a pcre alternative escaped test from array 13409b43617SGerry Weißbach function _preg_build_alternative($choices) { 13509b43617SGerry Weißbach // $choices = array_map(create_function('$e', 'return preg_replace(\'`([^a-zA-Z0-9])`\', \'\\\\\\\\$1\', $e);'), $choices); 13609b43617SGerry Weißbach return '('.implode('|', $choices).')'; 13709b43617SGerry Weißbach } 13809b43617SGerry Weißbach 13909b43617SGerry Weißbach // tells if buffer starts with a test operator 14009b43617SGerry Weißbach function _is_test(&$match, &$test) { 14109b43617SGerry Weißbach $match = preg_replace('`^\s+`', '', $match); // trim heading whitespaces 14209b43617SGerry Weißbach if(preg_match('`^'.$this->_preg_build_alternative($this->allowedtests).'`', $match, $r)) { $test = $r[1]; return true; } 14309b43617SGerry Weißbach return false; 14409b43617SGerry Weißbach } 14509b43617SGerry Weißbach 14609b43617SGerry Weißbach // fetch value from buffer, handles value quoting 14709b43617SGerry Weißbach function _fetch_value(&$match) { 14809b43617SGerry Weißbach $match = preg_replace('`^\s+`', '', $match); // trim heading whitespaces 149*1821ce0fSdwightmulcahy if($match[0] == '"') { 15009b43617SGerry Weißbach $match = substr($match, 1); 15109b43617SGerry Weißbach $value = substr($match, 0, strpos($match, '"')); 15209b43617SGerry Weißbach $match = substr($match, strlen($value) + 1); 15309b43617SGerry Weißbach }else{ 15409b43617SGerry Weißbach $psp = strpos($match, ')'); 15509b43617SGerry Weißbach $wsp = strpos($match, ' '); 15609b43617SGerry Weißbach $esp = strpos($match, '>'); 15709b43617SGerry Weißbach $sp = 0; 15809b43617SGerry Weißbach $bug = false; 15909b43617SGerry Weißbach if(($wsp === false) && ($esp === false) && ($psp === false)) { 16009b43617SGerry Weißbach return null; // BUG 16109b43617SGerry Weißbach }else if(($wsp === false) && ($esp === false)) { 16209b43617SGerry Weißbach $sp = $psp; 16309b43617SGerry Weißbach }else if(($wsp === false) && ($psp === false)) { 16409b43617SGerry Weißbach $sp = $esp; 16509b43617SGerry Weißbach }else if(($psp === false) && ($esp === false)) { 16609b43617SGerry Weißbach $sp = $wsp; 16709b43617SGerry Weißbach }else if($wsp === false) { 16809b43617SGerry Weißbach $sp = min($esp, $psp); 16909b43617SGerry Weißbach }else if($esp === false) { 17009b43617SGerry Weißbach $sp = min($wsp, $psp); 17109b43617SGerry Weißbach }else if($psp === false) { 17209b43617SGerry Weißbach $sp = min($esp, $wsp); 17309b43617SGerry Weißbach }else $sp = min($wsp, $esp, $psp); 17409b43617SGerry Weißbach 17509b43617SGerry Weißbach $value = substr($match, 0, $sp); 17609b43617SGerry Weißbach $match = substr($match, strlen($value)); 17709b43617SGerry Weißbach } 17809b43617SGerry Weißbach return $value; 17909b43617SGerry Weißbach } 18009b43617SGerry Weißbach 18109b43617SGerry Weißbach // fetch a logic operator from buffer 18209b43617SGerry Weißbach function _fetch_op(&$match, $head=false) { 18309b43617SGerry Weißbach $match = preg_replace('`^\s+`', '', $match); // trim heading whitespaces 18409b43617SGerry Weißbach $ops = $this->allowedoperators; 18509b43617SGerry Weißbach if($head) $ops = array('!'); 18609b43617SGerry Weißbach if(preg_match('`^'.$this->_preg_build_alternative($ops).'`', $match, $r)) return $r[1]; 18709b43617SGerry Weißbach return null; 18809b43617SGerry Weißbach } 18909b43617SGerry Weißbach 19009b43617SGerry Weißbach /** 19109b43617SGerry Weißbach * Create output 19209b43617SGerry Weißbach */ 193a9797ad3SAndreas Gohr function render($mode, Doku_Renderer $renderer, $data) { 19409b43617SGerry Weißbach global $INFO; 19509b43617SGerry Weißbach if(count($data) != 2) return false; 19609b43617SGerry Weißbach if($mode == 'xhtml') { 19709b43617SGerry Weißbach global $ID; 19809b43617SGerry Weißbach // prevent caching to ensure good user data detection for tests 19909b43617SGerry Weißbach $renderer->info['cache'] = false; 20009b43617SGerry Weißbach 20109b43617SGerry Weißbach $blocks = $data[0]; 20209b43617SGerry Weißbach $content = $data[1]; 20309b43617SGerry Weißbach 20409b43617SGerry Weißbach // parsing content for a <else> statement 20509b43617SGerry Weißbach $else = ''; 20609b43617SGerry Weißbach if(strpos($content, '<else>') !== false) { 20709b43617SGerry Weißbach $i = explode('<else>', $content); 20809b43617SGerry Weißbach $content = $i[0]; 20909b43617SGerry Weißbach $else = implode('', array_slice($i, 1)); 21009b43617SGerry Weißbach } 21109b43617SGerry Weißbach 21209b43617SGerry Weißbach // Process condition blocks 21309b43617SGerry Weißbach $bug = false; 21409b43617SGerry Weißbach $this->_loadtester(); 21509b43617SGerry Weißbach $ok = $this->_processblocks($blocks, $bug); 21609b43617SGerry Weißbach 21709b43617SGerry Weißbach // Render content if all went well 21809b43617SGerry Weißbach $toc = $renderer->toc; 21909b43617SGerry Weißbach if(!$bug) { 22009b43617SGerry Weißbach $instr = p_get_instructions($ok ? $content : $else); 22109b43617SGerry Weißbach foreach($instr as $instruction) { 22209b43617SGerry Weißbach if ( in_array($instruction[0], array('document_start', 'document_end') ) ) continue; 223*1821ce0fSdwightmulcahy call_user_func_array([$renderer, $instruction[0]], $instruction[1]); 22409b43617SGerry Weißbach } 22509b43617SGerry Weißbach } 22609b43617SGerry Weißbach $renderer->toc = array_merge($toc, $renderer->toc); 22709b43617SGerry Weißbach 22809b43617SGerry Weißbach return true; 22909b43617SGerry Weißbach } 23009b43617SGerry Weißbach if($mode == 'metadata') { 23109b43617SGerry Weißbach global $ID; 23209b43617SGerry Weißbach // prevent caching to ensure good user data detection for tests 23309b43617SGerry Weißbach $renderer->info['cache'] = false; 23409b43617SGerry Weißbach 23509b43617SGerry Weißbach $blocks = $data[0]; 23609b43617SGerry Weißbach $content = $data[1]; 23709b43617SGerry Weißbach 23809b43617SGerry Weißbach // parsing content for a <else> statement 23909b43617SGerry Weißbach $else = ''; 24009b43617SGerry Weißbach if(strpos($content, '<else>') !== false) { 24109b43617SGerry Weißbach $i = explode('<else>', $content); 24209b43617SGerry Weißbach $content = $i[0]; 24309b43617SGerry Weißbach $else = implode('', array_slice($i, 1)); 24409b43617SGerry Weißbach } 24509b43617SGerry Weißbach 24609b43617SGerry Weißbach // Process condition blocks 24709b43617SGerry Weißbach $bug = false; 24809b43617SGerry Weißbach $this->_loadtester(); 24909b43617SGerry Weißbach $ok = $this->_processblocks($blocks, $bug); 25009b43617SGerry Weißbach // Render content if all went well 25109b43617SGerry Weißbach $metatoc = $renderer->meta['description']['tableofcontents']; 25209b43617SGerry Weißbach if(!$bug) { 25309b43617SGerry Weißbach $instr = p_get_instructions($ok ? $content : $else); 25409b43617SGerry Weißbach foreach($instr as $instruction) { 25509b43617SGerry Weißbach if ( in_array($instruction[0], array('document_start', 'document_end') ) ) continue; 256*1821ce0fSdwightmulcahy call_user_func_array([$renderer, $instruction[0]], $instruction[1]); 25709b43617SGerry Weißbach } 25809b43617SGerry Weißbach } 25909b43617SGerry Weißbach 260c406d8d7SGerry Weißbach if ( !is_array($renderer->meta['description']['tableofcontents']) ) { 261c406d8d7SGerry Weißbach $renderer->meta['description']['tableofcontents'] = array(); 262c406d8d7SGerry Weißbach } 263c406d8d7SGerry Weißbach 26409b43617SGerry Weißbach $renderer->meta['description']['tableofcontents'] = array_merge($metatoc, $renderer->meta['description']['tableofcontents']); 26509b43617SGerry Weißbach 26609b43617SGerry Weißbach return true; 26709b43617SGerry Weißbach } 26809b43617SGerry Weißbach return false; 26909b43617SGerry Weißbach } 27009b43617SGerry Weißbach 27109b43617SGerry Weißbach // Strips the heading <p> and trailing </p> added by p_render xhtml to acheive inline behavior 27209b43617SGerry Weißbach function _stripp($data) { 27309b43617SGerry Weißbach $data = preg_replace('`^\s*<p[^>]*>\s*`', '', $data); 27409b43617SGerry Weißbach $data = preg_replace('`\s*</p[^>]*>\s*$`', '', $data); 27509b43617SGerry Weißbach return $data; 27609b43617SGerry Weißbach } 27709b43617SGerry Weißbach 27809b43617SGerry Weißbach // evaluates the logical result from a set of blocks 27909b43617SGerry Weißbach function _processblocks($b, &$bug) { 28009b43617SGerry Weißbach for($i=0; $i<count($b); $i++) { 28109b43617SGerry Weißbach if(($b[$i]['type'] == 'block') || ($b[$i]['type'] == 'nblock')) { 28209b43617SGerry Weißbach $b[$i]['r'] = $this->_processblocks($b[$i]['value'], $bug); 28309b43617SGerry Weißbach if($b[$i]['type'] == 'nblock') $b[$i]['r'] = !$b[$i]['r']; 28409b43617SGerry Weißbach }else{ 28509b43617SGerry Weißbach $b[$i]['r'] = $this->_evaluate($b[$i], $bug); 28609b43617SGerry Weißbach } 28709b43617SGerry Weißbach } 28809b43617SGerry Weißbach if(!count($b)) $bug = true; // no condition in block 28909b43617SGerry Weißbach if($bug) return false; 29009b43617SGerry Weißbach 29109b43617SGerry Weißbach // assemble conditions 29209b43617SGerry Weißbach /* CUSTOMISATION : 29309b43617SGerry Weißbach * You can add custom mixing operators here, don't forget to add them to 29409b43617SGerry Weißbach * the "allowedoperators" list at the top of this file 29509b43617SGerry Weißbach */ 29609b43617SGerry Weißbach $r = $b[0]['r']; 29709b43617SGerry Weißbach for($i=1; $i<count($b); $i++) { 29809b43617SGerry Weißbach if($b[$i-1]['next'] == '') { 29909b43617SGerry Weißbach $bug = true; 30009b43617SGerry Weißbach return false; 30109b43617SGerry Weißbach } 30209b43617SGerry Weißbach switch($b[$i-1]['next']) { 30309b43617SGerry Weißbach case '&&' : 30409b43617SGerry Weißbach case 'and' : 30509b43617SGerry Weißbach $r &= $b[$i]['r']; 30609b43617SGerry Weißbach break; 30709b43617SGerry Weißbach case '||' : 30809b43617SGerry Weißbach case 'or' : 30909b43617SGerry Weißbach $r |= $b[$i]['r']; 31009b43617SGerry Weißbach break; 31109b43617SGerry Weißbach case '^' : 31209b43617SGerry Weißbach case 'xor' : 31309b43617SGerry Weißbach $r ^= $b[$i]['r']; 31409b43617SGerry Weißbach break; 31509b43617SGerry Weißbach } 31609b43617SGerry Weißbach } 31709b43617SGerry Weißbach return $r; 31809b43617SGerry Weißbach } 31909b43617SGerry Weißbach 32009b43617SGerry Weißbach // evaluates a single test, loads custom tests if class exists, default test set otherwise 32109b43617SGerry Weißbach function _evaluate($b, &$bug) { 32209b43617SGerry Weißbach if(!$this->tester) { 32309b43617SGerry Weißbach $bug = true; 32409b43617SGerry Weißbach return false; 32509b43617SGerry Weißbach } 32609b43617SGerry Weißbach return $this->tester->run($b, $bug); 32709b43617SGerry Weißbach } 32809b43617SGerry Weißbach 32909b43617SGerry Weißbach // tries to load user defined tester, then base tester if previous failed 33009b43617SGerry Weißbach function _loadtester() { 33109b43617SGerry Weißbach global $conf; 33209b43617SGerry Weißbach $this->tester = null; 33309b43617SGerry Weißbach include_once(DOKU_PLUGIN.'condition/base_tester.php'); 33409b43617SGerry Weißbach if(@file_exists(DOKU_INC.'lib/tpl/'.$conf['template'].'/condition_plugin_custom_tester.php')) { 33509b43617SGerry Weißbach include_once(DOKU_INC.'lib/tpl/'.$conf['template'].'/condition_plugin_custom_tester.php'); 33609b43617SGerry Weißbach if(class_exists('condition_plugin_custom_tester')) { 33709b43617SGerry Weißbach $this->tester = new condition_plugin_custom_tester(); 33809b43617SGerry Weißbach } 33909b43617SGerry Weißbach } 34009b43617SGerry Weißbach if(!$this->tester) { 34109b43617SGerry Weißbach if(class_exists('condition_plugin_base_tester')) { 34209b43617SGerry Weißbach $this->tester = new condition_plugin_base_tester(); 34309b43617SGerry Weißbach } 34409b43617SGerry Weißbach } 34509b43617SGerry Weißbach } 34609b43617SGerry Weißbach} //class 34709b43617SGerry Weißbach?> 348