Skip to content

Metabox data attributes are escaped as one string, so their values carry literal quotes #40

Description

@donnchawp

zeroBSCRM_do_meta_box_html() builds a string of data-* attributes and then runs esc_attr() over the whole string rather than over each value, at includes/ZeroBSCRM.MetaBox.php:839:

echo '<div class="' . esc_attr( $classes ) . '" id="' . esc_attr( $box['id'] ) . '" ' . esc_attr( $extraAttrs . $dataAttrStr ) . '>';

$dataAttrStr is already markup by then, built a few lines up at :826:

$dataAttrStr .= 'data-' . str_replace( '_', '-', $capKey ) . '="' . $capVStr . '"';

So every quote in it becomes &quot;. What the renderer builds:

 data-tab="zbs-contact-files-metabox"data-can-hide="1" data-areas="normal,side" data-can-accept-tabs="" ...

and what it echoes:

 data-tab=&quot;zbs-contact-files-metabox&quot;data-can-hide=&quot;1&quot; data-areas=&quot;normal,side&quot; ...

The browser reads those as unquoted attribute values, decodes the character references, and hands back values with literal quote characters in them: data-areas is "normal,side" rather than normal,side.

There is a second thing wrong on the same line. $extraAttrs . $dataAttrStr has no separator between the two, so on a tab pane data-tab and data-can-hide run together. An unquoted attribute value only ends at whitespace, so the parser folds the second attribute into the first one's value.

What actually breaks

Less than you would think, and I want to be honest about which part is real.

The data-can-* and data-areas attributes are written and never read. I grepped js/, includes/, admin/ and the root, and nothing anywhere consumes them. That half of this is inert, and only matters if someone later writes JS against them and can't work out why the values are wrong.

The one that looks live is data-tab on a tab pane, set at :850. Semantic UI's tab module is initialised on metabox tab groups at js/ZeroBSCRM.admin.metabox.manager.js:101, and it matches panes to menu items by data-tab. The menu item's own data-tab is escaped correctly, in zeroBSCRM_do_meta_box_htmlTabHead() at :699, so the two values don't agree: the menu item says zbs-contact-files-metabox and the pane says "zbs-contact-files-metabox"data-can-hide="1". Tab groups are reachable, several metaboxes set can_become_tab => true (Contacts :848, :1155, :2275, Quotes :976), and they're formed by dragging one metabox onto another in the edit screen.

I have confirmed the PHP output exactly, by running the concatenation and esc_attr() through WP. I have not confirmed the broken-tab symptom in a browser, so treat that last paragraph as a strong reading of the code rather than a reproduction. Worth doing before anyone spends time on it.

Fix

Escape values, not attribute strings. Something like: give $dataAttrStr and $extraAttrs an esc_attr() on each value where they are built, at :826 and :850, and echo them unescaped with a phpcs:ignore saying so. Put the missing space back between the two while you're in there.

Origin

Long-standing. The Feb 2026 formatting sweep (1285bad, #46809) reindented the line but the esc_attr($extraAttrs.$dataAttrStr) shape predates it.

Found while working on #38.

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageMaintainer needs to evaluate this issue

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions