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