1 <?php
2
3 4 5 6 7
8 abstract class rex_var
9 {
10 const ENV_FRONTEND = 1;
11 const ENV_BACKEND = 2;
12 const ENV_INPUT = 4;
13 const ENV_OUTPUT = 8;
14
15 private static $vars = [];
16 private static $env = null;
17 private static $context = null;
18 private static $contextData = null;
19
20 private $args = [];
21
22 private static $variableIndex = 0;
23
24 25 26 27 28 29 30 31 32 33
34 public static function parse($content, $env = null, $context = null, $contextData = null)
35 {
36 $env = (int) $env;
37
38 if (($env & self::ENV_INPUT) != self::ENV_INPUT) {
39 $env = $env | self::ENV_OUTPUT;
40 }
41
42 self::$env = $env;
43 self::$context = $context;
44 self::$contextData = $contextData;
45
46 self::$variableIndex = 0;
47
48 $tokens = token_get_all($content);
49 $countTokens = count($tokens);
50 $content = '';
51 for ($i = 0; $i < $countTokens; ++$i) {
52 $token = $tokens[$i];
53 if (is_string($token)) {
54 $content .= $token;
55 continue;
56 }
57
58 if (!in_array($token[0], [T_INLINE_HTML, T_CONSTANT_ENCAPSED_STRING, T_STRING, T_START_HEREDOC])) {
59 $content .= $token[1];
60 continue;
61 }
62
63 $add = $token[1];
64 switch ($token[0]) {
65 case T_INLINE_HTML:
66 $format = '<?= %s@@@INLINE_HTML_REPLACEMENT_END@@@';
67 $add = self::replaceVars($add, $format);
68 $add = preg_replace_callback('/@@@INLINE_HTML_REPLACEMENT_END@@@(\r?\n?)/', function (array $match) {
69 return $match[1]
70 ? ', "'.addcslashes($match[1], "\r\n").'" ?>'.$match[1]
71 : ' ?>';
72 }, $add);
73 break;
74
75 case T_CONSTANT_ENCAPSED_STRING:
76 $format = $token[1][0] == '"' ? '" . %s . "' : "' . %s . '";
77 $add = self::replaceVars($add, $format, false, $token[1][0]);
78
79 $start = substr($add, 0, 5);
80 $end = substr($add, -5);
81 if ($start == '"" . ' || $start == "'' . ") {
82 $add = substr($add, 5);
83 }
84 if ($end == ' . ""' || $end == " . ''") {
85 $add = substr($add, 0, -5);
86 }
87 break;
88
89 case T_STRING:
90 while (isset($tokens[++$i])
91 && (is_string($tokens[$i]) && in_array($tokens[$i], ['=', '[', ']'])
92 || in_array($tokens[$i][0], [T_WHITESPACE, T_STRING, T_CONSTANT_ENCAPSED_STRING, T_LNUMBER, T_ISSET]))
93 ) {
94 $add .= is_string($tokens[$i]) ? $tokens[$i] : $tokens[$i][1];
95 }
96 --$i;
97 $add = self::replaceVars($add);
98 break;
99
100 case T_START_HEREDOC:
101 while (isset($tokens[++$i]) && (is_string($tokens[$i]) || $tokens[$i][0] != T_END_HEREDOC)) {
102 $add .= is_string($tokens[$i]) ? $tokens[$i] : $tokens[$i][1];
103 }
104 --$i;
105 if (preg_match("/'(.*)'/", $token[1], $match)) {
106 $format = "\n" . $match[1] . "\n. %s . <<<'" . $match[1] . "'\n";
107 $add = self::replaceVars($add, $format);
108 } else {
109 $add = self::replaceVars($add, '{%s}', true);
110 }
111 break;
112 }
113
114 $content .= $add;
115 }
116 return $content;
117 }
118
119 120 121 122 123 124 125
126 private static function getVar($var)
127 {
128 if (!isset(self::$vars[$var])) {
129 $class = 'rex_var_' . strtolower(substr($var, 4));
130 if (!class_exists($class) || !is_subclass_of($class, self::class)) {
131 return false;
132 }
133 self::$vars[$var] = $class;
134 }
135 $class = self::$vars[$var];
136 return new $class();
137 }
138
139 140 141 142 143 144 145 146 147 148
149 private static function replaceVars($content, $format = '%s', $useVariables = false, $stripslashes = null)
150 {
151 $matches = self::getMatches($content);
152
153 if (empty($matches)) {
154 return $content;
155 }
156
157 $iterator = new AppendIterator();
158 $iterator->append(new ArrayIterator($matches));
159 $variables = [];
160 $replacements = [];
161
162 foreach ($iterator as $match) {
163 if (isset($replacements[$match[0]])) {
164 continue;
165 }
166
167 $var = self::getVar($match[1]);
168 $replaced = false;
169
170 if ($var !== false) {
171 $args = str_replace(['\[', '\]'], ['@@@OPEN_BRACKET@@@', '@@@CLOSE_BRACKET@@@'], $match[2]);
172 if ($stripslashes) {
173 $args = str_replace(['\\' . $stripslashes, '\\' . $stripslashes], $stripslashes, $args);
174 }
175 $var->setArgs($args);
176 if (($output = $var->getGlobalArgsOutput()) !== false) {
177 $output .= str_repeat("\n", max(0, substr_count($match[0], "\n") - substr_count($output, "\n") - substr_count($format, "\n")));
178 if ($useVariables) {
179 $replace = '$__rex_var_content_' . ++self::$variableIndex;
180 $variables[] = '/* '. $match[0] .' */ ' . $replace . ' = ' . $output;
181 } else {
182 $replace = '/* '. $match[0] .' */ '. $output;
183 }
184
185 $replacements[$match[0]] = sprintf($format, $replace);
186 $replaced = true;
187 }
188 }
189
190 if (!$replaced && $matches = self::getMatches($match[2])) {
191 $iterator->append(new ArrayIterator($matches));
192 }
193 }
194
195 if ($replacements) {
196 $content = strtr($content, $replacements);
197 }
198
199 if ($useVariables && !empty($variables)) {
200 $content = 'rex_var::nothing(' . implode(', ', $variables) . ') . ' . $content;
201 }
202
203 return $content;
204 }
205
206 207 208 209 210 211 212
213 private static function getMatches($content)
214 {
215 preg_match_all('/(REX_[A-Z_]+)\[((?:[^\[\]]|\\\\[\[\]]|(?R))*)(?<!\\\\)\]/s', $content, $matches, PREG_SET_ORDER);
216 return $matches;
217 }
218
219 220 221 222 223
224 private function setArgs($arg_string)
225 {
226 $this->args = rex_string::split($arg_string);
227 }
228
229 230 231 232 233 234 235 236
237 protected function hasArg($key, $defaultArg = false)
238 {
239 return isset($this->args[$key]) || $defaultArg && isset($this->args[0]);
240 }
241
242 243 244 245 246 247 248 249 250
251 protected function getArg($key, $default = null, $defaultArg = false)
252 {
253 if (!$this->hasArg($key, $defaultArg)) {
254 return $default;
255 }
256 return isset($this->args[$key]) ? $this->args[$key] : $this->args[0];
257 }
258
259 260 261 262 263 264 265 266 267
268 protected function getParsedArg($key, $default = null, $defaultArg = false)
269 {
270 if (!$this->hasArg($key, $defaultArg)) {
271 return $default;
272 }
273 $arg = isset($this->args[$key]) ? $this->args[$key] : $this->args[0];
274 $begin = '<<<addslashes>>>';
275 $end = '<<</addslashes>>>';
276 $arg = $begin . self::replaceVars($arg, $end . "' . %s . '" . $begin) . $end;
277 $arg = preg_replace_callback("@$begin(.*)$end@Us", function ($match) {
278 return addcslashes($match[1], "\'");
279 }, $arg);
280 $arg = str_replace(['@@@OPEN_BRACKET@@@', '@@@CLOSE_BRACKET@@@'], ['[', ']'], $arg);
281 return is_numeric($arg) ? $arg : "'$arg'";
282 }
283
284 285 286 287 288 289 290
291 protected function environmentIs($env)
292 {
293 return (self::$env & $env) == $env;
294 }
295
296 297 298 299 300
301 protected function getContext()
302 {
303 return self::$context;
304 }
305
306 307 308 309 310
311 protected function getContextData()
312 {
313 return self::$contextData;
314 }
315
316 317 318 319 320
321 abstract protected function getOutput();
322
323 324 325 326 327 328 329
330 protected static function quote($string)
331 {
332 $string = addcslashes($string, "\\'");
333 $string = preg_replace('/\v+/', '\' . "$0" . \'', $string);
334 $string = addcslashes($string, "\r\n");
335 return "'" . $string . "'";
336 }
337
338 339 340 341 342
343 private function getGlobalArgsOutput()
344 {
345 if (($content = $this->getOutput()) === false) {
346 return false;
347 }
348
349 if ($this->hasArg('callback')) {
350 $args = ["'subject' => " . $content];
351 foreach ($this->args as $key => $value) {
352 $args[] = "'$key' => " . $this->getParsedArg($key);
353 }
354 $args = '[' . implode(', ', $args) . ']';
355 return 'call_user_func(' . $this->getParsedArg('callback') . ', ' . $args . ')';
356 }
357
358 $prefix = $this->hasArg('prefix') ? $this->getParsedArg('prefix') . ' . ' : '';
359 $suffix = $this->hasArg('suffix') ? ' . ' . $this->getParsedArg('suffix') : '';
360 $instead = $this->hasArg('instead');
361 $ifempty = $this->hasArg('ifempty');
362 if ($prefix || $suffix || $instead || $ifempty) {
363 if ($instead) {
364 $if = $content;
365 $then = $this->getParsedArg('instead');
366 } else {
367 $if = '$__rex_var_content_' . ++self::$variableIndex . ' = ' . $content;
368 $then = '$__rex_var_content_' . self::$variableIndex;
369 }
370 if ($ifempty) {
371 return $prefix . '((' . $if . ') ? ' . $then . ' : ' . $this->getParsedArg('ifempty') . ')' . $suffix;
372 }
373 return '((' . $if . ') ? ' . $prefix . $then . $suffix . " : '')";
374 }
375 return $content;
376 }
377
378 379 380 381 382 383 384
385 public static function toArray($value)
386 {
387 $value = json_decode(htmlspecialchars_decode($value), true);
388 return is_array($value) ? $value : null;
389 }
390
391 392 393 394 395
396 public static function nothing()
397 {
398 return '';
399 }
400 }
401