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() ); } }