followed directly by an

), which breaks the document outline for * assistive technology. */ final class HeadingOrderRule implements Rule { /** * Returns the stable rule identifier. * * @return string */ public function id(): string { return 'heading-order'; } /** * Returns the WCAG success criterion this rule maps to. * * @return string */ public function wcag(): string { return '1.3.1'; } /** * Evaluates heading order and returns issues for any skipped levels. * * @param DOMDocument $dom Parsed document. * @param DOMXPath $xpath XPath helper for the document. * @param array $context Optional scan context. * @return list */ public function evaluate( DOMDocument $dom, DOMXPath $xpath, array $context = array() ): array { $issues = array(); $previousLevel = 0; foreach ( $xpath->query( '//h1|//h2|//h3|//h4|//h5|//h6' ) as $heading ) { $level = (int) substr( $heading->nodeName, 1 ); if ( $previousLevel > 0 && $level > $previousLevel + 1 ) { $issues[] = new Issue( ruleId: $this->id(), wcag: $this->wcag(), severity: Severity::Warning, message: sprintf( /* translators: 1: previous heading level, 2: current heading level. */ __( 'Heading level jumps from h%1$d to h%2$d, skipping a level.', 'a11yfleet' ), $previousLevel, $level ), context: '<' . $heading->nodeName . '>' . trim( $heading->textContent ) . 'nodeName . '>', howToFix: __( 'Use heading levels in order (do not skip levels) so the document outline stays logical.', 'a11yfleet' ), ); } $previousLevel = $level; } return $issues; } }