PluginProbe
WP Accessibility / 2.3.4
WP Accessibility v2.3.4
2.3.5 trunk 1.0.0 1.2.3 1.2.9 1.3.0 1.3.11 1.4.0 1.4.6 1.5.0 1.5.10 1.6.0 1.6.10 1.7.0 1.7.14 1.8.0 1.8.1 1.9.0 1.9.2 2.0.0 2.0.1 2.1.0 2.1.19 2.2.0 2.2.5 All 29 releases
wp-accessibility / wp-accessibility.php

wp-accessibility.php in WP Accessibility 2.3.4, at wp-accessibility.php

1,050 lines 35.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WP Accessibility
4 *
5 * @package WP Accessibility
6 * @author Joe Dolson
7 * @copyright 2012-2026 Joe Dolson
8 * @license GPL-2.0+
9 *
10 * @wordpress-plugin
11 * Plugin Name: WP Accessibility
12 * Plugin URI: https://www.joedolson.com/wp-accessibility/
13 * Description: Helps improve accessibility in your WordPress site, like removing title attributes.
14 * Author: Joe Dolson
15 * Author URI: https://www.joedolson.com/
16 * Text Domain: wp-accessibility
17 * Domain Path: /lang
18 * License: GPL-2.0+
19 * License URI: http://www.gnu.org/license/gpl-2.0.txt
20 * Version: 2.3.4
21 */
22
23 /*
24 Copyright 2012-2026 Joe Dolson (email : joe@joedolson.com)
25
26 This program is free software; you can redistribute it and/or modify
27 it under the terms of the GNU General Public License as published by
28 the Free Software Foundation; either version 2 of the License, or
29 (at your option) any later version.
30
31 This program is distributed in the hope that it will be useful,
32 but WITHOUT ANY WARRANTY; without even the implied warranty of
33 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
34 GNU General Public License for more details.
35
36 You should have received a copy of the GNU General Public License
37 along with this program; if not, write to the Free Software
38 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
39 */
40
41 if ( 'on' === get_option( 'wpa_toolbar' ) || 'on' === get_option( 'wpa_widget_toolbar' ) ) {
42 require_once __DIR__ . '/wp-accessibility-toolbar.php';
43 }
44 require_once __DIR__ . '/wp-accessibility-longdesc.php';
45 require_once __DIR__ . '/wp-accessibility-alt.php';
46 require_once __DIR__ . '/wp-accessibility-contrast.php';
47 require_once __DIR__ . '/wp-accessibility-settings.php';
48 require_once __DIR__ . '/wp-accessibility-overlay.php';
49 require_once __DIR__ . '/wp-accessibility-admin.php';
50 require_once __DIR__ . '/wp-accessibility-help.php';
51 if ( 'off' !== get_option( 'wpa_track_stats' ) ) {
52 require_once __DIR__ . '/wp-accessibility-stats.php';
53 }
54
55 define( 'WP_ACCESSIBILITY_VERSION', '2.3.4' );
56
57 register_activation_hook( __FILE__, 'wpa_install' );
58
59 add_action( 'admin_notices', 'wpa_status_notice', 10 );
60 /**
61 * Display notice in Playground for demo purposes.
62 */
63 function wpa_status_notice() {
64 // Only shown when in the Playground preview.
65 if ( 'true' === get_option( 'wpa_show_playground_intro', '' ) ) {
66 echo '<div class="notice notice-info">';
67 echo '<h3>' . __( 'Thanks for trying out WP Accessibility!', 'wp-accessibility' ) . '</h3>';
68 echo '<p>' . __( "Let me give you a few quick things to try out while you're here:", 'wp-accessibility' ) . '</p>';
69 echo '<ol>';
70 echo '<li>' . __( 'Create a new post and add media to observe some WP Accessibility features.', 'wp-accessibility' ) . '</li>';
71 echo '<li>' . __( 'Go to the Themes page and install a theme of your choice, then explore the site. Errors corrected are logged in the console and saved to stats.', 'wp-accessibility' ) . '</li>';
72 echo '<li>' . __( 'Visit the WP Accessibility Stats to view what WP Accessibility did on each page.', 'wp-accessibility' ) . '</li>';
73 echo '</ol>';
74 // translators: link to plugin documentation.
75 echo '<p>' . sprintf( __( 'To learn more, check out the <a href="%s">plugin documentation</a>.', 'wp-accessibility' ), 'https://docs.joedolson.com/wp-accessibility/' ) . '</p>';
76 echo '</div>';
77 }
78 }
79
80 add_action( 'admin_menu', 'wpa_admin_menu' );
81 /**
82 * Set up admin menu.
83 */
84 function wpa_admin_menu() {
85 add_menu_page( __( 'WP Accessibility - Features', 'wp-accessibility' ), __( 'WP Accessibility', 'wp-accessibility' ), 'manage_options', 'wp-accessibility', 'wpa_admin_settings', 'dashicons-universal-access' );
86 add_submenu_page( 'wp-accessibility', __( 'WP Accessibility - Fixes', 'wp-accessibility' ), __( 'Accessibility Fixes', 'wp-accessibility' ), 'manage_options', 'wp-accessibility-overlay', 'wpa_admin_overlay_settings' );
87 add_submenu_page( 'wp-accessibility', __( 'WP Accessibility - Admin & Testing', 'wp-accessibility' ), __( 'Testing & Admin', 'wp-accessibility' ), 'manage_options', 'wp-accessibility-admin', 'wpa_admin_admin_settings' );
88 add_submenu_page( 'wp-accessibility', __( 'WP Accessibility - Help', 'wp-accessibility' ), __( 'Get Help', 'wp-accessibility' ), 'manage_options', 'wp-accessibility-help', 'wpa_help_screen' );
89 }
90
91 /**
92 * Install on activation.
93 */
94 function wpa_install() {
95 if ( 'true' !== get_option( 'wpa_installed' ) ) {
96 add_option( 'asl_styles_focus', '' );
97 add_option( 'asl_styles_passive', '' );
98 add_option( 'asl_default_styles', 'true' );
99 add_option( 'wpa_target', 'on' );
100 add_option( 'wpa_search', 'on' );
101 add_option( 'wpa_tabindex', 'on' );
102 add_option( 'wpa_continue', 'Continue Reading' );
103 add_option( 'wpa_focus', '' );
104 add_option( 'wpa_installed', 'true' );
105 add_option( 'wpa_longdesc', 'button' );
106 add_option( 'wpa_post_types', array( 'post' ) );
107 } else {
108 wpa_check_version();
109 }
110 }
111
112 /**
113 * Check current version and upgrade if needed.
114 *
115 * @return string
116 */
117 function wpa_check_version() {
118 // upgrade for version 1.3.0.
119 $version = WP_ACCESSIBILITY_VERSION;
120 if ( version_compare( $version, '2.3.0', '<' ) ) {
121 add_option( 'wpa_lang_attributes', 'on' );
122 }
123 if ( version_compare( $version, '1.3.0', '<' ) ) {
124 add_option( 'wpa_longdesc', 'button' );
125 }
126 // upgrade for version 1.9.0.
127 if ( version_compare( $version, '1.9.0', '<' ) ) {
128 add_option( 'wpa_post_types', array( 'post' ) );
129 $wpa_toolbar_fs = get_option( 'wpa_toolbar_fs', '' );
130 $wpa_toolbar_ct = get_option( 'wpa_toolbar_ct', '' );
131 if ( '' === $wpa_toolbar_fs ) {
132 update_option( 'wpa_toolbar_fs', 'on' );
133 }
134 if ( '' === $wpa_toolbar_ct ) {
135 update_option( 'wpa_toolbar_ct', 'on' );
136 }
137 }
138 if ( version_compare( $version, '2.2.0', '<' ) ) {
139 $ld_option = get_option( 'wpa_longdesc' );
140 if ( 'jquery' === $ld_option ) {
141 update_option( 'wpa_longdesc', 'button' );
142 }
143 }
144
145 return $version;
146 }
147
148 add_filter( 'plugin_action_links', 'wpa_plugin_action', 10, 2 );
149 /**
150 * Add plugin action links.
151 *
152 * @param array $links Existing links.
153 * @param string $file File name.
154 */
155 function wpa_plugin_action( $links, $file ) {
156 if ( plugin_basename( __DIR__ . '/wp-accessibility.php' ) === $file ) {
157 $admin_url = admin_url( 'admin.php?page=wp-accessibility' );
158 $links[] = "<a href='$admin_url'>" . __( 'Accessibility Settings', 'wp-accessibility' ) . '</a>';
159 }
160
161 return $links;
162 }
163
164 add_action( 'wp_enqueue_scripts', 'wpa_stylesheet' );
165 /**
166 * Enqueue stylesheets for WP Accessibility.
167 */
168 function wpa_stylesheet() {
169 $version = ( SCRIPT_DEBUG ) ? wp_rand( 10000, 100000 ) : wpa_check_version();
170 wp_register_style( 'wpa-style', plugins_url( 'css/wpa-style.css', __FILE__ ), array(), $version );
171 $ld = get_option( 'wpa_longdesc' );
172 if ( 'link' === $ld || 'jquery' === $ld || 'button' === $ld || 'on' === get_option( 'asl_enable' ) || ! empty( get_option( 'wpa_post_types', array() ) ) ) {
173 wp_enqueue_style( 'wpa-style' );
174 // these styles are derived from the WordPress skip link defaults.
175 $top = '7px';
176 if ( is_admin_bar_showing() ) {
177 $top = '37px';
178 }
179 $add_css = ( ! wpa_accessible_theme() ) ? wpa_css() : '';
180 $custom_css = ':root { --admin-bar-top : ' . $top . '; }';
181 wp_add_inline_style( 'wpa-style', wp_filter_nohtml_kses( wp_unslash( $add_css . $custom_css ) ) );
182 }
183 if ( current_user_can( 'edit_files' ) && 'on' === get_option( 'wpa_diagnostics' ) ) {
184 wp_register_style( 'diagnostic', plugins_url( 'css/diagnostic.css', __FILE__ ) );
185 wp_register_style( 'diagnostic-head', plugins_url( 'css/diagnostic-head.css', __FILE__ ) );
186 wp_enqueue_style( 'diagnostic' );
187 wp_enqueue_style( 'diagnostic-head' );
188 }
189 }
190
191 add_action( 'admin_head', 'wpa_admin_stylesheet' );
192 /**
193 * Enqueue admin stylesheets if enabled
194 */
195 function wpa_admin_stylesheet() {
196 // Used to provide an admin CSS from plug-in, now only enqueue if custom provided in theme.
197 if ( file_exists( get_stylesheet_directory() . '/wp-admin.css' ) ) {
198 $file = get_stylesheet_directory_uri() . '/wp-admin.css';
199 wp_register_style( 'wp-a11y-css', $file );
200 wp_enqueue_style( 'wp-a11y-css' );
201 }
202 }
203
204 /**
205 * Generate skiplink CSS.
206 *
207 * @param bool $defaults 'true' to return defaults regardless of settings.
208 *
209 * @return string
210 */
211 function wpa_skiplink_css( $defaults = false ) {
212 $use_defaults = get_option( 'asl_default_styles', '' );
213 $styles = '';
214 $focus = '';
215 $passive = '';
216 $visibility = ( 'on' === get_option( 'asl_visible' ) ) ? 'wpa-visible' : 'wpa-hide';
217 $is_rtl = ( is_rtl() ) ? '-rtl' : '-ltr';
218 $off_vis = ( 'on' === get_option( 'asl_visible' ) ) ? 'wpa-hide' : 'wpa-visible';
219 $off_rtl = ( is_rtl() ) ? '-ltr' : '-rtl';
220 // If not using default styles.
221 if ( 'true' !== $use_defaults && ! $defaults ) {
222 $default_focus = '';
223 $default_passive = '';
224 if ( '' !== get_option( 'asl_styles' ) ) {
225 $styles = wp_filter_nohtml_kses( get_option( 'asl_styles' ) );
226 // Ensure custom styles match settings.
227 $styles = str_replace( array( $off_vis, $off_rtl ), array( $visibility, $is_rtl ), $styles );
228 // If custom styles contain #skiplinks, we can just return this; it's probably a fully realized selector.
229 if ( false !== stripos( $styles, '#skiplinks' ) ) {
230 return $styles;
231 }
232 } else {
233 $focus = wp_filter_nohtml_kses( get_option( 'asl_styles_focus' ) );
234 $passive = wp_filter_nohtml_kses( get_option( 'asl_styles_passive' ) );
235 }
236 } else {
237 // these styles are derived from the WordPress skip link defaults.
238 $default_focus = 'background-color: #f1f1f1;
239 box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.6);
240 color: #0073aa;
241 display: block;
242 font-weight: 600;
243 height: auto;
244 line-height: normal;
245 padding: 15px 23px 14px;
246 position: absolute;
247 left: 6px;
248 top: var(--admin-bar-top);
249 text-decoration: none;
250 text-transform: none;
251 width: auto;
252 z-index: 100000;';
253
254 // Passive default styles derived from WordPress default focus styles.
255 $default_passive = 'background-color: #fff;
256 box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.2);
257 color: #333;
258 display: block;
259 font-weight: 600;
260 height: auto;
261 line-height: normal;
262 padding: 15px 23px 14px;
263 position: absolute;
264 left: 6px;
265 top: var(--admin-bar-top);
266 text-decoration: none;
267 text-transform: none;
268 width: auto;
269 z-index: 100000;';
270 }
271 if ( is_admin() && '' !== $styles ) {
272 return $styles;
273 }
274
275 if ( ! $focus ) {
276 $focus = $default_focus;
277 } else {
278 $focus = $default_focus . $focus;
279 }
280
281 $vis = '';
282 $invis = '';
283 $class = '.' . $visibility . $is_rtl;
284 // If links are visible, "hover" is a focus style, otherwise, it's a passive style.
285 if ( 'on' === get_option( 'asl_visible' ) ) {
286 $vis = $class . '#skiplinks a:hover,';
287 $passive = $default_passive . $passive;
288 } else {
289 $invis = $class . '#skiplinks a:hover,';
290 $passive = '';
291 }
292 $styles .= "
293 $class#skiplinks a, $invis $class#skiplinks a:visited {
294 $passive
295 }
296 $class#skiplinks a:active, $vis $class#skiplinks a:focus {
297 $focus
298 }
299 ";
300 /**
301 * Filter CSS styles output on front-end for skip links.
302 *
303 * @hook wpa_skiplink_styles
304 *
305 * @param string $styles Styles configured by settings.
306 *
307 * @return string
308 */
309 $styles = apply_filters( 'wpa_skiplink_styles', $styles );
310
311 return $styles;
312 }
313
314 /**
315 * Generate styles needed for WP Accessibility options.
316 */
317 function wpa_css() {
318 $styles = '';
319 if ( get_option( 'asl_enable' ) === 'on' ) {
320 $styles .= wpa_skiplink_css();
321 }
322 if ( 'on' === get_option( 'wpa_focus' ) ) {
323 $color = ( false !== (bool) get_option( 'wpa_focus_color' ) ) ? ' #' . get_option( 'wpa_focus_color' ) : '#233c7f';
324 $styles .= "
325 :focus { outline: 2px solid$color!important; outline-offset: 2px !important; }
326 ";
327 }
328
329 return $styles;
330 }
331
332 /**
333 * Test whether a URL is validly structured.
334 *
335 * @param string $url A purported URL.
336 *
337 * @return mixed URL if valid, false if not.
338 */
339 function wpa_is_url( $url ) {
340 return preg_match( '|^http(s)?://[a-z0-9-]+(.[a-z0-9-]+)*(:[0-9]+)?(/.*)?$|i', $url );
341 }
342
343 add_action( 'wp_enqueue_scripts', 'wpa_enqueue_js', 100 );
344 /**
345 * Enqueue JS needed for WP Accessibility options.
346 */
347 function wpa_enqueue_js() {
348 $version = ( SCRIPT_DEBUG ) ? wp_rand( 10000, 100000 ) : wpa_check_version();
349 $visibility = ( 'on' === get_option( 'asl_visible' ) ) ? 'wpa-visible' : 'wpa-hide';
350 $output = '';
351 if ( 'on' === get_option( 'asl_enable' ) && ! wpa_accessible_theme() ) {
352 $html = '';
353 /**
354 * Customize the default value for extra skiplink. Turns on extra skiplink options in WP Accessibility versions > 1.9.0.
355 *
356 * @hook asl_extra_target
357 * @param string Value to use as a default for the extra skiplink.
358 *
359 * @return string
360 */
361 $default_extra = apply_filters( 'asl_extra_target', '' );
362 $extra = get_option( 'asl_extra_target', $default_extra );
363 $extra = ( wpa_is_url( $extra ) ) ? esc_url( $extra ) : str_replace( '#', '', esc_attr( $extra ) );
364 if ( '' !== $extra && ! wpa_is_url( $extra ) ) {
365 $extra = "#$extra";
366 }
367 $extra_text = (string) stripslashes( get_option( 'asl_extra_text' ) );
368 $content = str_replace( '#', '', esc_attr( get_option( 'asl_content' ) ) );
369 $nav = str_replace( '#', '', esc_attr( get_option( 'asl_navigation' ) ) );
370 /**
371 * Customize the default value for sitemap skiplink. Turns on sitemap skiplink options in WP Accessibility versions > 1.9.0.
372 *
373 * @hook asl_sitemap
374 * @param string Value to use as a default for the sitemap.
375 *
376 * @return string
377 */
378 $default_sitemap = apply_filters( 'asl_sitemap', '' );
379 $sitemap = esc_url( get_option( 'asl_sitemap', $default_sitemap ) );
380 $html .= ( '' !== $content ) ? "<a href=\"#$content\" class='no-scroll et_smooth_scroll_disabled'>" . __( 'Skip to Content', 'wp-accessibility' ) . '</a> ' : '';
381 $html .= ( '' !== $nav ) ? "<a href=\"#$nav\" class='no-scroll et_smooth_scroll_disabled'>" . __( 'Skip to navigation', 'wp-accessibility' ) . '</a> ' : '';
382 $html .= ( '' !== $sitemap ) ? "<a href=\"$sitemap\" class='no-scroll et_smooth_scroll_disabled'>" . __( 'Site map', 'wp-accessibility' ) . '</a> ' : '';
383 $html .= ( '' !== $extra && '' !== $extra_text ) ? "<a href=\"$extra\" class='no-scroll et_smooth_scroll_disabled'>$extra_text</a> " : '';
384 $is_rtl = ( is_rtl() ) ? '-rtl' : '-ltr';
385 $skiplinks = __( 'Skip links', 'wp-accessibility' );
386 $output = ( '' !== $html ) ? "<div class=\"$visibility$is_rtl\" id=\"skiplinks\" role=\"navigation\" aria-label=\"" . esc_attr( $skiplinks ) . "\">$html</div>" : '';
387 }
388
389 $labels = array(
390 's' => _x( 'Search', 'Search field label', 'wp-accessibility' ),
391 'author' => _x( 'Name', 'Commenter name label', 'wp-accessibility' ),
392 'email' => _x( 'Email', 'Commenter email label', 'wp-accessibility' ),
393 'url' => _x( 'Website', 'Commenter website label', 'wp-accessibility' ),
394 'comment' => _x( 'Comment', 'Commenter message label', 'wp-accessibility' ),
395 );
396 /**
397 * Customize labels passed to automatically label core WordPress fields.
398 *
399 * @hook wpa_labels
400 * @param array $labels Array of labels for search and comment fields.
401 *
402 * @return array
403 */
404 $labels = apply_filters( 'wpa_labels', $labels );
405 $dir = '';
406 $lang = '';
407 if ( 'off' !== get_option( 'wpa_lang_attributes' ) && ! wpa_accessible_theme() ) {
408 $dir = ( is_rtl() ) ? 'rtl' : 'ltr';
409 $lang = get_bloginfo( 'language' );
410 }
411
412 if ( SCRIPT_DEBUG ) {
413 $wpajs = plugins_url( 'js/wp-accessibility.js', __FILE__ );
414 } else {
415 $wpajs = plugins_url( 'js/wp-accessibility.min.js', __FILE__ );
416 }
417 wp_enqueue_script(
418 'wp-accessibility',
419 $wpajs,
420 array(),
421 $version,
422 array(
423 'in_footer' => true,
424 'strategy' => 'defer',
425 )
426 );
427
428 $longdesc_type = false;
429 if ( 'link' === get_option( 'wpa_longdesc' ) ) {
430 $longdesc_type = 'link';
431 } elseif ( 'jquery' === get_option( 'wpa_longdesc' ) || 'button' === get_option( 'wpa_longdesc' ) ) {
432 $longdesc_type = 'button';
433 }
434 /**
435 * Modify the selector used to attach the alt attribute toggle button on images. Default `.hentry img[alt!=""], .comment-content img[alt!=""]`.
436 *
437 * @hook wpa_show_alt_selector
438 *
439 * @since 2.0.0
440 *
441 * @param string $selector Valid CSS selector string.
442 *
443 * @return string
444 */
445 $selector = apply_filters( 'wpa_show_alt_selector', '.hentry img[alt]:not([alt=""]), .comment-content img[alt]:not([alt=""]), #content img[alt]:not([alt=""]),.entry-content img[alt]:not([alt=""])' );
446 $alttext = ( 'on' === get_option( 'wpa_show_alt' ) ) ? true : false;
447
448 /**
449 * Filter target element selector for underlines. Default `a`.
450 *
451 * @hook wpa_underline_target
452 *
453 * @param string $el Target element selector.
454 *
455 * @return string
456 */
457 $target = apply_filters( 'wpa_underline_target', 'a' );
458 /**
459 * Filter whether console log messages about remediation actions will be sent.
460 *
461 * @hook wpa_view_remediation_logs
462 *
463 * @param bool $visible Default `true` if user is logged in and has capabilities to manage options.
464 *
465 * @return bool
466 */
467 $errors_enabled = apply_filters( 'wpa_view_remediation_logs', current_user_can( 'manage_options' ) );
468 $track = ( '' === get_option( 'wpa_track_stats', '' ) ) ? current_user_can( 'manage_options' ) : true;
469 $track = ( 'off' === get_option( 'wpa_track_stats' ) ) ? false : $track;
470 /**
471 * Filter whether data from views will be tracked.
472 *
473 * @hook wpa_track_view_statistics
474 *
475 * @param bool $visible Default `true` if user is logged in and has capabilities to manage options or if enabled in settings.
476 *
477 * @return bool
478 */
479 $tracking_enabled = apply_filters( 'wpa_track_view_statistics', $track );
480 $apply_labels = ( 'off' === get_option( 'wpa_labels' ) ) ? false : true;
481 /**
482 * Filter whether automatic labeling is enabled.
483 *
484 * @hook wpa_disable_labels
485 *
486 * @param bool $enabled True if labels are automatically added.
487 *
488 * @return bool
489 */
490 $apply_labels = apply_filters( 'wpa_disable_labels', $apply_labels );
491 $remove_titles = ( 'off' === get_option( 'wpa_remove_titles' ) ) ? false : true;
492 /**
493 * Filter whether title attributes are removed. Used to be image titles only, now applies buttons and links, as well.
494 *
495 * @hook wpa_remove_titles
496 *
497 * @param bool $enabled True if title attributes are removed.
498 *
499 * @return bool
500 */
501 $remove_titles = apply_filters( 'wpa_remove_titles', $remove_titles );
502 $set_viewport = ( 'off' === get_option( 'wpa_viewport' ) ) ? false : true;
503 /**
504 * Filter whether WP Accessibility manipulates the viewport.
505 *
506 * @hook wpa_viewport
507 *
508 * @param bool $enabled True viewport can be modified.
509 *
510 * @return bool
511 */
512 $viewport = apply_filters( 'wpa_viewport', $set_viewport );
513 wp_localize_script(
514 'wp-accessibility',
515 'wpa',
516 array(
517 'skiplinks' => array(
518 'enabled' => ( 'on' === get_option( 'asl_enable' ) ) ? true : false,
519 'output' => $output,
520 ),
521 'target' => ( 'on' === get_option( 'wpa_target' ) ) ? true : false,
522 'tabindex' => ( 'on' === get_option( 'wpa_tabindex' ) ) ? true : false,
523 'underline' => array(
524 'enabled' => ( 'on' === get_option( 'wpa_underline' ) ) ? true : false,
525 'target' => $target,
526 ),
527 'videos' => ( 'on' === get_option( 'wpa_videos' ) ) ? true : false,
528 'dir' => $dir,
529 'viewport' => $viewport,
530 'lang' => $lang,
531 'titles' => $remove_titles,
532 'labels' => $apply_labels,
533 'wpalabels' => $labels,
534 'alt' => $alttext,
535 'altSelector' => $selector,
536 'current' => ( version_compare( $GLOBALS['wp_version'], '5.3', '<' ) ) ? true : false,
537 'errors' => ( $errors_enabled ) ? true : false,
538 'tracking' => ( $tracking_enabled ) ? true : false,
539 'ajaxurl' => admin_url( 'admin-ajax.php' ),
540 'security' => wp_create_nonce( 'wpa-stats-action' ),
541 'action' => 'wpa_stats_action',
542 'url' => ( function_exists( 'wpa_get_current_url' ) ) ? wpa_get_current_url() : 'disabled',
543 'post_id' => ( is_singular() ) ? get_the_ID() : '',
544 'continue' => ( wp_is_block_theme() ) ? true : false,
545 'pause' => __( 'Pause video', 'wp-accessibility' ),
546 'play' => __( 'Play video', 'wp-accessibility' ),
547 'restUrl' => get_rest_url( null, 'wp/v2/media' ),
548 'ldType' => $longdesc_type,
549 'ldHome' => home_url(),
550 'ldText' => '<span class="dashicons dashicons-media-text" aria-hidden="true"></span><span class="screen-reader">' . __( 'Long Description', 'wp-accessibility' ) . '</span>',
551 )
552 );
553 }
554
555 // courtesy of Graham Armfield (modified).
556 add_action( 'admin_bar_menu', 'wpa_logout_item', 11 );
557 /**
558 * Add adminbar menu logout.
559 *
560 * @link http://www.coolfields.co.uk/2013/02/wordpress-permanently-visible-log-out-link-plugin-version-0-1/
561 * @param object $admin_bar Admin bar object.
562 */
563 function wpa_logout_item( $admin_bar ) {
564 if ( ! is_user_logged_in() || 'on' === get_option( 'wpa_disable_logout', 'true' ) ) {
565 return;
566 }
567
568 $args = array(
569 'id' => 'wpa-logout',
570 'title' => __( 'Log Out', 'wp-accessibility' ),
571 'href' => wp_logout_url(),
572 );
573 $admin_bar->add_node( $args );
574 }
575
576 add_filter( 'posts_clauses', 'wpa_search_attachment_alt', 20, 2 );
577 /**
578 * Allow users to search alt attributes in the media library.
579 *
580 * @param array $clauses WordPress post query clauses.
581 * @param object $query WordPress query object.
582 *
583 * @return array
584 */
585 function wpa_search_attachment_alt( $clauses, $query ) {
586 if ( is_admin() && 'on' === get_option( 'wpa_search_alt' ) ) {
587 global $wpdb;
588 if ( isset( $query->query['post_type'] ) && 'attachment' === $query->query['post_type'] && '' !== $query->query_vars['s'] ) {
589 $clauses['join'] = " LEFT JOIN {$wpdb->postmeta} AS sq1 ON ( {$wpdb->posts}.ID = sq1.post_id AND ( sq1.meta_key = '_wp_attached_file' OR sq1.meta_key = '_wp_attachment_image_alt' ) )";
590 }
591 }
592
593 return $clauses;
594 }
595
596 add_filter( 'mce_css', 'wpa_diagnostic_css' );
597 /**
598 * Add diagnostic CSS.
599 *
600 * @param string $mce_css Existing CSS.
601 *
602 * @return full string css.
603 */
604 function wpa_diagnostic_css( $mce_css ) {
605 if ( get_option( 'wpa_diagnostics' ) === 'on' ) {
606 $mce_css .= ', ' . plugins_url( 'css/diagnostic.css', __FILE__ );
607 }
608
609 return $mce_css;
610 }
611
612 if ( 'on' === get_option( 'wpa_search' ) ) {
613 add_filter( 'pre_get_posts', 'wpa_filter' );
614 }
615
616 /**
617 * Filter search queries to ensure that an error page is returned if no results.
618 *
619 * @param object $query Main WP_Query object.
620 *
621 * @return $query.
622 */
623 function wpa_filter( $query ) {
624 if ( ! is_admin() ) {
625 if ( isset( $_GET['s'] ) && '' === trim( $_GET['s'] ) && ( $query->is_main_query() ) ) {
626 $query->query_vars['s'] = '&#32;';
627 $query->set( 'is_search', 1 );
628 add_action( 'template_include', 'wpa_search_error' );
629 add_filter(
630 'get_search_query',
631 function ( $search_query ) {
632 if ( '&#32;' === $search_query ) {
633 return '';
634 }
635 return $search_query;
636 }
637 );
638 }
639 }
640
641 return $query;
642 }
643
644 /**
645 * Locate template for the search error page.
646 *
647 * @param string $template Current template name.
648 *
649 * @return string New template name if changed.
650 */
651 function wpa_search_error( $template ) {
652 $search = locate_template( 'search.php' );
653 if ( $search ) {
654 return $search;
655 }
656
657 return $template;
658 }
659
660 if ( 'on' === get_option( 'wpa_more' ) && ! wpa_accessible_theme() ) {
661 add_filter(
662 'body_class',
663 function ( $classes ) {
664 return array_merge( $classes, array( 'wpa-excerpt' ) );
665 }
666 );
667 add_filter( 'get_the_excerpt', 'wpa_custom_excerpt_more', 100 );
668 add_filter( 'excerpt_more', 'wpa_excerpt_more', 100 );
669 add_filter( 'the_content_more_link', 'wpa_content_more', 100 );
670 }
671
672 /**
673 * Custom "Continue Reading" with post title context.
674 *
675 * @param int $id Post ID.
676 *
677 * @return string HTML link & text.
678 */
679 function wpa_continue_reading( $id ) {
680 return '<a class="continue" href="' . esc_url( get_permalink( $id ) ) . '">' . get_option( 'wpa_continue' ) . '<span> ' . get_the_title( $id ) . '</span></a>';
681 }
682
683 /**
684 * Add custom continue reading text to excerpts.
685 *
686 * @return string Ellipsis + continue reading text.
687 */
688 function wpa_excerpt_more() {
689 global $id;
690
691 return '&hellip; ' . wpa_continue_reading( $id );
692 }
693
694 /**
695 * Add custom continue reading text to content.
696 *
697 * @return continue reading text.
698 */
699 function wpa_content_more() {
700 global $id;
701
702 return wpa_continue_reading( $id );
703 }
704
705 /**
706 * Add custom continue reading text to custom excerpts.
707 *
708 * @param string $output Existing content.
709 *
710 * @return continue reading text.
711 */
712 function wpa_custom_excerpt_more( $output ) {
713 if ( has_excerpt() && ! is_attachment() ) {
714 global $id;
715 $output .= ' ' . wpa_continue_reading( $id ); // insert a blank space.
716 }
717
718 return $output;
719 }
720
721 /**
722 * Create support form.
723 */
724 function wpa_get_support_form() {
725 global $current_user;
726 $current_user = wp_get_current_user();
727 $request = '';
728 $version = wpa_check_version();
729 // send fields for all plugins.
730 $wp_version = get_bloginfo( 'version' );
731 $home_url = home_url();
732 $wp_url = site_url();
733 $language = get_bloginfo( 'language' );
734 $charset = get_bloginfo( 'charset' );
735 // server.
736 $php_version = phpversion();
737
738 // theme data.
739 $theme = wp_get_theme();
740 $theme_name = $theme->get( 'Name' );
741 $theme_uri = $theme->get( 'ThemeURI' );
742 $theme_parent = $theme->get( 'Template' );
743 $theme_parent = ( $theme_parent ) ? $theme_parent : __( 'None', 'wp-accessibility' );
744 $theme_version = $theme->get( 'Version' );
745
746 // plugin data.
747 $plugins = get_plugins();
748 $plugins_string = '';
749 foreach ( array_keys( $plugins ) as $key ) {
750 if ( is_plugin_active( $key ) ) {
751 $plugin =& $plugins[ $key ];
752 $plugin_name = $plugin['Name'];
753 $plugin_uri = $plugin['PluginURI'];
754 $plugin_version = $plugin['Version'];
755 $plugins_string .= "$plugin_name: $plugin_version; $plugin_uri\n";
756 }
757 }
758 $data = "
759 ================ Installation Data ====================
760 ==WP Accessibility==
761 Version: $version
762
763 ==WordPress:==
764 Version: $wp_version
765 URL: $home_url
766 Install: $wp_url
767 Language: $language
768 Charset: $charset
769 Admin Email: $current_user->user_email
770
771 ==Extra info:==
772 PHP Version: $php_version
773 Server Software: $_SERVER[SERVER_SOFTWARE]
774 User Agent: $_SERVER[HTTP_USER_AGENT]
775
776 ==Theme:==
777 Name: $theme_name
778 URI: $theme_uri
779 Parent: $theme_parent
780 Version: $theme_version
781
782 ==Active Plugins:==
783 $plugins_string
784 ";
785 if ( isset( $_POST['wpa_support'] ) ) {
786 $nonce = $_REQUEST['_wpnonce'];
787 if ( ! wp_verify_nonce( $nonce, 'wpa-nonce' ) ) {
788 wp_die( 'WP Accessibility: Security check failed' );
789 }
790 $request = ( ! empty( $_POST['support_request'] ) ) ? sanitize_textarea_field( wp_unslash( $_POST['support_request'] ) ) : false;
791 $has_donated = ( 'on' === $_POST['has_donated'] ) ? 'Donor' : 'No donation';
792 $subject = "WP Accessibility support request. $has_donated";
793 $message = $request . "\n\n" . $data;
794 // Get the site domain and get rid of www. from pluggable.php.
795 $sitename = sanitize_text_field( strtolower( $_SERVER['SERVER_NAME'] ) );
796 if ( 'www.' === substr( $sitename, 0, 4 ) ) {
797 $sitename = substr( $sitename, 4 );
798 }
799 $from_email = 'wordpress@' . $sitename;
800 $from = "From: $current_user->display_name <$from_email>\r\nReply-to: $current_user->display_name <$current_user->user_email>\r\n";
801
802 if ( ! $request ) {
803 echo "<div class='message error'><p>" . esc_html__( 'Please describe your problem.', 'wp-accessibility' ) . '</p></div>';
804 } else {
805 wp_mail( 'plugins@joedolson.com', $subject, $message, $from );
806 if ( 'Donor' === $has_donated ) {
807 echo "<div class='message updated'><p>" . esc_html__( 'Thank you for supporting the continuing development of this plug-in! I\'ll get back to you as soon as I can.', 'wp-accessibility' ) . '</p></div>';
808 } else {
809 echo "<div class='message updated'><p>" . esc_html__( 'I cannot provide support, but will treat your request as a bug report, and will incorporate any permanent solutions I discover into the plug-in.', 'wp-accessibility' ) . '</p></div>';
810 }
811 }
812 }
813 $admin_url = admin_url( 'admin.php?page=wp-accessibility' );
814
815 echo "
816 <form method='post' action='" . esc_url( $admin_url ) . "'>
817 <div><input type='hidden' name='_wpnonce' value='" . wp_create_nonce( 'wpa-nonce' ) . "' /></div>
818 <div>";
819 echo '
820 <p>
821 <code>' . __( 'From:', 'wp-accessibility' ) . " \"$current_user->display_name\" &lt;$current_user->user_email&gt;</code>
822 </p>
823 <p>
824 <input type='checkbox' name='has_donated' id='has_donated' value='on' /> <label for='has_donated'>";
825 // Translators: Donation URL.
826 echo sprintf( __( 'I <a href="%s">made a donation</a> to help support this plugin', 'wp-accessibility' ), 'https://www.joedolson.com/donate/' ) . "</label>
827 </p>
828 <p>
829 <label for='support_request'>" . __( 'Support Request:', 'wp-accessibility' ) . "</label><br /><textarea name='support_request' required id='support_request' cols='80' rows='10' class='widefat'>" . esc_textarea( stripslashes( $request ) ) . "</textarea>
830 </p>
831 <p>
832 <input type='submit' value='" . __( 'Send Support Request', 'wp-accessibility' ) . "' name='wpa_support' class='button-primary' />
833 </p>
834 <p>" . __( 'The following additional information will be sent with your support request:', 'wp-accessibility' ) . "</p>
835 <div class='wpa_support'>
836 " . wpautop( $data ) . '
837 </div>
838 </div>
839 </form>';
840 }
841
842 /**
843 * Tests whether the current theme is labeled accessibility-ready
844 *
845 * @return boolean True if this theme has the tag 'accessibility-ready'.
846 */
847 function wpa_accessible_theme() {
848 // This is Oxygen Builder, and the active theme test is invalid.
849 if ( defined( 'CT_VERSION' ) ) {
850 return false;
851 }
852 $theme = wp_get_theme();
853 $tags = $theme->get( 'Tags' );
854 if ( is_array( $tags ) && in_array( 'accessibility-ready', $tags, true ) ) {
855 return true;
856 }
857 return false;
858 }
859
860 /**
861 * Disable full screen block editor by default.
862 */
863 function wpa_disable_editor_fullscreen_by_default() {
864 if ( 'on' === get_option( 'wpa_disable_fullscreen' ) ) {
865 $script = "window.onload = function() { const isFullscreenMode = wp.data.select( 'core/edit-post' ).isFeatureActive( 'fullscreenMode' ); if ( isFullscreenMode ) { wp.data.dispatch( 'core/edit-post' ).toggleFeature( 'fullscreenMode' ); } }";
866 wp_add_inline_script( 'wp-blocks', $script );
867 }
868 }
869 add_action( 'enqueue_block_editor_assets', 'wpa_disable_editor_fullscreen_by_default' );
870
871 /**
872 * Disable file embeds by default.
873 *
874 * @param array $blockdata Array of block configuration meta data.
875 *
876 * @return array
877 */
878 function wpa_disable_file_embed_by_default( $blockdata ) {
879 if ( 'core/file' === $blockdata['name'] && 'on' === get_option( 'wpa_disable_file_embed' ) ) {
880 if ( isset( $blockdata['attributes']['displayPreview'] ) ) {
881 $blockdata['attributes']['displayPreview']['default'] = false;
882 }
883 }
884
885 return $blockdata;
886 }
887 add_filter( 'block_type_metadata', 'wpa_disable_file_embed_by_default' );
888
889 /**
890 * Remove the H1 heading from the headings block.
891 *
892 * @param array $args Block type arguments array.
893 * @param string $block_type Block type declaration.
894 *
895 * @return array
896 */
897 function wpa_remove_h1( $args, $block_type ) {
898 // The H1 is not allowed by default.
899 $allowed = get_option( 'wpa_allow_h1', '' );
900 if ( 'on' !== $allowed ) {
901 // Only proceed if the current block is a Heading.
902 if ( 'core/heading' !== $block_type ) {
903 return $args;
904 }
905
906 // Declare valid heading levels.
907 $args['attributes']['levelOptions']['default'] = array( 2, 3, 4, 5, 6 );
908 }
909 return $args;
910 }
911 add_filter( 'register_block_type_args', 'wpa_remove_h1', 10, 2 );
912
913 /**
914 * Insert content summary at top of article content.
915 *
916 * @param string $content Post content.
917 *
918 * @return string
919 */
920 function wpa_content_summary( $content ) {
921 if ( is_singular() && wpa_in_post_type( get_queried_object_id() ) ) {
922 $post_id = get_the_ID();
923 $summary = wpa_get_content_summary( $post_id );
924 if ( ! $summary ) {
925 return $content;
926 }
927 $content = $summary . $content;
928 }
929
930 return $content;
931 }
932 add_filter( 'the_content', 'wpa_content_summary' );
933
934 /**
935 * Get a simplified summary for content.
936 *
937 * @param int $post_id Post ID.
938 *
939 * @return string
940 */
941 function wpa_get_content_summary( $post_id ) {
942 $summary = trim( get_post_meta( $post_id, '_wpa_content_summary', true ) );
943 if ( ! $summary ) {
944 return '';
945 }
946 /**
947 * Filter the heading text for content summaries. Default `Summary`.
948 *
949 * @hook wpa_summary_heading
950 *
951 * @param string $heading Heading text.
952 * @param int $post_id Post ID.
953 *
954 * @return string
955 */
956 $heading = apply_filters( 'wpa_summary_heading', __( 'Summary', 'wp-accessibility' ), $post_id );
957 /**
958 * Filter the heading level for content summaries. Default `h2`.
959 *
960 * @hook wpa_summary_heading_level
961 *
962 * @param string $heading Element selector.
963 * @param int $post_id Post ID.
964 *
965 * @return string
966 */
967 $level = apply_filters( 'wpa_summary_heading_level', 'h2', $post_id );
968
969 $heading = "<$level>" . $heading . "</$level>";
970 $content = '<section class="wpa-content-summary" id="summary-' . absint( $post_id ) . '"><div class="wpa-content-summary-inner">' . $heading . wpautop( wp_kses_post( stripslashes( $summary ) ) ) . '</div></section>';
971
972 return $content;
973 }
974
975 /**
976 * Check whether a given post is in an allowed post type for content summaries.
977 *
978 * @param integer $post_id Post ID.
979 *
980 * @return boolean True if post is allowed, false otherwise.
981 */
982 function wpa_in_post_type( $post_id ) {
983 $settings = get_option( 'wpa_post_types', array() );
984 if ( is_array( $settings ) && ! empty( $settings ) ) {
985 $type = get_post_type( $post_id );
986 if ( in_array( $type, $settings, true ) ) {
987 return true;
988 }
989 }
990
991 return false;
992 }
993
994 add_action( 'admin_menu', 'wpa_add_outer_box' );
995 /**
996 * Add metabox for content summaries.
997 */
998 function wpa_add_outer_box() {
999 $allowed = get_option( 'wpa_post_types', array() );
1000 if ( is_array( $allowed ) ) {
1001 foreach ( $allowed as $post_type ) {
1002 add_meta_box( 'wpa_content_summary_container', __( 'Content Summary', 'wp-accessibility' ), 'wpa_add_inner_box', $post_type, 'normal', 'high' );
1003 }
1004 }
1005 }
1006
1007 /**
1008 * Render content summary form.
1009 */
1010 function wpa_add_inner_box() {
1011 global $post;
1012 $summary = get_post_meta( $post->ID, '_wpa_content_summary', true );
1013 $nonce = wp_nonce_field( 'wpa-nonce-field', 'wpa_nonce_name', true, false );
1014 echo $nonce;
1015 ?>
1016 <p class='wpa-content-summary-field'>
1017 <label for="wpa_content_summary"><?php _e( 'Simple Content Summary', 'wp-accessibility' ); ?></label><br/>
1018 <textarea class="wpa-content-summary widefat" name="wpa_content_summary" id="wpa_content_summary" rows="4" cols="60" aria-describedy="content-summary-description"><?php echo esc_textarea( $summary ); ?></textarea>
1019 <span id="content-summary-description"><?php _e( 'Provide a simplified summary to aid comprehension of complex content.', 'wp-accessibility' ); ?></span>
1020 </p>
1021 <?php
1022 }
1023
1024 /**
1025 * Save content summary from post meta box.
1026 *
1027 * @param int $id Post ID.
1028 * @param object $post Post.
1029 *
1030 * @return int
1031 */
1032 function wpa_save_content_summary( $id, $post ) {
1033 if ( empty( $_POST ) || ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || wp_is_post_revision( $id ) || isset( $_POST['_inline_edit'] ) || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) || ! wpa_in_post_type( $id ) ) {
1034 return $id;
1035 }
1036
1037 // verify this came from our screen and with proper authorization.
1038 // because save_post can be triggered at other times.
1039 if ( isset( $_POST['wpa_nonce_name'] ) ) {
1040 if ( ! wp_verify_nonce( $_POST['wpa_nonce_name'], 'wpa-nonce-field' ) ) {
1041 return $id;
1042 }
1043 $summary = isset( $_POST['wpa_content_summary'] ) ? wp_kses_post( $_POST['wpa_content_summary'] ) : '';
1044 update_post_meta( $id, '_wpa_content_summary', $summary );
1045 }
1046
1047 return $id;
1048 }
1049 add_action( 'save_post', 'wpa_save_content_summary', 10, 2 );
1050