]+))/i', $tag, $m ) ) {
$value = '';
foreach ( array( 1, 2, 3 ) as $i ) {
if ( isset( $m[ $i ] ) && '' !== $m[ $i ] ) {
$value = $m[ $i ];
break;
}
}
return html_entity_decode( $value, ENT_QUOTES, 'UTF-8' );
}
if ( preg_match( '/\s' . preg_quote( $name, '/' ) . '(?=[\s>\/])/i', $tag ) ) {
return '';
}
return null;
}
/**
* Checks a fragment of HTML (post content).
*
* @param string $html Post content.
* @return array[] List of issues: array( 'code' => string, 'level' => 'error'|'warning', 'detail' => string ).
*/
function agevole_a11y_check_html( $html ) {
$issues = array();
$html = (string) $html;
// Images: missing alt attribute (error) or empty alt (fine only for decorative images).
if ( preg_match_all( '/
]*>/i', $html, $imgs ) ) {
foreach ( $imgs[0] as $img ) {
$alt = agevole_a11y_attr( $img, 'alt' );
$src = (string) agevole_a11y_attr( $img, 'src' );
$src = wp_basename( (string) wp_parse_url( $src, PHP_URL_PATH ) );
if ( null === $alt ) {
$issues[] = array(
'code' => 'img_no_alt',
'level' => 'error',
'detail' => $src,
);
} elseif ( '' === trim( $alt ) && 'presentation' !== agevole_a11y_attr( $img, 'role' ) ) {
$issues[] = array(
'code' => 'img_empty_alt',
'level' => 'warning',
'detail' => $src,
);
}
}
}
// Headings: levels must not be skipped going down (h2 -> h4); h1 is usually the page title.
if ( preg_match_all( '/ 'heading_h1',
'level' => 'warning',
'detail' => 'H1',
);
} elseif ( $level > $prev + 1 ) {
$issues[] = array(
'code' => 'heading_skip',
'level' => 'error',
'detail' => 'H' . $prev . ' → H' . $level,
);
}
$prev = $level;
}
}
// Links: empty links and texts that do not describe the destination.
if ( preg_match_all( '/]*)>(.*?)<\/a>/is', $html, $links, PREG_SET_ORDER ) ) {
$generic = agevole_a11y_generic_link_texts();
foreach ( $links as $link ) {
$tag = '';
if ( null === agevole_a11y_attr( $tag, 'href' ) ) {
continue;
}
$label = trim( (string) agevole_a11y_attr( $tag, 'aria-label' ) );
$text = trim( preg_replace( '/\s+/u', ' ', html_entity_decode( wp_strip_all_tags( $link[2] ), ENT_QUOTES, 'UTF-8' ) ) );
if ( '' === $text && preg_match_all( '/
]*>/i', $link[2], $inner ) ) {
foreach ( $inner[0] as $img ) {
$text .= trim( (string) agevole_a11y_attr( $img, 'alt' ) );
}
}
if ( '' === $text && '' === $label ) {
$issues[] = array(
'code' => 'link_empty',
'level' => 'error',
'detail' => (string) agevole_a11y_attr( $tag, 'href' ),
);
continue;
}
$normalized = trim( function_exists( 'mb_strtolower' ) ? mb_strtolower( $text, 'UTF-8' ) : strtolower( $text ), " \t\n\r\0\x0B.:!?»›→>…" );
if ( '' === $label && in_array( $normalized, $generic, true ) ) {
$issues[] = array(
'code' => 'link_generic',
'level' => 'warning',
'detail' => $text,
);
}
}
}
// Data tables without header cells.
if ( preg_match_all( '//is', $html, $tables ) ) {
foreach ( $tables[0] as $table ) {
if ( ! preg_match( '/| ' ) . '>', 'role' ) ) {
$issues[] = array(
'code' => 'table_no_th',
'level' => 'warning',
'detail' => '',
);
}
}
}
return $issues;
}
/**
* Human-readable message for an issue.
*
* @param array $issue Issue from agevole_a11y_check_html().
* @return string
*/
function agevole_a11y_issue_message( $issue ) {
switch ( $issue['code'] ) {
case 'img_no_alt':
/* translators: %s: image file name. */
return sprintf( __( 'Image without alternative text: %s', 'agevole-accessibility' ), $issue['detail'] );
case 'img_empty_alt':
/* translators: %s: image file name. */
return sprintf( __( 'Image with empty alternative text (fine only if it is decorative): %s', 'agevole-accessibility' ), $issue['detail'] );
case 'heading_h1':
return __( 'H1 heading in the content: the page title is usually already the H1.', 'agevole-accessibility' );
case 'heading_skip':
/* translators: %s: heading levels, for example "H2 → H4". */
return sprintf( __( 'Skipped heading level: %s', 'agevole-accessibility' ), $issue['detail'] );
case 'link_empty':
/* translators: %s: link address. */
return sprintf( __( 'Link without text: %s', 'agevole-accessibility' ), $issue['detail'] );
case 'link_generic':
/* translators: %s: link text. */
return sprintf( __( 'Link text does not say where it goes: "%s"', 'agevole-accessibility' ), $issue['detail'] );
case 'table_no_th':
return __( 'Table without header cells.', 'agevole-accessibility' );
}
return $issue['code'];
}
|