Skip to content

fix: stop encoding underscores in HTML attribute values#26

Merged
PrintNow merged 1 commit into
PrintNow:2.0from
chipin:fix/widget-attr-html5-encoding
May 25, 2026
Merged

fix: stop encoding underscores in HTML attribute values#26
PrintNow merged 1 commit into
PrintNow:2.0from
chipin:fix/widget-attr-html5-encoding

Conversation

@chipin
Copy link
Copy Markdown

@chipin chipin commented May 25, 2026

Problem

Helper::buildHtmlAttributes() serializes widget attributes via:

htmlentities($value, ENT_QUOTES | ENT_HTML5, 'UTF-8')

The HTML5 named-entity table encodes _ as _, as  , and many other ordinary characters that should not appear as entities inside an attribute value. When the resulting attribute is later combined with another layer of escaping (e.g. &&), the source HTML for a Radio widget ends up like:

<input value="..." name="account&amp;lowbar;category" type="radio">

…which the browser parses into a DOM attribute whose literal value is account&lowbar;category (with literal & and the chars lowbar;), not account_category. That breaks:

  • document.querySelector('[name="account_category"]') — finds 0 elements
  • jQuery $('[name="account_category"]') — finds 0 elements
  • Form submission — the POST body contains account&lowbar;category=... instead of account_category=..., so the controller never sees the value under its expected key

Repro

// in any Form callback
$form->radio('account_category', '科目分類')
    ->options(['a' => 'A', 'b' => 'B']);

Inspect the rendered HTML — every radio <input> has name="account&amp;lowbar;category".

Affected widgets

Anything that goes through Helper::buildHtmlAttributes() via formatHtmlAttributes():
Radio, Checkbox, Switch, and any custom Widget subclass. Standard form fields rendered through Blade {{ }} are not affected.

Fix

Switch the entity flag set to ENT_QUOTES only — the same character set htmlspecialchars() uses, which is the correct rule for serializing HTML attribute values (& < > " ' only):

- $element = $key.'="'.htmlentities($value, ENT_QUOTES | ENT_HTML5, 'UTF-8').'" ';
+ // FIX: ENT_HTML5 named-entity table encodes "_" -> "&lowbar;" etc.,
+ // breaking attribute values like name="foo_bar". Use ENT_QUOTES only.
+ $element = $key.'="'.htmlentities($value, ENT_QUOTES, 'UTF-8').'" ';

Before:

name="account&amp;lowbar;category" class="field&amp;lowbar;account&amp;lowbar;category"

After:

name="account_category" class="field_account_category"

Notes

  • No behaviour change for attribute values that contain only ASCII letters/digits/no special chars.
  • For values that contain & < > " ', behaviour is identical to htmlspecialchars($v, ENT_QUOTES).
  • For values that contain non-ASCII (e.g. CJK), they are still emitted as-is in UTF-8 (no entity conversion), which is what attribute serialization should do and what every browser parses correctly.

Helper::buildHtmlAttributes() serializes widget attributes via
htmlentities($v, ENT_QUOTES | ENT_HTML5, 'UTF-8'). The HTML5 named-entity
table encodes "_" as "&lowbar;" (and " " as "&nbsp;", etc.), so a radio
widget rendered with name="account_category" ends up in the DOM as the
literal string "account&lowbar;category" — breaking [name="..."] selectors
and form submission for any column whose name contains an underscore.

Repro: $form->radio('account_category', '...') — source HTML shows
  name="account&amp;lowbar;category"
parsed DOM attribute value:
  account&lowbar;category   (literal, not "account_category")

Fix: use ENT_QUOTES only — same character set htmlspecialchars uses,
which is the correct rule for serializing HTML attribute values.

Affected widgets: Radio, Checkbox, Switch, and any custom widget that
uses formatHtmlAttributes(). Standard form fields rendered via Blade
{{ }} are unaffected.
@PrintNow
Copy link
Copy Markdown
Owner

PrintNow commented May 25, 2026

@chipin 👍🏻 Thanks for the contribution! I'll merge it shortly.

@PrintNow PrintNow merged commit bea3fb1 into PrintNow:2.0 May 25, 2026
5 checks passed
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