xref: /plugin/condition/syntax.php (revision 46e822a4803d96b8dcd16061c1dd0d9099f211ab)
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