PluginProbe
WP Accessibility / 2.3.0
WP Accessibility v2.3.0
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.0, at wp-accessibility.php

1,041 lines 35.8 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-2025 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.0
21 */
22
23 /*
24 Copyright 2012-2025 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.0' );
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( stripcslashes( $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 }
630 }
631
632 return $query;
633 }
634
635 /**
636 * Locate template for the search error page.
637 *
638 * @param string $template Current template name.
639 *
640 * @return string New template name if changed.
641 */
642 function wpa_search_error( $template ) {
643 $search = locate_template( 'search.php' );
644 if ( $search ) {
645 return $search;
646 }
647
648 return $template;
649 }
650
651 if ( 'on' === get_option( 'wpa_more' ) && ! wpa_accessible_theme() ) {
652 add_filter(
653 'body_class',
654 function ( $classes ) {
655 return array_merge( $classes, array( 'wpa-excerpt' ) );
656 }
657 );
658 add_filter( 'get_the_excerpt', 'wpa_custom_excerpt_more', 100 );
659 add_filter( 'excerpt_more', 'wpa_excerpt_more', 100 );
660 add_filter( 'the_content_more_link', 'wpa_content_more', 100 );
661 }
662
663 /**
664 * Custom "Continue Reading" with post title context.
665 *
666 * @param int $id Post ID.
667 *
668 * @return string HTML link & text.
669 */
670 function wpa_continue_reading( $id ) {
671 return '<a class="continue" href="' . esc_url( get_permalink( $id ) ) . '">' . get_option( 'wpa_continue' ) . '<span> ' . get_the_title( $id ) . '</span></a>';
672 }
673
674 /**
675 * Add custom continue reading text to excerpts.
676 *
677 * @return string Ellipsis + continue reading text.
678 */
679 function wpa_excerpt_more() {
680 global $id;
681
682 return '&hellip; ' . wpa_continue_reading( $id );
683 }
684
685 /**
686 * Add custom continue reading text to content.
687 *
688 * @return continue reading text.
689 */
690 function wpa_content_more() {
691 global $id;
692
693 return wpa_continue_reading( $id );
694 }
695
696 /**
697 * Add custom continue reading text to custom excerpts.
698 *
699 * @param string $output Existing content.
700 *
701 * @return continue reading text.
702 */
703 function wpa_custom_excerpt_more( $output ) {
704 if ( has_excerpt() && ! is_attachment() ) {
705 global $id;
706 $output .= ' ' . wpa_continue_reading( $id ); // insert a blank space.
707 }
708
709 return $output;
710 }
711
712 /**
713 * Create support form.
714 */
715 function wpa_get_support_form() {
716 global $current_user;
717 $current_user = wp_get_current_user();
718 $request = '';
719 $version = wpa_check_version();
720 // send fields for all plugins.
721 $wp_version = get_bloginfo( 'version' );
722 $home_url = home_url();
723 $wp_url = site_url();
724 $language = get_bloginfo( 'language' );
725 $charset = get_bloginfo( 'charset' );
726 // server.
727 $php_version = phpversion();
728
729 // theme data.
730 $theme = wp_get_theme();
731 $theme_name = $theme->get( 'Name' );
732 $theme_uri = $theme->get( 'ThemeURI' );
733 $theme_parent = $theme->get( 'Template' );
734 $theme_parent = ( $theme_parent ) ? $theme_parent : __( 'None', 'wp-accessibility' );
735 $theme_version = $theme->get( 'Version' );
736
737 // plugin data.
738 $plugins = get_plugins();
739 $plugins_string = '';
740 foreach ( array_keys( $plugins ) as $key ) {
741 if ( is_plugin_active( $key ) ) {
742 $plugin =& $plugins[ $key ];
743 $plugin_name = $plugin['Name'];
744 $plugin_uri = $plugin['PluginURI'];
745 $plugin_version = $plugin['Version'];
746 $plugins_string .= "$plugin_name: $plugin_version; $plugin_uri\n";
747 }
748 }
749 $data = "
750 ================ Installation Data ====================
751 ==WP Accessibility==
752 Version: $version
753
754 ==WordPress:==
755 Version: $wp_version
756 URL: $home_url
757 Install: $wp_url
758 Language: $language
759 Charset: $charset
760 Admin Email: $current_user->user_email
761
762 ==Extra info:==
763 PHP Version: $php_version
764 Server Software: $_SERVER[SERVER_SOFTWARE]
765 User Agent: $_SERVER[HTTP_USER_AGENT]
766
767 ==Theme:==
768 Name: $theme_name
769 URI: $theme_uri
770 Parent: $theme_parent
771 Version: $theme_version
772
773 ==Active Plugins:==
774 $plugins_string
775 ";
776 if ( isset( $_POST['wpa_support'] ) ) {
777 $nonce = $_REQUEST['_wpnonce'];
778 if ( ! wp_verify_nonce( $nonce, 'wpa-nonce' ) ) {
779 wp_die( 'WP Accessibility: Security check failed' );
780 }
781 $request = ( ! empty( $_POST['support_request'] ) ) ? sanitize_textarea_field( wp_unslash( $_POST['support_request'] ) ) : false;
782 $has_donated = ( 'on' === $_POST['has_donated'] ) ? 'Donor' : 'No donation';
783 $subject = "WP Accessibility support request. $has_donated";
784 $message = $request . "\n\n" . $data;
785 // Get the site domain and get rid of www. from pluggable.php.
786 $sitename = sanitize_text_field( strtolower( $_SERVER['SERVER_NAME'] ) );
787 if ( 'www.' === substr( $sitename, 0, 4 ) ) {
788 $sitename = substr( $sitename, 4 );
789 }
790 $from_email = 'wordpress@' . $sitename;
791 $from = "From: $current_user->display_name <$from_email>\r\nReply-to: $current_user->display_name <$current_user->user_email>\r\n";
792
793 if ( ! $request ) {
794 echo "<div class='message error'><p>" . esc_html__( 'Please describe your problem.', 'wp-accessibility' ) . '</p></div>';
795 } else {
796 wp_mail( 'plugins@joedolson.com', $subject, $message, $from );
797 if ( 'Donor' === $has_donated ) {
798 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>';
799 } else {
800 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>';
801 }
802 }
803 }
804 $admin_url = admin_url( 'admin.php?page=wp-accessibility' );
805
806 echo "
807 <form method='post' action='" . esc_url( $admin_url ) . "'>
808 <div><input type='hidden' name='_wpnonce' value='" . wp_create_nonce( 'wpa-nonce' ) . "' /></div>
809 <div>";
810 echo '
811 <p>
812 <code>' . __( 'From:', 'wp-accessibility' ) . " \"$current_user->display_name\" &lt;$current_user->user_email&gt;</code>
813 </p>
814 <p>
815 <input type='checkbox' name='has_donated' id='has_donated' value='on' /> <label for='has_donated'>";
816 // Translators: Donation URL.
817 echo sprintf( __( 'I <a href="%s">made a donation</a> to help support this plugin', 'wp-accessibility' ), 'https://www.joedolson.com/donate/' ) . "</label>
818 </p>
819 <p>
820 <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>
821 </p>
822 <p>
823 <input type='submit' value='" . __( 'Send Support Request', 'wp-accessibility' ) . "' name='wpa_support' class='button-primary' />
824 </p>
825 <p>" . __( 'The following additional information will be sent with your support request:', 'wp-accessibility' ) . "</p>
826 <div class='wpa_support'>
827 " . wpautop( $data ) . '
828 </div>
829 </div>
830 </form>';
831 }
832
833 /**
834 * Tests whether the current theme is labeled accessibility-ready
835 *
836 * @return boolean True if this theme has the tag 'accessibility-ready'.
837 */
838 function wpa_accessible_theme() {
839 // This is Oxygen Builder, and the active theme test is invalid.
840 if ( defined( 'CT_VERSION' ) ) {
841 return false;
842 }
843 $theme = wp_get_theme();
844 $tags = $theme->get( 'Tags' );
845 if ( is_array( $tags ) && in_array( 'accessibility-ready', $tags, true ) ) {
846 return true;
847 }
848 return false;
849 }
850
851 /**
852 * Disable full screen block editor by default.
853 */
854 function wpa_disable_editor_fullscreen_by_default() {
855 if ( 'on' === get_option( 'wpa_disable_fullscreen' ) ) {
856 $script = "window.onload = function() { const isFullscreenMode = wp.data.select( 'core/edit-post' ).isFeatureActive( 'fullscreenMode' ); if ( isFullscreenMode ) { wp.data.dispatch( 'core/edit-post' ).toggleFeature( 'fullscreenMode' ); } }";
857 wp_add_inline_script( 'wp-blocks', $script );
858 }
859 }
860 add_action( 'enqueue_block_editor_assets', 'wpa_disable_editor_fullscreen_by_default' );
861
862 /**
863 * Disable file embeds by default.
864 *
865 * @param array $blockdata Array of block configuration meta data.
866 *
867 * @return array
868 */
869 function wpa_disable_file_embed_by_default( $blockdata ) {
870 if ( 'core/file' === $blockdata['name'] && 'on' === get_option( 'wpa_disable_file_embed' ) ) {
871 if ( isset( $blockdata['attributes']['displayPreview'] ) ) {
872 $blockdata['attributes']['displayPreview']['default'] = false;
873 }
874 }
875
876 return $blockdata;
877 }
878 add_filter( 'block_type_metadata', 'wpa_disable_file_embed_by_default' );
879
880 /**
881 * Remove the H1 heading from the headings block.
882 *
883 * @param array $args Block type arguments array.
884 * @param string $block_type Block type declaration.
885 *
886 * @return array
887 */
888 function wpa_remove_h1( $args, $block_type ) {
889 // The H1 is not allowed by default.
890 $allowed = get_option( 'wpa_allow_h1', '' );
891 if ( 'on' !== $allowed ) {
892 // Only proceed if the current block is a Heading.
893 if ( 'core/heading' !== $block_type ) {
894 return $args;
895 }
896
897 // Declare valid heading levels.
898 $args['attributes']['levelOptions']['default'] = array( 2, 3, 4, 5, 6 );
899 }
900 return $args;
901 }
902 add_filter( 'register_block_type_args', 'wpa_remove_h1', 10, 2 );
903
904 /**
905 * Insert content summary at top of article content.
906 *
907 * @param string $content Post content.
908 *
909 * @return string
910 */
911 function wpa_content_summary( $content ) {
912 if ( is_singular() && wpa_in_post_type( get_queried_object_id() ) ) {
913 $post_id = get_the_ID();
914 $summary = wpa_get_content_summary( $post_id );
915 if ( ! $summary ) {
916 return $content;
917 }
918 $content = $summary . $content;
919 }
920
921 return $content;
922 }
923 add_filter( 'the_content', 'wpa_content_summary' );
924
925 /**
926 * Get a simplified summary for content.
927 *
928 * @param int $post_id Post ID.
929 *
930 * @return string
931 */
932 function wpa_get_content_summary( $post_id ) {
933 $summary = trim( get_post_meta( $post_id, '_wpa_content_summary', true ) );
934 if ( ! $summary ) {
935 return '';
936 }
937 /**
938 * Filter the heading text for content summaries. Default `Summary`.
939 *
940 * @hook wpa_summary_heading
941 *
942 * @param {string} $heading Heading text.
943 * @param {int} $post_id Post ID.
944 *
945 * @return {string}
946 */
947 $heading = apply_filters( 'wpa_summary_heading', __( 'Summary', 'wp-accessibility' ), $post_id );
948 /**
949 * Filter the heading level for content summaries. Default `h2`.
950 *
951 * @hook wpa_summary_heading_level
952 *
953 * @param {string} $heading Element selector.
954 * @param {int} $post_id Post ID.
955 *
956 * @return {string}
957 */
958 $level = apply_filters( 'wpa_summary_heading_level', 'h2', $post_id );
959
960 $heading = "<$level>" . $heading . "</$level>";
961 $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>';
962
963 return $content;
964 }
965
966 /**
967 * Check whether a given post is in an allowed post type for content summaries.
968 *
969 * @param integer $post_id Post ID.
970 *
971 * @return boolean True if post is allowed, false otherwise.
972 */
973 function wpa_in_post_type( $post_id ) {
974 $settings = get_option( 'wpa_post_types', array() );
975 if ( is_array( $settings ) && ! empty( $settings ) ) {
976 $type = get_post_type( $post_id );
977 if ( in_array( $type, $settings, true ) ) {
978 return true;
979 }
980 }
981
982 return false;
983 }
984
985 add_action( 'admin_menu', 'wpa_add_outer_box' );
986 /**
987 * Add metabox for content summaries.
988 */
989 function wpa_add_outer_box() {
990 $allowed = get_option( 'wpa_post_types', array() );
991 if ( is_array( $allowed ) ) {
992 foreach ( $allowed as $post_type ) {
993 add_meta_box( 'wpa_content_summary_container', __( 'Content Summary', 'wp-accessibility' ), 'wpa_add_inner_box', $post_type, 'normal', 'high' );
994 }
995 }
996 }
997
998 /**
999 * Render content summary form.
1000 */
1001 function wpa_add_inner_box() {
1002 global $post;
1003 $summary = get_post_meta( $post->ID, '_wpa_content_summary', true );
1004 $nonce = wp_nonce_field( 'wpa-nonce-field', 'wpa_nonce_name', true, false );
1005 echo $nonce;
1006 ?>
1007 <p class='wpa-content-summary-field'>
1008 <label for="wpa_content_summary"><?php _e( 'Simple Content Summary', 'wp-accessibility' ); ?></label><br/>
1009 <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>
1010 <span id="content-summary-description"><?php _e( 'Provide a simplified summary to aid comprehension of complex content.', 'wp-accessibility' ); ?></span>
1011 </p>
1012 <?php
1013 }
1014
1015 /**
1016 * Save content summary from post meta box.
1017 *
1018 * @param int $id Post ID.
1019 * @param object $post Post.
1020 *
1021 * @return int
1022 */
1023 function wpa_save_content_summary( $id, $post ) {
1024 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 ) ) {
1025 return $id;
1026 }
1027
1028 // verify this came from our screen and with proper authorization.
1029 // because save_post can be triggered at other times.
1030 if ( isset( $_POST['wpa_nonce_name'] ) ) {
1031 if ( ! wp_verify_nonce( $_POST['wpa_nonce_name'], 'wpa-nonce-field' ) ) {
1032 return $id;
1033 }
1034 $summary = isset( $_POST['wpa_content_summary'] ) ? wp_kses_post( $_POST['wpa_content_summary'] ) : '';
1035 update_post_meta( $id, '_wpa_content_summary', $summary );
1036 }
1037
1038 return $id;
1039 }
1040 add_action( 'save_post', 'wpa_save_content_summary', 10, 2 );
1041