Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 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
1 change: 1 addition & 0 deletions includes/classes/class-enqueue-frontend.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ public static function maybe_enqueue_frontend_highlighter() {
'nonce' => wp_create_nonce( 'ajax-nonce' ),
'restNonce' => wp_create_nonce( 'wp_rest' ),
'userCanFix' => current_user_can( apply_filters( 'edac_filter_settings_capability', 'manage_options' ) ),
'userCanEdit' => current_user_can( 'edit_post', $post_id ),
'edacUrl' => esc_url_raw( get_site_url() ),
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'loggedIn' => is_user_logged_in(),
Expand Down
26 changes: 22 additions & 4 deletions includes/classes/class-rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ function () use ( $ns, $version ) {
},
],
],
'permission_callback' => function () {
return current_user_can( 'edit_posts' );
'permission_callback' => function ( $request ) {
return $this->user_can_edit_passed_post_id( $request );
},
]
);
Expand Down Expand Up @@ -176,8 +176,8 @@ function () use ( $ns, $version ) {
},
],
],
'permission_callback' => function () {
return current_user_can( 'edit_posts' );
'permission_callback' => function ( $request ) {
return $this->user_can_edit_passed_post_id( $request );
},
]
);
Expand All @@ -203,6 +203,24 @@ function () use ( $ns, $version ) {
);
}

/**
* Check if the user can edit a post.
*
* This is a permission callback to replace several places where we check if the user can edit a post.
*
* @param int $request The request object passed from the REST call. This should contain the 'id' of the post to check permissions for.
Comment thread
pattonwebz marked this conversation as resolved.
Outdated
*
* @return bool
*/
public function user_can_edit_passed_post_id( $request ) {
// Check if the user has permission to edit posts.
if ( ! isset( $request['id'] ) ) {
return new \WP_REST_Response( [ 'message' => 'A required parameter is missing.' ], 400 );
Comment thread
pattonwebz marked this conversation as resolved.
Outdated
}
$post_id = (int) $request['id'];
return current_user_can( 'edit_post', $post_id ); // able to edit the post.
}
Comment thread
pattonwebz marked this conversation as resolved.

Comment thread
pattonwebz marked this conversation as resolved.
/**
* REST handler to clear issues results for a given post ID.
*
Expand Down
8 changes: 4 additions & 4 deletions src/frontendHighlighterApp/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -394,12 +394,12 @@ class AccessibilityCheckerHighlight {
addHighlightPanel() {
const widgetPosition = edacFrontendHighlighterApp?.widgetPosition || 'right';

const isLoggedInUser = edacFrontendHighlighterApp && edacFrontendHighlighterApp?.loggedIn;
const clearButtonMarkup = isLoggedInUser
const userCanEdit = edacFrontendHighlighterApp && edacFrontendHighlighterApp?.userCanEdit && edacFrontendHighlighterApp?.loggedIn;
const clearButtonMarkup = userCanEdit
? `<button id="edac-highlight-clear-issues" class="edac-highlight-clear-issues">${ __( 'Clear Issues', 'accessibility-checker' ) }</button>`
: '';

const rescanButton = isLoggedInUser
const rescanButton = userCanEdit
? `<button id="edac-highlight-rescan" class="edac-highlight-rescan">${ __( 'Rescan This Page', 'accessibility-checker' ) }</button>`
: '';

Expand All @@ -416,7 +416,7 @@ class AccessibilityCheckerHighlight {
<button id="edac-highlight-panel-controls-close" class="edac-highlight-panel-controls-close" aria-label="Close">×</button>
<div class="edac-highlight-panel-controls-title">Accessibility Checker</div>
<div class="edac-highlight-panel-controls-summary">Loading...</div>
<div class="edac-highlight-panel-controls-buttons ${ ! isLoggedInUser ? ' single_button' : '' }">
<div class="edac-highlight-panel-controls-buttons ${ ! userCanEdit ? ' single_button' : '' }">
<div>
<button id="edac-highlight-previous" disabled="true"><span aria-hidden="true">« </span>Previous</button>
<button id="edac-highlight-next" disabled="true">Next<span aria-hidden="true"> »</span></button><br />
Expand Down
163 changes: 163 additions & 0 deletions tests/phpunit/includes/classes/RestApiEndpointsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
<?php
/**
* REST API endpoints behavior tests.
*
* @package Accessibility_Checker
*/

