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..a0470bfb56688 --- /dev/null +++ b/tests/phpunit/tests/admin/includes/plugin/validateActivePlugins.php @@ -0,0 +1,199 @@ +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(); + + $this->remove_plugin(); + + $dir = WP_PLUGIN_DIR . '/' . self::NO_HEADER_DIR; + $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' ); + + parent::tear_down(); + } + + /** + * Tests that an active plugins option that is not an array is reset. + * + * @ticket 65819 + * + * @dataProvider data_invalid_options + * + * @param mixed $value The option value. + */ + public function test_should_reset_option_that_is_not_an_array( $value ) { + update_option( 'active_plugins', $value ); + + $this->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.' ); + } + + /** + * 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 ); + } + } +}