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 ' : '<title>' ); } } 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', '