true ), 'names' ); foreach ( $types as $type ) { add_filter( "manage_{$type}_posts_columns", array( $this, 'add_column' ) ); add_action( "manage_{$type}_posts_custom_column", array( $this, 'render_column' ), 10, 2 ); add_filter( "manage_edit-{$type}_sortable_columns", array( $this, 'make_sortable' ) ); } } /** * Inserts the "A11y" column right before the Date column when present. * * @param array $columns Existing columns. * @return array */ public function add_column( array $columns ): array { $new = array(); foreach ( $columns as $key => $label ) { if ( 'date' === $key ) { $new['acceguru_issues'] = esc_html__( 'A11y', 'acceguru' ); } $new[ $key ] = $label; } if ( ! isset( $new['acceguru_issues'] ) ) { $new['acceguru_issues'] = esc_html__( 'A11y', 'acceguru' ); } return $new; } /** * Renders the column cell for a single post. * * @param string $column Column key. * @param int $post_id Post ID. */ public function render_column( string $column, int $post_id ): void { if ( 'acceguru_issues' !== $column ) { return; } $summary = get_post_meta( $post_id, self::META_KEY, true ); if ( ! is_array( $summary ) ) { echo '-'; return; } if ( ! Acceguru_Scanner::summary_is_current( $summary ) ) { echo '↻'; return; } $total = isset( $summary['total_issues'] ) ? (int) $summary['total_issues'] : 0; $by_impact = isset( $summary['by_impact'] ) && is_array( $summary['by_impact'] ) ? $summary['by_impact'] : array(); $worst = $this->worst_impact( $by_impact ); if ( 0 === $total ) { printf( '%s 0', esc_attr__( 'No automated violations found in last scan', 'acceguru' ), '✓' // ✓ ); return; } $tooltip_parts = array(); foreach ( array( 'critical', 'serious', 'moderate', 'minor' ) as $level ) { $count = isset( $by_impact[ $level ] ) ? (int) $by_impact[ $level ] : 0; if ( $count > 0 ) { $tooltip_parts[] = Acceguru_Admin::impact_label( $level ) . ': ' . $count; } } $view_url = (string) get_permalink( $post_id ); $href = '' !== $view_url ? add_query_arg( 'acceguru', 'open', $view_url ) : ''; printf( '%4$s', esc_attr( $worst ), '' !== $href ? ' href="' . esc_url( $href ) . '" target="_blank" rel="noopener noreferrer"' : '', ' title="' . esc_attr( implode( ' · ', $tooltip_parts ) ) . '"', esc_html( (string) (int) $total ) ); } /** * Marks the column sortable. * * @param array $columns Sortable columns. * @return array */ public function make_sortable( array $columns ): array { $columns['acceguru_issues'] = 'acceguru_issues'; return $columns; } /** * Orders the list table by the numeric issue count when the A11y column * header is clicked. * * @param WP_Query $query Query object. */ public function handle_sort( WP_Query $query ): void { if ( ! is_admin() || ! $query->is_main_query() ) { return; } if ( 'acceguru_issues' !== $query->get( 'orderby' ) ) { return; } // Order by the plain numeric count, never by the serialized summary: // sorting that blob as a string compares the digits as text, so a page // with 19 issues would land below one with 2. The OR/NOT EXISTS pair // keeps never-scanned posts in the list instead of filtering them out. $query->set( 'meta_query', array( 'relation' => 'OR', array( 'key' => self::COUNT_KEY, 'compare' => 'EXISTS', ), array( 'key' => self::COUNT_KEY, 'compare' => 'NOT EXISTS', ), ) ); $query->set( 'orderby', 'meta_value_num' ); } /** * Returns the worst impact level present in the per-impact breakdown. * * @param array $by_impact Counts per impact level. */ private function worst_impact( array $by_impact ): string { foreach ( array( 'critical', 'serious', 'moderate', 'minor' ) as $level ) { if ( ! empty( $by_impact[ $level ] ) ) { return $level; } } return 'minor'; } }