-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathhelper.php
More file actions
277 lines (246 loc) · 10.2 KB
/
helper.php
File metadata and controls
277 lines (246 loc) · 10.2 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
<?php
/**
* DokuWiki Plugin parserfunctions (Helper Component)
*
* @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
* @author Daniel "Nerun" Rodrigues <danieldiasr@gmail.com>
* @created Tue, 01 jul 2025 15:06:42 -0300
*/
if (!defined('DOKU_INC')) die();
class helper_plugin_parserfunctions extends DokuWiki_Plugin {
/**
* Processes raw function input into normalized parameters, handling pipe escapes
*
* Safely splits the input string by pipes (`|`) while respecting escaped pipes (`%%|%%`).
* Performs whitespace trimming on all resulting parameters. This enables DokuWiki's
* standard pipe syntax while supporting escaped pipes in parameter values.
*
* @param string $input The raw function input between delimiters (e.g. "a|b%%|%%c|d")
*
* @return array Normalized parameters with:
* - Escaped pipes restored (`%%TEMP_PIPE%%` → `%%|%%`)
* - Whitespace trimmed from both ends
* - Empty strings preserved as valid parameters
*
* @example Basic usage:
* parseParameters("a|b|c") → ["a", "b", "c"]
*
* @example With escaped pipe:
* parseParameters("a|b%%|%%c|d") → ["a", "b|c", "d"]
*
* @example With whitespace:
* parseParameters(" a | b ") → ["a", "b"]
*
* @example Empty parameters:
* parseParameters("a||b") → ["a", "", "b"]
*
* @note Preserves DokuWiki's standard %% escape syntax
* @note Empty strings are valid parameters (unlike array_filter)
* @note Trims only outer whitespace (inner spaces remain)
*/
public function parseParameters($input) {
// 1) Replace escaped pipes with temporary marker
$input = str_replace('%%|%%', '%%TEMP_PIPE%%', $input);
// 2) Split by unescaped pipes
$params = explode('|', $input);
// 3) Restore escaped pipes
$params = array_map(function($param) {
return str_replace('%%TEMP_PIPE%%', '%%|%%', $param);
}, $params);
// 4) Remove whitespace
return array_map('trim', $params);
}
/**
* Parses parameters for a SWITCH parser function and structures them for evaluation
*
* Processes the parameters into cases, test value, and default value, with support for:
* - Explicit value cases (`case = value`)
* - Fallthrough behavior (cases without values inherit the last defined value)
* - Both explicit (`#default = value`) and implicit default values (last parameter)
* - Whitespace normalization (trim) for all keys and values
* - Escaped equals signs (`%%=%%`) in values
*
* @param array $params The raw parameters from the parser function call:
* - First element: The test value to compare against cases
* - Subsequent elements: Cases in format "case = value" or fallthrough/default markers
*
* @return array Structured data with:
* - 'cases': Associative array of [case => value] pairs
* - 'test': Normalized test value (with whitespace trimmed)
* - 'default': The default value (either explicit #default or last parameter)
*
* @example For input [" test ", "a=1", "b", "c=3", "#default=final"]
* Returns:
* [
* 'cases' => ['a' => '1', 'b' => '3', 'c' => '3'],
* 'test' => 'test',
* 'default' => 'final'
* ]
*
* @example Fallthrough behavior:
* ["val", "a=1", "b", "c=2"] produces:
* [
* 'cases' => ['a' => '1', 'b' => '1', 'c' => '2'],
* 'test' => 'val',
* 'default' => '2'
* ]
*
* @note Escaped equals signs (`%%=%%`) in values are preserved
* @note All case keys and test values are trimmed of whitespace
* @note Empty strings are valid as both test values and case values
*/
public function parseSwitchCases($params) {
$cases = [];
$default = null;
$testString = null;
$lastValue = null;
foreach ($params as $param) {
$param = str_replace('%%=%%', '%%TEMP_EQUAL%%', $param);
$parts = explode('=', $param, 2);
$parts = array_map('trim', $parts);
if (count($parts) === 2) {
// Case with explicit value (case = value)
$parts[1] = str_replace('%%TEMP_EQUAL%%', '%%=%%', $parts[1]);
$cases[$parts[0]] = $parts[1];
$lastValue = $parts[1];
} else {
// Case without explicit value (fallthrough or default)
$parts[0] = str_replace('%%TEMP_EQUAL%%', '%%=%%', $parts[0]);
if ($testString === null) {
$testString = trim($parts[0]); // First parameter is the test value
} elseif (trim($parts[0]) === '#default') {
$default = $lastValue; // Explicit default
} else {
$cases[trim($parts[0])] = $lastValue; // Fallthrough - uses last defined value
}
}
}
return [
'cases' => $cases,
'test' => $testString,
'default' => $default ?? $lastValue // Implicit default is the last value
];
}
/**
* Checks for the existence of a folder (namespace) or a file (media or page)
*
* Accepts:
* - Absolute or relative filesystem paths
* - DokuWiki page/media IDs (e.g. "wiki:start", "wiki:image.png")
* - DokuWiki namespaces (must end with a colon, e.g. "wiki:")
*
* @param string $target The identifier or path to check
* @return bool True if it exists (file, page, media, or namespace), false otherwise
*/
public function checkExistence($target) {
// Normalize spaces around ':', transform "wiki : help" → "wiki:help"
$target = preg_replace('/\s*:\s*/', ':', $target);
// If it is a real absolute or relative path, test as file or folder
if (file_exists($target)) {
return true;
}
// If path started with '/', try as relative to DOKU_INC by removing '/'
if (strlen($target) > 0 && $target[0] === '/') {
$relativePath = ltrim($target, '/');
$fullPath = DOKU_INC . $relativePath;
if (file_exists($fullPath)) {
return true;
}
}
// Try as DokuWiki page
if (page_exists($target)) {
return true;
}
// Try as DokuWiki media
if (file_exists(mediaFN($target))) {
return true;
}
// Try as namespace (directory inside data/pages/)
$namespacePath = str_replace(':', '/', $target);
$namespaceDir = DOKU_INC . 'data/pages/' . $namespacePath;
if (is_dir($namespaceDir)) {
return true;
}
return false;
}
/**
* Escape sequence handling (for backwards compatibility)
*
* To add more escapes, please refer to:
* https://www.freeformatter.com/html-entities.html
*
* Before 2025-01-18, escape sequences had to use "&#NUMBER;" instead of
* "&#;NUMBER;", because "#" was not escaped.
*
* After 2025-01-18, the "#" can be typed directly, and does not need to be
* escaped. So use the normal spelling for HTML entity codes ("="
* instead of "&#61;") when adding NEW escapes.
*
* Additionally, after 2025-01-18, '=', '|', '{' and '}' signs can be
* escaped only by wrapping them in '%%', following the standard DokuWiki
* syntax. So, the escapes below are DEPRECATED, but kept for backwards
* compatibility.
*
*/
public function processEscapes($text) {
// DEPRECATED, but kept for backwards compatibility:
$escapes = [
"&#61;" => "=",
"&#123;" => "%%{%%",
"&#124;" => "|",
"&#125;" => "%%}%%",
"#" => "#" // Always leave this as the last element!
];
foreach ($escapes as $key => $value) {
$text = str_replace($key, $value, $text);
}
return $text;
}
/**
* Format error messages consistently
*/
public function formatError($type, $function, $messageKey) {
$wrapPluginExists = file_exists(DOKU_INC . 'lib/plugins/wrap');
$errorMsg = '**' . $this->getLang('error') . ' ' . $function . ': '
. $this->getLang($messageKey) . '**';
if ($wrapPluginExists) {
return "<wrap $type>$errorMsg</wrap>";
}
return $errorMsg;
}
/**
* Evaluates a mathematical expression consistently
*/
public function evaluateMathExpression($expr) {
$funcName = 'expr';
$expr = trim($expr);
// Rejects characters outside the permitted set
if (!preg_match('/^(
\s*|
\b(?:and|or|xor|not)\b| # Reserved words first
==|!=|<=|>=|<|>| # Comparisons
\+|\-|\*|\/|%| # Arithmetic
\(|\)| # Parentheses
&&|\|\||!| # Symbolic logics
[0-9]+(\.[0-9]+)?([eE][\+\-]?[0-9]+)? # Numbers with period and exponent
)+$/ix'
, $expr)) {
return $this->formatError('alert', $funcName, 'invalid_expression');
}
try {
$expr = preg_replace('/\bnot\b/i', '!', $expr);
// Simple evaluation
$result = eval('return (' . $expr . ');');
if (!is_numeric($result) || is_infinite($result) || is_nan($result)) {
if (is_bool($result)) {
return $result ? 1 : 0;
} else {
return $this->formatError('alert', $funcName, 'undefined_result');
}
}
return $result;
} catch (Throwable $e) {
return $this->formatError('alert', $funcName, 'evaluation_error');
}
}
}