Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
ae4a32a
Enhance link validation to trim whitespace and normalize href checks
SteveJonesDev Feb 13, 2026
8aa48bc
Fix cached table name reuse in validator
SteveJonesDev Feb 19, 2026
183fd07
Initial plan
Copilot Feb 20, 2026
573af34
Initial plan
Copilot Apr 23, 2026
4c65878
Skip 1x1 tracking pixels with empty alt in img_alt_empty_check
Copilot Apr 23, 2026
fcd7033
Fix: use correct ID when site homepage is set to latest posts
SteveJonesDev May 29, 2026
f3637f6
Address code review feedback on PRO-726 homepage scan fix
SteveJonesDev Jun 1, 2026
6f8f7df
Fix test assertion for wp_localize_script boolean serialization
SteveJonesDev Jun 1, 2026
1dd16ab
Fix home URL assertions for WP < 6.9 JSON slash escaping
SteveJonesDev Jun 1, 2026
4771d9e
Address code review feedback: use trimmedHref for empty check and add…
SteveJonesDev Jun 1, 2026
79f4f9c
Update composer.lock to pull in PHPUnit 9.6.34 (fixes CVE-2026-24765)
SteveJonesDev Jun 1, 2026
bf1952c
Apply suggestions from code review
SteveJonesDev Jun 1, 2026
0e491be
chore: initial plan
Copilot Feb 20, 2026
faecdc9
feat: cache is_domain_loopback DNS lookup in a short-lived transient
Copilot Feb 20, 2026
b757770
feat: remove is_domain_loopback — unused function with no production …
Copilot Feb 20, 2026
fdf6db2
Merge branch 'develop' into copilot/cache-domain-loopback-check
SteveJonesDev Jun 1, 2026
1f771dc
Add programmatic label to Simplified Summary textarea
claude Jul 5, 2026
109bfeb
Add white contrast ring around highlighted element outline
claude Jul 5, 2026
a9196c9
Merge pull request #1831 from equalizedigital/release/1.46.0
pattonwebz Jul 8, 2026
42e5f49
fix: render SVG code snippets in the Accessibility Analysis panel as …
pattonwebz Jul 9, 2026
be59792
fix: harden edac_svg_markup_to_data_uri() against non-string input, t…
pattonwebz Jul 9, 2026
09ec082
docs: document the esc_url() protocols requirement for the SVG data URI
pattonwebz Jul 10, 2026
6149777
Merge branch 'develop' into steve/pro-997-simplified-summary-text-are…
pattonwebz Jul 10, 2026
930856e
Merge pull request #1823 from equalizedigital/steve/pro-997-simplifie…
pattonwebz Jul 14, 2026
3f47a69
Merge pull request #1832 from equalizedigital/william/render-svg-issu…
pattonwebz Jul 14, 2026
b334071
fix: adjust outline offset and box-shadow for selected elements
SteveJonesDev Jul 14, 2026
495908b
Merge pull request #1824 from equalizedigital/claude/accessibility-ch…
SteveJonesDev Jul 14, 2026
0164425
Merge branch 'develop' into copilot/skip-1x1-images-empty-alt
SteveJonesDev Jul 14, 2026
1fe86b6
fix: update test case for image with empty alt attribute to include s…
SteveJonesDev Jul 14, 2026
2c4d280
docs: tighten verbose comments on the homepage scan fix
pattonwebz Jul 14, 2026
7235c55
Merge pull request #1661 from equalizedigital/copilot/skip-1x1-images…
SteveJonesDev Jul 14, 2026
0f5e9ae
Merge remote-tracking branch 'refs/remotes/origin/develop' into steve…
pattonwebz Jul 14, 2026
2e5e557
Merge pull request #1437 from equalizedigital/copilot/cache-domain-lo…
SteveJonesDev Jul 14, 2026
e19e7af
Merge pull request #1429 from equalizedigital/codex/fix-valid-table-n…
SteveJonesDev Jul 14, 2026
ae89430
Merge pull request #1389 from equalizedigital/steve/no-issues/link-im…
SteveJonesDev Jul 14, 2026
8681cd1
Merge pull request #1727 from equalizedigital/steve/pro-726-when-site…
pattonwebz Jul 14, 2026
b7f0e59
Bump version * Version: 1.46.0 -> 1.47.0
pattonwebz Jul 14, 2026
6d566ff
Update @since placeholders to 1.47.0
pattonwebz Jul 14, 2026
bcffe72
Add changelog for v1.47.0
pattonwebz Jul 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions accessibility-checker.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* Plugin Name: Accessibility Checker
* Plugin URI: https://equalizedigital.com/accessibility-checker
* Description: Audit and check your website for accessibility before you hit publish. In-post accessibility scanner and guidance.
* Version: 1.46.0
* Version: 1.47.0
* Requires PHP: 7.4
* Author: Equalize Digital
* Author URI: https://equalizedigital.com
Expand All @@ -36,7 +36,7 @@

