-
Notifications
You must be signed in to change notification settings - Fork 19
Be more explicit with who can save and clear issues for a given post #1202
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
3396532
27a248e
c735aaf
52487c4
2c71f90
897ebbd
fa863b0
0f4b8b0
35227d5
b7ed073
aed5128
3e32884
44983e0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -79,13 +79,14 @@ function () use ( $ns, $version ) { | |
| 'callback' => [ $this, 'set_post_scan_results' ], | ||
| 'args' => [ | ||
| 'id' => [ | ||
| 'required' => true, | ||
| 'validate_callback' => function ( $param ) { | ||
| return is_numeric( $param ); | ||
| }, | ||
| return is_numeric( $param ); }, | ||
| 'sanitize_callback' => 'absint', | ||
| ], | ||
| ], | ||
| 'permission_callback' => function () { | ||
| return current_user_can( 'edit_posts' ); | ||
| 'permission_callback' => function ( $request ) { | ||
| return $this->user_can_edit_passed_post_id( $request ); | ||
| }, | ||
| ] | ||
| ); | ||
|
|
@@ -171,13 +172,14 @@ function () use ( $ns, $version ) { | |
| 'callback' => [ $this, 'clear_issues_for_post' ], | ||
| 'args' => [ | ||
| 'id' => [ | ||
| 'required' => true, | ||
| 'validate_callback' => function ( $param ) { | ||
| return is_numeric( $param ); | ||
| }, | ||
| return is_numeric( $param ); }, | ||
|
||
| 'sanitize_callback' => 'absint', | ||
| ], | ||
| ], | ||
| 'permission_callback' => function () { | ||
| return current_user_can( 'edit_posts' ); | ||
| 'permission_callback' => function ( $request ) { | ||
| return $this->user_can_edit_passed_post_id( $request ); | ||
| }, | ||
| ] | ||
| ); | ||
|
|
@@ -203,10 +205,29 @@ 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. | ||
| * | ||
| * @since 1.31.0 | ||
| * | ||
| * @param \WP_REST_Request $request The request object passed from the REST call. This should contain the 'id' of the post to check permissions for. | ||
| * | ||
| * @return bool|\WP_Error | ||
| */ | ||
| public function user_can_edit_passed_post_id( $request ) { | ||
| if ( ! isset( $request['id'] ) ) { | ||
| return new \WP_Error( 'rest_post_invalid_id', __( 'A required parameter is missing.', 'accessibility-checker' ), [ 'status' => 400 ] ); | ||
| } | ||
| $post_id = (int) $request['id']; | ||
| return current_user_can( 'edit_post', $post_id ); // able to edit the post. | ||
| } | ||
|
pattonwebz marked this conversation as resolved.
|
||
|
|
||
|
pattonwebz marked this conversation as resolved.
|
||
| /** | ||
| * REST handler to clear issues results for a given post ID. | ||
| * | ||
| * @param WP_REST_Request $request The request passed from the REST call. | ||
| * @param \WP_REST_Request $request The request passed from the REST call. | ||
| * | ||
| * @return \WP_REST_Response | ||
| */ | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,202 @@ | ||||||
| <?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.' ); | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Verify that a limited user can manage their own post. | ||||||
| * | ||||||
| * @return void | ||||||
| */ | ||||||
| public function test_limited_user_can_manage_own_post() { | ||||||
| wp_set_current_user( self::$limited_id ); | ||||||
| $own_post_id = self::factory()->post->create( | ||||||
| [ | ||||||
| 'post_type' => 'post', | ||||||
| 'post_status' => 'draft', | ||||||
| 'post_author' => self::$limited_id, | ||||||
| ] | ||||||
|
||||||
| ] | |
| ] |
Copilot
AI
Aug 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trailing whitespace after the closing bracket should be removed.
| ] | |
| ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The validate callback function should be properly formatted on separate lines for better readability and consistency with WordPress coding standards.