Skip to content

Commit f1fec1d

Browse files
Merge pull request #1712 from equalizedigital/fix/ajax-add-ignore-missing-capability-check
Fix: add per-post capability check to add_ignore AJAX handler
2 parents 78e5d25 + f5ee5d7 commit f1fec1d

7 files changed

Lines changed: 67 additions & 79 deletions

File tree

accessibility-checker.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* Plugin Name: Accessibility Checker
1111
* Plugin URI: https://a11ychecker.com
1212
* Description: Audit and check your website for accessibility before you hit publish. In-post accessibility scanner and guidance.
13-
* Version: 1.42.0
13+
* Version: 1.42.1
1414
* Requires PHP: 7.4
1515
* Author: Equalize Digital
1616
* Author URI: https://equalizedigital.com
@@ -36,7 +36,7 @@
3636

3737
// Current plugin version.
3838
if ( ! defined( 'EDAC_VERSION' ) ) {
39-
define( 'EDAC_VERSION', '1.42.0' );
39+
define( 'EDAC_VERSION', '1.42.1' );
4040
}
4141

4242
// Current database version.

admin/class-ajax.php

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -819,17 +819,54 @@ public function add_ignore() {
819819
}
820820

821821
global $wpdb;
822-
$table_name = $wpdb->prefix . 'accessibility_checker';
823-
$raw_ids = isset( $_REQUEST['ids'] ) ? (array) wp_unslash( $_REQUEST['ids'] ) : []; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitization handled below.
824-
$ids = array_map(
822+
$table_name = $wpdb->prefix . 'accessibility_checker';
823+
$raw_ids = isset( $_REQUEST['ids'] ) ? (array) wp_unslash( $_REQUEST['ids'] ) : []; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitization handled below.
824+
$ids = array_map(
825825
function ( $value ) {
826826
return (int) $value;
827827
},
828828
$raw_ids
829829
); // Sanitizing array elements to integers.
830-
$action = isset( $_REQUEST['ignore_action'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['ignore_action'] ) ) : '';
831-
$type = isset( $_REQUEST['ignore_type'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['ignore_type'] ) ) : '';
832-
$siteid = get_current_blog_id();
830+
$action = isset( $_REQUEST['ignore_action'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['ignore_action'] ) ) : '';
831+
$type = isset( $_REQUEST['ignore_type'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['ignore_type'] ) ) : '';
832+
$siteid = get_current_blog_id();
833+
834+
// Capability check: verify edit_post on every post affected by this request.
835+
$batch_object = null;
836+
$first_id = reset( $ids );
837+
$valid_table = edac_get_valid_table_name( $table_name );
838+
839+
if ( ! $first_id || ! $valid_table ) {
840+
wp_send_json_error( new \WP_Error( '-2', __( 'No ignore data to return', 'accessibility-checker' ) ) );
841+
}
842+
843+
if ( isset( $_REQUEST['largeBatch'] ) && 'true' === $_REQUEST['largeBatch'] ) {
844+
// largeBatch updates every row sharing the same object string across the site;
845+
// collect all distinct postids that would be affected and check each one.
846+
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Permission check requires direct lookup.
847+
$batch_object = $wpdb->get_var( $wpdb->prepare( 'SELECT object FROM %i WHERE id = %d', $valid_table, $first_id ) );
848+
if ( ! $batch_object ) {
849+
wp_send_json_error( new \WP_Error( '-2', __( 'No ignore data to return', 'accessibility-checker' ) ) );
850+
}
851+
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Permission check requires direct lookup.
852+
$affected_post_ids = $wpdb->get_col( $wpdb->prepare( 'SELECT DISTINCT postid FROM %i WHERE siteid = %d AND object = %s', $valid_table, $siteid, $batch_object ) );
853+
} else {
854+
// Small batch: look up the post for every supplied ID in one query.
855+
$id_placeholders = implode( ', ', array_fill( 0, count( $ids ), '%d' ) );
856+
$query_args = array_merge( [ $valid_table ], $ids );
857+
$affected_post_ids = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT postid FROM %i WHERE id IN ({$id_placeholders})", $query_args ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnquotedComplexPlaceholder -- Permission check requires direct lookup.
858+
}
859+
860+
if ( empty( $affected_post_ids ) ) {
861+
wp_send_json_error( new \WP_Error( '-2', __( 'No ignore data to return', 'accessibility-checker' ) ) );
862+
}
863+
864+
foreach ( $affected_post_ids as $affected_post_id ) {
865+
if ( ! current_user_can( 'edit_post', (int) $affected_post_id ) ) {
866+
wp_send_json_error( new \WP_Error( '-5', __( 'Permission Denied', 'accessibility-checker' ) ) );
867+
}
868+
}
869+
833870
$ignre = ( 'enable' === $action ) ? 1 : 0;
834871
$ignre_user = ( 'enable' === $action ) ? get_current_user_id() : null;
835872
$ignre_user_info = ( 'enable' === $action ) ? get_userdata( $ignre_user ) : '';
@@ -844,14 +881,8 @@ function ( $value ) {
844881
// instead of IDs. It is a much less efficient query than by IDs - but many IDs run
845882
// into request size limits which caused this to not function at all.
846883
if ( isset( $_REQUEST['largeBatch'] ) && 'true' === $_REQUEST['largeBatch'] ) {
847-
// Get the 'object' from the first id.
848-
$first_id = $ids[0];
849-
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- We need to get the latest value, not a cached value.
850-
$object = $wpdb->get_var( $wpdb->prepare( 'SELECT object FROM %i WHERE id = %d', $table_name, $first_id ) );
851-
852-
if ( ! $object ) {
853-
wp_send_json_error( new \WP_Error( '-2', __( 'No ignore data to return', 'accessibility-checker' ) ) );
854-
}
884+
// $batch_object was already resolved during the capability check above.
885+
$object = $batch_object;
855886
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Safe variable used for table name, caching not required for one time operation.
856887
$wpdb->query( $wpdb->prepare( 'UPDATE %i SET ignre = %d, ignre_user = %d, ignre_date = %s, ignre_comment = %s, ignre_reason = %s, ignre_global = %d WHERE siteid = %d and object = %s', $table_name, $ignre, $ignre_user, $ignre_date, $ignre_comment, $ignre_reason, $ignore_global, $siteid, $object ) );
857888
} else {

changelog.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
*** Accessibility Checker ***
22

3+
2026-05-20 - version 1.42.1
4+
* Updated - improved permission handling for the ignore feature.
5+
36
2026-05-18 - version 1.42.0
47
* Updated - add support for role="none" in several of the link and image alt related rules.
58
* Updated - added support for named anchors as jump links.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "accessibility-checker",
3-
"version": "1.42.0",
3+
"version": "1.42.1",
44
"description": "Audit and check your website for accessibility before you hit publish. In-post accessibility scanner and guidance.",
55
"author": "Equalize Digital",
66
"license": "GPL-2.0+",

readme.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Contributors: equalizedigital, alh0319, stevejonesdev
33
Tags: accessibility, EAA, WCAG, ADA, WP accessibility, accessibility scanner
44
Requires at least: 6.7
55
Tested up to: 7.0
6-
Stable tag: 1.42.0
6+
Stable tag: 1.42.1
77
Requires PHP: 7.4
88
License: GPL-2.0-or-later
99
License URI: https://www.gnu.org/licenses/gpl-2.0.html
@@ -279,6 +279,9 @@ You can report security bugs through the Patchstack Vulnerability Disclosure Pro
279279

280280
== Changelog ==
281281

282+
2026-05-20 - version 1.42.1
283+
* Updated - improved permission handling for the ignore feature.
284+
282285
2026-05-18 - version 1.42.0
283286
* Updated - add support for role="none" in several of the link and image alt related rules.
284287
* Updated - added support for named anchors as jump links.

tests/phpunit/Admin/EnqueueAdminTest.php

Lines changed: 6 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -375,40 +375,11 @@ public function testSrOnlyFormatDoesNotEnqueueOnOtherAdminPages() {
375375
* @param bool $is_block_editor Whether the screen should behave as block editor.
376376
*/
377377
private function set_mock_screen( bool $is_block_editor ): void {
378-
$GLOBALS['current_screen'] = new class( $is_block_editor ) {
379-
/**
380-
* True or false whether the screen is block editor.
381-
*
382-
* @var bool
383-
*/
384-
private $is_block_editor;
385-
386-
/**
387-
* Constructor.
388-
*
389-
* @param bool $is_block_editor Whether the screen should behave as block editor.
390-
*/
391-
public function __construct( bool $is_block_editor ) {
392-
$this->is_block_editor = $is_block_editor;
393-
}
394-
395-
/**
396-
* Mock is_block_editor method.
397-
*
398-
* @return bool
399-
*/
400-
public function is_block_editor(): bool {
401-
return $this->is_block_editor;
402-
}
403-
404-
/**
405-
* Mock in_admin method.
406-
*
407-
* @return bool
408-
*/
409-
public function in_admin(): bool {
410-
return true;
411-
}
412-
};
378+
// As of WP 7.0, get_current_screen() requires an actual WP_Screen
379+
// instance. Use WP_Screen::get() to obtain one without the side effects
380+
// of set_current_screen() (e.g. setting $hook_suffix, $typenow, firing
381+
// the current_screen action).
382+
$GLOBALS['current_screen'] = WP_Screen::get( 'post' );
383+
$GLOBALS['current_screen']->is_block_editor = $is_block_editor;
413384
}
414385
}

tests/phpunit/Admin/MetaBoxesTest.php

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -128,32 +128,12 @@ public function test_register_meta_boxes_keeps_classic_editor_when_setting_disab
128128
* @param bool $is_block_editor Whether the screen should behave as block editor.
129129
*/
130130
private function set_mock_screen( bool $is_block_editor ): void {
131-
$GLOBALS['current_screen'] = new class( $is_block_editor ) {
132-
/**
133-
* True or false whether the screen is block editor.
134-
*
135-
* @var bool
136-
*/
137-
private $is_block_editor;
138-
139-
/**
140-
* Constructor.
141-
*
142-
* @param bool $is_block_editor Whether the screen should behave as block editor.
143-
*/
144-
public function __construct( bool $is_block_editor ) {
145-
$this->is_block_editor = $is_block_editor;
146-
}
147-
148-
/**
149-
* Mock is_block_editor method.
150-
*
151-
* @return bool
152-
*/
153-
public function is_block_editor(): bool {
154-
return $this->is_block_editor;
155-
}
156-
};
131+
// As of WP 7.0, get_current_screen() requires an actual WP_Screen
132+
// instance. Use WP_Screen::get() to obtain one without the side effects
133+
// of set_current_screen() (e.g. setting $hook_suffix, $typenow, firing
134+
// the current_screen action).
135+
$GLOBALS['current_screen'] = WP_Screen::get( 'post' );
136+
$GLOBALS['current_screen']->is_block_editor = $is_block_editor;
157137
}
158138

159139
/**

0 commit comments

Comments
 (0)