// Current plugin version.
if ( ! defined( 'EDAC_VERSION' ) ) {
define( 'EDAC_VERSION', '1.46.0' );
define( 'EDAC_VERSION', '1.47.0' );
}

// Current database version.
Expand Down
10 changes: 9 additions & 1 deletion admin/class-ajax.php
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,15 @@ function ( $a, $b ) {
)
) . '" />';
} elseif ( $object_svg ) {
$html .= $object_svg;
// Rendered as an <img> via a data URI, not injected as inline markup -
// see edac_svg_markup_to_data_uri()'s docblock for why.
$html .= '<img src="' . esc_url( edac_svg_markup_to_data_uri( $object_svg ), [ 'data', 'http', 'https' ] ) . '" alt="' . esc_attr(
sprintf(
/* translators: %d: issue ID number */
__( 'image for issue %d', 'accessibility-checker' ),
$id
)
) . '" />';
}

$html .= '</div>';
Expand Down
21 changes: 17 additions & 4 deletions admin/class-enqueue-admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ public static function maybe_enqueue_admin_and_editor_app_scripts() {

global $post;
$post_id = is_object( $post ) ? $post->ID : null;

// On a latest-posts homepage the global $post is the first blog post, not the page;
// let extensions supply the correct ID (e.g. a Pro virtual-page ID).
$post_id = apply_filters( 'edac_filter_admin_post_id', $post_id );

wp_enqueue_script( 'edac', plugin_dir_url( EDAC_PLUGIN_FILE ) . 'build/admin.bundle.js', [ 'jquery' ], EDAC_VERSION, false );
wp_set_script_translations( 'edac', 'accessibility-checker', plugin_dir_path( EDAC_PLUGIN_FILE ) . 'languages' );

Expand All @@ -99,8 +104,9 @@ public static function maybe_enqueue_admin_and_editor_app_scripts() {

if ( 'post.php' === $pagenow || 'post-new.php' === $pagenow ) {

// Is this posttype setup to be checked?
$active = $is_scannable_post;
// Base the scannable check on the filtered $post_id, not the original global $post.
$filtered_post_type = $post_id ? get_post_type( $post_id ) : false;
$active = $filtered_post_type && is_array( $post_types ) && in_array( $filtered_post_type, $post_types, true );

$pro = defined( 'EDACP_VERSION' ) && EDAC_KEY_VALID;

Expand All @@ -113,8 +119,15 @@ public static function maybe_enqueue_admin_and_editor_app_scripts() {
wp_enqueue_script( 'edac-editor-app', plugin_dir_url( EDAC_PLUGIN_FILE ) . 'build/editorApp.bundle.js', false, EDAC_VERSION, false );
wp_set_script_translations( 'edac-editor-app', 'accessibility-checker', plugin_dir_path( EDAC_PLUGIN_FILE ) . 'languages' );

// If this is the frontpage or homepage, preview URLs won't work. Use the live URL.
if ( (int) get_option( 'page_on_front' ) === $post_id || (int) get_option( 'page_for_posts' ) === $post_id ) {
// Preview URLs don't work for the homepage. On a latest-posts homepage (including the
// show_on_front=page fallback with no static front page) use the live home URL instead.
$show_on_front = get_option( 'show_on_front', 'posts' );
$is_latest_posts_home = ( 'posts' === $show_on_front || ( 'page' === $show_on_front && ! get_option( 'page_on_front' ) ) )
&& apply_filters( 'edac_filter_post_is_latest_posts_home', false, $post_id );

if ( $is_latest_posts_home ) {
$scan_url = add_query_arg( 'edac_pageScanner', 1, trailingslashit( get_home_url() ) );
} elseif ( (int) get_option( 'page_on_front' ) === $post_id || (int) get_option( 'page_for_posts' ) === $post_id ) {
$scan_url = add_query_arg( 'edac_pageScanner', 1, get_permalink( $post_id ) );
} else {
$post_view_link = apply_filters(
Expand Down
48 changes: 0 additions & 48 deletions admin/class-helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -139,54 +139,6 @@ public static function get_option_as_array( $option_name ) {
return [];
}


/**
* Determine if a domain is hosted on a local loopback
*
* @param string $domain The domain to check.
* @return boolean
*/
public static function is_domain_loopback( $domain ) {

// Check if this is an ipv4 address in the loopback range.

$record = gethostbyname( $domain );
$loopback_start = ip2long( '127.0.0.0' );
$loopback_end = ip2long( '127.255.255.255' );
$ip_long = ip2long( $record );

if ( $ip_long >= $loopback_start && $ip_long <= $loopback_end ) {
return true;
}

// Check if this is an ipv6 loopback.

try {
$records = dns_get_record( $domain, DNS_AAAA );
} catch ( \Throwable $th ) {
return false;
}

foreach ( $records as $record ) {

// Do ipv6 check.
if ( isset( $record['type'] ) && 'AAAA' === $record['type'] ) {

// Normalize the IPv6 address for comparison.
$normalized_ipv6 = inet_pton( $record['ipv6'] );

// Normalize the loopback address.
$loopback_ipv6 = inet_pton( '::1' );

if ( $normalized_ipv6 === $loopback_ipv6 ) {
return true;
}
}
}

return false;
}

/**
* Filter out inactive rules from the results returned.
*
Expand Down
10 changes: 10 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
*** Accessibility Checker ***

2026-07-14 - version 1.47.0
* Updated - the frontend highlighter now draws a white ring around the outline so highlighted elements stay visible on any background color.
* Updated - the empty alt text check no longer flags 1x1 tracking pixels.
* Updated - the link protocol checks now handle href values that contain leading whitespace.
* Updated - SVG code snippets in the Accessibility Analysis panel are now rendered as images instead of raw markup.
* Fix - scan results for the homepage are now stored against the homepage instead of the first blog post when the site is set to show latest posts.
* Fix - the Simplified Summary text areas in the editor sidebar now have a label that assistive technology can announce.
* Fix - corrected a caching issue that could cause database table name lookups to return the wrong table.
* Remove - deleted an unused internal helper function.

2026-07-08 - version 1.46.0
* Updated - the aria-hidden rule no longer flags core Cover blocks.
* Updated - the Incorrect Heading Order summary no longer claims specific heading levels that may not match the actual issue.
Expand Down
9 changes: 8 additions & 1 deletion includes/classes/class-enqueue-frontend.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,14 @@ public static function maybe_enqueue_frontend_highlighter() {

// Don't load on the frontend if we don't have a post to work with.
global $post;
$post_id = apply_filters( 'edac_filter_frontend_highlight_post_id', is_object( $post ) ? $post->ID : null );

// On a latest-posts homepage the global $post is the first blog post, so using its ID
// would misattribute results; pass null and let the filter supply an ID (Pro) or bail.
$default_post_id = ( is_home() && is_front_page() )
? null
: ( is_object( $post ) ? $post->ID : null );

$post_id = apply_filters( 'edac_filter_frontend_highlight_post_id', $default_post_id );

if ( null === $post_id ) {
return;
Expand Down
31 changes: 26 additions & 5 deletions includes/helper-functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -248,10 +248,10 @@ function edac_get_post_type_label( string $post_type ): string {
*/
function edac_get_valid_table_name( $table_name ) {
global $wpdb;
static $found_table_name;
static $found_table_names = [];

if ( isset( $found_table_name ) ) {
return $found_table_name;
if ( isset( $found_table_names[ $table_name ] ) ) {
return $found_table_names[ $table_name ];
}

// Check if table name only contains alphanumeric characters, underscores, or hyphens.
Expand All @@ -267,8 +267,8 @@ function edac_get_valid_table_name( $table_name ) {
return null;
}

$found_table_name = $table_name;
return $found_table_name;
$found_table_names[ $table_name ] = $table_name;
return $table_name;
}

/**
Expand Down Expand Up @@ -788,6 +788,27 @@ function edac_parse_html_for_media( $html ) {
];
}

/**
* Convert raw SVG markup into a data: URI, safe as an <img> src - browsers
* don't execute scripts or event handlers in SVGs loaded as images. Returns
* a bare (payload-less) data URI if given anything other than a string.
*
* @since 1.47.0
*
* @param mixed $svg_markup Raw SVG markup - expected to be a string.
* @return string Unescaped data URI - callers must esc_url() it before output,
* passing a protocols list that includes 'data' (e.g.
* esc_url( $uri, [ 'data', 'http', 'https' ] )); with the
* default protocols esc_url() rejects data: URIs and returns ''.
*/
function edac_svg_markup_to_data_uri( $svg_markup ): string {
if ( ! is_string( $svg_markup ) ) {
return 'data:image/svg+xml,';
}

return 'data:image/svg+xml,' . rawurlencode( $svg_markup );
}

/**
* Remove corrected posts
*
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "accessibility-checker",
"version": "1.46.0",
"version": "1.47.0",
"description": "Audit and check your website for accessibility before you hit publish. In-post accessibility scanner and guidance.",
"author": "Equalize Digital",
"license": "GPL-2.0+",
Expand Down
14 changes: 12 additions & 2 deletions readme.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
=== Equalize Digital Accessibility Checker - WCAG, ADA, EAA and Section 508 compliance ===
Contributors: equalizedigital, alh0319, stevejonesdev
Tags: accessibility, EAA, WCAG, ADA, WP accessibility
Requires at least: 6.7
Requires at least: 6.8
Tested up to: 7.0
Stable tag: 1.46.0
Stable tag: 1.47.0
Requires PHP: 7.4
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Expand Down Expand Up @@ -279,6 +279,16 @@ You can report security bugs through the Patchstack Vulnerability Disclosure Pro

== Changelog ==

2026-07-14 - version 1.47.0
* Updated - the frontend highlighter now draws a white ring around the outline so highlighted elements stay visible on any background color.
* Updated - the empty alt text check no longer flags 1x1 tracking pixels.
* Updated - the link protocol checks now handle href values that contain leading whitespace.
* Updated - SVG code snippets in the Accessibility Analysis panel are now rendered as images instead of raw markup.
* Fix - scan results for the homepage are now stored against the homepage instead of the first blog post when the site is set to show latest posts.
* Fix - the Simplified Summary text areas in the editor sidebar now have a label that assistive technology can announce.
* Fix - corrected a caching issue that could cause database table name lookups to return the wrong table.
* Remove - deleted an unused internal helper function.

2026-07-08 - version 1.46.0
* Updated - the aria-hidden rule no longer flags core Cover blocks.
* Updated - the Incorrect Heading Order summary no longer claims specific heading levels that may not match the actual issue.
Expand Down
3 changes: 2 additions & 1 deletion src/frontendHighlighterApp/sass/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ body {

&-element-selected {
outline: dashed 4px transparent !important;
outline-offset: 5px !important;
outline-offset: 2px !important;
outline-color: magenta !important;
box-shadow: 0 0 0 8px variables.$color-white !important;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

According to the general rules, the use of !important is acceptable only when accompanied by an explanatory comment. Please add a comment explaining why !important is required for this box-shadow rule.

Suggested change
box-shadow: 0 0 0 8px variables.$color-white !important;
// Box shadow requires !important to override theme-specific focus styles on highlighted elements.
box-shadow: 0 0 0 8px variables.$color-white !important;
References
  1. If !important is required for compatibility reasons, its use is acceptable when accompanied by an explanatory comment.


&-min-width {
min-width: 25px !important;
Expand Down
32 changes: 32 additions & 0 deletions src/pageScanner/checks/img-alt-empty-check.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ export default {
return true;
}

// Skip 1x1 tracking pixels (any src or base64)
if ( hasEmptyAlt && isTrackingPixel( node ) ) {
return true;
}

// Return false if alt is empty and none of the exceptions apply
return ! hasEmptyAlt;
},
Expand Down Expand Up @@ -84,6 +89,33 @@ function isInsideValidCaption( node ) {
return false;
}

/**
* Check if image is a 1x1 tracking pixel.
* Checks both HTML attributes and computed natural dimensions.
* @param {HTMLElement} node - The node to check
* @return {boolean} True if image is a 1x1 tracking pixel
*/
function isTrackingPixel( node ) {
// Check HTML width/height attributes
const widthAttr = node.getAttribute( 'width' );
const heightAttr = node.getAttribute( 'height' );
if ( widthAttr === '1' && heightAttr === '1' ) {
return true;
}
Comment on lines +100 to +104

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using strict string equality (=== '1') to check the width and height attributes can be fragile if the attributes contain units (e.g., 1px) or leading/trailing whitespace. Parsing the attributes with parseInt is more robust and prevents potential false positives.

Suggested change
const widthAttr = node.getAttribute( 'width' );
const heightAttr = node.getAttribute( 'height' );
if ( widthAttr === '1' && heightAttr === '1' ) {
return true;
}
const widthAttr = node.getAttribute( 'width' );
const heightAttr = node.getAttribute( 'height' );
if ( parseInt( widthAttr, 10 ) === 1 && parseInt( heightAttr, 10 ) === 1 ) {
return true;
}


// Check computed natural dimensions (e.g. for base64 or loaded images without explicit attributes)
if (
typeof node.naturalWidth === 'number' &&
typeof node.naturalHeight === 'number' &&
node.naturalWidth === 1 &&
node.naturalHeight === 1
) {
return true;
}

return false;
}

/**
* Check if image should be ignored due to plugin-specific cases
* @param {HTMLElement} node - The node to check
Expand Down
13 changes: 7 additions & 6 deletions src/pageScanner/checks/link-has-valid-href-or-role.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,21 +50,22 @@ export default {
}

const trimmedHref = href ? href.trim() : '';
const normalizedHref = trimmedHref.toLowerCase();

// Fail if href is missing, just '#', or contains invalid protocols
if ( ! href ||
if ( ! trimmedHref ||
trimmedHref === '#' ||
href.toLowerCase().startsWith( 'javascript:' ) ||
href.toLowerCase().startsWith( 'data:' ) ||
href.toLowerCase().startsWith( 'file:' )
normalizedHref.startsWith( 'javascript:' ) ||
normalizedHref.startsWith( 'data:' ) ||
normalizedHref.startsWith( 'file:' )
) {
return false;
}

// Optionally validate URL format if it's an absolute URL
if ( href.includes( '://' ) ) {
if ( trimmedHref.includes( '://' ) ) {
try {
new URL( href );
new URL( trimmedHref );
} catch ( e ) {
return false; // Invalid URL formats
}
Expand Down
4 changes: 4 additions & 0 deletions src/sidebar/components/Panels/ReadabilityAnalysis.js
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,8 @@ const ReadabilityAnalysis = () => {
{ __( 'Simplified Summary', 'accessibility-checker' ) }
</h4>
<TextareaControl
label={ __( 'Simplified Summary', 'accessibility-checker' ) }
hideLabelFromVision={ true }
value={ summaryText }
onChange={ setSummaryText }
placeholder={ __( 'Enter simplified summary...', 'accessibility-checker' ) }
Expand Down Expand Up @@ -403,6 +405,8 @@ const ReadabilityAnalysis = () => {
{ __( 'Simplified Summary', 'accessibility-checker' ) }
</h4>
<TextareaControl
label={ __( 'Simplified Summary', 'accessibility-checker' ) }
hideLabelFromVision={ true }
value={ summaryText }
onChange={ setSummaryText }
placeholder={ __( 'Enter simplified summary...', 'accessibility-checker' ) }
Expand Down
22 changes: 22 additions & 0 deletions tests/jest/rules/imgAltEmpty.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,28 @@ describe( 'Image Alt Empty Validation', () => {
shouldPass: true,
},

// Tracking pixel (1x1) edge cases
{
name: 'should pass for 1x1 tracking pixel with empty alt (gif)',
html: '<img src="track.gif" alt="" width="1" height="1">',
shouldPass: true,
},
{
name: 'should pass for 1x1 tracking pixel with empty alt (arbitrary src)',
html: '<img src="https://example.com/whatever.png" alt="" width="1" height="1">',
shouldPass: true,
},
{
name: 'should fail for 2x1 image with empty alt (not a tracking pixel)',
html: '<img src="tiny.png" alt="" width="2" height="1">',
shouldPass: false,
},
{
name: 'should fail for img with empty alt and no dimension attributes (cannot confirm tracking pixel without dimensions)',
html: '<img src="maybe-tracker.gif" alt="">',
shouldPass: false,
},

// Button context - image with empty alt inside button with accessible name
{
name: 'should pass for img with empty alt inside button with aria-label',
Expand Down
Loading
Loading