diff --git a/plugins/woocommerce/changelog/fix-48132-ssr-active-plugins b/plugins/woocommerce/changelog/fix-48132-ssr-active-plugins new file mode 100644 index 00000000000..414c485738c --- /dev/null +++ b/plugins/woocommerce/changelog/fix-48132-ssr-active-plugins @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Ensure that active plugins shown in the System Status API endpoint actually exist diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index a5915ec3172..e6d591d8d11 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -17,7 +17,7 @@ use Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Synchro use Automattic\WooCommerce\Internal\Utilities\DatabaseUtil; use Automattic\WooCommerce\Internal\WCCom\ConnectionHelper as WCConnectionHelper; use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods; -use Automattic\WooCommerce\Utilities\OrderUtil; +use Automattic\WooCommerce\Utilities\{ OrderUtil, PluginUtil }; use Automattic\WooCommerce\Internal\Utilities\PluginInstaller; defined( 'ABSPATH' ) || exit; @@ -1283,7 +1283,8 @@ class WC_Install { return; } - if ( in_array( $legacy_api_plugin, wp_get_active_and_valid_plugins(), true ) ) { + $active_valid_plugins = wc_get_container()->get( PluginUtil::class )->get_all_active_valid_plugins(); + if ( in_array( $legacy_api_plugin, $active_valid_plugins, true ) ) { return; } diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-v2-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-v2-controller.php index 8c5caec0bd6..f8c244c7e68 100644 --- a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-v2-controller.php +++ b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-v2-controller.php @@ -13,7 +13,7 @@ defined( 'ABSPATH' ) || exit; use Automattic\WooCommerce\Internal\WCCom\ConnectionHelper; use Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Register as Download_Directories; use Automattic\WooCommerce\Internal\DataStores\Orders\DataSynchronizer as Order_DataSynchronizer; -use Automattic\WooCommerce\Utilities\{ LoggingUtil, OrderUtil }; +use Automattic\WooCommerce\Utilities\{ LoggingUtil, OrderUtil, PluginUtil }; /** * System status controller class. @@ -373,7 +373,41 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller { 'context' => array( 'view' ), 'readonly' => true, 'items' => array( - 'type' => 'string', + 'type' => 'object', + 'properties' => array( + 'plugin' => array( + 'description' => __( 'Plugin basename. The path to the main plugin file relative to the plugins directory.', 'woocommerce' ), + 'type' => 'string', + ), + 'name' => array( + 'description' => __( 'Name of the plugin.', 'woocommerce' ), + 'type' => 'string', + ), + 'version' => array( + 'description' => __( 'Current plugin version.', 'woocommerce' ), + 'type' => 'string', + ), + 'version_latest' => array( + 'description' => __( 'Latest available plugin version.', 'woocommerce' ), + 'type' => 'string', + ), + 'url' => array( + 'description' => __( 'Plugin URL.', 'woocommerce' ), + 'type' => 'string', + ), + 'author_name' => array( + 'description' => __( 'Plugin author name.', 'woocommerce' ), + 'type' => 'string', + ), + 'author_url' => array( + 'description' => __( 'Plugin author URL.', 'woocommerce' ), + 'type' => 'string', + ), + 'network_activated' => array( + 'description' => __( 'Whether the plugin can only be activated network-wide.', 'woocommerce' ), + 'type' => 'boolean', + ), + ), ), ), 'inactive_plugins' => array( @@ -382,7 +416,41 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller { 'context' => array( 'view' ), 'readonly' => true, 'items' => array( - 'type' => 'string', + 'type' => 'object', + 'properties' => array( + 'plugin' => array( + 'description' => __( 'Plugin basename. The path to the main plugin file relative to the plugins directory.', 'woocommerce' ), + 'type' => 'string', + ), + 'name' => array( + 'description' => __( 'Name of the plugin.', 'woocommerce' ), + 'type' => 'string', + ), + 'version' => array( + 'description' => __( 'Current plugin version.', 'woocommerce' ), + 'type' => 'string', + ), + 'version_latest' => array( + 'description' => __( 'Latest available plugin version.', 'woocommerce' ), + 'type' => 'string', + ), + 'url' => array( + 'description' => __( 'Plugin URL.', 'woocommerce' ), + 'type' => 'string', + ), + 'author_name' => array( + 'description' => __( 'Plugin author name.', 'woocommerce' ), + 'type' => 'string', + ), + 'author_url' => array( + 'description' => __( 'Plugin author URL.', 'woocommerce' ), + 'type' => 'string', + ), + 'network_activated' => array( + 'description' => __( 'Whether the plugin can only be activated network-wide.', 'woocommerce' ), + 'type' => 'boolean', + ), + ), ), ), 'dropins_mu_plugins' => array( @@ -1044,15 +1112,10 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller { return array(); } - $active_plugins = (array) get_option( 'active_plugins', array() ); - if ( is_multisite() ) { - $network_activated_plugins = array_keys( get_site_option( 'active_sitewide_plugins', array() ) ); - $active_plugins = array_merge( $active_plugins, $network_activated_plugins ); - } + $active_valid_plugins = wc_get_container()->get( PluginUtil::class )->get_all_active_valid_plugins(); + $active_plugins_data = array(); - $active_plugins_data = array(); - - foreach ( $active_plugins as $plugin ) { + foreach ( $active_valid_plugins as $plugin ) { $data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin ); $active_plugins_data[] = $this->format_plugin_data( $plugin, $data ); } diff --git a/plugins/woocommerce/src/Internal/Utilities/PluginInstaller.php b/plugins/woocommerce/src/Internal/Utilities/PluginInstaller.php index c1985ca81d3..c3acb6ba4a9 100644 --- a/plugins/woocommerce/src/Internal/Utilities/PluginInstaller.php +++ b/plugins/woocommerce/src/Internal/Utilities/PluginInstaller.php @@ -4,7 +4,7 @@ namespace Automattic\WooCommerce\Internal\Utilities; use Automattic\WooCommerce\Internal\RegisterHooksInterface; use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods; -use Automattic\WooCommerce\Utilities\StringUtil; +use Automattic\WooCommerce\Utilities\{ PluginUtil, StringUtil }; /** * This class allows installing a plugin programmatically. @@ -206,7 +206,14 @@ class PluginInstaller implements RegisterHooksInterface { * @return bool True if WooCommerce is installed and active in the current blog, false otherwise. */ private static function woocommerce_is_active_in_current_site(): bool { - return ! empty( array_filter( wp_get_active_and_valid_plugins(), fn( $plugin ) => substr_compare( $plugin, '/woocommerce.php', -strlen( '/woocommerce.php' ) ) === 0 ) ); + $active_valid_plugins = wc_get_container()->get( PluginUtil::class )->get_all_active_valid_plugins(); + + return ! empty( + array_filter( + $active_valid_plugins, + fn( $plugin ) => substr_compare( $plugin, '/woocommerce.php', -strlen( '/woocommerce.php' ) ) === 0 + ) + ); } /** diff --git a/plugins/woocommerce/src/Utilities/PluginUtil.php b/plugins/woocommerce/src/Utilities/PluginUtil.php index e3c229a64fb..d206e0e2e32 100644 --- a/plugins/woocommerce/src/Utilities/PluginUtil.php +++ b/plugins/woocommerce/src/Utilities/PluginUtil.php @@ -78,7 +78,7 @@ class PluginUtil { * Note that the doc block for `wp_get_active_and_valid_plugins` says it returns "Array of paths to plugin files * relative to the plugins directory", but it actually returns absolute paths. * - * @return string[] Array of absolute paths to plugin files. + * @return string[] Array of plugin basenames (paths relative to the plugin directory). */ public function get_all_active_valid_plugins() { $local = wp_get_active_and_valid_plugins(); @@ -91,8 +91,11 @@ class PluginUtil { } $all = array_merge( $local, $network ); + $all = array_unique( $all ); + $all = array_map( 'plugin_basename', $all ); + sort( $all ); - return array_unique( $all ); + return $all; } /** diff --git a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version2/system-status.php b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version2/system-status.php index a8548813c13..58738a44f15 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version2/system-status.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version2/system-status.php @@ -154,9 +154,8 @@ class WC_Tests_REST_System_Status_V2 extends WC_REST_Unit_Test_Case { * @since 3.0.0 */ public function test_get_system_status_info_active_plugins() { - $this->skip_on_php_8_1(); - wp_set_current_user( self::$administrator_user ); + delete_transient( 'wc_system_status_active_plugins' ); $actual_plugins = array( 'hello.php' ); update_option( 'active_plugins', $actual_plugins ); @@ -165,9 +164,20 @@ class WC_Tests_REST_System_Status_V2 extends WC_REST_Unit_Test_Case { $data = $response->get_data(); $plugins = (array) $data['active_plugins']; - $this->assertEquals( 1, count( $plugins ) ); - $this->assertEquals( 'Hello Dolly', $plugins[0]['name'] ); + + $plugin = reset( $plugins ); + $this->assertArrayHasKey( 'plugin', $plugin ); + $this->assertEquals( 'hello.php', $plugin['plugin'] ); + $this->assertArrayHasKey( 'name', $plugin ); + $this->assertEquals( 'Hello Dolly', $plugin['name'] ); + $this->assertArrayHasKey( 'version', $plugin ); + $this->assertArrayHasKey( 'version_latest', $plugin ); + $this->assertArrayHasKey( 'url', $plugin ); + $this->assertArrayHasKey( 'author_name', $plugin ); + $this->assertArrayHasKey( 'author_url', $plugin ); + $this->assertArrayHasKey( 'network_activated', $plugin ); + $this->assertEquals( false, $plugin['network_activated'] ); } /** diff --git a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/system-status.php b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/system-status.php index 54c81fea123..c78cb349c1c 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/system-status.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/system-status.php @@ -183,6 +183,8 @@ class WC_Tests_REST_System_Status extends WC_REST_Unit_Test_Case { */ public function test_get_system_status_info_active_plugins() { wp_set_current_user( self::$administrator_user ); + delete_transient( 'wc_system_status_active_plugins' ); + $actual_plugins = array( 'hello.php' ); update_option( 'active_plugins', $actual_plugins ); $response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/v3/system_status' ) ); @@ -190,9 +192,20 @@ class WC_Tests_REST_System_Status extends WC_REST_Unit_Test_Case { $data = $response->get_data(); $plugins = (array) $data['active_plugins']; - $this->assertEquals( 1, count( $plugins ) ); - $this->assertEquals( 'Hello Dolly', $plugins[0]['name'] ); + + $plugin = reset( $plugins ); + $this->assertArrayHasKey( 'plugin', $plugin ); + $this->assertEquals( 'hello.php', $plugin['plugin'] ); + $this->assertArrayHasKey( 'name', $plugin ); + $this->assertEquals( 'Hello Dolly', $plugin['name'] ); + $this->assertArrayHasKey( 'version', $plugin ); + $this->assertArrayHasKey( 'version_latest', $plugin ); + $this->assertArrayHasKey( 'url', $plugin ); + $this->assertArrayHasKey( 'author_name', $plugin ); + $this->assertArrayHasKey( 'author_url', $plugin ); + $this->assertArrayHasKey( 'network_activated', $plugin ); + $this->assertEquals( false, $plugin['network_activated'] ); } /** diff --git a/plugins/woocommerce/tests/php/src/Utilities/PluginUtilTests.php b/plugins/woocommerce/tests/php/src/Utilities/PluginUtilTests.php index ad0f44c40da..c41cb54094f 100644 --- a/plugins/woocommerce/tests/php/src/Utilities/PluginUtilTests.php +++ b/plugins/woocommerce/tests/php/src/Utilities/PluginUtilTests.php @@ -64,12 +64,12 @@ class PluginUtilTests extends \WC_Unit_Test_Case { if ( is_multisite() ) { $this->assertCount( 2, $active_valid_plugins ); - $this->assertContains( WP_PLUGIN_DIR . '/test3/test3.php', $active_valid_plugins ); + $this->assertContains( 'test3/test3.php', $active_valid_plugins ); } else { $this->assertCount( 1, $active_valid_plugins ); } - $this->assertContains( WP_PLUGIN_DIR . '/test1/test1.php', $active_valid_plugins ); + $this->assertContains( 'test1/test1.php', $active_valid_plugins ); if ( false === $orig_local_plugins ) { delete_option( 'active_plugins' );