register_hooks(); } return self::$instance; } /** * Register WordPress hooks. */ private function register_hooks() { add_action( 'admin_menu', array( $this, 'add_admin_menu' ) ); add_action( 'admin_init', array( $this, 'register_settings' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) ); add_filter( 'manage_pages_columns', array( $this, 'add_scan_column' ) ); add_action( 'manage_pages_custom_column', array( $this, 'render_scan_column' ), 10, 2 ); add_filter( 'manage_posts_columns', array( $this, 'add_scan_column' ) ); add_action( 'manage_posts_custom_column', array( $this, 'render_scan_column' ), 10, 2 ); } /** * Add admin menu pages. */ public function add_admin_menu() { add_menu_page( __( 'Accessibility Guard', 'accessibility-guard' ), __( 'A11y Guard', 'accessibility-guard' ), 'manage_options', 'wpag-dashboard', array( $this, 'render_dashboard_page' ), 'dashicons-universal-access-alt', 80 ); add_submenu_page( 'wpag-dashboard', __( 'Dashboard', 'accessibility-guard' ), __( 'Dashboard', 'accessibility-guard' ), 'manage_options', 'wpag-dashboard', array( $this, 'render_dashboard_page' ) ); add_submenu_page( 'wpag-dashboard', __( 'Settings', 'accessibility-guard' ), __( 'Settings', 'accessibility-guard' ), 'manage_options', 'wpag-settings', array( $this, 'render_settings_page' ) ); } /** * Register settings. */ public function register_settings() { register_setting( 'wpag_settings_group', 'wpag_settings', array( 'type' => 'array', 'sanitize_callback' => array( $this, 'sanitize_settings' ), 'default' => wpag_default_settings(), ) ); // Auto-fix section. add_settings_section( 'wpag_auto_fix_section', __( 'Auto-Fix Options', 'accessibility-guard' ), function () { echo '

' . esc_html__( 'Enable automatic fixes applied to the front-end output.', 'accessibility-guard' ) . '

'; }, 'wpag-settings' ); $auto_fixes = array( 'auto_fix_skip_link' => __( 'Inject skip navigation link', 'accessibility-guard' ), 'auto_fix_lang_attr' => __( 'Ensure HTML lang attribute', 'accessibility-guard' ), 'auto_fix_empty_headings' => __( 'Remove empty headings', 'accessibility-guard' ), 'auto_fix_form_labels' => __( 'Add missing form labels', 'accessibility-guard' ), ); foreach ( $auto_fixes as $key => $label ) { add_settings_field( 'wpag_' . $key, $label, array( $this, 'render_checkbox_field' ), 'wpag-settings', 'wpag_auto_fix_section', array( 'key' => $key ) ); } // Scan settings section. add_settings_section( 'wpag_scan_section', __( 'Scan Settings', 'accessibility-guard' ), function () { echo '

' . esc_html__( 'Configure which post types to include in scans.', 'accessibility-guard' ) . '

'; }, 'wpag-settings' ); add_settings_field( 'wpag_scan_post_types', __( 'Post types to scan', 'accessibility-guard' ), array( $this, 'render_post_types_field' ), 'wpag-settings', 'wpag_scan_section' ); } /** * Render a checkbox settings field. * * @param array $args Field args containing 'key'. */ public function render_checkbox_field( $args ) { $settings = get_option( 'wpag_settings', wpag_default_settings() ); $key = $args['key']; $checked = ! empty( $settings[ $key ] ); ?> true ), 'objects' ); foreach ( $post_types as $pt ) { if ( $pt->name === 'attachment' ) { continue; } $checked = in_array( $pt->name, $selected, true ); ?> admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'wpag_scan_nonce' ), 'scanning_text' => __( 'Scanning...', 'accessibility-guard' ), 'scan_text' => __( 'Scan', 'accessibility-guard' ), 'rescan_text' => __( 'Re-scan', 'accessibility-guard' ), 'complete_text' => __( 'Scan Complete! Reloading...', 'accessibility-guard' ), 'confirm_clear' => __( 'Are you sure you want to clear all scan results?', 'accessibility-guard' ), 'scan_failed' => __( 'Scan failed.', 'accessibility-guard' ), 'request_failed' => __( 'Request failed. Please try again.', 'accessibility-guard' ), 'clear_failed' => __( 'Failed to clear results.', 'accessibility-guard' ), 'error_generating' => __( 'Error generating statement.', 'accessibility-guard' ), /* translators: 1: current count, 2: total count, 3: percentage */ 'progress_format' => __( '%1$s / %2$s pages (%3$s%%)', 'accessibility-guard' ), ) ); } } /** * Render the main dashboard page. */ public function render_dashboard_page() { // Check for detail view. // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- View-only, no data modification. $detail_post_id = isset( $_GET['post_id'] ) ? absint( wp_unslash( $_GET['post_id'] ) ) : 0; if ( $detail_post_id ) { $this->render_detail_view( $detail_post_id ); return; } $settings = get_option( 'wpag_settings', wpag_default_settings() ); $post_types = ! empty( $settings['scan_post_types'] ) ? $settings['scan_post_types'] : array( 'page', 'post' ); // Get all scanned pages. // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Required to filter scanned posts only. $query = new WP_Query( array( 'post_type' => $post_types, 'post_status' => 'publish', 'posts_per_page' => -1, 'meta_key' => '_wpag_last_scan', 'orderby' => 'meta_value', 'order' => 'DESC', ) ); $scanned_posts = $query->posts; // Get all published posts count. $total_query = new WP_Query( array( 'post_type' => $post_types, 'post_status' => 'publish', 'posts_per_page' => -1, 'fields' => 'ids', ) ); $total_pages = $total_query->found_posts; // Calculate totals. $total_errors = 0; $total_warnings = 0; $total_notices = 0; $total_issues = 0; foreach ( $scanned_posts as $post ) { $count = (int) get_post_meta( $post->ID, '_wpag_issue_count', true ); $errors = (int) get_post_meta( $post->ID, '_wpag_error_count', true ); $total_issues += $count; $total_errors += $errors; $cached = get_transient( 'wpag_scan_' . $post->ID ); if ( is_array( $cached ) ) { foreach ( $cached as $issue ) { if ( $issue['severity'] === 'warning' ) { $total_warnings++; } elseif ( $issue['severity'] === 'notice' ) { $total_notices++; } } } } $auto_fixes_count = 0; foreach ( array( 'auto_fix_skip_link', 'auto_fix_lang_attr', 'auto_fix_empty_headings', 'auto_fix_form_labels' ) as $fix_key ) { if ( ! empty( $settings[ $fix_key ] ) ) { $auto_fixes_count++; } } ?>

/
0 && count( $scanned_posts ) > 0 ) { $clean = 0; foreach ( $scanned_posts as $post ) { if ( (int) get_post_meta( $post->ID, '_wpag_error_count', true ) === 0 ) { $clean++; } } echo esc_html( round( ( $clean / count( $scanned_posts ) ) * 100 ) ) . '%'; } else { echo '—'; } ?>
ID, '_wpag_error_count', true ); $total = (int) get_post_meta( $post->ID, '_wpag_issue_count', true ); $last = get_post_meta( $post->ID, '_wpag_last_scan', true ); $warnings = 0; $notices = 0; $cached = get_transient( 'wpag_scan_' . $post->ID ); if ( is_array( $cached ) ) { foreach ( $cached as $issue ) { if ( $issue['severity'] === 'warning' ) { $warnings++; } elseif ( $issue['severity'] === 'notice' ) { $notices++; } } } $post_type_obj = get_post_type_object( $post->post_type ); ?>
post_title ); ?> labels->singular_name : $post->post_type ); ?> 0 ) : ?> 0 0 ) : ?> 0 0 ) : ?> 0

→

' . esc_html__( 'Post not found.', 'accessibility-guard' ) . '

'; return; } $issues = get_transient( 'wpag_scan_' . $post_id ); if ( ! is_array( $issues ) ) { $issues = array(); } $last_scan = get_post_meta( $post_id, '_wpag_last_scan', true ); ?>

←  /  post_title ); ?>

80 ) : ?>
…
—

→

●'; return; } $errors = (int) get_post_meta( $post_id, '_wpag_error_count', true ); $issues = (int) get_post_meta( $post_id, '_wpag_issue_count', true ); if ( $errors > 0 ) { $class = 'wpag-dot-red'; /* translators: %d: number of errors */ $title = sprintf( __( '%d errors', 'accessibility-guard' ), $errors ); } elseif ( $issues > 0 ) { $class = 'wpag-dot-yellow'; /* translators: %d: number of warnings/notices */ $title = sprintf( __( '%d warnings/notices', 'accessibility-guard' ), $issues ); } else { $class = 'wpag-dot-green'; $title = __( 'No issues', 'accessibility-guard' ); } printf( '●', esc_url( admin_url( 'admin.php?page=wpag-dashboard&post_id=' . $post_id ) ), esc_attr( $class ), esc_attr( $title ) ); } }