Skip to content

fix: only validate PHP when activating snippets in bulk - #491

Merged
imantsk merged 3 commits into
release/v3.10.2/corefrom
fix/bulk-activate-validates-php-only/core
Aug 31, 2026
Merged

fix: only validate PHP when activating snippets in bulk#491
imantsk merged 3 commits into
release/v3.10.2/corefrom
fix/bulk-activate-validates-php-only/core

Conversation

@TallblokeUK

Copy link
Copy Markdown
Contributor

Prompted by Issue saving custom JS snippets due to strict validation/sanitization rules.

The reporter's explanation is not right — we do not validate JavaScript syntax, and there are no regex rules on declarations or DOM calls. I tested every pattern they named and all are fine:

pattern result
el => { … } fine
Array.from().filter() fine
MutationObserver fine
URLSearchParams fine

But their experience was real, and there is a genuine bug underneath it.

The bug

activate_snippets() runs every selected snippet through the PHP validator, whatever its type:

foreach ( $snippets as $snippet ) {
    $validator = new Validator( $snippet->code );

The other two callers guard this on 'php' === $snippet->type. This one was missed.

The validator does not check syntax. It looks for redeclarations of functions and classes that already exist in PHP. Run against a script, any function sharing a name with a PHP built-in reads as a redeclaration. All of these are refused:

next, reset, current, key, count, sort, end, compact, extract, header

Those are ordinary names in JavaScript. A carousel with next() and reset() cannot be bulk activated.

And the same snippet activates fine on its own, because the single-activation path is guarded. That inconsistency is what makes it look like arbitrary validation rules from the outside.

It fails silently. Invalid snippets are dropped from the batch rather than reported, so the snippet just stays inactive with nothing said. Worse, one bad snippet makes the whole call return null.

The fix

Validate only PHP, matching the other two callers.

Testing

6 unit tests. Two fail without the fix; the other four are guards proving PHP checking is intact:

test without fix
JS naming PHP built-ins can be bulk activated fails
One invalid PHP snippet does not block the batch fails
PHP redeclaring an existing function is still refused passes (guard)
Valid PHP is still bulk activated passes (guard)
Single activation already worked passes (guard)
Stylesheets can be bulk activated passes (guard)

Also verified through the admin UI on WP 7.1: created a JavaScript snippet containing next() and reset(), selected it, chose Activate, pressed Apply.

result
Before stays inactive, nothing reported
After activates

Full suite 163 tests, phpcs clean.

Related

The silent failure is the same problem as #488 — a batch can fail entirely and say nothing. That draft would surface this class of thing; this fixes the cause of one instance of it.

Worth asking the reporter whether their scripts declare functions with any of the names above, to confirm this is what they hit rather than something still hiding.

@TallblokeUK

Copy link
Copy Markdown
Contributor Author

Worth adding, because it makes this bigger than the fixed list of PHP built-ins I gave above.

check_duplicate_identifier() builds its list from get_defined_functions():

array_merge( $defined_functions['internal'], $defined_functions['user'] )

So it is not just PHP internals. It is every function declared by WordPress, the active plugins and the theme. On a modest test install that is 5,658 names — 1,623 internal and 4,035 from user code.

Measured against thirty names a script author might plausibly use, twenty collided:

next, reset, current, key, count, sort, end, compact, extract, header,
range, min, max, round, floor, abs, join, trim, shuffle, log

log, min, max, round, floor, abs, join, trim are hard to avoid in JavaScript.

Two consequences:

  • The affected set was specific to each site, and grew with every plugin activated. Two installs with identical snippets behaved differently, which is exactly why this reads as arbitrary validation from the outside and is awkward to reproduce on request.
  • It was getting worse over time. The more a site grew, the more script names became unusable.

I have added a seventh test that derives its name from the live get_defined_functions() list rather than hard-coding one, so it stays honest whatever is loaded in the test environment. It fails without the fix ("A script declaring next should still activate") and passes with it.

@imantsk
imantsk changed the base branch from core to release/v3.10.2/core August 31, 2026 17:21
@imantsk
imantsk force-pushed the fix/bulk-activate-validates-php-only/core branch from 43a73be to f7e6831 Compare August 31, 2026 17:37
Bulk activation ran every selected snippet through the PHP code validator,
whatever its type:

  foreach ( $snippets as $snippet ) {
    $validator = new Validator( $snippet->code );

The other two callers guard this on the snippet being PHP; this one was
missed.

The validator does not check syntax. It looks for redeclarations of
functions and classes that already exist in PHP, which says nothing
meaningful about CSS or JavaScript. Read against a script, any function
sharing a name with a PHP built-in looks like a redeclaration, so these
were all refused:

  next, reset, current, key, count, sort, end, compact, extract, header

Those are ordinary names in JavaScript. A carousel with next() and
reset() could not be activated in bulk, while the same snippet activated
perfectly well from its own toggle, because that path is guarded.

It also failed silently. Invalid snippets are dropped from the batch
rather than reported, so the snippet simply stayed inactive with nothing
said, and one such snippet returned null for the whole call.

Validate only PHP, matching the other two callers.

Reported on the support forum as JavaScript being rejected by strict
validation rules. The reporter's explanation was not right, but the
experience was: their scripts would not activate.
check_duplicate_identifier() builds its list from get_defined_functions(),
so it covers PHP internals plus every function declared by WordPress, the
active plugins and the theme. On a modest install that is over five and a
half thousand names, four thousand of them from plugins.

The set of JavaScript names that used to be refused was therefore
specific to each site and grew as plugins were added, which is why the
behaviour looked arbitrary and was hard to reproduce.

Deriving the name from the live list rather than hard-coding one keeps the
test honest whatever happens to be loaded.
@imantsk
imantsk force-pushed the fix/bulk-activate-validates-php-only/core branch from f7e6831 to 27ce583 Compare August 31, 2026 17:44
@imantsk
imantsk merged commit 6f1b898 into release/v3.10.2/core Aug 31, 2026
7 checks passed
@imantsk
imantsk deleted the fix/bulk-activate-validates-php-only/core branch August 31, 2026 17:47
@imantsk imantsk mentioned this pull request Aug 31, 2026
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants