Files
skrift-preisrechner/formel.php
2026-08-24 12:14:00 +02:00

156 lines
5.5 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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;
}
}