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