Rule id => count and an example.
*/
public static function check( $html ) {
$doc = Html_Analyzer::analyze( $html );
$results = array();
$add = static function ( $rule, $sample = '' ) use ( &$results ) {
if ( ! isset( $results[ $rule ] ) ) {
$results[ $rule ] = array(
'count' => 0,
'sample' => '',
);
}
++$results[ $rule ]['count'];
if ( '' === $results[ $rule ]['sample'] && '' !== $sample ) {
$results[ $rule ]['sample'] = function_exists( 'mb_substr' ) ? mb_substr( $sample, 0, 200 ) : substr( $sample, 0, 200 );
}
};
if ( null !== $doc['html_ord'] ) {
$html_element = isset( $doc['elements'][ $doc['html_ord'] ] ) ? $doc['elements'][ $doc['html_ord'] ] : array( 'attrs' => array() );
$lang = isset( $html_element['attrs']['lang'] ) ? $html_element['attrs']['lang'] : null;
if ( ! is_string( $lang ) || '' === trim( $lang ) ) {
$add( 'html-has-lang', '' );
}
if ( null === $doc['title'] || '' === $doc['title'] ) {
$add( 'document-title', null === $doc['title'] ? '
without ' : '' );
}
}
if ( null !== $doc['body_ord'] && ! $doc['skip_link'] ) {
$add( 'skip-link', '' );
}
$h1_count = 0;
foreach ( $doc['elements'] as $element ) {
$attrs = $element['attrs'];
switch ( $element['tag'] ) {
case 'IMG':
if ( Fixer::image_needs_alt( $element ) ) {
$add( 'image-alt', self::describe( $element, 'src' ) );
}
break;
case 'IFRAME':
if ( ! $element['hidden'] && ! self::non_empty( $attrs, 'title' ) && ! self::non_empty( $attrs, 'aria-label' ) ) {
$add( 'frame-title', self::describe( $element, 'src' ) );
}
break;
case 'META':
if ( isset( $attrs['name'], $attrs['content'] ) && 'viewport' === strtolower( (string) $attrs['name'] ) && Fixer::unlock_viewport( (string) $attrs['content'] ) !== $attrs['content'] ) {
$add( 'meta-viewport', '' );
}
break;
case 'INPUT':
case 'SELECT':
case 'TEXTAREA':
if ( Html_Analyzer::control_needs_name( $element ) && ! Html_Analyzer::control_has_name( $element, $doc ) ) {
$add( 'label', self::describe( $element, 'name' ) );
}
break;
case 'BUTTON':
if ( ! $element['hidden'] && ! Html_Analyzer::has_accessible_name( $element ) ) {
$add( 'button-name', self::describe( $element, 'class' ) );
}
break;
case 'A':
if ( ! Html_Analyzer::is_live_link( $element ) ) {
break;
}
$href = (string) $attrs['href'];
if ( ! Html_Analyzer::has_accessible_name( $element ) ) {
$add( 'link-name', self::describe( $element, 'href' ) );
break;
}
$labelled = self::non_empty( $attrs, 'aria-label' ) || self::non_empty( $attrs, 'aria-labelledby' );
$describedby = isset( $attrs['aria-describedby'] ) && is_string( $attrs['aria-describedby'] ) ? ' ' . $attrs['aria-describedby'] . ' ' : '';
$spoken = strtolower( $element['text'] . ' ' . ( self::non_empty( $attrs, 'aria-label' ) ? $attrs['aria-label'] : '' ) );
if ( ! $labelled && Fixer::is_ambiguous( $element['text'] ) ) {
$add( 'link-ambiguous', '"' . $element['text'] . '" → ' . $href );
}
if ( isset( $attrs['target'] ) && '_blank' === strtolower( (string) $attrs['target'] ) && false === strpos( $describedby, ' ' . Fixer::NEW_TAB_ID . ' ' ) && ! Fixer::mentions_new_tab( $spoken ) ) {
$add( 'link-new-tab', '"' . $element['text'] . '" → ' . $href );
}
if ( Fixer::is_pdf( $href ) && false === strpos( $describedby, ' ' . Fixer::PDF_ID . ' ' ) && false === strpos( $spoken, 'pdf' ) ) {
$add( 'link-pdf', '"' . $element['text'] . '" → ' . $href );
}
break;
case 'VIDEO':
case 'AUDIO':
if ( isset( $attrs['autoplay'] ) && ! isset( $attrs['controls'] ) ) {
$add( 'media-autoplay', '<' . strtolower( $element['tag'] ) . ' autoplay>' );
}
break;
case 'H1':
++$h1_count;
break;
}
}
foreach ( $doc['positive_tabindex'] as $ord ) {
$add( 'tabindex', '' );
}
foreach ( $doc['videos'] as $media ) {
$attrs = isset( $doc['elements'][ $media['ord'] ] ) ? $doc['elements'][ $media['ord'] ]['attrs'] : array();
// A muted autoplay video without controls is a background animation: no speech to caption.
$background = isset( $attrs['autoplay'], $attrs['muted'] ) && ! isset( $attrs['controls'] );
if ( 'VIDEO' === $media['tag'] && ! $media['captions'] && ! $background ) {
$add( 'video-caption', '