From 71ead43c61e9f2278d7d1fbb12be48f807b05565 Mon Sep 17 00:00:00 2001 From: Corey McKrill <916023+coreymckrill@users.noreply.github.com> Date: Mon, 5 Jun 2023 12:35:03 -0700 Subject: [PATCH 01/10] WC Tracker: Add enabled features, plugin feature compatibility * Adds a data key with the list of WC features that are currently enabled on the site. * Adds a `feature_compat` key to each plugin that shows its compatibility with each feature (compatibile, incompatible, uncertain) if the plugin is "woocommerce aware", otherwise just an empty array. --- .../woocommerce/includes/class-wc-tracker.php | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce/includes/class-wc-tracker.php b/plugins/woocommerce/includes/class-wc-tracker.php index a93d25fa469..9ae815cd0f6 100644 --- a/plugins/woocommerce/includes/class-wc-tracker.php +++ b/plugins/woocommerce/includes/class-wc-tracker.php @@ -12,8 +12,8 @@ use Automattic\Jetpack\Constants; use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore; +use Automattic\WooCommerce\Utilities\{ FeaturesUtil, OrderUtil, PluginUtil }; use Automattic\WooCommerce\Internal\Utilities\BlocksUtil; -use Automattic\WooCommerce\Utilities\OrderUtil; defined( 'ABSPATH' ) || exit; @@ -153,7 +153,6 @@ class WC_Tracker { $data['inactive_plugins'] = $all_plugins['inactive_plugins']; // Jetpack & WooCommerce Connect. - $data['jetpack_version'] = Constants::is_defined( 'JETPACK__VERSION' ) ? Constants::get_constant( 'JETPACK__VERSION' ) : 'none'; $data['jetpack_connected'] = ( class_exists( 'Jetpack' ) && is_callable( 'Jetpack::is_active' ) && Jetpack::is_active() ) ? 'yes' : 'no'; $data['jetpack_is_staging'] = self::is_jetpack_staging_site() ? 'yes' : 'no'; @@ -177,6 +176,9 @@ class WC_Tracker { // Shipping method info. $data['shipping_methods'] = self::get_active_shipping_methods(); + // Features + $data['enabled_features'] = self::get_enabled_features(); + // Get all WooCommerce options info. $data['settings'] = self::get_all_woocommerce_options_values(); @@ -329,6 +331,10 @@ class WC_Tracker { if ( isset( $v['PluginURI'] ) ) { $formatted['plugin_uri'] = wp_strip_all_tags( $v['PluginURI'] ); } + $formatted['feature_compat'] = array(); + if ( wc_get_container()->get( PluginUtil::class )->is_woocommerce_aware_plugin( $k ) ) { + $formatted['feature_compat'] = array_filter( FeaturesUtil::get_compatible_features_for_plugin( $k ) ); + } if ( in_array( $k, $active_plugins_keys, true ) ) { // Remove active plugins from list so we can show active and inactive separately. unset( $plugins[ $k ] ); @@ -904,6 +910,23 @@ class WC_Tracker { return $active_methods; } + /** + * Get an array of slugs for WC features that are enabled on the site. + * + * @return string[] + */ + private static function get_enabled_features() { + $all_features = FeaturesUtil::get_features( true, true ); + $enabled_features = array_filter( + $all_features, + function( $feature ) { + return $feature['is_enabled']; + } + ); + + return array_keys( $enabled_features ); + } + /** * Get all options starting with woocommerce_ prefix. * From 2a3b5bfe64febea374373439e64792d478349b2f Mon Sep 17 00:00:00 2001 From: Corey McKrill <916023+coreymckrill@users.noreply.github.com> Date: Mon, 5 Jun 2023 12:39:59 -0700 Subject: [PATCH 02/10] Add changelog file --- plugins/woocommerce/changelog/try-wc-tracker-feature-compat | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/try-wc-tracker-feature-compat diff --git a/plugins/woocommerce/changelog/try-wc-tracker-feature-compat b/plugins/woocommerce/changelog/try-wc-tracker-feature-compat new file mode 100644 index 00000000000..6247d668528 --- /dev/null +++ b/plugins/woocommerce/changelog/try-wc-tracker-feature-compat @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Adds info about features and plugin compatibility to the data collected by WC Tracker From f0f586aa5ebf823c98af5e936da7efec56575851 Mon Sep 17 00:00:00 2001 From: Corey McKrill <916023+coreymckrill@users.noreply.github.com> Date: Mon, 5 Jun 2023 12:40:55 -0700 Subject: [PATCH 03/10] phpcs cleanup --- plugins/woocommerce/includes/class-wc-tracker.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/includes/class-wc-tracker.php b/plugins/woocommerce/includes/class-wc-tracker.php index 9ae815cd0f6..599a3fdb6d4 100644 --- a/plugins/woocommerce/includes/class-wc-tracker.php +++ b/plugins/woocommerce/includes/class-wc-tracker.php @@ -176,7 +176,7 @@ class WC_Tracker { // Shipping method info. $data['shipping_methods'] = self::get_active_shipping_methods(); - // Features + // Features. $data['enabled_features'] = self::get_enabled_features(); // Get all WooCommerce options info. From 318a133e1785ba73367898bdb4286f49ccf20a5f Mon Sep 17 00:00:00 2001 From: Corey McKrill <916023+coreymckrill@users.noreply.github.com> Date: Wed, 7 Jun 2023 11:08:01 -0700 Subject: [PATCH 04/10] Rename feature_compat to feature_compatibility --- plugins/woocommerce/includes/class-wc-tracker.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce/includes/class-wc-tracker.php b/plugins/woocommerce/includes/class-wc-tracker.php index 599a3fdb6d4..f51065c2846 100644 --- a/plugins/woocommerce/includes/class-wc-tracker.php +++ b/plugins/woocommerce/includes/class-wc-tracker.php @@ -331,9 +331,9 @@ class WC_Tracker { if ( isset( $v['PluginURI'] ) ) { $formatted['plugin_uri'] = wp_strip_all_tags( $v['PluginURI'] ); } - $formatted['feature_compat'] = array(); + $formatted['feature_compatibility'] = array(); if ( wc_get_container()->get( PluginUtil::class )->is_woocommerce_aware_plugin( $k ) ) { - $formatted['feature_compat'] = array_filter( FeaturesUtil::get_compatible_features_for_plugin( $k ) ); + $formatted['feature_compatibility'] = array_filter( FeaturesUtil::get_compatible_features_for_plugin( $k ) ); } if ( in_array( $k, $active_plugins_keys, true ) ) { // Remove active plugins from list so we can show active and inactive separately. From 50f4e63ddbff492e37c9b7ef4d2fa26ae7fd6563 Mon Sep 17 00:00:00 2001 From: Corey McKrill <916023+coreymckrill@users.noreply.github.com> Date: Fri, 9 Jun 2023 17:14:48 -0700 Subject: [PATCH 05/10] Add unit tests --- .../tests/legacy/mockable-functions.php | 1 + .../php/includes/class-wc-tracker-test.php | 99 +++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/plugins/woocommerce/tests/legacy/mockable-functions.php b/plugins/woocommerce/tests/legacy/mockable-functions.php index 974619882e0..45ae0aabc25 100644 --- a/plugins/woocommerce/tests/legacy/mockable-functions.php +++ b/plugins/woocommerce/tests/legacy/mockable-functions.php @@ -10,6 +10,7 @@ return array( 'current_user_can', 'get_bloginfo', + 'get_plugins', 'get_woocommerce_currencies', 'get_woocommerce_currency_symbol', 'wc_get_price_excluding_tax', diff --git a/plugins/woocommerce/tests/php/includes/class-wc-tracker-test.php b/plugins/woocommerce/tests/php/includes/class-wc-tracker-test.php index 7f299540c83..a5260da9c18 100644 --- a/plugins/woocommerce/tests/php/includes/class-wc-tracker-test.php +++ b/plugins/woocommerce/tests/php/includes/class-wc-tracker-test.php @@ -5,6 +5,10 @@ * @package WooCommerce\Tests\WC_Tracker. */ +use Automattic\WooCommerce\Internal\Features\FeaturesController; +use Automattic\WooCommerce\Utilities\PluginUtil; +use Automattic\WooCommerce\Testing\Tools\CodeHacking\Hacks\FunctionsMockerHack; + // phpcs:disable Squiz.Classes.ClassFileName.NoMatch, Squiz.Classes.ValidClassName.NotCamelCaps -- Backward compatibility. /** * Class WC_Tracker_Test @@ -68,6 +72,92 @@ class WC_Tracker_Test extends \WC_Unit_Test_Case { $this->assertEquals( 'no', $tracking_data['wc_admin_disabled'] ); } + /** + * @testDox Test the features compatibility data for plugin tracking data. + */ + public function test_get_tracking_data_plugin_feature_compatibility() { + $legacy_mocks = array( + 'get_plugins' => function() { + return array( + 'plugin1' => array( + 'Name' => 'Plugin 1' + ), + 'plugin2' => array( + 'Name' => 'Plugin 2', + ), + 'plugin3' => array( + 'Name' => 'Plugin 3', + ), + ); + } + ); + FunctionsMockerHack::add_function_mocks( $legacy_mocks ); + + update_option( 'active_plugins', array( 'plugin1', 'plugin2' ) ); + + $pluginutil_mock = new class() extends PluginUtil { + public function is_woocommerce_aware_plugin( $plugin ): bool { + if ( 'plugin1' === $plugin ) { + return false; + } + + return true; + } + }; + + $featurescontroller_mock = new class() extends FeaturesController { + public function get_compatible_features_for_plugin( string $plugin_name, bool $enabled_features_only = false ): array { + $compat = array(); + switch( $plugin_name ) { + case 'plugin2': + $compat = array( + 'compatible' => array( 'feature1' ), + 'incompatible' => array( 'feature2' ), + 'uncertain' => array( 'feature3' ), + ); + break; + case 'plugin3': + $compat = array( + 'compatible' => array( 'feature2' ), + 'incompatible' => array(), + 'uncertain' => array( 'feature1', 'feature3' ), + ); + break; + } + + return $compat; + } + }; + + $container = wc_get_container(); + $container->get( PluginUtil::class ); + $container->reset_all_resolved(); + $container->replace( PluginUtil::class, $pluginutil_mock ); + $container->replace( FeaturesController::class, $featurescontroller_mock ); + + $tracking_data = WC_Tracker::get_tracking_data(); + + $this->assertEquals( + array(), + $tracking_data['active_plugins']['plugin1']['feature_compatibility'] + ); + $this->assertEquals( + array( + 'compatible' => array( 'feature1' ), + 'incompatible' => array( 'feature2' ), + 'uncertain' => array( 'feature3' ), + ), + $tracking_data['active_plugins']['plugin2']['feature_compatibility'] + ); + $this->assertEquals( + array( + 'compatible' => array( 'feature2' ), + 'uncertain' => array( 'feature1', 'feature3' ), + ), + $tracking_data['inactive_plugins']['plugin3']['feature_compatibility'] + ); + } + /** * @testDox Test orders tracking data. */ @@ -118,4 +208,13 @@ class WC_Tracker_Test extends \WC_Unit_Test_Case { $this->assertEquals( ( $order_count / count( $created_via_entries ) ), $order_data['created_via'][ $created_via_entry ] ); } } + + /** + * @testDox Test enabled features tracking data. + */ + public function test_get_tracking_data_enabled_features() { + $tracking_data = WC_Tracker::get_tracking_data(); + + $this->assertIsArray( $tracking_data['enabled_features'] ); + } } From d2a9feda2abd42654b5e93cfe084f15c0debc932 Mon Sep 17 00:00:00 2001 From: Corey McKrill <916023+coreymckrill@users.noreply.github.com> Date: Fri, 9 Jun 2023 17:26:38 -0700 Subject: [PATCH 06/10] phpcs cleanup --- .../tests/php/includes/class-wc-tracker-test.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/plugins/woocommerce/tests/php/includes/class-wc-tracker-test.php b/plugins/woocommerce/tests/php/includes/class-wc-tracker-test.php index a5260da9c18..66fa9bf9f94 100644 --- a/plugins/woocommerce/tests/php/includes/class-wc-tracker-test.php +++ b/plugins/woocommerce/tests/php/includes/class-wc-tracker-test.php @@ -80,7 +80,7 @@ class WC_Tracker_Test extends \WC_Unit_Test_Case { 'get_plugins' => function() { return array( 'plugin1' => array( - 'Name' => 'Plugin 1' + 'Name' => 'Plugin 1', ), 'plugin2' => array( 'Name' => 'Plugin 2', @@ -89,13 +89,14 @@ class WC_Tracker_Test extends \WC_Unit_Test_Case { 'Name' => 'Plugin 3', ), ); - } + }, ); FunctionsMockerHack::add_function_mocks( $legacy_mocks ); update_option( 'active_plugins', array( 'plugin1', 'plugin2' ) ); $pluginutil_mock = new class() extends PluginUtil { + // phpcs:ignore Squiz.Commenting.FunctionComment.Missing public function is_woocommerce_aware_plugin( $plugin ): bool { if ( 'plugin1' === $plugin ) { return false; @@ -106,9 +107,10 @@ class WC_Tracker_Test extends \WC_Unit_Test_Case { }; $featurescontroller_mock = new class() extends FeaturesController { + // phpcs:ignore Squiz.Commenting.FunctionComment.Missing public function get_compatible_features_for_plugin( string $plugin_name, bool $enabled_features_only = false ): array { $compat = array(); - switch( $plugin_name ) { + switch ( $plugin_name ) { case 'plugin2': $compat = array( 'compatible' => array( 'feature1' ), @@ -151,8 +153,8 @@ class WC_Tracker_Test extends \WC_Unit_Test_Case { ); $this->assertEquals( array( - 'compatible' => array( 'feature2' ), - 'uncertain' => array( 'feature1', 'feature3' ), + 'compatible' => array( 'feature2' ), + 'uncertain' => array( 'feature1', 'feature3' ), ), $tracking_data['inactive_plugins']['plugin3']['feature_compatibility'] ); From 7cbebac61c7790310721b89819f1c55137b77714 Mon Sep 17 00:00:00 2001 From: Corey McKrill <916023+coreymckrill@users.noreply.github.com> Date: Mon, 12 Jun 2023 14:49:22 -0700 Subject: [PATCH 07/10] Ensure test mocks don't pollute other tests --- .../tests/php/includes/class-wc-tracker-test.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/woocommerce/tests/php/includes/class-wc-tracker-test.php b/plugins/woocommerce/tests/php/includes/class-wc-tracker-test.php index 66fa9bf9f94..a782f518376 100644 --- a/plugins/woocommerce/tests/php/includes/class-wc-tracker-test.php +++ b/plugins/woocommerce/tests/php/includes/class-wc-tracker-test.php @@ -132,7 +132,7 @@ class WC_Tracker_Test extends \WC_Unit_Test_Case { }; $container = wc_get_container(); - $container->get( PluginUtil::class ); + $container->get( PluginUtil::class ); // Ensure that the class is loaded. $container->reset_all_resolved(); $container->replace( PluginUtil::class, $pluginutil_mock ); $container->replace( FeaturesController::class, $featurescontroller_mock ); @@ -158,6 +158,10 @@ class WC_Tracker_Test extends \WC_Unit_Test_Case { ), $tracking_data['inactive_plugins']['plugin3']['feature_compatibility'] ); + + // Reset the mocked classes so they don't affect other tests. + $container->replace( PluginUtil::class, PluginUtil::class ); + $container->replace( FeaturesController::class, FeaturesController::class ); } /** From 9db19e48485f1cf0d2eafca5ab6b5d5a32771c8e Mon Sep 17 00:00:00 2001 From: Corey McKrill <916023+coreymckrill@users.noreply.github.com> Date: Tue, 13 Jun 2023 15:47:27 -0700 Subject: [PATCH 08/10] Tweak unit tests --- .../woocommerce/tests/php/includes/class-wc-tracker-test.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/woocommerce/tests/php/includes/class-wc-tracker-test.php b/plugins/woocommerce/tests/php/includes/class-wc-tracker-test.php index a782f518376..0a9c5fb1368 100644 --- a/plugins/woocommerce/tests/php/includes/class-wc-tracker-test.php +++ b/plugins/woocommerce/tests/php/includes/class-wc-tracker-test.php @@ -92,6 +92,7 @@ class WC_Tracker_Test extends \WC_Unit_Test_Case { }, ); FunctionsMockerHack::add_function_mocks( $legacy_mocks ); + $this->register_legacy_proxy_function_mocks( $legacy_mocks ); update_option( 'active_plugins', array( 'plugin1', 'plugin2' ) ); @@ -161,7 +162,10 @@ class WC_Tracker_Test extends \WC_Unit_Test_Case { // Reset the mocked classes so they don't affect other tests. $container->replace( PluginUtil::class, PluginUtil::class ); + $container->get( PluginUtil::class ); $container->replace( FeaturesController::class, FeaturesController::class ); + + $this->reset_legacy_proxy_mocks(); } /** From 1804d44be11c83d37a3c637d3e828079b5ccbaea Mon Sep 17 00:00:00 2001 From: Corey McKrill <916023+coreymckrill@users.noreply.github.com> Date: Wed, 14 Jun 2023 10:49:50 -0700 Subject: [PATCH 09/10] Remove feature compatibility unit test The mocks in this test were affecting other unit tests for the PluginUtil class, but only when run in GitHub actions (the tests were working fine locally). Rather than hold up this PR, I've created issue #38720 as a future task to add the unit test back in once we've figured out if there's something buggy happening with dependency injection. --- .../php/includes/class-wc-tracker-test.php | 100 ------------------ 1 file changed, 100 deletions(-) diff --git a/plugins/woocommerce/tests/php/includes/class-wc-tracker-test.php b/plugins/woocommerce/tests/php/includes/class-wc-tracker-test.php index 0a9c5fb1368..41da24722d7 100644 --- a/plugins/woocommerce/tests/php/includes/class-wc-tracker-test.php +++ b/plugins/woocommerce/tests/php/includes/class-wc-tracker-test.php @@ -5,10 +5,6 @@ * @package WooCommerce\Tests\WC_Tracker. */ -use Automattic\WooCommerce\Internal\Features\FeaturesController; -use Automattic\WooCommerce\Utilities\PluginUtil; -use Automattic\WooCommerce\Testing\Tools\CodeHacking\Hacks\FunctionsMockerHack; - // phpcs:disable Squiz.Classes.ClassFileName.NoMatch, Squiz.Classes.ValidClassName.NotCamelCaps -- Backward compatibility. /** * Class WC_Tracker_Test @@ -72,102 +68,6 @@ class WC_Tracker_Test extends \WC_Unit_Test_Case { $this->assertEquals( 'no', $tracking_data['wc_admin_disabled'] ); } - /** - * @testDox Test the features compatibility data for plugin tracking data. - */ - public function test_get_tracking_data_plugin_feature_compatibility() { - $legacy_mocks = array( - 'get_plugins' => function() { - return array( - 'plugin1' => array( - 'Name' => 'Plugin 1', - ), - 'plugin2' => array( - 'Name' => 'Plugin 2', - ), - 'plugin3' => array( - 'Name' => 'Plugin 3', - ), - ); - }, - ); - FunctionsMockerHack::add_function_mocks( $legacy_mocks ); - $this->register_legacy_proxy_function_mocks( $legacy_mocks ); - - update_option( 'active_plugins', array( 'plugin1', 'plugin2' ) ); - - $pluginutil_mock = new class() extends PluginUtil { - // phpcs:ignore Squiz.Commenting.FunctionComment.Missing - public function is_woocommerce_aware_plugin( $plugin ): bool { - if ( 'plugin1' === $plugin ) { - return false; - } - - return true; - } - }; - - $featurescontroller_mock = new class() extends FeaturesController { - // phpcs:ignore Squiz.Commenting.FunctionComment.Missing - public function get_compatible_features_for_plugin( string $plugin_name, bool $enabled_features_only = false ): array { - $compat = array(); - switch ( $plugin_name ) { - case 'plugin2': - $compat = array( - 'compatible' => array( 'feature1' ), - 'incompatible' => array( 'feature2' ), - 'uncertain' => array( 'feature3' ), - ); - break; - case 'plugin3': - $compat = array( - 'compatible' => array( 'feature2' ), - 'incompatible' => array(), - 'uncertain' => array( 'feature1', 'feature3' ), - ); - break; - } - - return $compat; - } - }; - - $container = wc_get_container(); - $container->get( PluginUtil::class ); // Ensure that the class is loaded. - $container->reset_all_resolved(); - $container->replace( PluginUtil::class, $pluginutil_mock ); - $container->replace( FeaturesController::class, $featurescontroller_mock ); - - $tracking_data = WC_Tracker::get_tracking_data(); - - $this->assertEquals( - array(), - $tracking_data['active_plugins']['plugin1']['feature_compatibility'] - ); - $this->assertEquals( - array( - 'compatible' => array( 'feature1' ), - 'incompatible' => array( 'feature2' ), - 'uncertain' => array( 'feature3' ), - ), - $tracking_data['active_plugins']['plugin2']['feature_compatibility'] - ); - $this->assertEquals( - array( - 'compatible' => array( 'feature2' ), - 'uncertain' => array( 'feature1', 'feature3' ), - ), - $tracking_data['inactive_plugins']['plugin3']['feature_compatibility'] - ); - - // Reset the mocked classes so they don't affect other tests. - $container->replace( PluginUtil::class, PluginUtil::class ); - $container->get( PluginUtil::class ); - $container->replace( FeaturesController::class, FeaturesController::class ); - - $this->reset_legacy_proxy_mocks(); - } - /** * @testDox Test orders tracking data. */ From cf3346e7397df44620f0f46f2cf39f1e7568964a Mon Sep 17 00:00:00 2001 From: Corey McKrill <916023+coreymckrill@users.noreply.github.com> Date: Wed, 14 Jun 2023 10:56:22 -0700 Subject: [PATCH 10/10] Test removal cleanup --- plugins/woocommerce/tests/legacy/mockable-functions.php | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/woocommerce/tests/legacy/mockable-functions.php b/plugins/woocommerce/tests/legacy/mockable-functions.php index 45ae0aabc25..974619882e0 100644 --- a/plugins/woocommerce/tests/legacy/mockable-functions.php +++ b/plugins/woocommerce/tests/legacy/mockable-functions.php @@ -10,7 +10,6 @@ return array( 'current_user_can', 'get_bloginfo', - 'get_plugins', 'get_woocommerce_currencies', 'get_woocommerce_currency_symbol', 'wc_get_price_excluding_tax',