refactor: ValueCleaner is the named choke point (H1)
The choke point moves out of AttrWriter into ValueCleaner (catalog, the composition's VariableRef, sanitizer): clean() (was AttrWriter::cleanValue), kindOf(), guardPath() and the private cleanNode/kind/unknownKind/tightens with VALUE_KEY, URL_KEY, URL_SEGMENT, STRICTNESS, BUILDER_ONLY (its docblock now sits on it; STRUCTURE stays in AttrWriter). Code of the pipeline moved unchanged. - ValueCleaner's docblock is the one canonical description: steps 0-8 with their rulings in code order, invariants I1-I3; the step comments in cleanNode carry the same numbers. AttrWriter/ShortformWriter/Composer/TextReplacer docblocks point there and keep only their own rules. - ValueCleaner::textReplacementFor($module) holds the D10 rule that was a closure in DiviReplaceTextAbility::replace (text/html only, clean() by kind). - AttrWriter, ShortformWriter and Composer take the ValueCleaner; DiviComposition builds one per composition (cleaner()). Tests: the eight AttrWriterTest methods that only exercised cleanValue/kindOf moved verbatim to ValueCleanerTest (clean/kindOf, ComposeFixture::cleaner()); mixed tests call the cleaner in place; +1 test for textReplacementFor. Every provider row and per-test assertion count unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
@@ -13,10 +13,11 @@ use LIT\MCP\Integrations\Divi\Schema\ShortformMap;
|
||||
* kind, resolves placeholders (not for code) and sets it at "<path>.desktop.value[.<sub>]";
|
||||
* null removes the field. Pure PHP.
|
||||
*
|
||||
* Every string goes through AttrWriter::cleanValue() – the single choke point under the kses
|
||||
* bypass (D7) – with the curated kind of the field: $variable(…)$ values only whole and
|
||||
* re-serialized by VariableRef (S1/S2/S25), the final string checked again (S28), URL fields
|
||||
* through the URL sanitizer (S3), builder-only paths guarded (S7/S26). Nothing is passed through.
|
||||
* Every string goes through ValueCleaner::clean() – the single choke point under the kses
|
||||
* bypass (D7), see its pipeline – with the curated kind of the field; apply() adds step 0 (S35)
|
||||
* and set() the guard for removals (S7/S26). Own rules on top: colors are placeholders, color
|
||||
* references or plain CSS colors, checked before and after cleaning (S13); an attachment's alt
|
||||
* text carrying a reference is dropped (S33b). Nothing is passed through.
|
||||
*/
|
||||
final class ShortformWriter
|
||||
{
|
||||
@@ -25,7 +26,7 @@ final class ShortformWriter
|
||||
|
||||
private const COLOR_ERROR = 'Farbe erwartet, z. B. {{color:primary}} oder #1a1a1a';
|
||||
|
||||
/** Curated kind => sanitizer kind of AttrWriter::cleanValue(). */
|
||||
/** Curated kind => sanitizer kind of ValueCleaner::clean(). */
|
||||
private const CLEAN_KIND = ['html' => 'html', 'code' => 'code', 'url' => 'url', 'text' => 'text'];
|
||||
|
||||
/**
|
||||
@@ -35,7 +36,7 @@ final class ShortformWriter
|
||||
public function __construct(
|
||||
private Placeholders $placeholders,
|
||||
private MediaResolver $media,
|
||||
private AttrWriter $attrs,
|
||||
private ValueCleaner $cleaner,
|
||||
private ?array $modules = null
|
||||
) {
|
||||
}
|
||||
@@ -106,7 +107,7 @@ final class ShortformWriter
|
||||
|
||||
$value = trim($this->placeholders->resolve($value));
|
||||
if (VariableRef::contains($value)) {
|
||||
$reference = $this->attrs->cleanValue($module, $path, $value, $subKey, 'text');
|
||||
$reference = $this->cleaner->clean($module, $path, $value, $subKey, 'text');
|
||||
if (! is_string($reference) || VariableRef::typeOf($reference) !== 'color') {
|
||||
throw new InvalidArgumentException(self::COLOR_ERROR);
|
||||
}
|
||||
@@ -118,7 +119,7 @@ final class ShortformWriter
|
||||
throw new InvalidArgumentException(self::COLOR_ERROR);
|
||||
}
|
||||
|
||||
$clean = $this->attrs->cleanValue($module, $path, $value, $subKey, 'text');
|
||||
$clean = $this->cleaner->clean($module, $path, $value, $subKey, 'text');
|
||||
if (! is_string($clean) || preg_match(self::COLOR, $clean) !== 1) {
|
||||
throw new InvalidArgumentException(self::COLOR_ERROR);
|
||||
}
|
||||
@@ -138,7 +139,7 @@ final class ShortformWriter
|
||||
$path = (string) $spec['path'];
|
||||
$sub = $spec['sub'] === null ? null : (string) $spec['sub'];
|
||||
$base = $path . '.desktop.value';
|
||||
$this->attrs->guardPath($path);
|
||||
$this->cleaner->guardPath($path);
|
||||
if ($value === null) {
|
||||
return AttrMap::remove($attrs, $sub === null ? $path : $base . '.' . $sub);
|
||||
}
|
||||
@@ -174,7 +175,7 @@ final class ShortformWriter
|
||||
|
||||
[$urlKey, $idKey, $altKey] = $keys;
|
||||
$base = $path . '.desktop.value.';
|
||||
$url = $this->attrs->cleanValue($module, $path, $image['url'], $urlKey, 'url');
|
||||
$url = $this->cleaner->clean($module, $path, $image['url'], $urlKey, 'url');
|
||||
if ($url === '') {
|
||||
throw new InvalidArgumentException($notFound);
|
||||
}
|
||||
@@ -205,7 +206,7 @@ final class ShortformWriter
|
||||
}
|
||||
|
||||
try {
|
||||
$clean = $this->attrs->cleanValue($module, $path, $alt, $altKey, 'text');
|
||||
$clean = $this->cleaner->clean($module, $path, $alt, $altKey, 'text');
|
||||
} catch (InvalidArgumentException) {
|
||||
return '';
|
||||
}
|
||||
@@ -262,7 +263,7 @@ final class ShortformWriter
|
||||
$value = $this->placeholders->resolve($value);
|
||||
}
|
||||
|
||||
$clean = $this->attrs->cleanValue($module, $path, $value, $sub, $kind);
|
||||
$clean = $this->cleaner->clean($module, $path, $value, $sub, $kind);
|
||||
if ($kind === 'url' && $clean === '' && trim($value) !== '') {
|
||||
throw new InvalidArgumentException('ungültige URL');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user