Every file under src/ exits outside WordPress (defined('ABSPATH') ||
exit; after the use block, so a direct request no longer prints a fatal
error with the server path); divi-convert-fixture.php gets the WP_CLI
guard the other integration scripts have. A unit test keeps both true;
PSR1 side-effects warning excluded for the guard.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
- HtmlTokens (static, with the S45 docblock): SPLIT, RAW_OPEN, tokenize(),
structure(), closes(), unterminated() moved unchanged; new RAW_ELEMENTS list.
- StoredForms: of() (was TextReplacer::forms), mentions(), unicodeEscaped() –
the DB prefilter forms; TextReplacer::mentions() delegates, the ability's
candidate query uses StoredForms::of().
- TextReplacer keeps problems(), apply() and the splice/safe() verification;
safe() still runs the tokenizer and HtmlContext as two independent scans, in
the same order.
HtmlTokensTest asserts RAW_ELEMENTS === HtmlContext::RAW and that RAW_OPEN opens
exactly those elements. TextReplacerTest and HtmlContextTest unchanged.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
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
- ControlChars::strip()/stripDeep() (was VariableRef::withoutControls/Deep) with
the S32/S32a/S35 docblock; callers AttrWriter, ShortformWriter, TextReplacer and
the setting cleaner use it.
- DynamicContentRules (design system + dynamic content map): fieldsFor() (was
contentFields: meta prefix -> map -> loop_ -> design names -> unknown),
cleanSetting() (was setting(): raw strip -> raw marker -> field type ->
final-string check -> meta key check, same order), one private method per
field family (textSetting, selectPostSetting, scalarSetting), isMetaKey(),
checkMap(), and the constants DEFAULT_DYNAMIC_CONTENT, META_SETTINGS,
DESIGN_SETTINGS, META_KEY.
- VariableRef keeps the syntax: const END = ')$' replaces the six literals,
payloadOf() is shared by typeOf() and canonical(); unknownName() is public for
the rules. Error messages unchanged.
Tests unchanged apart from a class name in DynamicContentMapTest.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
TextReplacer replaces only in plain-text segments (S8 split), outside
script/style bodies, inserts the replacement cleaned per field kind and
refuses any result whose tags, entities or references differ from the
original plus the inserted text. Unchanged strings are never cleaned.
Per page all or nothing with the tree_rev lock; Throwables are reported
per page (S10).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7