PluginProbe ʕ •ᴥ•ʔ
Web Accessibility Toolkit – Accessibility Checker & ARIA for WCAG, Section 508 & ADA Compliance / trunk
Web Accessibility Toolkit – Accessibility Checker & ARIA for WCAG, Section 508 & ADA Compliance vtrunk
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-admin.php
aria-accessibility-toolkit / includes Last commit date
class-admin.php 3 days ago class-menu-aria-labels.php 5 months ago class-review-notice.php 3 days ago index.php 1 year ago
class-admin.php
975 lines
1 <?php
2
3 // Exit if accessed directly
4 if ( ! defined( 'ABSPATH' ) ) exit;
5
6 class ARIAAT_Admin {
7
8 /**
9 * Constructor to hook the necessary actions.
10 */
11 public function __construct() {
12
13 add_action( 'admin_enqueue_scripts', [ $this, 'admin_scripts_styles' ] );
14
15 add_action('admin_menu', [$this, 'register_admin_menu']);
16 add_action('admin_init', [$this, 'register_settings']);
17
18 }
19
20 public function admin_scripts_styles( $hook ) {
21 $v = ARIAATVERSION;
22 //$v = time();
23
24 if ( strpos( $hook, 'ariaat' ) !== false ) {
25 wp_enqueue_script(
26 'ariaat-admin',
27 ARIAATURL . 'assets/js/ariaat-admin.js',
28 ['jquery'],
29 $v,
30 true
31 );
32
33 wp_localize_script('ariaat-admin', 'ARIAAT_Data', [
34 'aria_options' => array_map(function($item) {
35 return [
36 'label' => $item['label'],
37 ];
38 }, $this->get_aria_attributes()),
39 'role_options' => array_map(function($item) {
40 return [
41 'label' => $item['label'],
42 ];
43 }, $this->get_role_options()),
44 ]);
45
46 wp_enqueue_style( 'ariaat-style', ARIAATURL .'assets/css/ariaat-admin.css', false, $v );
47 }
48 }
49
50 public function register_admin_menu() {
51
52 add_menu_page(
53 'Web Accessibility', // Page title
54 'Web Accessibility', // Menu title
55 'manage_options', // Capability
56 'ariaat', // Menu slug
57 [ $this, 'render_settings_page' ], // Callback
58 'dashicons-universal-access', // Icon
59 58 // Position (below Comments, above Appearance)
60 );
61
62 // Legacy Settings → Web Accessibility (temporary)
63 add_options_page(
64 'Web Accessibility',
65 'Web Accessibility',
66 'manage_options',
67 'ariaat-legacy',
68 function () {
69 wp_safe_redirect( admin_url( 'admin.php?page=ariaat' ) );
70 exit;
71 }
72 );
73
74 }
75
76
77 public function register_settings() {
78
79 register_setting('ariaat_general_group', 'ariaat_general_settings', [$this, 'sanitize_general_settings']);
80
81 register_setting('ariaat_aria_group', 'ariaat_aria_mappings', [$this, 'sanitize_array']);
82 register_setting('ariaat_aria_group', 'ariaat_aria_menus', [$this, 'sanitize_menu_selection']);
83
84 register_setting('ariaat_role_group', 'ariaat_role_mappings', [$this, 'sanitize_array']);
85 register_setting('ariaat_role_group', 'ariaat_role_menus', [$this, 'sanitize_menu_selection']);
86
87 register_setting('ariaat_contrast_group', 'ariaat_contrast_mappings', [$this, 'sanitize_array']);
88
89 register_setting('ariaat_images_group', 'ariaat_image_settings', [$this, 'sanitize_array']);
90
91 }
92
93
94 public function get_all_menus() {
95 $menus = wp_get_nav_menus();
96 $output = [];
97 foreach ($menus as $menu) {
98 $output[$menu->term_id] = $menu->name;
99 }
100 return $output;
101 }
102
103
104 public function render_settings_page() {
105
106 $active_tab = isset($_GET['tab']) ? sanitize_key($_GET['tab']) : 'general';
107 ?>
108 <div class="wrap ariaat">
109
110 <div class="ariaat-title">
111 <h1 class="wp-heading-inline">
112 <img
113 src="<?php echo esc_url( ARIAATURL . 'assets/img/web-accessibility-toolkit.svg' ); ?>"
114 alt="<?php echo esc_attr__( 'Web Accessibility Toolkit', 'ariaat' ); ?>"
115 />
116 <span><?php echo esc_html( ARIAATVERSION ); ?></span>
117 </h1>
118 </div>
119
120
121 <div class="ariaat-header">
122 <div class="ariaat-header-buttons">
123 <a href="https://wcagforwp.com/docs/getting-started/?utm_source=plugin&utm_medium=admin&utm_campaign=docs&utm_content=header" target="_blank" rel="noopener noreferrer" class="button-primary small">Docs & Support<span class="screen-reader-text"> (opens in new tab)</span></a>
124 </div>
125 </div>
126
127 <div class="main_content">
128 <h2 class="nav-tab-wrapper" role="tablist" aria-label="Web Accessibility settings tabs">
129 <a href="?page=ariaat&tab=general" class="nav-tab <?php echo $active_tab == 'general' ? 'nav-tab-active' : '' ?>">General</a>
130 <a href="?page=ariaat&tab=aria" class="nav-tab <?php echo $active_tab == 'aria' ? 'nav-tab-active' : '' ?>">ARIA</a>
131 <a href="?page=ariaat&tab=roles" class="nav-tab <?php echo $active_tab == 'roles' ? 'nav-tab-active' : '' ?>">Roles</a>
132 <a href="?page=ariaat&tab=contrast" class="nav-tab <?php echo $active_tab == 'contrast' ? 'nav-tab-active' : '' ?>">Contrast</a>
133 <a href="?page=ariaat&tab=images" class="nav-tab <?php echo $active_tab == 'images' ? 'nav-tab-active' : '' ?>">Images</a>
134 <a href="?page=ariaat&tab=forms" class="nav-tab <?php echo $active_tab == 'forms' ? 'nav-tab-active' : '' ?>">Forms</a>
135 <?php do_action( 'ariaat_after_nav_tab_wrapper', $active_tab ); ?>
136 </h2>
137
138
139 <form method="post" action="options.php">
140 <?php
141 if ($active_tab === 'general') {
142 settings_fields('ariaat_general_group');
143 $settings = get_option('ariaat_general_settings', []);
144
145 ?>
146 <div class="section">
147 <h3><span class="dashicons dashicons-laptop"></span> Frontend Accessibility Checker</h3>
148 <div class="setting-row no-border">
149 <div class="setting-label">
150 <p class="description">
151 Toggle visibility of the accessibility checker. Only visible to logged-in admins - it will never be visible to your users.
152 <?php if (! defined('ARIAAT_PRO_ACTIVE')) { ?>
153 <br>The <a href="https://wcagforwp.com/pricing/?utm_source=plugin&utm_medium=admin&utm_campaign=pro_upsell&utm_content=scanner" class="pro">PRO plugin</a> also includes deep scanning of forms & works with all form plugins.
154 <?php } ?>
155 </p>
156 </div>
157 <div class="setting-control">
158 <label class="toggle-switch">
159 <input type="checkbox" name="ariaat_general_settings[enable_frontend_checker]" value="1" <?php checked($settings['enable_frontend_checker'] ?? '', '1'); ?> />
160 <span class="toggle-slider"></span>
161 </label>
162 </div>
163 </div>
164
165 </div>
166 <div class="section">
167 <h3><span class="dashicons dashicons-universal-access-alt"></span> General Accessibility Enhancements</h3>
168
169 <div class="setting-row">
170 <div class="setting-label">
171 <strong>Language</strong>
172 <p class="description">
173 Sets the default "lang" attribute in your site’s HTML. This helps screen readers and search engines understand the language of your site. For example: <i>en</i> for English, <i>en-AU</i> for Australian English, <i>fr</i> for French.
174 <span class="wcag">WCAG: <a target="_blank" rel="noopener noreferrer" href="https://www.w3.org/WAI/WCAG22/Techniques/html/H57.html">3.1.1 Language of Page (A)</a></span>
175 </p>
176 </div>
177 <div class="setting-control">
178 <input type="text" name="ariaat_general_settings[language]" value="<?php echo esc_attr($settings['language'] ?? ''); ?>" class="regular-text" placeholder="e.g. en, en-AU" />
179 </div>
180 </div>
181
182 <div class="setting-row">
183 <div class="setting-label">
184 <strong>Skip Link Target</strong>
185 <p class="description">
186 Adds a hidden “Skip to content” link at the top of each page for keyboard and screen reader users.
187 Enter a CSS selector that matches your main content area (e.g., <i>#primary</i> or <i>.main-content</i>).
188 <span class="wcag">WCAG: <a target="_blank" rel="noopener noreferrer" href="https://www.w3.org/WAI/WCAG22/Understanding/bypass-blocks.html">2.4.1 Bypass Blocks (A)</a></span>
189 </p>
190 </div>
191 <div class="setting-control">
192 <input type="text" name="ariaat_general_settings[skip_link]" value="<?php echo esc_attr($settings['skip_link'] ?? ''); ?>" class="regular-text" placeholder=".main-content" />
193 </div>
194 </div>
195
196 <div class="setting-row">
197 <div class="setting-label">
198 <strong>Show Focus Outline</strong>
199 <p class="description">
200 Ensures keyboard users can clearly see which element is currently focused, even if a theme hides outlines.
201 <span class="wcag">WCAG: <a target="_blank" rel="noopener noreferrer" href="https://www.w3.org/WAI/WCAG22/Understanding/focus-visible.html">2.4.7 Focus Visible (AA)</a></span>
202 </p>
203 </div>
204 <div class="setting-control">
205 <label class="toggle-switch">
206 <input type="checkbox" name="ariaat_general_settings[focus_outline]" value="1" <?php checked($settings['focus_outline'] ?? '', '1'); ?> />
207 <span class="toggle-slider"></span>
208 </label>
209 </div>
210 </div>
211
212 <div class="setting-row">
213 <div class="setting-label">
214 <strong>Fix Tab Order</strong>
215 <p class="description">
216 Removes <i>tabindex</i> values greater than 0 to maintain a logical, predictable keyboard focus order.
217 <span class="wcag">WCAG: <a target="_blank" rel="noopener noreferrer" href="https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html">2.4.3 Focus Order (A)</a></span>
218 </p>
219 </div>
220 <div class="setting-control">
221 <label class="toggle-switch">
222 <input type="checkbox" name="ariaat_general_settings[fix_tabindex]" value="1" <?php checked($settings['fix_tabindex'] ?? '', '1'); ?> />
223 <span class="toggle-slider"></span>
224 </label>
225 </div>
226 </div>
227
228 <div class="setting-row">
229 <div class="setting-label">
230 <strong>Non-text Fallback Helper</strong>
231 <p class="description">
232 Adds safe defaults for non-text media that has no accessible alternative. Unlabeled <i>&lt;svg&gt;</i> elements are treated as decorative, and unlabeled <i>&lt;object&gt;</i>, <i>&lt;embed&gt;</i>, and <i>&lt;canvas&gt;</i> get fallback labeling.
233 <span class="wcag">WCAG: <a target="_blank" rel="noopener noreferrer" href="https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html">1.1.1 Non-text Content (A)</a></span>
234 </p>
235 </div>
236 <div class="setting-control">
237 <label class="toggle-switch">
238 <input type="checkbox" name="ariaat_general_settings[fix_non_text_fallback]" value="1" <?php checked($settings['fix_non_text_fallback'] ?? '', '1'); ?> />
239 <span class="toggle-slider"></span>
240 </label>
241 </div>
242 </div>
243
244 <div class="setting-row">
245 <div class="setting-label">
246 <strong>Make Viewport Scalable</strong>
247 <p class="description">
248 Removes <i>user-scalable=no</i> from the viewport meta tag to allow pinch-zooming on mobile devices.
249 <span class="wcag">WCAG: <a target="_blank" rel="noopener noreferrer" href="https://www.w3.org/WAI/WCAG22/Understanding/reflow.html">1.4.10 Reflow (AA)</a> and
250 <a target="_blank" rel="noopener noreferrer" href="https://www.w3.org/WAI/WCAG22/Understanding/resize-text.html">1.4.4 Resize Text (AA)</a></span>
251 </p>
252 </div>
253 <div class="setting-control">
254 <label class="toggle-switch">
255 <input type="checkbox" name="ariaat_general_settings[make_viewport_scalable]" value="1" <?php checked($settings['make_viewport_scalable'] ?? '', '1'); ?> />
256 <span class="toggle-slider"></span>
257 </label>
258 </div>
259 </div>
260
261 <?php
262 $default_control = sprintf(
263 '<div class="pro-label"><a target="_blank" rel="noopener noreferrer" href="%s">Unlock</a></div>',
264 esc_url('https://wcagforwp.com/pricing/?utm_source=plugin&utm_medium=admin&utm_campaign=pro_upsell&utm_content=control')
265 );
266 ?>
267
268 <!-- Open External Links in New Tab -->
269 <div class="setting-row">
270 <div class="setting-label">
271 <strong>Open External Links in New Tab</strong>
272 <p class="description">
273 Adds <i>target="_blank"</i> and <i>rel="noopener noreferrer"</i> to external links and appends an SR-only (opens in new tab) hint.
274 <span class="wcag">WCAG: <a target="_blank" rel="noopener noreferrer" href="https://www.w3.org/WAI/WCAG22/Understanding/change-on-request.html">3.2.5 Change on Request (AA)</a></span>
275 </p>
276 </div>
277 <div class="setting-control">
278 <?php
279 echo apply_filters('ariaat_control_open_external_new_tab', $default_control, $settings);
280 ?>
281 </div>
282 </div>
283
284 <!-- Disable autoplaying media -->
285 <div class="setting-row">
286 <div class="setting-label">
287 <strong>Disable autoplaying media</strong>
288 <p class="description">
289 Prevents audio and video from autoplaying on the page.
290 <span class="wcag">WCAG: <a target="_blank" rel="noopener noreferrer" href="https://www.w3.org/WAI/WCAG22/Understanding/audio-control.html">1.4.2 Audio Control (A)</a></span>
291 </p>
292 </div>
293 <div class="setting-control">
294 <?php echo apply_filters('ariaat_control_disable_autoplay_media', $default_control, $settings); ?>
295 </div>
296 </div>
297
298 <!-- Enable reduced motion -->
299 <div class="setting-row">
300 <div class="setting-label">
301 <strong>Enable reduced motion</strong>
302 <p class="description">
303 Respects <i>prefers-reduced-motion</i> and disables animations where possible.
304 <span class="wcag">WCAG: <a target="_blank" rel="noopener noreferrer" href="https://www.w3.org/WAI/WCAG22/Understanding/animation-from-interactions.html">2.3.3 Animation from Interactions (AAA)</a></span>
305 </p>
306 </div>
307 <div class="setting-control">
308 <?php echo apply_filters('ariaat_control_enable_reduced_motion', $default_control, $settings); ?>
309 </div>
310 </div>
311
312 <!-- Force Underline on Text Links -->
313 <div class="setting-row">
314 <div class="setting-label">
315 <strong>Force Underline on Text Links</strong>
316 <p class="description">
317 Ensures text links are visually distinct without relying on color alone, improving visibility for all users.
318 <span class="wcag">WCAG:
319 <a target="_blank" rel="noopener noreferrer" href="https://www.w3.org/WAI/WCAG22/Understanding/use-of-color.html">1.4.1 Use of Color (A)</a>
320 and
321 <a target="_blank" rel="noopener noreferrer" href="https://www.w3.org/WAI/WCAG22/Understanding/focus-visible.html">2.4.7 Focus Visible (AA)</a></span>
322 </p>
323 </div>
324 <div class="setting-control">
325 <?php
326 echo apply_filters('ariaat_control_force_link_underlines', $default_control, $settings);
327 ?>
328 </div>
329 </div>
330
331 <!-- Mark Decorative Images -->
332 <div class="setting-row no-border">
333 <div class="setting-label">
334 <strong>Mark Decorative Images</strong>
335 <p class="description">
336 Adds <i>role="presentation"</i> to <i>&lt;img alt=""&gt;</i> elements that aren’t links, so screen readers skip purely decorative content.
337 <span class="wcag">WCAG:
338 <a target="_blank" rel="noopener noreferrer" href="https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html">1.1.1 Non-text Content (A)</a></span>
339 </p>
340 </div>
341 <div class="setting-control">
342 <?php
343 echo apply_filters('ariaat_control_mark_decorative_images', $default_control, $settings);
344 ?>
345 </div>
346 </div>
347 </div>
348
349
350 <div class="section">
351 <h3><span class="dashicons dashicons-heart"></span> Like this plugin?</h3>
352 <div class="setting-row no-border">
353 <div class="setting-label">
354 <p class="description">We'd love it if you could please <a href="https://wordpress.org/support/plugin/aria-accessibility-toolkit/reviews/#new-post" target="_blank" rel="noopener noreferrer">leave us a review</a> on WordPress.org</p>
355 </div>
356 </div>
357 </div>
358
359 <?php
360 } else if ($active_tab === 'aria') {
361 settings_fields('ariaat_aria_group');
362 $items = get_option('ariaat_aria_mappings', []);
363 ?>
364
365 <div class="section">
366 <h3><span class="dashicons dashicons-menu"></span> Menus</h3>
367 <p class="desc">
368 Selecting a menu below will add a new <b>ARIA Label</b> field to all items within that menu. Once a menu is selected and you have saved this setting, visit the <a href="<?php echo esc_url( admin_url('/nav-menus.php') ); ?>" target="_blank" rel="noopener noreferrer">WordPress menus</a> page to add ARIA labels to each menu item.
369 </p>
370
371 <?php
372 $all_menus = wp_get_nav_menus();
373 $menus = apply_filters('ariaat_allowed_menus', array_slice($all_menus, 0, 2), $all_menus);
374 $selected = get_option('ariaat_aria_menus', []);
375 $total_menus = count($all_menus);
376
377 foreach ($all_menus as $index => $menu) :
378 $checked = is_array( $selected ) && in_array($menu->term_id, $selected);
379 $is_free_limited = $index >= 2 && ! defined('ARIAAT_PRO_ACTIVE');
380 $is_last = ($index === $total_menus - 1);
381 ?>
382 <div class="setting-row <?php echo $is_last ? 'no-border' : 'border-bottom'; ?>">
383 <div class="setting-label">
384 <strong><?php echo esc_html($menu->name); ?></strong>
385 </div>
386 <div class="setting-control">
387 <?php if ($is_free_limited) { ?>
388 <div class="pro-label"><a href="https://wcagforwp.com/pricing/?utm_source=plugin&utm_medium=admin&utm_campaign=pro_upsell&utm_content=aria-menus" target="_blank" rel="noopener noreferrer">Unlock</a></div>
389 <?php } else { ?>
390 <label class="toggle-switch">
391 <input type="checkbox" name="ariaat_aria_menus[]" value="<?php echo esc_attr($menu->term_id); ?>" <?php checked($checked); ?> />
392 <span class="toggle-slider"></span>
393 </label>
394 <?php } ?>
395 </div>
396 </div>
397 <?php endforeach; ?>
398 <p class="desc">
399 <?php if (! defined('ARIAAT_PRO_ACTIVE')) { ?>
400 The free version supports up to 2 menus with ARIA labels. Click "Unlock" to enable all menus.
401 <?php } ?>
402 </p>
403 </div>
404
405 <div class="section">
406 <h3><span class="dashicons dashicons-editor-code"></span> Custom ARIA Attributes</h3>
407 <p class="desc">
408 Add custom <code>aria-*</code> attributes to elements on your site by targeting them with CSS selectors. For example, you might target a navigation item with a selector like <code>.nav-primary .current</code>. To find the correct selectors, right click on your page and click "Inspect" - some knowledge of CSS and HTML is required.
409 </p>
410
411 <table class="form-table" id="aria-table">
412 <thead>
413 <tr>
414 <th>CSS Selector</th>
415 <th>ARIA Attribute</th>
416 <th>Value</th>
417 <th></th>
418 </tr>
419 </thead>
420 <tbody>
421 <?php foreach ($items as $index => $item) : ?>
422 <tr>
423 <td><input type="text" name="ariaat_aria_mappings[<?php echo esc_attr($index); ?>][selector]" value="<?php echo esc_attr($item['selector']); ?>" class="regular-text" /></td>
424 <td>
425 <select name="ariaat_aria_mappings[<?php echo esc_attr($index); ?>][attribute]">
426 <?php foreach ($this->get_aria_attributes() as $attr => $attr_item): ?>
427 <option value="<?php echo esc_attr($attr); ?>" <?php selected($item['attribute'], $attr); ?>>
428 <?php echo esc_html($attr_item['label']); ?>
429 </option>
430 <?php endforeach; ?>
431 </select>
432 </td>
433 <td><input type="text" name="ariaat_aria_mappings[<?php echo esc_attr($index); ?>][value]" value="<?php echo esc_attr($item['value']); ?>" class="regular-text" /></td>
434 <td><button type="button" class="button remove-row">Remove</button></td>
435 </tr>
436 <?php endforeach; ?>
437 </tbody>
438 </table>
439
440 <p><button type="button" class="button" id="add-aria-row">Add ARIA Attribute</button></p>
441
442 <p class="desc">
443 <?php if (! defined('ARIAAT_PRO_ACTIVE')) { ?>
444 <br>Unlock all ARIA attributes with the <a href="https://wcagforwp.com/pricing/?utm_source=plugin&utm_medium=admin&utm_campaign=pro_upsell&utm_content=aria-attributes" class="pro">PRO plugin</a> such as 'aria-checked', 'aria-selected', 'aria-current', 'aria-required', 'aria-level' & more.
445 <?php } ?>
446 </p>
447 </div>
448
449 <?php
450
451
452 } elseif ($active_tab == 'roles') {
453 settings_fields('ariaat_role_group');
454 $items = get_option('ariaat_role_mappings', []);
455 ?>
456
457 <div class="section">
458 <h3><span class="dashicons dashicons-menu"></span> Menus</h3>
459 <p class="desc">
460 Automatically adds <code>role="navigation"</code> to selected menus. Do not use this setting if the menu container is a <code>&lt;nav&gt;</code> element, as there is no need.
461 </p>
462
463 <?php
464 $all_menus = wp_get_nav_menus();
465 $menus = apply_filters('ariaat_allowed_menus', array_slice($all_menus, 0, 2), $all_menus);
466 $selected_roles = get_option('ariaat_role_menus', []);
467 $total_menus = count($all_menus);
468
469 foreach ($all_menus as $index => $menu) :
470 $checked = is_array( $selected_roles ) && in_array($menu->term_id, $selected_roles);
471 $is_free_limited = $index >= 2 && ! defined('ARIAAT_PRO_ACTIVE');
472 $is_last = ($index === $total_menus - 1);
473 ?>
474 <div class="setting-row <?php echo $is_last ? 'no-border' : 'border-bottom'; ?>">
475 <div class="setting-label">
476 <strong><?php echo esc_html($menu->name); ?></strong>
477 </div>
478 <div class="setting-control">
479 <?php if ($is_free_limited) { ?>
480 <div class="pro-label"><a href="https://wcagforwp.com/pricing/?utm_source=plugin&utm_medium=admin&utm_campaign=pro_upsell&utm_content=roles-menu" target="_blank" rel="noopener noreferrer">Unlock</a></div>
481 <?php } else { ?>
482 <label class="toggle-switch">
483 <input type="checkbox" name="ariaat_role_menus[]" value="<?php echo esc_attr($menu->term_id); ?>" <?php checked($checked); ?> />
484 <span class="toggle-slider"></span>
485 </label>
486 <?php } ?>
487 </div>
488 </div>
489 <?php endforeach; ?>
490
491 <p class="desc">
492 <?php if (! defined('ARIAAT_PRO_ACTIVE')) { ?>
493 The free version supports up to 2 menus with roles. Click "Unlock" to enable all menus.
494 <?php } ?>
495 </p>
496 </div>
497 <div class="section">
498 <h3><span class="dashicons dashicons-editor-code"></span> Custom ARIA Attributes</h3>
499 <p class="desc">
500 Assign ARIA landmark <code>role</code> attributes to elements on your site using CSS selectors.
501 This is helpful when your theme doesn’t use semantic HTML or lacks proper roles for key regions like navigation, main content, or banners.
502 To find the right selectors, right-click any element on your site and choose "Inspect." Some familiarity with CSS and HTML is recommended.
503 </p>
504
505
506 <table class="form-table" id="role-table">
507 <thead>
508 <tr>
509 <th>CSS Selector</th>
510 <th>Role</th>
511 <th></th>
512 </tr>
513 </thead>
514 <tbody>
515 <?php foreach ($items as $index => $item) : ?>
516 <tr>
517 <td><input type="text" name="ariaat_role_mappings[<?php echo esc_attr($index); ?>][selector]" value="<?php echo esc_attr($item['selector']); ?>" class="regular-text" /></td>
518 <td>
519 <select name="ariaat_role_mappings[<?php echo esc_attr($index); ?>][role]">
520 <?php foreach ($this->get_role_options() as $role => $role_item): ?>
521 <option value="<?php echo esc_attr($role); ?>" <?php selected($item['role'], $role); ?>>
522 <?php echo esc_html($role_item['label']); ?>
523 </option>
524 <?php endforeach; ?>
525 </select>
526 </td>
527 <td><button type="button" class="button remove-row">Remove</button></td>
528 </tr>
529 <?php endforeach; ?>
530 </tbody>
531 </table>
532
533 <p><button type="button" class="button" id="add-role-row">Add Role</button></p>
534 <p class="desc">
535 <?php if (! defined('ARIAAT_PRO_ACTIVE')) { ?>
536 <br>The <a href="https://wcagforwp.com/pricing/?utm_source=plugin&utm_medium=admin&utm_campaign=pro_upsell&utm_content=landmark-roles" class="pro">PRO plugin</a> adds more ARIA landmark roles such as 'alert', 'dialog', 'menu', 'menuitem', 'searchbox', 'tab', 'tooltip' & more.
537 <?php } ?>
538 </p>
539 </div>
540
541 <?php
542
543
544 } else if( $active_tab == 'contrast' ) {
545 settings_fields('ariaat_contrast_group');
546 $items = get_option('ariaat_contrast_mappings', []);
547 ?>
548
549 <div class="section">
550 <h3><span class="dashicons dashicons-star-half"></span> High Contrast Adjustments</h3>
551 <p class="desc">
552 Apply custom foreground and background colors to specific elements to improve visual contrast and accessibility. This is especially useful for meeting WCAG contrast ratio requirements and making content easier to read for users with visual impairments.
553 </p>
554
555 <table class="form-table" id="contrast-table">
556 <thead>
557 <tr>
558 <th class="selector">HTML Selector</th>
559 <th class="color">Text</th>
560 <th class="color">Background</th>
561 <th>Contrast</th>
562 <th class="remove"></th>
563 </tr>
564 </thead>
565 <tbody>
566 <?php foreach ($items as $index => $item) : ?>
567 <tr>
568 <td class="selector"><input type="text" name="ariaat_contrast_mappings[<?php echo esc_attr( $index ) ?>][selector]" value="<?php echo esc_attr($item['selector']) ?>" class="regular-text" /></td>
569 <td>
570 <input type="color"
571 id="text-color-picker-<?php echo esc_attr($index); ?>"
572 class="text-color"
573 data-index="<?php echo esc_attr($index); ?>"
574 value="<?php echo esc_attr($item['color']); ?>"
575 >
576 <input type="hidden"
577 name="ariaat_contrast_mappings[<?php echo esc_attr($index); ?>][color]"
578 id="real-text-color-<?php echo esc_attr($index); ?>"
579 value="<?php echo esc_attr($item['color']); ?>"
580 >
581 <button type="button" class="button clear-color" data-target="<?php echo esc_attr($index); ?>" data-type="text">Clear</button>
582 </td>
583 <td>
584 <input type="color"
585 id="bg-color-picker-<?php echo esc_attr($index); ?>"
586 class="bg-color"
587 data-index="<?php echo esc_attr($index); ?>"
588 value="<?php echo esc_attr($item['background']); ?>"
589 >
590 <input type="hidden"
591 name="ariaat_contrast_mappings[<?php echo esc_attr($index); ?>][background]"
592 id="real-bg-color-<?php echo esc_attr($index); ?>"
593 value="<?php echo esc_attr($item['background']); ?>"
594 >
595 <button type="button" class="button clear-color" data-target="<?php echo esc_attr($index); ?>" data-type="bg">Clear</button>
596 </td>
597 <td>
598 <div class="contrast-result" id="contrast-result-<?php echo esc_attr( $index ); ?>"></div>
599 </td>
600
601 <td><button type="button" class="button remove-row">Remove</button></td>
602 </tr>
603 <?php endforeach; ?>
604 </tbody>
605 </table>
606 <p><button type="button" class="button" id="add-contrast-row">Add Contrast Item</button></p>
607
608 </div>
609
610 <?php
611
612 } else if ($active_tab === 'images') {
613
614
615 settings_fields('ariaat_images_group');
616 $settings = get_option('ariaat_image_settings', []);
617
618 /**
619 * PRO gate:
620 * - Free: limit total results to 20 (no pagination beyond first 50)
621 * - Pro: allow all results (normal pagination)
622 *
623 * PRO plugin should add: add_filter('ariaat_is_pro', '__return_true');
624 */
625 $is_pro = (bool) defined('ARIAAT_PRO_ACTIVE');
626
627 $show_all = isset($_POST['ariaat_image_settings']['show_all'])
628 ? (bool) $_POST['ariaat_image_settings']['show_all']
629 : ! empty($settings['show_all']);
630
631 $paged = isset($_GET['paged']) ? max(1, (int) $_GET['paged']) : 1;
632 $per_page = 50;
633
634 $offset = ($paged - 1) * $per_page;
635
636 $meta_query = $show_all ? [] : [
637 'relation' => 'OR',
638 [
639 'key' => '_wp_attachment_image_alt',
640 'value' => '',
641 'compare' => '='
642 ],
643 [
644 'key' => '_wp_attachment_image_alt',
645 'value' => ' ',
646 'compare' => '='
647 ],
648 [
649 'key' => '_wp_attachment_image_alt',
650 'compare' => 'NOT EXISTS'
651 ]
652 ];
653
654 $query = new WP_Query([
655 'post_type' => 'attachment',
656 'post_mime_type' => 'image',
657 'post_status' => 'inherit',
658 'posts_per_page' => $per_page,
659 'offset' => $offset,
660 'meta_query' => $meta_query,
661 ]);
662
663 $attachments = $query->posts;
664 $found_total = (int) $query->found_posts;
665
666 $total = $found_total;
667 $total_pages = (int) ceil($total / $per_page);
668 ?>
669 <div class="section">
670 <h3><span class="dashicons dashicons-format-gallery"></span> Images Missing Alt Text</h3>
671 <p class="desc">
672 Lists images on your site that are missing alt text. Add the alt text below and click "Save" to update each image. Does not scan images that are within themes or plugins, only images from your WordPress Media Library.
673 </p>
674
675 <p>
676 <label>
677 <input type="checkbox" name="ariaat_image_settings[show_all]" value="1"
678 <?php checked($settings['show_all'] ?? '', '1'); ?> />
679 Show all images (not just those missing alt text)
680 </label>
681 </p>
682
683 <?php if (empty($attachments)) : ?>
684 <p>No images without alt text were found.</p>
685 <?php else : ?>
686 <?php $can_edit_on_page = $is_pro || $offset < 10; ?>
687
688 <p style="margin-bottom: 1em;">
689 <button type="button" class="button copy-all-filenames" <?php disabled(! $can_edit_on_page); ?>>Copy all Filenames to Alt</button>
690 <button type="button" class="button copy-all-titles" <?php disabled(! $can_edit_on_page); ?>>Copy all Titles to Alt Tags</button>
691 </p>
692
693 <table class="form-table widefat fixed striped" id="alt-images" style="margin-bottom: 2em;">
694 <thead>
695 <tr>
696 <th class="id">ID</th>
697 <th class="thumb">Thumb</th>
698 <th>Alt Tag</th>
699 <th>Filename</th>
700 <th>Attached To</th>
701 <th class="save">Save</th>
702 </tr>
703 </thead>
704 <tbody>
705 <?php
706 $rendered_row_count = 0;
707 foreach ($attachments as $attachment) :
708
709 $parent_id = $attachment->post_parent;
710
711 if ($parent_id) {
712 $parent = get_post($parent_id);
713 if (!$parent || $parent->post_status !== 'publish') {
714 continue; // skip this image
715 }
716 }
717
718 $id = $attachment->ID;
719 $alt = get_post_meta($id, '_wp_attachment_image_alt', true);
720 $thumb = wp_get_attachment_image_url($id, [48, 48]);
721 $nonce = wp_create_nonce("ariaat_update_alt_{$id}");
722 $filename = basename(get_attached_file($id));
723 $parent_id = $attachment->post_parent;
724 $attached_to = $parent_id ? get_post($parent_id) : null;
725 $global_row_index = $offset + $rendered_row_count;
726 $can_edit_row = $is_pro || $global_row_index < 10;
727 $rendered_row_count++;
728 ?>
729 <tr data-id="<?php echo esc_attr($id); ?>">
730 <td><?php echo esc_html($id); ?></td>
731 <td><img src="<?php echo esc_url($thumb); ?>" width="48" height="48" /></td>
732 <td><input type="text" class="ariaat-alt" value="<?php echo esc_attr($alt); ?>" placeholder="no alt tag" <?php disabled(! $can_edit_row); ?> /></td>
733 <td>
734 <button type="button" class="button button-small copy-filename" data-filename="<?php echo esc_attr($filename); ?>" <?php disabled(! $can_edit_row); ?>>
735 Copy to Alt
736 </button>
737 <br>
738 <span class="small"><?php echo esc_html($filename); ?></span>
739 </td>
740 <td>
741 <?php if ($attached_to) : ?>
742 <button type="button"
743 class="button button-small copy-title"
744 data-title="<?php echo esc_attr(get_the_title($attached_to->ID)); ?>"
745 <?php disabled(! $can_edit_row); ?>>
746 Copy to Alt
747 </button>
748 <br>
749 <span class="small">
750 <a href="<?php echo esc_url(get_edit_post_link($attached_to->ID)); ?>">
751 <?php echo esc_html(get_the_title($attached_to->ID)); ?>
752 </a> <small>(<?php echo esc_html(ucfirst(get_post_type($attached_to))); ?>)</small>
753 </span>
754 <?php else : ?>
755 <em>Not attached</em>
756 <?php endif; ?>
757 </td>
758 <td>
759 <?php if ($can_edit_row) { ?>
760 <button class="button ariaat-save-alt"
761 data-id="<?php echo esc_attr($id); ?>"
762 data-nonce="<?php echo esc_attr($nonce); ?>">
763 Save
764 </button>
765 <?php } else { ?>
766 <div class="pro-label"><a href="https://wcagforwp.com/pricing/" target="_blank" rel="noopener noreferrer">Unlock</a></div>
767 <?php } ?>
768 </td>
769 </tr>
770 <?php endforeach; ?>
771 </tbody>
772 </table>
773
774 <?php if ($total_pages > 1) : ?>
775 <div class="ariaat-pagination">
776 <?php
777 $base_url = remove_query_arg(['paged']);
778 for ($i = 1; $i <= $total_pages; $i++) :
779 $url = add_query_arg('paged', $i, $base_url);
780 $active_class = ($i == $paged) ? 'active' : '';
781 printf(
782 '<a class="%s" href="%s">%d</a> ',
783 esc_attr($active_class),
784 esc_url($url),
785 $i
786 );
787 endfor;
788 ?>
789 </div>
790 <?php endif; ?>
791
792 <?php endif; ?>
793
794 </div>
795
796
797
798 <?php } else if ($active_tab === 'forms') {
799
800 $forms_output = '
801 <div class="section">
802 <h3><span class="dashicons dashicons-info"></span> Unlock</h3>
803 <p>Get deep form scanning and automatic form fixes when you upgrade to the PRO plugin. <strong>Works with all form plugins.</strong></p>
804 <a style="margin-bottom:30px" href="https://wcagforwp.com/pricing/?utm_source=plugin&utm_medium=admin&utm_campaign=pro_upsell&utm_content=scanner" class="button button-primary pro" target="_blank" rel="noopener noreferrer">View PRO Features</a>
805 </div>
806 ';
807
808 echo apply_filters( 'ariaat_forms_page_output', $forms_output, $active_tab );
809
810 }
811
812 do_action( 'ariaat_before_admin_submit_button', $active_tab );
813
814 if ($active_tab !== 'forms' || ($active_tab === 'forms' && defined('ARIAAT_PRO_ACTIVE') ) )
815 submit_button();
816
817 ?>
818
819 </form>
820
821 </div>
822
823 </div>
824
825 <?php
826 }
827
828
829 private function get_aria_attributes() {
830 $attributes = [
831 'aria-describedby' => ['label' => 'aria-describedby'],
832 'aria-disabled' => ['label' => 'aria-disabled'],
833 'aria-expanded' => ['label' => 'aria-expanded'],
834 'aria-hidden' => ['label' => 'aria-hidden'],
835 'aria-label' => ['label' => 'aria-label'],
836 'aria-labelledby' => ['label' => 'aria-labelledby'],
837 ];
838
839 /**
840 * Filter the list of ARIA attributes available in the plugin.
841 *
842 * @param array $attributes The default set of ARIA attributes.
843 */
844 return apply_filters('ariaat_aria_attributes_options', $attributes);
845 }
846
847
848 private function get_role_options() {
849 $roles = [
850 'banner' => ['label' => 'banner'],
851 'button' => ['label' => 'button'],
852 'checkbox' => ['label' => 'checkbox'],
853 'complementary' => ['label' => 'complementary'],
854 'contentinfo' => ['label' => 'contentinfo'],
855 'form' => ['label' => 'form'],
856 'grid' => ['label' => 'grid'],
857 'heading' => ['label' => 'heading'],
858 'img' => ['label' => 'img'],
859 'link' => ['label' => 'link'],
860 'list' => ['label' => 'list'],
861 'listitem' => ['label' => 'listitem'],
862 'main' => ['label' => 'main'],
863 'navigation' => ['label' => 'navigation'],
864 'region' => ['label' => 'region'],
865 'row' => ['label' => 'row'],
866 'rowheader' => ['label' => 'rowheader'],
867 'search' => ['label' => 'search'],
868 'table' => ['label' => 'table'],
869 'textbox' => ['label' => 'textbox'],
870 ];
871
872 /**
873 * Filter the list of available roles.
874 *
875 * @param array $roles Array of role definitions.
876 */
877 return apply_filters('ariaat_roles_options', $roles);
878 }
879
880
881 public function sanitize_array($input) {
882 if (!is_array($input)) {
883 return sanitize_text_field($input);
884 }
885
886 foreach ($input as $key => $item) {
887 if (is_array($item)) {
888 foreach ($item as $subkey => $value) {
889 // Sanitize based on known keys
890 if (in_array($subkey, ['selector'], true)) {
891 // Allow basic CSS selectors: letters, numbers, dashes, underscores, dots, #, >, space, :
892 $input[$key][$subkey] = preg_replace('/[^a-zA-Z0-9_\s\.\#\>\:\[\]\=\"~\+\-\*(),]/', '', $value);
893 } elseif (in_array($subkey, ['attribute', 'role'], true)) {
894 // Whitelist attribute/role names to be alphanumeric with optional dashes
895 $input[$key][$subkey] = preg_replace('/[^a-zA-Z0-9\-_]/', '', $value);
896 } else {
897 $input[$key][$subkey] = sanitize_text_field($value);
898 }
899 }
900 } else {
901 $input[$key] = sanitize_text_field($item);
902 }
903 }
904
905 return $input;
906 }
907
908
909
910 public function sanitize_menu_selection($input) {
911 $allowed_menus = array_keys($this->get_all_menus());
912
913 if( ! $input || ! array( $allowed_menus ) || ! array( $input ) )
914 return;
915 $selected = array_intersect($input, $allowed_menus);
916
917 return $selected;
918 }
919
920 public function sanitize_general_settings($input) {
921 error_log( 'ARIAAT raw general input: ' . print_r( $input, true ) );
922
923 $input = is_array($input) ? $input : [];
924 $output = [];
925
926 // Text fields
927 $output['language'] = isset($input['language']) ? sanitize_text_field($input['language']) : '';
928 $output['skip_link'] = isset($input['skip_link']) ? sanitize_text_field($input['skip_link']) : '';
929
930 // Checkboxes / toggles
931 $output['enable_frontend_checker'] = ! empty($input['enable_frontend_checker']) ? '1' : '';
932 $output['focus_outline'] = ! empty($input['focus_outline']) ? '1' : '';
933 $output['fix_tabindex'] = ! empty($input['fix_tabindex']) ? '1' : '';
934 $output['fix_non_text_fallback'] = ! empty($input['fix_non_text_fallback']) ? '1' : '';
935 $output['make_viewport_scalable'] = ! empty($input['make_viewport_scalable']) ? '1' : '';
936 $output['open_external_new_tab'] = ! empty($input['open_external_new_tab']) ? '1' : '';
937 $output['disable_autoplay_media'] = ! empty($input['disable_autoplay_media']) ? '1' : '';
938 $output['enable_reduced_motion'] = ! empty($input['enable_reduced_motion']) ? '1' : '';
939 $output['force_link_underlines'] = ! empty($input['force_link_underlines']) ? '1' : '';
940 $output['mark_decorative_images'] = ! empty($input['mark_decorative_images']) ? '1' : '';
941
942 return $output;
943 }
944
945 public function sanitize_form_settings($input) {
946
947 $sanitized = [];
948
949 $checkboxes = [
950 'auto_generate_labels',
951 'fix_label_for',
952 'generate_aria_labels',
953 'fix_empty_buttons',
954 'remove_positive_tabindex',
955 'group_form_fields',
956 'clean_hidden_labels',
957 'placeholders_to_aria',
958 'fix_select_labels',
959 'fix_submit_labels',
960 ];
961
962 foreach ($checkboxes as $key) {
963 $sanitized[$key] = isset($input[$key]) && $input[$key] === '1' ? '1' : '0';
964 }
965
966 return $sanitized;
967
968 }
969
970
971 }
972
973 // Initialize the class
974 new ARIAAT_Admin();
975