diff --git a/README.md b/README.md
index 8d519dc4..0f539c1f 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
**Requires at least:** 6.6 \
**Tested up to:** 6.8 \
**Requires PHP:** 7.4 \
-**Stable tag:** 3.5.0 \
+**Stable tag:** 3.5.1 \
**License:** GPLv2 or later
Ally: Make your site more inclusive by scanning for accessibility violations, fixing them easily, and adding a usability widget and accessibility statement.
@@ -213,6 +213,16 @@ These are smart suggestions generated by Ally to help you resolve issues more ef
## Changelog
+### 3.5.1 - 2025-07-23
+
+* Tweak: Admin panel UI updates
+* Tweak: Assistant UI updates
+* Tweak: Ensure case sensitive attributes in remediations
+* Fix: Assistant accessibility issues
+* Fix: Custom Icon issue in edge cases
+* Fix: Critical Error on plugin conflict
+
+
### 3.5.0 - 2025-07-08
* New: Introducing URL Scanner – find 180+ issues instantly (WCAG 2.1 AA)
diff --git a/modules/remediation/actions/attribute.php b/modules/remediation/actions/attribute.php
index 7ed3c23c..86385761 100644
--- a/modules/remediation/actions/attribute.php
+++ b/modules/remediation/actions/attribute.php
@@ -27,11 +27,11 @@ public function run() : ?DOMDocument {
//Disable duplicates attr for image
$exclusions = [
- 'alt' => ['role', 'title'],
- 'role' => ['alt', 'title']
+ 'alt' => [ 'role', 'title' ],
+ 'role' => [ 'alt', 'title' ],
];
- if ( isset( $exclusions[$this->data['attribute_name']] ) ) {
- foreach ( $exclusions[$this->data['attribute_name']] as $attr_to_remove ) {
+ if ( isset( $exclusions[ $this->data['attribute_name'] ] ) ) {
+ foreach ( $exclusions[ $this->data['attribute_name'] ] as $attr_to_remove ) {
$element_node->removeAttribute( $attr_to_remove );
}
}
diff --git a/modules/remediation/actions/replace.php b/modules/remediation/actions/replace.php
index f9f32105..d785e313 100644
--- a/modules/remediation/actions/replace.php
+++ b/modules/remediation/actions/replace.php
@@ -24,19 +24,19 @@ public function run() : ?DOMDocument {
$outer_html = $this->dom->saveHTML( $element_node );
- if ( strpos( $outer_html, $this->data['find'] ) === false ) {
+ if ( stripos( $outer_html, $this->data['find'] ) === false ) {
return $this->dom;
}
- $updated_html = str_replace( $this->data['find'], $this->data['replace'], $outer_html );
+ $updated_html = str_ireplace( $this->data['find'], $this->data['replace'], $outer_html );
if ( $updated_html === $outer_html ) {
return $this->dom;
}
- $tmp_dom = new DOMDocument('1.0', 'UTF-8');
+ $tmp_dom = new DOMDocument( '1.0', 'UTF-8' );
$tmp_dom->loadHTML(
- mb_convert_encoding($updated_html, 'HTML-ENTITIES', 'UTF-8'),
+ mb_convert_encoding( $updated_html, 'HTML-ENTITIES', 'UTF-8' ),
LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD | LIBXML_NOERROR | LIBXML_NOWARNING
);
diff --git a/modules/remediation/assets/js/actions/replace.js b/modules/remediation/assets/js/actions/replace.js
index 255925bd..d51f33c5 100644
--- a/modules/remediation/assets/js/actions/replace.js
+++ b/modules/remediation/assets/js/actions/replace.js
@@ -1,6 +1,18 @@
import { RemediationBase } from './base';
export class ReplaceRemediation extends RemediationBase {
+ replaceIgnoreCase(outerHtml, find, replace, lowerOuterHTML, lowerFind) {
+ const index = lowerOuterHTML.indexOf(lowerFind);
+ if (index === -1) {
+ return outerHtml;
+ }
+ return (
+ outerHtml.substring(0, index) +
+ replace +
+ outerHtml.substring(index + find.length)
+ );
+ }
+
run() {
const { xpath, find, replace } = this.data;
const el = this.getElementByXPath(xpath);
@@ -11,13 +23,24 @@ export class ReplaceRemediation extends RemediationBase {
if (typeof find !== 'string' || typeof replace !== 'string') {
return false;
}
- if (!outerHTML.includes(find)) {
+ const lowerOuterHTML = outerHTML.toLowerCase();
+ const lowerFind = find.toLowerCase();
+ if (!lowerOuterHTML.includes(lowerFind)) {
return false;
}
- const updatedHTML = outerHTML.replace(find, replace);
+
+ const updatedHTML = this.replaceIgnoreCase(
+ outerHTML,
+ find,
+ replace,
+ lowerOuterHTML,
+ lowerFind,
+ );
+
if (updatedHTML === outerHTML) {
return false;
}
+
// Create a temporary container to parse the HTML string
const tmp = document.createElement('div');
tmp.innerHTML = updatedHTML;
diff --git a/modules/remediation/classes/utils.php b/modules/remediation/classes/utils.php
index f09e3648..18d864ba 100644
--- a/modules/remediation/classes/utils.php
+++ b/modules/remediation/classes/utils.php
@@ -71,7 +71,7 @@ public static function get_current_object_type() : string {
if ( $wp_query->is_singular() ) {
if ( $wp_query->is_single() ) {
- return $wp_query->query_vars['post_type'] ?? 'unknown';
+ return get_post_type($wp_query->get_queried_object_id()) ?? 'unknown';
} elseif ( $wp_query->is_page() ) {
return 'page';
} elseif ( $wp_query->is_attachment() ) {
diff --git a/modules/scanner/assets/js/app.js b/modules/scanner/assets/js/app.js
index c8c17d37..6c65785e 100644
--- a/modules/scanner/assets/js/app.js
+++ b/modules/scanner/assets/js/app.js
@@ -90,7 +90,9 @@ const App = () => {
}>
+
{showResolvedMessage ? : getBlock()}
+
diff --git a/modules/scanner/assets/js/components/block-button/index.js b/modules/scanner/assets/js/components/block-button/index.js
index eea020ce..0a7acb4e 100644
--- a/modules/scanner/assets/js/components/block-button/index.js
+++ b/modules/scanner/assets/js/components/block-button/index.js
@@ -47,12 +47,17 @@ export const BlockButton = ({
color={resolved ? 'success' : 'default'}
>
- {title}
+
+ {title}
+
+
{showChip && (
)}
+
{resolved && }
+
{isManage && (
{
>
+
-
+
{BLOCK_TITLES[openedBlock]}
+
{BLOCK_INFO[openedBlock] && (
{
});
};
- const onRunNewScan = () => {
+ const onRescan = () => {
runNewScan();
- sendOnClickEvent('New Scan');
+ sendOnClickEvent('Rescan');
};
const goToManagement = () => {
@@ -61,12 +61,14 @@ export const DropdownMenu = () => {
>
+
+
{showMainBlock && (
{isMainHeader ? (
@@ -176,6 +179,12 @@ const StyledCard = styled(Card)`
`;
const StyledTitle = styled(Typography)`
+ font-size: 16px;
+ font-weight: 500;
+ line-height: 130%;
+ letter-spacing: 0.15px;
+ margin: 0;
+
.MuiChip-root {
margin-inline-start: ${({ theme }) => theme.spacing(1)};
diff --git a/modules/scanner/assets/js/components/manual-fix-form/index.js b/modules/scanner/assets/js/components/manual-fix-form/index.js
index da314fbe..ebc31e9b 100644
--- a/modules/scanner/assets/js/components/manual-fix-form/index.js
+++ b/modules/scanner/assets/js/components/manual-fix-form/index.js
@@ -15,11 +15,7 @@ import { useToastNotification } from '@ea11y-apps/global/hooks';
import { mixpanelEvents, mixpanelService } from '@ea11y-apps/global/services';
import { APIScanner } from '@ea11y-apps/scanner/api/APIScanner';
import { ResolveWithAi } from '@ea11y-apps/scanner/components/manual-fix-form/resolve-with-ai';
-import {
- BLOCK_TITLES,
- BLOCKS,
- EXCLUDE_FROM_AI,
-} from '@ea11y-apps/scanner/constants';
+import { BLOCKS, EXCLUDE_FROM_AI } from '@ea11y-apps/scanner/constants';
import { uxMessaging } from '@ea11y-apps/scanner/constants/ux-messaging';
import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wizard-context';
import { useCopyToClipboard } from '@ea11y-apps/scanner/hooks/use-copy-to-clipboard';
@@ -32,6 +28,7 @@ import {
StyledSnippet,
} from '@ea11y-apps/scanner/styles/manual-fixes.styles';
import { scannerItem } from '@ea11y-apps/scanner/types/scanner-item';
+import { speak } from '@wordpress/a11y';
import { useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
@@ -54,16 +51,31 @@ export const ManualFixForm = ({ item, current, setOpen }) => {
const sendMixpanelEvent = (event) => {
mixpanelService.sendEvent(event, {
- category_name: BLOCK_TITLES[openedBlock],
+ category_name: openedBlock,
issue_type: item.message,
element_selector: item.path.dom,
});
};
+ const focusOnActive = () => {
+ const rootNode = document.getElementById('ea11y-scanner-wizard-widget');
+ const currentItem = rootNode.shadowRoot.querySelector(
+ `#manual-panel-${current}`,
+ );
+
+ if (currentItem) {
+ currentItem.focus();
+ }
+ };
+
const handleSkip = () => {
closeExample();
setOpen(current + 1);
+ focusOnActive();
+
sendMixpanelEvent(mixpanelEvents.issueSkipped);
+
+ speak(__('Issue skipped', 'pojo-accessibility'), 'polite');
};
const handleMarkResolved = async () => {
@@ -72,7 +84,11 @@ export const ManualFixForm = ({ item, current, setOpen }) => {
closeExample();
markResolved();
+ focusOnActive();
+
sendMixpanelEvent(mixpanelEvents.markAsResolveClicked);
+
+ speak(__('Issue resolved', 'pojo-accessibility'), 'polite');
} catch (e) {
error(__('An error occurred.', 'pojo-accessibility'));
}
@@ -87,10 +103,12 @@ export const ManualFixForm = ({ item, current, setOpen }) => {
-
+
{__('What’s the issue', 'pojo-accessibility')}
+
{
'pojo-accessibility',
)}
+
{uxMessaging[item.ruleId].whyItMatters}
@@ -115,17 +134,21 @@ export const ManualFixForm = ({ item, current, setOpen }) => {
+
{uxMessaging[item.ruleId]?.whatsTheIssue ?? item.message}
+
-
+
{__('Where is it', 'pojo-accessibility')}
+
{item.snippet}
+
{
+
{showAIBlock && }
+
{uxMessaging[item.ruleId] && (
<>
-
+
{__('How to resolve it', 'pojo-accessibility')}
+
{uxMessaging[item.ruleId].howToResolve}
+
{
justifyContent="space-between"
alignItems="start"
>
-
+
{__('See an example', 'pojo-accessibility')}
+
{
-
+
{__('Issue:', 'pojo-accessibility')}
+
{
>
{uxMessaging[item.ruleId].seeAnExample.issue}
-
+
+
{__('Resolution:', 'pojo-accessibility')}
+
{uxMessaging[item.ruleId].seeAnExample.resolution.flatMap(
(resolution, index) => (
{
),
)}
+
-