WordPress Version: 6.3
/**
* Removes all attributes, if none are allowed for this element.
*
* If some are allowed it calls `wp_kses_hair()` to split them further, and then
* it builds up new HTML code from the data that `wp_kses_hair()` returns. It also
* removes `<` and `>` characters, if there are any left. One more thing it does
* is to check if the tag has a closing XHTML slash, and if it does, it puts one
* in the returned code as well.
*
* An array of allowed values can be defined for attributes. If the attribute value
* doesn't fall into the list, the attribute will be removed from the tag.
*
* Attributes can be marked as required. If a required attribute is not present,
* KSES will remove all attributes from the tag. As KSES doesn't match opening and
* closing tags, it's not possible to safely remove the tag itself, the safest
* fallback is to strip all attributes from the tag, instead.
*
* @since 1.0.0
* @since 5.9.0 Added support for an array of allowed values for attributes.
* Added support for required attributes.
*
* @param string $element HTML element/tag.
* @param string $attr HTML attributes from HTML element to closing HTML element tag.
* @param array[]|string $allowed_html An array of allowed HTML elements and attributes,
* or a context name such as 'post'. See wp_kses_allowed_html()
* for the list of accepted context names.
* @param string[] $allowed_protocols Array of allowed URL protocols.
* @return string Sanitized HTML element.
*/
function wp_kses_attr($element, $attr, $allowed_html, $allowed_protocols)
{
if (!is_array($allowed_html)) {
$allowed_html = wp_kses_allowed_html($allowed_html);
}
// Is there a closing XHTML slash at the end of the attributes?
$xhtml_slash = '';
if (preg_match('%\s*/\s*$%', $attr)) {
$xhtml_slash = ' /';
}
// Are any attributes allowed at all for this element?
$element_low = strtolower($element);
if (empty($allowed_html[$element_low]) || true === $allowed_html[$element_low]) {
return "<{$element}{$xhtml_slash}>";
}
// Split it.
$attrarr = wp_kses_hair($attr, $allowed_protocols);
// Check if there are attributes that are required.
$required_attrs = array_filter($allowed_html[$element_low], static function ($required_attr_limits) {
return isset($required_attr_limits['required']) && true === $required_attr_limits['required'];
});
/*
* If a required attribute check fails, we can return nothing for a self-closing tag,
* but for a non-self-closing tag the best option is to return the element with attributes,
* as KSES doesn't handle matching the relevant closing tag.
*/
$stripped_tag = '';
if (empty($xhtml_slash)) {
$stripped_tag = "<{$element}>";
}
// Go through $attrarr, and save the allowed attributes for this element in $attr2.
$attr2 = '';
foreach ($attrarr as $arreach) {
// Check if this attribute is required.
$required = isset($required_attrs[strtolower($arreach['name'])]);
if (wp_kses_attr_check($arreach['name'], $arreach['value'], $arreach['whole'], $arreach['vless'], $element, $allowed_html)) {
$attr2 .= ' ' . $arreach['whole'];
// If this was a required attribute, we can mark it as found.
if ($required) {
unset($required_attrs[strtolower($arreach['name'])]);
}
} elseif ($required) {
// This attribute was required, but didn't pass the check. The entire tag is not allowed.
return $stripped_tag;
}
}
// If some required attributes weren't set, the entire tag is not allowed.
if (!empty($required_attrs)) {
return $stripped_tag;
}
// Remove any "<" or ">" characters.
$attr2 = preg_replace('/[<>]/', '', $attr2);
return "<{$element}{$attr2}{$xhtml_slash}>";
}