156 lines
5.5 KiB
PHP
156 lines
5.5 KiB
PHP
<?php
|
||
/**
|
||
* Kleiner, sicherer Formel-Interpreter (kein eval).
|
||
* Unterstützt: Zahlen, Variablen, + - * /, Klammern, unäres Minus,
|
||
* Vergleiche (>= <= > < == !=), Ternär (a ? b : c) und die Funktionen
|
||
* sqrt, pow, abs, min, max, round, floor, ceil.
|
||
*
|
||
* Damit rechnet das Plugin mit GENAU DER Formel, die in Directus hinterlegt
|
||
* ist – Änderungen an der Formel wirken automatisch mit.
|
||
*/
|
||
|
||
if (!defined('ABSPATH')) exit;
|
||
|
||
/** Zerlegt den Ausdruck in Tokens. */
|
||
function skrift_formel_tokens($ausdruck) {
|
||
$s = (string) $ausdruck;
|
||
$s = str_replace('Math.', '', $s); // Math.sqrt(...) → sqrt(...)
|
||
$s = preg_replace('/%(\w+)%/', '$1', $s); // %qty% → qty
|
||
$tokens = array();
|
||
$pos = 0; $len = strlen($s);
|
||
while ($pos < $len) {
|
||
$c = $s[$pos];
|
||
if (ctype_space($c)) { $pos++; continue; }
|
||
if (ctype_digit($c) || ($c === '.' && $pos + 1 < $len && ctype_digit($s[$pos + 1]))) {
|
||
$start = $pos;
|
||
while ($pos < $len && (ctype_digit($s[$pos]) || $s[$pos] === '.')) $pos++;
|
||
$tokens[] = array('num', (float) substr($s, $start, $pos - $start));
|
||
continue;
|
||
}
|
||
if (ctype_alpha($c) || $c === '_') {
|
||
$start = $pos;
|
||
while ($pos < $len && (ctype_alnum($s[$pos]) || $s[$pos] === '_')) $pos++;
|
||
$tokens[] = array('id', substr($s, $start, $pos - $start));
|
||
continue;
|
||
}
|
||
$zwei = substr($s, $pos, 2);
|
||
if (in_array($zwei, array('>=', '<=', '==', '!='), true)) { $tokens[] = array('op', $zwei); $pos += 2; continue; }
|
||
$tokens[] = array('op', $c); $pos++;
|
||
}
|
||
return $tokens;
|
||
}
|
||
|
||
function skrift_formel_ist($t, $i, $wert) {
|
||
return $i < count($t) && $t[$i][0] === 'op' && $t[$i][1] === $wert;
|
||
}
|
||
|
||
function skrift_formel_fn($name, $args) {
|
||
$a0 = isset($args[0]) ? (float) $args[0] : 0.0;
|
||
switch ($name) {
|
||
case 'sqrt': return $a0 < 0 ? 0.0 : sqrt($a0);
|
||
case 'pow': return pow($a0, isset($args[1]) ? (float) $args[1] : 0.0);
|
||
case 'abs': return abs($a0);
|
||
case 'min': return empty($args) ? 0.0 : min($args);
|
||
case 'max': return empty($args) ? 0.0 : max($args);
|
||
case 'round': return round($a0);
|
||
case 'floor': return floor($a0);
|
||
case 'ceil': return ceil($a0);
|
||
}
|
||
return 0.0;
|
||
}
|
||
|
||
function skrift_formel_primary(&$i, $t, $v) {
|
||
if ($i >= count($t)) return 0.0;
|
||
$tk = $t[$i];
|
||
if ($tk[0] === 'num') { $i++; return $tk[1]; }
|
||
if ($tk[0] === 'id') {
|
||
$name = $tk[1]; $i++;
|
||
if (skrift_formel_ist($t, $i, '(')) {
|
||
$i++; $args = array();
|
||
if (!skrift_formel_ist($t, $i, ')')) {
|
||
$args[] = skrift_formel_ternary($i, $t, $v);
|
||
while (skrift_formel_ist($t, $i, ',')) { $i++; $args[] = skrift_formel_ternary($i, $t, $v); }
|
||
}
|
||
if (skrift_formel_ist($t, $i, ')')) $i++;
|
||
return skrift_formel_fn($name, $args);
|
||
}
|
||
return isset($v[$name]) ? (float) $v[$name] : 0.0;
|
||
}
|
||
if ($tk[0] === 'op' && $tk[1] === '(') {
|
||
$i++; $r = skrift_formel_ternary($i, $t, $v);
|
||
if (skrift_formel_ist($t, $i, ')')) $i++;
|
||
return $r;
|
||
}
|
||
$i++; return 0.0;
|
||
}
|
||
|
||
function skrift_formel_unary(&$i, $t, $v) {
|
||
if (skrift_formel_ist($t, $i, '-')) { $i++; return -skrift_formel_unary($i, $t, $v); }
|
||
if (skrift_formel_ist($t, $i, '+')) { $i++; return skrift_formel_unary($i, $t, $v); }
|
||
return skrift_formel_primary($i, $t, $v);
|
||
}
|
||
|
||
function skrift_formel_mul(&$i, $t, $v) {
|
||
$l = skrift_formel_unary($i, $t, $v);
|
||
while (skrift_formel_ist($t, $i, '*') || skrift_formel_ist($t, $i, '/')) {
|
||
$op = $t[$i][1]; $i++;
|
||
$r = skrift_formel_unary($i, $t, $v);
|
||
$l = $op === '*' ? $l * $r : ($r == 0 ? 0.0 : $l / $r);
|
||
}
|
||
return $l;
|
||
}
|
||
|
||
function skrift_formel_add(&$i, $t, $v) {
|
||
$l = skrift_formel_mul($i, $t, $v);
|
||
while (skrift_formel_ist($t, $i, '+') || skrift_formel_ist($t, $i, '-')) {
|
||
$op = $t[$i][1]; $i++;
|
||
$r = skrift_formel_mul($i, $t, $v);
|
||
$l = $op === '+' ? $l + $r : $l - $r;
|
||
}
|
||
return $l;
|
||
}
|
||
|
||
function skrift_formel_compare(&$i, $t, $v) {
|
||
$l = skrift_formel_add($i, $t, $v);
|
||
while ($i < count($t) && $t[$i][0] === 'op'
|
||
&& in_array($t[$i][1], array('>=', '<=', '==', '!=', '>', '<'), true)) {
|
||
$op = $t[$i][1]; $i++;
|
||
$r = skrift_formel_add($i, $t, $v);
|
||
switch ($op) {
|
||
case '>=': $l = $l >= $r; break;
|
||
case '<=': $l = $l <= $r; break;
|
||
case '>': $l = $l > $r; break;
|
||
case '<': $l = $l < $r; break;
|
||
case '==': $l = $l == $r; break;
|
||
case '!=': $l = $l != $r; break;
|
||
}
|
||
}
|
||
return $l;
|
||
}
|
||
|
||
function skrift_formel_ternary(&$i, $t, $v) {
|
||
$c = skrift_formel_compare($i, $t, $v);
|
||
if (skrift_formel_ist($t, $i, '?')) {
|
||
$i++;
|
||
$a = skrift_formel_ternary($i, $t, $v);
|
||
if (skrift_formel_ist($t, $i, ':')) $i++;
|
||
$b = skrift_formel_ternary($i, $t, $v);
|
||
return $c ? $a : $b;
|
||
}
|
||
return $c;
|
||
}
|
||
|
||
/** Wertet den Ausdruck aus. Bei Problemen wird $fallback zurückgegeben. */
|
||
function skrift_formel_eval($ausdruck, $vars, $fallback = 1.0) {
|
||
if (!is_string($ausdruck) || trim($ausdruck) === '') return $fallback;
|
||
try {
|
||
$t = skrift_formel_tokens($ausdruck);
|
||
$i = 0;
|
||
$r = (float) skrift_formel_ternary($i, $t, $vars);
|
||
if (!is_finite($r)) return $fallback;
|
||
return $r;
|
||
} catch (Throwable $e) {
|
||
return $fallback;
|
||
}
|
||
}
|