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