0 && get_post_status( $post_id ) !== 'publish' ) { $scan = []; } } if ( empty( $scan ) ) { return [ 'has_data' => false, 'score' => 0, 'grade' => 'F', 'scanned_at' => null, 'url' => null, 'issues' => [], 'groups' => [], 'summary' => self::empty_summary(), ]; } $issues = $scan['issues'] ?? []; return [ 'has_data' => true, 'score' => $scan['score'] ?? 0, 'grade' => $scan['grade'] ?? 'F', 'scanned_at' => $scan['scanned_at'] ?? null, 'url' => $scan['url'] ?? null, 'passed' => $scan['passed'] ?? 0, 'failed' => $scan['failed'] ?? 0, 'issues' => $issues, 'groups' => self::group_by_severity( $issues ), 'pour' => self::pour_breakdown( $issues ), 'summary' => self::summary_stats( $issues ), ]; } /** * Group issues by severity for the report view. */ private static function group_by_severity( array $issues ): array { $groups = [ 'critical' => [], 'serious' => [], 'warning' => [], 'notice' => [], ]; foreach ( $issues as $issue ) { $sev = $issue['severity'] ?? 'notice'; if ( isset( $groups[ $sev ] ) ) { $groups[ $sev ][] = $issue; } } return $groups; } /** * Map issues to WCAG POUR principles (Perceivable, Operable, Understandable, Robust). */ private static function pour_breakdown( array $issues ): array { $pour = [ 'perceivable' => [ 'score' => 100, 'issues' => [] ], 'operable' => [ 'score' => 100, 'issues' => [] ], 'understandable' => [ 'score' => 100, 'issues' => [] ], 'robust' => [ 'score' => 100, 'issues' => [] ], ]; $wcag_map = [ // Perceivable '1.1.1' => 'perceivable', '1.3.1' => 'perceivable', '1.3.6' => 'perceivable', '1.4.3' => 'perceivable', // Operable '2.1.1' => 'operable', '2.3.3' => 'operable', '2.4.4' => 'operable', '2.4.6' => 'operable', '2.4.7' => 'operable', // Understandable '3.1.1' => 'understandable', // Robust '4.1.2' => 'robust', ]; // POUR principle lookup by WCAG criterion leading digit — covers all WCAG 2.x criteria. $pour_by_digit = [ '1' => 'perceivable', '2' => 'operable', '3' => 'understandable', '4' => 'robust' ]; foreach ( $issues as $issue ) { // Best-practice issues are not part of WCAG POUR and should not affect POUR scores. if ( ( $issue['category'] ?? 'wcag' ) === 'best-practice' ) continue; $wcag = $issue['wcag'] ?? ''; if ( isset( $wcag_map[ $wcag ] ) ) { $principle = $wcag_map[ $wcag ]; } else { // Derive from leading digit (e.g. 1.x.x → perceivable, 2.x.x → operable). $principle = $pour_by_digit[ $wcag[0] ?? '' ] ?? 'perceivable'; } $pour[ $principle ]['issues'][] = $issue; $weight = $issue['severity'] === 'critical' ? 20 : ( $issue['severity'] === 'serious' ? 15 : 10 ); $pour[ $principle ]['score'] = max( 0, $pour[ $principle ]['score'] - $weight ); } return $pour; } private static function summary_stats( array $issues ): array { $critical = 0; $serious = 0; $warnings = 0; $notices = 0; foreach ( $issues as $issue ) { switch ( $issue['severity'] ?? '' ) { case 'critical': $critical++; break; case 'serious': $serious++; break; case 'warning': $warnings++; break; default: $notices++; break; } } return compact( 'critical', 'serious', 'warnings', 'notices' ); } private static function empty_summary(): array { return [ 'critical' => 0, 'serious' => 0, 'warnings' => 0, 'notices' => 0 ]; } /** * Return the rolling scan history (last 10 entries). */ public static function get_history(): array { return get_option( 'accessmate_scan_history', [] ); } }