feat: VariableValue prüft Variablenwerte je Typ (numbers, strings, images, links, fonts)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-10-19 13:59:33 +02:00
co-authored by Claude Sonnet 5.5
parent 39a5782562
commit 5bb25e1e8f
2 changed files with 215 additions and 0 deletions
@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace LIT\MCP\Integrations\Divi\Design;
defined('ABSPATH') || exit;
/**
* Checks the value of a global variable before it goes to Divi (ruling V6, D7 principle). The type "gradients"
* (new in Divi 5.15) is passed through by VariableBook, but the tools never create or change it. The check of fonts
* against Divi's font list follows with 4d. Pure PHP.
*/
final class VariableValue
{
/** Types the tools may create (V6). */
public const TYPES = ['numbers', 'strings', 'images', 'links', 'fonts'];
private const NUMBERS_MAX = 200;
private const STRINGS_MAX = 2000;
private const URL_MAX = 2000;
private const FONT_MAX = 100;
/** Control characters except the line feed. */
private const CONTROL = '/[\x00-\x09\x0B-\x1F\x7F]/';
private const URL = '/^(?:https?:\/\/[^\s<>"]+|\/[^\s<>"]*|mailto:[^\s<>"]+|tel:[^\s<>"]+)$/iD';
private const WEB_URL = '/^https?:\/\/[^\s<>"]+$/iD';
/** @return string|null Null when $value is valid for $type, else a German message. */
public static function check(string $type, string $value): ?string
{
if (! in_array($type, self::TYPES, true)) {
return sprintf('Typ "%s" ist nicht erlaubt; erlaubt: %s.', $type, implode(', ', self::TYPES));
}
if ($value === '') {
return sprintf('Wert fehlt (Typ "%s").', $type);
}
if (! mb_check_encoding($value, 'UTF-8')) {
return 'Wert ist kein gültiges UTF-8.';
}
return match ($type) {
'numbers' => self::numbers($value),
'strings' => self::strings($value),
'links' => self::links($value),
'images' => self::images($value),
default => self::font($value),
};
}
private static function numbers(string $value): ?string
{
if (mb_strlen($value) > self::NUMBERS_MAX || preg_match('/[;{}<>\x00-\x1F\x7F]/', $value) === 1) {
return 'Zahlenwert: ein CSS-Ausdruck mit höchstens 200 Zeichen ohne ; { } < > und Zeilenumbruch '
. '(z. B. 8px oder clamp(1.5rem, 4vw, 2.5rem)).';
}
return null;
}
private static function strings(string $value): ?string
{
if (mb_strlen($value) > self::STRINGS_MAX || preg_match(self::CONTROL, $value) === 1) {
return 'Textwert: höchstens 2000 Zeichen, keine Steuerzeichen außer dem Zeilenumbruch.';
}
return null;
}
private static function links(string $value): ?string
{
if (mb_strlen($value) > self::URL_MAX || preg_match(self::URL, $value) !== 1) {
return 'Link: eine http(s)://- oder /-URL, mailto: oder tel: mit höchstens 2000 Zeichen.';
}
return null;
}
private static function images(string $value): ?string
{
$valid = preg_match('/^\d+$/D', $value) === 1
|| (mb_strlen($value) <= self::URL_MAX && preg_match(self::WEB_URL, $value) === 1);
return $valid ? null : 'Bild: eine Anhang-ID (Ziffern) oder eine http(s)://-URL mit höchstens 2000 Zeichen.';
}
private static function font(string $value): ?string
{
if (mb_strlen($value) > self::FONT_MAX || preg_match('/[;{}<>"\'\\\\\x00-\x1F\x7F]/', $value) === 1) {
return 'Schrift: ein Schriftname mit höchstens 100 Zeichen ohne ; { } < > " \' \\.';
}
return null;
}
}