Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Binary file modified .wporg-assets/screenshot-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .wporg-assets/screenshot-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .wporg-assets/screenshot-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .wporg-assets/screenshot-4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .wporg-assets/screenshot-5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .wporg-assets/screenshot-6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .wporg-assets/screenshot-7.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
49 changes: 37 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
**Requires at least:** 6.6 \
**Tested up to:** 6.8 \
**Requires PHP:** 7.4 \
**Stable tag:** 3.6.0 \
**Stable tag:** 3.8.0 \
**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.

## Description

https://www.youtube.com/watch?v=-2ig5D348vo

Ally (formerly One Click Accessibility) is a free, powerful, and user-friendly plugin that helps WordPress creators build more accessible websites with ease.
It simplifies accessibility with three essential tools:

Expand Down Expand Up @@ -194,29 +196,53 @@ You can report security bugs through the Patchstack Vulnerability Disclosure Pro

## Screenshots

### 1. Design: Customize the button's icon, size, and color and widget branding.
### 1. Usability widget: Large, well organized controls let visitors adjust text, contrast, animations, and more for a more comfortable browsing experience.

![Usability widget: Large, well organized controls let visitors adjust text, contrast, animations, and more for a more comfortable browsing experience.](https://ps.w.org/pojo-accessibility/assets/screenshot-1.png)

### 2. Accessibility button: Add a button that opens the accessibility menu in one click, and tailor its icon, color, label, and placement to match your brand.

![Accessibility button: Add a button that opens the accessibility menu in one click, and tailor its icon, color, label, and placement to match your brand.](https://ps.w.org/pojo-accessibility/assets/screenshot-2.png)

### 3. Accessibility statement: Quickly generate and publish a custom statement that signals your commitment, improves transparency, and offers a clear way to report issues.

![Design: Customize the button's icon, size, and color and widget branding.](https://ps.w.org/pojo-accessibility/assets/screenshot-1.png)
![Accessibility statement: Quickly generate and publish a custom statement that signals your commitment, improves transparency, and offers a clear way to report issues.](https://ps.w.org/pojo-accessibility/assets/screenshot-3.png)

### 2. Position Settings: Set widget placement or hide it on desktop and mobile.
### 4. Accessibility Assistant: Scan any page to instantly detect over 180 common accessibility issues and get clear, guided steps for remediation inside your site editor.

![Position Settings: Set widget placement or hide it on desktop and mobile.](https://ps.w.org/pojo-accessibility/assets/screenshot-2.png)
![Accessibility Assistant: Scan any page to instantly detect over 180 common accessibility issues and get clear, guided steps for remediation inside your site editor.](https://ps.w.org/pojo-accessibility/assets/screenshot-4.png)

### 3. Capabilities: Toggle accessibility features on or off as needed.
### 5. Scan results: View issues highlighted in context and grouped by type such as alt text, ARIA, page structure, and more. Expand any item for step-by-step guidance and optional AI-powered suggestions.

![Capabilities: Toggle accessibility features on or off as needed.](https://ps.w.org/pojo-accessibility/assets/screenshot-3.png)
![Scan results: View issues highlighted in context and grouped by type such as alt text, ARIA, page structure, and more. Expand any item for step-by-step guidance and optional AI-powered suggestions.](https://ps.w.org/pojo-accessibility/assets/screenshot-5.png)

### 4. Accessibility Statement: Add or create a custom accessibility statement.
### 6. Color contrast: Fine tune text and background colors with live checks that validate contrast ratios.

![Accessibility Statement: Add or create a custom accessibility statement.](https://ps.w.org/pojo-accessibility/assets/screenshot-4.png)
![Color contrast: Fine tune text and background colors with live checks that validate contrast ratios.](https://ps.w.org/pojo-accessibility/assets/screenshot-6.png)

### 5. Widget on Site: This is how the accessibility widget appears on a live website.
### 7. Scanner dashboard: Track your site’s accessibility scans, monitor open issues, and follow progress over time.

![Widget on Site: This is how the accessibility widget appears on a live website.](https://ps.w.org/pojo-accessibility/assets/screenshot-5.png)
![Scanner dashboard: Track your site’s accessibility scans, monitor open issues, and follow progress over time.](https://ps.w.org/pojo-accessibility/assets/screenshot-7.png)


## Changelog

### 3.8.0 – 2025-09-29

* New: Headings Remediation Management - Review and correct heading hierarchy with a guided flow
* Tweak: Improved AI remediation results with added context for clearer responses
* Fix: Disabled remediation action not working
* Fix: Quota number displayed incorrectly for Standard plan

### 3.7.0 - 2025-09-02

* New: Redesigned Accessibility Widget – Clearer structure, Wider accessible buttons, and Improved mobile view
* New: Intro banner for users who connected Ally
* New: Reviews & CSAT flow to gather user feedback
* Tweak: Added cache clearing option in Assistant panel and WordPress Admin Bar
* Tweak: Enhanced color contrast evaluation to fix issues with gradient/video backgrounds
* Fix: found issue count wrong in edge cases

### 3.6.0 - 2025-08-02

* New: Smart color contrast remediation flow in the accessibility assistant
Expand All @@ -226,7 +252,6 @@ You can report security bugs through the Patchstack Vulnerability Disclosure Pro
* Fix: Added WPML compatibility
* Fix: WooCommerce AJAX conflict


### 3.5.2 - 2025-07-28

* Tweak: Improved performance by enqueuing Assistant only when logged in
Expand Down
4 changes: 4 additions & 0 deletions assets/dev/js/services/mixpanel/mixpanel-events.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,8 @@ export const mixpanelEvents = {
feedbackSubmitted: 'review_feedback_submitted',
publicRedirectClicked: 'review_public_redirect_clicked',
},

// Heading Structure
headingClicked: 'heading_clicked',
headingSelected: 'heading_selected',
};
21 changes: 21 additions & 0 deletions assets/dev/js/utils/inject-template-vars.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { createElement } from '@wordpress/element';

export const injectTemplateVars = (message, components) => {
const regex = /\{\{(\w+)\}\}([^]*?)\{\{\/\1\}\}/g;
const splitMessage = message.split(regex);

// eslint-disable-next-line array-callback-return
return splitMessage.map((part, index) => {
if (index % 3 === 0) {
return part;
}

if (index % 3 === 1) {
return createElement(
components[part],
{ key: index },
splitMessage[index + 1],
);
}
});
};
3 changes: 2 additions & 1 deletion modules/connect/classes/data.php
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,8 @@ public static function get_refresh_token() {
public static function get_home_url() {
$raw = self::get_connect_mode_data( self::HOME_URL, false );
$is_base64 = base64_encode( base64_decode( $raw, true ) ) === $raw;
return $is_base64 ? base64_decode( $raw ) : $raw;
$url = $is_base64 ? base64_decode( $raw ) : $raw;
return apply_filters( 'ally_connect_home_url', $url );
}

/**
Expand Down
1 change: 1 addition & 0 deletions modules/core/components/skip-link.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public function __construct() {

if ( $this->settings && ! empty( $this->settings['enabled'] ) ) {
remove_action( 'wp_footer', 'the_block_template_skip_link' );
add_filter( 'hello_elementor_enable_skip_link', '__return_false' );

add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_skip_link_styles' ] );
add_action( 'wp_body_open', [ $this, 'render_skip_link' ] );
Expand Down
6 changes: 5 additions & 1 deletion modules/remediation/classes/utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,11 @@ public static function get_current_object_type_name() : string {
}

if ( $wp_query->is_post_type_archive() ) {
return $wp_query->query_vars['post_type'] ?? 'unknown';
$post_type = $wp_query->query_vars['post_type'];
if ( is_array( $post_type ) ) {
$post_type = implode( '_', $post_type );
}
return $post_type ?? 'unknown';
}

if ( $wp_query->is_singular() ) {
Expand Down
2 changes: 1 addition & 1 deletion modules/remediation/components/remediation-runner.php
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ private function should_run_remediation(): bool {
]);

$this->page_html = $this->page->get_page_html();
$this->page_remediations = Remediation_Entry::get_page_remediations( $current_url );
$this->page_remediations = Remediation_Entry::get_page_active_remediations( $current_url );
$status = $this->page->__get( Page_Table::STATUS );

if ( empty( $this->page_remediations ) || Page_Table::STATUSES['ACTIVE'] !== $status ) {
Expand Down
24 changes: 24 additions & 0 deletions modules/remediation/database/remediation-entry.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,30 @@ public static function get_page_remediations( string $url, bool $total = false )
return Remediation_Table::select( $select, $where );
}

/**
* get_page_active_remediations
*
* @param string $url
* @return array
*/
public static function get_page_active_remediations( string $url ) : array {
$where = [
[
'column' => Remediation_Table::URL,
'value' => $url,
'operator' => '=',
'relation_after' => 'AND',
],
[
'column' => Remediation_Table::ACTIVE,
'value' => 1,
'operator' => '=',
],
];

return Remediation_Table::select( '*', $where );
}

public static function get_all_remediations( int $period ) : array {
$date_threshold = gmdate( 'Y-m-d', strtotime( "-{$period} days" ) ) . ' 00:00:00';

Expand Down
2 changes: 2 additions & 0 deletions modules/remediation/module.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ public static function routes_list(): array {
'Items',
'Item',
'Trigger_Save',
'Heading_Level',
'Dismiss_Heading_Issue',
'Clear_Cache',
];
}
Expand Down
69 changes: 69 additions & 0 deletions modules/remediation/rest/dismiss-heading-issue.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

namespace EA11y\Modules\Remediation\Rest;

use EA11y\Modules\Remediation\Classes\Route_Base;
use Throwable;
use WP_Error;
use WP_REST_Response;

if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}

class Dismiss_Heading_Issue extends Route_Base {
const META_KEY = 'ea11y-scanner-heading-issues-dismissed';
public string $path = 'dismiss-heading-issue';

public function get_methods(): array {
return [ 'POST' ];
}

public function get_name(): string {
return 'dismiss-heading-issue';
}

/**
*
* @return WP_Error|WP_REST_Response
*
*/
public function POST( $request ) {
try {
$error = $this->verify_capability();

if ( $error ) {
return $error;
}

$post_id = (int) sanitize_text_field( $request->get_param( 'pageId' ) );
$xpath = sanitize_text_field( $request->get_param( 'xpath' ) );

if ( ! $post_id || ! $xpath ) {
return $this->respond_error_json( [
'message' => 'Missing required parameters',
'code' => 'missing_parameters',
] );
}

$data = get_post_meta( $post_id, self::META_KEY, true ) ?? [];

if ( ! $data ) {
$data = [];
}

$data[] = $xpath;

update_post_meta( $post_id, self::META_KEY, array_unique( $data ) );

return $this->respond_success_json( [
'message' => 'Heading issue dismissed',
] );
} catch ( Throwable $t ) {
return $this->respond_error_json( [
'message' => $t->getMessage(),
'code' => 'internal_server_error',
] );
}
}
}
123 changes: 123 additions & 0 deletions modules/remediation/rest/heading-level.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<?php

namespace EA11y\Modules\Remediation\Rest;

use EA11y\Classes\Utils as Global_Utils;
use EA11y\Modules\Remediation\Classes\Route_Base;
use EA11y\Modules\Remediation\Database\Page_Entry;
use EA11y\Modules\Remediation\Database\Remediation_Entry;
use EA11y\Modules\Remediation\Database\Remediation_Table;
use Throwable;
use WP_Error;
use WP_REST_Response;

if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}

class Heading_Level extends Route_Base {
public string $path = 'heading-level';

public function get_methods(): array {
return [ 'POST' ];
}

public function get_name(): string {
return 'heading-level';
}

/**
* @return WP_Error|WP_REST_Response
*/
public function POST( $request ) {
try {
$error = $this->verify_capability();

if ( $error ) {
return $error;
}

$url = esc_url( $request->get_param( 'url' ) );
$level = sanitize_text_field( $request->get_param( 'level' ) );
$xpath = sanitize_text_field( $request->get_param( 'xpath' ) );
$rule = sanitize_text_field( $request->get_param( 'rule' ) ) ?? '';

// Remove existing remediations for this xpath before creating new ones
$this->remove_existing_remediations( $url, $xpath );

if ( 'p' === $level ) {
$this
->create_remediation_entry( $url, $rule, [
'xpath' => $xpath,
'attribute_name' => 'role',
'attribute_value' => 'presentation',
] )
->save();
} else {
$this
->create_remediation_entry( $url, $rule, [
'xpath' => $xpath,
'attribute_name' => 'role',
'attribute_value' => 'heading',
] )
->save();

$this
->create_remediation_entry( $url, $rule, [
'xpath' => $xpath,
'attribute_name' => 'aria-level',
'attribute_value' => trim( $level, 'h' ),
] )
->save();
}

Page_Entry::clear_cache( $url );

return $this->respond_success_json( [
'message' => 'Remediation added',
] );
} catch ( Throwable $t ) {
return $this->respond_error_json( [
'message' => $t->getMessage(),
'code' => 'internal_server_error',
] );
}
}

private function remove_existing_remediations( string $url, string $xpath ): void {
$existing_entries = Remediation_Entry::get_page_remediations( $url );

foreach ( $existing_entries as $entry ) {
if ( isset( $entry->group ) && 'headingStructure' === $entry->group ) {
$content = json_decode( $entry->content, true );

if ( isset( $content['xpath'] ) &&
$content['xpath'] === $xpath &&
isset( $content['type'] ) &&
'ATTRIBUTE' === $content['type'] &&
isset( $content['attribute_name'] ) &&
in_array( $content['attribute_name'], [ 'role', 'aria-level' ], true )
) {
Remediation_Table::delete( [ 'id' => $entry->id ] );
}
}
}
}

private function create_remediation_entry( string $url, string $rule, array $data ): Remediation_Entry {
return new Remediation_Entry( [
'data' => [
Remediation_Table::URL => $url,
Remediation_Table::CATEGORY => 'A',
Remediation_Table::RULE => $rule,
Remediation_Table::GROUP => 'headingStructure',
Remediation_Table::CONTENT => wp_json_encode( array_merge( [
'category' => 'A',
'type' => 'ATTRIBUTE',
'action' => 'update',
], $data ) ),
Remediation_Table::ACTIVE => true,
],
] );
}
}
Loading
Loading