diff --git a/.editorconfig b/.editorconfig index 15c079d5..cc982c2d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,7 +11,7 @@ indent_style = space insert_final_newline = true trim_trailing_whitespace = true -[*.{php,neon}] +[*.{php,neon,php.hbs}] indent_size = 4 indent_style = tab diff --git a/.scaffolder/feature/config.yml b/.scaffolder/feature/config.yml new file mode 100644 index 00000000..0965d1de --- /dev/null +++ b/.scaffolder/feature/config.yml @@ -0,0 +1,17 @@ +name: wp-alleyvate@feature + +inputs: + - name: featureName + description: "Feature Name" + type: string + - name: tests + description: "Include Tests?" + type: boolean + default: true + +files: + - source: feature.php.hbs + destination: src/alley/wp/alleyvate/features/{{ wpClassFilename inputs.featureName }} + - source: test.php.hbs + if: "{{ inputs.tests }}" + destination: tests/alley/wp/alleyvate/features/{{ wpClassFilename inputs.featureName prefix="test-" }} diff --git a/.scaffolder/feature/feature.php.hbs b/.scaffolder/feature/feature.php.hbs new file mode 100644 index 00000000..8f9835e0 --- /dev/null +++ b/.scaffolder/feature/feature.php.hbs @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @package wp-alleyvate + */ + +namespace Alley\WP\Alleyvate\Features; + +use Alley\WP\Types\Feature; + +/** + * {{ wpClassName inputs.featureName }} feature. + */ +final class {{ wpClassName inputs.featureName }} implements Feature { + /** + * Boot the feature. + */ + public function boot(): void { + // ... + } +} diff --git a/.scaffolder/feature/test.php.hbs b/.scaffolder/feature/test.php.hbs new file mode 100644 index 00000000..dcd2fbcf --- /dev/null +++ b/.scaffolder/feature/test.php.hbs @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @package wp-alleyvate + */ + +declare( strict_types=1 ); + +namespace Alley\WP\Alleyvate\Features; + +use Mantle\Testing\Concerns\Refresh_Database; +use Mantle\Testkit\Test_Case; + +/** + * Tests for {{ wpClassName inputs.featureName }} feature. + */ +final class {{ wpClassName inputs.featureName prefix="Test_" }} extends Test_Case { + use Refresh_Database; + + /** + * Feature instance. + * + * @var {{ wpClassName inputs.featureName }} + */ + private {{ wpClassName inputs.featureName }} $feature; + + /** + * Set up. + */ + protected function setUp(): void { + parent::setUp(); + + $this->feature = new {{ wpClassName inputs.featureName }}(); + } + + /** + * Example Test. + */ + public function test_example(): void { + // Activate the feature. + $this->feature->boot(); + + $this->assertTrue( true ); + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 09242e5f..d713c74a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ This library adheres to [Semantic Versioning](https://semver.org/) and [Keep a CHANGELOG](https://keepachangelog.com/en/1.0.0/). +## 3.3.0 + +### Added + +* `disable_site_health_directories`: Added a feature to disable the site health check for information about the WordPress directories and their sizes. + ## 3.2.0 ### Added diff --git a/README.md b/README.md index 0ae125e8..d012f93c 100644 --- a/README.md +++ b/README.md @@ -99,10 +99,15 @@ which have been shown to force those environments to use an insecure protocol at This feature disables sending password change notification emails to site admins. +### `disable_site_health_directories` + +This feature disables the site health check for information about the WordPress directories and their sizes. + ### `disable_sticky_posts` This feature disables WordPress sticky posts entirely, including the ability to set and query sticky posts. + ### `disable_trackbacks` This feature disables WordPress from sending or receiving trackbacks or pingbacks. diff --git a/composer.json b/composer.json index caf8f5bc..9c5eb5f4 100644 --- a/composer.json +++ b/composer.json @@ -39,6 +39,7 @@ "phpcs": "phpcs", "phpstan": "phpstan --memory-limit=768M", "phpunit": "phpunit", + "scaffold": "npx npx @alleyinteractive/scaffolder@latest wp-alleyvate@feature", "test": [ "@lint", "@phpunit" diff --git a/src/alley/wp/alleyvate/class-feature.php b/src/alley/wp/alleyvate/class-feature.php index 515c3d73..01031862 100644 --- a/src/alley/wp/alleyvate/class-feature.php +++ b/src/alley/wp/alleyvate/class-feature.php @@ -81,7 +81,7 @@ public function add_debug_information( $info ): array { $info['wp-alleyvate']['fields'] ??= []; $info['wp-alleyvate']['fields'][] = [ - 'label' => sprintf( + 'label' => \sprintf( /* translators: %s: Feature name. */ __( 'Feature: %s', 'alley' ), $this->handle, diff --git a/src/alley/wp/alleyvate/features/class-disable-deep-pagination.php b/src/alley/wp/alleyvate/features/class-disable-deep-pagination.php index 50fbd872..eab66e23 100644 --- a/src/alley/wp/alleyvate/features/class-disable-deep-pagination.php +++ b/src/alley/wp/alleyvate/features/class-disable-deep-pagination.php @@ -52,7 +52,7 @@ public static function filter__posts_where( $where, $wp_query ) { } wp_die( - sprintf( + \sprintf( /* translators: The maximum number of pages. */ esc_html__( 'Invalid Request: Pagination beyond page %d has been disabled for performance reasons.', 'alley' ), esc_html( $max_pages ), diff --git a/src/alley/wp/alleyvate/features/class-disable-site-health-directories.php b/src/alley/wp/alleyvate/features/class-disable-site-health-directories.php new file mode 100644 index 00000000..7f123eec --- /dev/null +++ b/src/alley/wp/alleyvate/features/class-disable-site-health-directories.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @package wp-alleyvate + */ + +namespace Alley\WP\Alleyvate\Features; + +use Alley\WP\Types\Feature; + +/** + * Disable_Site_Health_Directories feature. + */ +final class Disable_Site_Health_Directories implements Feature { + /** + * Boot the feature. + */ + public function boot(): void { + add_filter( 'rest_pre_dispatch', [ $this, 'filter_rest_pre_dispatch' ], 10, 3 ); + add_filter( 'debug_information', [ $this, 'filter_debug_information' ] ); + } + + /** + * Filter REST API requests to remove Site Health directories. + * + * @param mixed $result Response to replace the requested version with. Can be anything a normal endpoint can return, or null to not hijack the request. + * @param \WP_REST_Server $server Server instance. + * @param \WP_REST_Request $request Request used to generate the response. + * @return mixed Response to replace the requested version with. + */ + public function filter_rest_pre_dispatch( $result, $server, $request ) { + if ( $request->get_route() === '/wp-site-health/v1/directory-sizes' ) { + return new \WP_Error( 'rest_disabled', 'REST API endpoint disabled.', [ 'status' => 403 ] ); + } + + return $result; + } + + /** + * Filter debug information to remove Site Health directories. + * + * @param array}> $info Debug information. + * @return array}> Debug information. + */ + public function filter_debug_information( $info ): array { + if ( ! \is_array( $info ) ) { + $info = []; + } + + if ( isset( $info['wp-paths-sizes'] ) ) { + unset( $info['wp-paths-sizes'] ); + } + + return $info; + } +} diff --git a/src/alley/wp/alleyvate/features/class-prevent-framing.php b/src/alley/wp/alleyvate/features/class-prevent-framing.php index f4d58861..40a8b724 100644 --- a/src/alley/wp/alleyvate/features/class-prevent-framing.php +++ b/src/alley/wp/alleyvate/features/class-prevent-framing.php @@ -81,7 +81,7 @@ public static function filter__wp_headers( $headers ): array { if ( ! \in_array( $headers['X-Frame-Options'], [ 'DENY', 'SAMEORIGIN' ], true ) && 0 !== strpos( $headers['X-Frame-Options'], 'ALLOW-FROM' ) ) { _doing_it_wrong( __METHOD__, - sprintf( + \sprintf( /* translators: %s: The value of the X-Frame-Options header. */ esc_html__( 'Invalid value for %s. Must be DENY, SAMEORIGIN, or ALLOW-FROM uri.', 'alley' ), 'X-Frame-Options' diff --git a/src/alley/wp/alleyvate/load.php b/src/alley/wp/alleyvate/load.php index ecaa7832..84ee4da0 100644 --- a/src/alley/wp/alleyvate/load.php +++ b/src/alley/wp/alleyvate/load.php @@ -57,6 +57,10 @@ function load(): void { 'disable_password_change_notification', new Features\Disable_Password_Change_Notification(), ), + new Feature( + 'disable_site_health_directories', + new Features\Disable_Site_Health_Directories(), + ), new Feature( 'disable_sticky_posts', new Features\Disable_Sticky_Posts(), diff --git a/tests/alley/wp/alleyvate/features/test-disable-block-editor-rest-api-preload-paths.php b/tests/alley/wp/alleyvate/features/test-disable-block-editor-rest-api-preload-paths.php index 60576591..ed1581f3 100644 --- a/tests/alley/wp/alleyvate/features/test-disable-block-editor-rest-api-preload-paths.php +++ b/tests/alley/wp/alleyvate/features/test-disable-block-editor-rest-api-preload-paths.php @@ -64,13 +64,13 @@ public function test_disable_block_editor_rest_api_preload_paths(): void { // ph rest_get_route_for_post_type_items( 'wp_block' ) ), add_query_arg( 'context', 'edit', $rest_path ), - sprintf( '/wp/v2/types/%s?context=edit', $post_type ), + \sprintf( '/wp/v2/types/%s?context=edit', $post_type ), '/wp/v2/users/me', [ rest_get_route_for_post_type_items( 'attachment' ), 'OPTIONS' ], [ rest_get_route_for_post_type_items( 'page' ), 'OPTIONS' ], [ rest_get_route_for_post_type_items( 'wp_block' ), 'OPTIONS' ], [ rest_get_route_for_post_type_items( 'wp_template' ), 'OPTIONS' ], - sprintf( '%s/autosaves?context=edit', $rest_path ), + \sprintf( '%s/autosaves?context=edit', $rest_path ), '/wp/v2/settings', [ '/wp/v2/settings', 'OPTIONS' ], ]; @@ -111,13 +111,13 @@ public function check_preloads_paths( mixed $preload_paths, string $rest_path, s $this->assertContains( '/wp/v2/types?context=view', $preload_paths ); $this->assertContains( '/wp/v2/taxonomies?context=view', $preload_paths ); $this->assertContains( add_query_arg( 'context', 'edit', $rest_path ), $preload_paths ); - $this->assertContains( sprintf( '/wp/v2/types/%s?context=edit', $post_type ), $preload_paths ); + $this->assertContains( \sprintf( '/wp/v2/types/%s?context=edit', $post_type ), $preload_paths ); $this->assertContains( '/wp/v2/users/me', $preload_paths ); $this->assertContains( [ rest_get_route_for_post_type_items( 'attachment' ), 'OPTIONS' ], $preload_paths ); $this->assertContains( [ rest_get_route_for_post_type_items( 'page' ), 'OPTIONS' ], $preload_paths ); $this->assertContains( [ rest_get_route_for_post_type_items( 'wp_block' ), 'OPTIONS' ], $preload_paths ); $this->assertContains( [ rest_get_route_for_post_type_items( 'wp_template' ), 'OPTIONS' ], $preload_paths ); - $this->assertContains( sprintf( '%s/autosaves?context=edit', $rest_path ), $preload_paths ); + $this->assertContains( \sprintf( '%s/autosaves?context=edit', $rest_path ), $preload_paths ); $this->assertContains( '/wp/v2/settings', $preload_paths ); $this->assertContains( [ '/wp/v2/settings', 'OPTIONS' ], $preload_paths ); } diff --git a/tests/alley/wp/alleyvate/features/test-disable-comments.php b/tests/alley/wp/alleyvate/features/test-disable-comments.php index 2d64cdf0..e2beecd1 100644 --- a/tests/alley/wp/alleyvate/features/test-disable-comments.php +++ b/tests/alley/wp/alleyvate/features/test-disable-comments.php @@ -187,7 +187,7 @@ public function test_remove_post_type_support(): void { $this->assertTrue( post_type_supports( 'post', 'comments' ) ); // Ensure the comment status is reported as open and the replies link exists out of the box. - $result = rest_do_request( sprintf( '/wp/v2/posts/%d', $post_id ) ); + $result = rest_do_request( \sprintf( '/wp/v2/posts/%d', $post_id ) ); $this->assertSame( 'open', $result->data['comment_status'] ); $this->assertArrayHasKey( 'replies', $result->get_links() ); @@ -198,7 +198,7 @@ public function test_remove_post_type_support(): void { $this->assertFalse( post_type_supports( 'post', 'comments' ) ); // Ensure the comment status is reported as closed and the replies link has been removed. - $result = rest_do_request( sprintf( '/wp/v2/posts/%d', $post_id ) ); + $result = rest_do_request( \sprintf( '/wp/v2/posts/%d', $post_id ) ); $this->assertSame( 'closed', $result->data['comment_status'] ); $this->assertArrayNotHasKey( 'replies', $result->get_links() ); } @@ -222,7 +222,7 @@ public function test_remove_rest_routes(): void { // Ensure comment routes are successful before the plugin is active. $result_generic = rest_do_request( new \WP_REST_Request( 'GET', '/wp/v2/comments' ) ); - $result_specific = rest_do_request( new \WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%d', $comment_id ) ) ); + $result_specific = rest_do_request( new \WP_REST_Request( 'GET', \sprintf( '/wp/v2/comments/%d', $comment_id ) ) ); $this->assertSame( 200, $result_generic->get_status() ); $this->assertSame( 200, $result_specific->get_status() ); @@ -236,7 +236,7 @@ public function test_remove_rest_routes(): void { // Ensure comment routes 404. $result_generic = rest_do_request( new \WP_REST_Request( 'GET', '/wp/v2/comments' ) ); - $result_specific = rest_do_request( new \WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%d', $comment_id ) ) ); + $result_specific = rest_do_request( new \WP_REST_Request( 'GET', \sprintf( '/wp/v2/comments/%d', $comment_id ) ) ); $this->assertSame( 404, $result_generic->get_status() ); $this->assertSame( 404, $result_specific->get_status() ); } diff --git a/tests/alley/wp/alleyvate/features/test-disable-site-health-directories.php b/tests/alley/wp/alleyvate/features/test-disable-site-health-directories.php new file mode 100644 index 00000000..da2f8719 --- /dev/null +++ b/tests/alley/wp/alleyvate/features/test-disable-site-health-directories.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @package wp-alleyvate + */ + +declare( strict_types=1 ); + +namespace Alley\WP\Alleyvate\Features; + +use Mantle\Testing\Concerns\Refresh_Database; +use Mantle\Testkit\Test_Case; + +/** + * Tests for Disable_Site_Health_Directories feature. + */ +final class Test_Disable_Site_Health_Directories extends Test_Case { + use Refresh_Database; + + /** + * Feature instance. + * + * @var Disable_Site_Health_Directories + */ + private Disable_Site_Health_Directories $feature; + + /** + * Set up. + */ + protected function setUp(): void { + parent::setUp(); + + $this->feature = new Disable_Site_Health_Directories(); + } + + /** + * Test that the REST API endpoint is disabled. + */ + public function test_rest_api_disabled(): void { + $this->feature->boot(); + + $this->acting_as( 'administrator' ); + + $this->get( rest_url( 'wp-site-health/v1/directory-sizes' ) )->assertStatus( 403 ); + } +} diff --git a/tests/alley/wp/alleyvate/features/test-disable-trackbacks.php b/tests/alley/wp/alleyvate/features/test-disable-trackbacks.php index 081265d4..7cee3c5a 100644 --- a/tests/alley/wp/alleyvate/features/test-disable-trackbacks.php +++ b/tests/alley/wp/alleyvate/features/test-disable-trackbacks.php @@ -90,7 +90,7 @@ public function test_remove_trackback_support(): void { $this->assertTrue( post_type_supports( 'post', 'trackbacks' ) ); // Ensure the ping status is reported as open out of the box. - $result = rest_do_request( sprintf( '/wp/v2/posts/%d', $post_id ) ); + $result = rest_do_request( \sprintf( '/wp/v2/posts/%d', $post_id ) ); $this->assertSame( 'open', $result->data['ping_status'] ); // Removing post type support happens on 'init', which has already occurred, so we need to call the callback directly. @@ -100,7 +100,7 @@ public function test_remove_trackback_support(): void { $this->assertFalse( post_type_supports( 'post', 'trackbacks' ) ); // Ensure the ping status is reported as closed. - $result = rest_do_request( sprintf( '/wp/v2/posts/%d', $post_id ) ); + $result = rest_do_request( \sprintf( '/wp/v2/posts/%d', $post_id ) ); $this->assertSame( 'closed', $result->data['ping_status'] ); } }