PluginProbe ʕ •ᴥ•ʔ
Web Accessibility Toolkit – Accessibility Checker & ARIA for WCAG, Section 508 & ADA Compliance / 1.7.0
Web Accessibility Toolkit – Accessibility Checker & ARIA for WCAG, Section 508 & ADA Compliance v1.7.0
1.7.0 trunk 1.3.0 1.3.1 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 1.5.10 1.5.11 1.5.12 1.5.13 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6
aria-accessibility-toolkit / includes / class-menu-aria-labels.php
aria-accessibility-toolkit / includes Last commit date
class-admin.php 1 month ago class-menu-aria-labels.php 1 month ago class-review-notice.php 1 month ago index.php 1 year ago
class-menu-aria-labels.php
158 lines
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) exit;
3
4 /**
5 * Conditional ARIA label for selected menus only.
6 * - Shows the field on nav-menus.php ONLY when ?menu=<ID> is in ariaat_aria_menus.
7 * - Injects aria-label on the frontend ONLY for items in selected menus.
8 */
9 class ARIAAT_Menu_Aria_Label {
10 const META_KEY = '_ariaat_aria_label';
11 const OPTION_MENUS = 'ariaat_aria_menus';
12 const OVERRIDE_VISIBLE_TEXT = false; // override visible text even if meaningful
13 const FALLBACK_TO_TITLE_ATTRIBUTE = true; // use Title Attribute when custom field empty
14
15 public function __construct() {
16 add_action( 'wp_nav_menu_item_custom_fields', [ $this, 'render_field' ], 10, 4 );
17 add_action( 'wp_update_nav_menu_item', [ $this, 'save_field' ], 10, 3 );
18 add_filter( 'nav_menu_link_attributes', [ $this, 'maybe_add_aria_label' ], 10, 3 );
19 }
20
21 /**
22 * Render ARIA Label input ONLY if the currently edited menu (?menu=ID) is selected.
23 */
24 public function render_field( $item_id, $item, $depth, $args ) {
25 if ( ! $this->current_menu_is_selected_strict() ) {
26 return;
27 }
28
29 $value = get_post_meta( $item_id, self::META_KEY, true );
30 $field_id = 'edit-' . self::META_KEY . '-' . $item_id;
31 ?>
32 <p class="field-ariaat-aria-label description description-wide">
33 <label for="<?php echo esc_attr( $field_id ); ?>">
34 <?php esc_html_e( 'ARIA Label', 'aria-accessibility-toolkit' ); ?><br>
35 <input
36 type="text"
37 id="<?php echo esc_attr( $field_id ); ?>"
38 class="widefat code edit-menu-item-ariaat-aria-label"
39 name="<?php echo esc_attr( self::META_KEY . '[' . $item_id . ']' ); ?>"
40 value="<?php echo esc_attr( $value ); ?>"
41 placeholder="<?php esc_attr_e( '', 'aria-accessibility-toolkit' ); ?>"
42 >
43 </label>
44 <span class="description">
45 <?php esc_html_e( 'Screen readers will announce this instead of the link text. Leave blank for clear text like “About” or “Contact”.', 'aria-accessibility-toolkit' ); ?>
46 </span>
47 </p>
48 <?php
49 }
50
51 /**
52 * Save ARIA Label meta only when editing a selected menu.
53 */
54 public function save_field( $menu_id, $menu_item_db_id, $args ) {
55 if ( ! $this->current_menu_is_selected_strict() ) {
56 // If the menu is not selected, ensure we don't keep stale values.
57 delete_post_meta( $menu_item_db_id, self::META_KEY );
58 return;
59 }
60
61 if ( isset( $_POST[ self::META_KEY ][ $menu_item_db_id ] ) ) {
62 $val = sanitize_text_field( wp_unslash( $_POST[ self::META_KEY ][ $menu_item_db_id ] ) );
63 if ( $val !== '' ) {
64 update_post_meta( $menu_item_db_id, self::META_KEY, $val );
65 } else {
66 delete_post_meta( $menu_item_db_id, self::META_KEY );
67 }
68 }
69 }
70
71 /**
72 * Frontend: add aria-label only for items in selected menus and when we have a label.
73 */
74 public function maybe_add_aria_label( $atts, $item, $args ) {
75 if ( ! $this->item_in_selected_menus( $item->ID ) ) {
76 return $atts;
77 }
78
79 $custom = get_post_meta( $item->ID, self::META_KEY, true );
80 $candidate = trim( (string) $custom );
81
82 if ( $candidate === '' && self::FALLBACK_TO_TITLE_ATTRIBUTE && ! empty( $item->attr_title ) ) {
83 $candidate = trim( $item->attr_title );
84 }
85 if ( $candidate === '' ) {
86 return $atts;
87 }
88
89 $link_text = isset( $item->title ) ? wp_strip_all_tags( $item->title ) : '';
90 $has_visible_text = ( $link_text !== '' );
91
92 if ( $has_visible_text && ! self::OVERRIDE_VISIBLE_TEXT ) {
93 if ( ! $this->is_ambiguous_text( $link_text ) ) {
94 return $atts; // Don’t override clear text like “About”
95 }
96 }
97
98 $atts['aria-label'] = $candidate;
99 return $atts;
100 }
101
102 /* =========================
103 * Helpers
104 * =======================*/
105
106 /**
107 * STRICT: Only returns true if we're on nav-menus.php AND there is a ?menu=<id> param
108 * AND that id is in ariaat_aria_menus. No sticky fallbacks.
109 */
110 private function current_menu_is_selected_strict() {
111 if ( ! is_admin() ) return false;
112 if ( ! function_exists( 'get_current_screen' ) ) return false;
113
114 $screen = get_current_screen();
115 if ( empty( $screen ) || $screen->base !== 'nav-menus' ) return false;
116
117 // Require explicit ?menu=<ID> in the URL. If it's missing, don't show.
118 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
119 $menu_id = isset( $_GET['menu'] ) ? absint( $_GET['menu'] ) : 0;
120 if ( ! $menu_id ) return false;
121
122 return in_array( $menu_id, $this->get_selected_menus(), true );
123 }
124
125 /**
126 * Is this menu item attached to any of the selected menus? (used on the frontend)
127 */
128 private function item_in_selected_menus( $item_id ) {
129 $selected = $this->get_selected_menus();
130 if ( empty( $selected ) ) return false;
131
132 $terms = wp_get_post_terms( $item_id, 'nav_menu', [ 'fields' => 'ids' ] );
133 if ( is_wp_error( $terms ) || empty( $terms ) ) return false;
134
135 return (bool) array_intersect( $terms, $selected );
136 }
137
138 private function get_selected_menus() {
139 $selected = get_option( self::OPTION_MENUS, [] );
140 if ( ! is_array( $selected ) ) return [];
141 // Normalize strings like "19" to integers.
142 return array_map( 'intval', $selected );
143 }
144
145 /**
146 * Consider short/ambiguous texts as candidates for aria-label replacement.
147 */
148 private function is_ambiguous_text( $text ) {
149 $t = strtolower( trim( preg_replace( '/\s+/', ' ', $text ) ) );
150 if ( $t === '' ) return true;
151 if ( mb_strlen( $t ) <= 4 ) return true; // e.g., “More”, “Info”, arrow-only
152 $phrases = [ 'read more', 'learn more', 'more', 'details', 'info', '', '»' ];
153 return in_array( $t, $phrases, true );
154 }
155 }
156
157 return new ARIAAT_Menu_Aria_Label();
158