/**
* Test class for REST API endpoints.
*
* @group rest
*/
class RestApiEndpointsTest extends WP_UnitTestCase {
/**
* Admin user ID.
*
* @var int
*/
protected static $admin_id;

/**
* Limited user ID.
*
* @var int
*/
protected static $limited_id;

/**
* Post ID used for tests.
*
* @var int
*/
protected static $post_id;

/**
* REST server instance for dispatching requests.
*
* @var WP_REST_Server|null
*/
private $server;

/**
* Set up before each test.
*
* @return void
*/
protected function setUp(): void {
parent::setUp();
// Initialize REST routes for each test.
do_action( 'init' );
do_action( 'rest_api_init' );
$this->server = rest_get_server();
}

/**
* Clean up after each test.
*
* @return void
*/
protected function tearDown(): void {
// Reset current user between tests.
wp_set_current_user( 0 );
parent::tearDown();
}

/**
* Create shared fixtures for this test class.
*
* @param WP_UnitTest_Factory $factory Factory instance.
* @return void
*/
public static function wpSetUpBeforeClass( $factory ) {
// Ensure posts are scannable by plugin.
update_option( 'edac_post_types', [ 'post' ] );

// Ensure plugin DB table exists for tests (normally created via admin_init).
( new \EDAC\Admin\Update_Database() )->edac_update_database();

self::$admin_id = $factory->user->create( [ 'role' => 'administrator' ] );
self::$limited_id = $factory->user->create( [ 'role' => 'subscriber' ] );
// Give limited user edit_posts but not edit_others_posts so they cannot edit this post.
$user = new WP_User( self::$limited_id );
$user->add_cap( 'edit_posts' );

self::$post_id = $factory->post->create(
[
'post_type' => 'post',
'post_status' => 'publish',
'post_author' => self::$admin_id,
'post_title' => 'EDAC PHPUnit Post',
'post_content' => '<main><h1>Title</h1><p>Img without alt <img src="/wp-includes/images/media/default.png"></p></main>',
]
);
}

/**
* Verify permissions for saving post scan results.
*
* @return void
*/
public function test_rest_post_scan_results_permissions() {
$this->assertNotNull( $this->server );

// Minimal violations payload similar to scanner output.
$violations = [
[
'ruleId' => 'image-alt',
'html' => '<img src="/wp-includes/images/media/default.png">',
'impact' => 'error',
'landmark' => null,
],
];

// Admin can POST results for the post.
wp_set_current_user( self::$admin_id );
$request = new WP_REST_Request( 'POST', '/accessibility-checker/v1/post-scan-results/' . self::$post_id );
$request->set_param( 'id', self::$post_id );
$request->set_param( 'violations', $violations );
$response = $this->server->dispatch( $request );
$this->assertSame( 200, $response->get_status(), 'Admin should be allowed to save scan results.' );
$data = $response->get_data();
$this->assertIsArray( $data );

// Limited user cannot POST results for the admin-owned post.
wp_set_current_user( self::$limited_id );
$request2 = new WP_REST_Request( 'POST', '/accessibility-checker/v1/post-scan-results/' . self::$post_id );
$request2->set_param( 'id', self::$post_id );
$request2->set_param( 'violations', $violations );
$response2 = $this->server->dispatch( $request2 );
$this->assertSame( 403, $response2->get_status(), 'Limited user must not be allowed to save scan results for another user\'s post.' );
}

/**
* Verify permissions for clearing issues for a post.
*
* @return void
*/
public function test_rest_clear_issues_permissions() {
$this->assertNotNull( $this->server );

// Admin can clear issues.
wp_set_current_user( self::$admin_id );
$r1 = new WP_REST_Request( 'POST', '/accessibility-checker/v1/clear-issues/' . self::$post_id );
$r1->set_param( 'id', self::$post_id );
$r1->set_body( wp_json_encode( [ 'flush' => true ] ) );
$r1->set_header( 'Content-Type', 'application/json' );
$resp1 = $this->server->dispatch( $r1 );
$this->assertSame( 200, $resp1->get_status(), 'Admin should be allowed to clear issues.' );
$body1 = $resp1->get_data();
$this->assertIsArray( $body1 );
$this->assertArrayHasKey( 'success', $body1 );
$this->assertTrue( $body1['success'] );

// Limited user cannot clear issues for a post they cannot edit.
wp_set_current_user( self::$limited_id );
$r2 = new WP_REST_Request( 'POST', '/accessibility-checker/v1/clear-issues/' . self::$post_id );
$r2->set_param( 'id', self::$post_id );
$r2->set_body( wp_json_encode( [ 'flush' => true ] ) );
$r2->set_header( 'Content-Type', 'application/json' );
$resp2 = $this->server->dispatch( $r2 );
$this->assertSame( 403, $resp2->get_status(), 'Limited user must not be allowed to clear issues.' );
}
}