array( 'action', 'method', 'novalidate', 'autocomplete', 'name', 'target' ),
'input' => array( 'type', 'name', 'value', 'checked', 'disabled', 'readonly', 'required', 'placeholder', 'autocomplete', 'autofocus', 'spellcheck', 'maxlength', 'minlength', 'min', 'max', 'step', 'size', 'pattern', 'inputmode', 'list', 'form', 'multiple', 'accept' ),
'select' => array( 'name', 'disabled', 'required', 'multiple', 'size', 'form', 'autocomplete' ),
'option' => array( 'value', 'selected', 'disabled', 'label' ),
'optgroup' => array( 'label', 'disabled' ),
'textarea' => array( 'name', 'rows', 'cols', 'disabled', 'readonly', 'required', 'placeholder', 'maxlength', 'spellcheck', 'autocomplete', 'form' ),
'button' => array( 'type', 'name', 'value', 'disabled', 'form', 'formaction', 'formmethod', 'formnovalidate' ),
'label' => array( 'for', 'form' ),
'fieldset' => array( 'disabled', 'form', 'name' ),
'legend' => array(),
'output' => array( 'for', 'form', 'name' ),
'details' => array( 'open', 'name' ),
'summary' => array(),
'dialog' => array( 'open' ),
'kbd' => array(),
'img' => array( 'decoding', 'fetchpriority', 'srcset', 'sizes' ), // 0.62.1: a table's thumbnail (the post list has loading only)
'template' => array(), // 0.59.0: inert markup a script clones (the media picker's row), its content filtered as any
'time' => array( 'datetime' ),
'meter' => array( 'value', 'min', 'max', 'low', 'high', 'optimum' ),
'progress' => array( 'value', 'max' ),
'svg' => array( 'width', 'height', 'viewbox', 'fill', 'focusable', 'xmlns', 'preserveaspectratio', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin' ),
'symbol' => array( 'viewbox', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin', 'preserveaspectratio' ),
'use' => array( 'href', 'xlink:href', 'x', 'y', 'width', 'height' ),
'g' => array( 'fill', 'stroke', 'stroke-width', 'transform' ),
'path' => array( 'd', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin', 'fill-rule', 'clip-rule', 'transform', 'opacity' ),
'circle' => array( 'cx', 'cy', 'r', 'fill', 'stroke', 'stroke-width', 'opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'transform' ),
'text' => array( 'x', 'y', 'dx', 'dy', 'text-anchor', 'dominant-baseline', 'transform', 'fill', 'font-size', 'font-weight' ),
'tspan' => array( 'x', 'y', 'dx', 'dy' ),
'title' => array(),
'desc' => array(),
'ellipse' => array( 'cx', 'cy', 'rx', 'ry', 'fill', 'stroke', 'stroke-width', 'opacity' ),
'rect' => array( 'x', 'y', 'width', 'height', 'rx', 'ry', 'fill', 'stroke', 'stroke-width', 'opacity', 'transform' ),
'line' => array( 'x1', 'y1', 'x2', 'y2', 'stroke', 'stroke-width', 'stroke-linecap' ),
'polyline' => array( 'points', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin' ),
'polygon' => array( 'points', 'fill', 'stroke', 'stroke-width', 'stroke-linejoin' ),
);
/**
* The list (built once per request).
*
* @var array>|null
*/
private static ?array $allowed = null;
/**
* The allowed tags and attributes, in the shape wp_kses() takes.
*
* @return array>
*/
public static function allowed(): array {
if ( null !== self::$allowed ) {
return self::$allowed;
}
$allowed = wp_kses_allowed_html( 'post' );
foreach ( self::EXTRA as $tag => $attrs ) {
$allowed[ $tag ] = array_merge( $allowed[ $tag ] ?? array(), array_fill_keys( $attrs, true ) );
}
$global = array_fill_keys( self::GLOBAL_ATTRS, true );
foreach ( $allowed as $tag => $attrs ) {
$allowed[ $tag ] = array_merge( is_array( $attrs ) ? $attrs : array(), $global );
}
unset( $allowed['script'], $allowed['style'], $allowed['iframe'], $allowed['object'], $allowed['embed'] );
self::$allowed = $allowed;
return $allowed;
}
/**
* Echoes escaped HTML through the list.
*
* @param string $html Escaped HTML.
*/
public static function out( string $html ): void {
echo wp_kses( $html, self::allowed() );
}
}