From 4332714a67caacb9fdb8b29eb93bb7951da97132 Mon Sep 17 00:00:00 2001 From: Huzaifa Al Mesbah Date: Wed, 21 Oct 2026 10:10:28 +0600 Subject: [PATCH 1/2] Tests: Add unit test coverage for validate_active_plugins(). --- .../includes/plugin/validateActivePlugins.php | 190 ++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 tests/phpunit/tests/admin/includes/plugin/validateActivePlugins.php diff --git a/tests/phpunit/tests/admin/includes/plugin/validateActivePlugins.php b/tests/phpunit/tests/admin/includes/plugin/validateActivePlugins.php new file mode 100644 index 0000000000000..f4431df4bc446 --- /dev/null +++ b/tests/phpunit/tests/admin/includes/plugin/validateActivePlugins.php @@ -0,0 +1,190 @@ +user->create( array( 'role' => 'administrator' ) ); + + if ( is_multisite() ) { + grant_super_admin( self::$super_admin_id ); + } + } + + /** + * Revokes the super admin grant before the fixture users are deleted. + */ + public static function wpTearDownAfterClass() { + if ( is_multisite() ) { + revoke_super_admin( self::$super_admin_id ); + } + } + + /** + * Creates a plugin file without a plugin header and resets the active plugins. + */ + public function set_up() { + parent::set_up(); + + $dir = WP_PLUGIN_DIR . '/' . self::NO_HEADER_DIR; + $this->assertDirectoryDoesNotExist( $dir, 'The test directory should not exist yet.' ); + $this->assertTrue( mkdir( $dir ), 'The test directory should be created.' ); + $this->assertNotFalse( file_put_contents( $dir . '/functions.php', "assertSame( array(), validate_active_plugins(), 'No invalid plugins should be returned.' ); + $this->assertSame( array(), get_option( 'active_plugins' ), 'The option should be reset.' ); + } + + /** + * Data provider. + * + * @return array[] + */ + public static function data_invalid_options() { + return array( + 'string' => array( 'hello.php' ), + 'integer' => array( 1 ), + ); + } + + /** + * Tests that valid active plugins are kept. + * + * @ticket 65819 + */ + public function test_should_keep_valid_plugins() { + update_option( 'active_plugins', array( 'hello.php', 'internationalized-plugin.php' ) ); + + $this->assertSame( array(), validate_active_plugins(), 'No invalid plugins should be returned.' ); + $this->assertSame( array( 'hello.php', 'internationalized-plugin.php' ), get_option( 'active_plugins' ), 'The plugins should stay active.' ); + } + + /** + * Tests that invalid active plugins are reported and deactivated silently. + * + * @ticket 65819 + */ + public function test_should_report_and_deactivate_invalid_plugins() { + $no_header = self::NO_HEADER_DIR . '/functions.php'; + update_option( 'active_plugins', array( 'hello.php', '../outside.php', 'missing/missing.php', $no_header ) ); + + $deactivate = new MockAction(); + add_action( 'deactivate_plugin', array( $deactivate, 'action' ) ); + + $invalid = validate_active_plugins(); + + $this->assertSame( array( '../outside.php', 'missing/missing.php', $no_header ), array_keys( $invalid ), 'Each invalid plugin should be reported.' ); + $this->assertSame( + array( + '../outside.php' => array( 'plugin_invalid', 'Invalid plugin path.' ), + 'missing/missing.php' => array( 'plugin_not_found', 'Plugin file does not exist.' ), + $no_header => array( 'no_plugin_header', 'The plugin does not have a valid header.' ), + ), + array_map( + static function ( $error ) { + return array( $error->get_error_code(), $error->get_error_message() ); + }, + $invalid + ), + 'Each invalid plugin should have its error.' + ); + $this->assertSame( array( 'hello.php' ), array_values( get_option( 'active_plugins' ) ), 'Only the valid plugin should stay active.' ); + $this->assertSame( 0, $deactivate->get_call_count(), 'Invalid plugins should be deactivated silently.' ); + } + + /** + * Tests that network-active plugins are validated for a user who can manage network plugins. + * + * @ticket 65819 + * + * @group ms-required + */ + public function test_should_validate_network_plugins_for_network_admin() { + wp_set_current_user( self::$super_admin_id ); + + update_option( 'active_plugins', array() ); + update_site_option( + 'active_sitewide_plugins', + array( + 'hello.php' => 1000, + 'missing/missing.php' => 1000, + ) + ); + + $this->assertSame( array( 'missing/missing.php' ), array_keys( validate_active_plugins() ), 'The invalid network plugin should be reported.' ); + $this->assertSame( array( 'hello.php' => 1000 ), get_site_option( 'active_sitewide_plugins' ), 'The invalid network plugin should be deactivated.' ); + } + + /** + * Tests that network-active plugins are not validated for other users. + * + * @ticket 65819 + * + * @group ms-required + */ + public function test_should_not_validate_network_plugins_for_other_users() { + update_option( 'active_plugins', array() ); + update_site_option( 'active_sitewide_plugins', array( 'missing/missing.php' => 1000 ) ); + + $this->assertSame( array(), validate_active_plugins(), 'No plugins should be validated.' ); + $this->assertSame( array( 'missing/missing.php' => 1000 ), get_site_option( 'active_sitewide_plugins' ), 'The network plugins should not change.' ); + } +} From 7d99d5d48826102d8d3386e033176852d79e701f Mon Sep 17 00:00:00 2001 From: Huzaifa Al Mesbah Date: Wed, 21 Oct 2026 12:12:45 +0600 Subject: [PATCH 2/2] Tests: Remove a leftover test plugin before creating it in the validate_active_plugins() tests. --- .../includes/plugin/validateActivePlugins.php | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/tests/phpunit/tests/admin/includes/plugin/validateActivePlugins.php b/tests/phpunit/tests/admin/includes/plugin/validateActivePlugins.php index f4431df4bc446..a0470bfb56688 100644 --- a/tests/phpunit/tests/admin/includes/plugin/validateActivePlugins.php +++ b/tests/phpunit/tests/admin/includes/plugin/validateActivePlugins.php @@ -53,8 +53,9 @@ public static function wpTearDownAfterClass() { public function set_up() { parent::set_up(); + $this->remove_plugin(); + $dir = WP_PLUGIN_DIR . '/' . self::NO_HEADER_DIR; - $this->assertDirectoryDoesNotExist( $dir, 'The test directory should not exist yet.' ); $this->assertTrue( mkdir( $dir ), 'The test directory should be created.' ); $this->assertNotFalse( file_put_contents( $dir . '/functions.php', "remove_plugin(); wp_cache_delete( 'plugins', 'plugins' ); @@ -187,4 +184,16 @@ public function test_should_not_validate_network_plugins_for_other_users() { $this->assertSame( array(), validate_active_plugins(), 'No plugins should be validated.' ); $this->assertSame( array( 'missing/missing.php' => 1000 ), get_site_option( 'active_sitewide_plugins' ), 'The network plugins should not change.' ); } + + /** + * Removes the plugin file without a header, including one left over by an interrupted run. + */ + private function remove_plugin() { + $dir = WP_PLUGIN_DIR . '/' . self::NO_HEADER_DIR; + + if ( is_dir( $dir ) ) { + $this->rmdir( $dir ); + rmdir( $dir ); + } + } }