diff --git a/plugins/woocommerce/changelog/pr-48169 b/plugins/woocommerce/changelog/pr-48169 new file mode 100644 index 00000000000..dfbd74c6f0e --- /dev/null +++ b/plugins/woocommerce/changelog/pr-48169 @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Improvements in the handling of feature compatibility for plugins diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/CLIRunner.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/CLIRunner.php index 1a421347627..9e15258c336 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/CLIRunner.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/CLIRunner.php @@ -7,6 +7,7 @@ use Automattic\WooCommerce\Internal\DataStores\Orders\DataSynchronizer; use Automattic\WooCommerce\Internal\DataStores\Orders\LegacyDataHandler; use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore; use Automattic\WooCommerce\Internal\Features\FeaturesController; +use Automattic\WooCommerce\Utilities\PluginUtil; use WP_CLI; /** @@ -76,6 +77,7 @@ class CLIRunner { WP_CLI::add_command( 'wc hpos status', array( $this, 'status' ) ); WP_CLI::add_command( 'wc hpos diff', array( $this, 'diff' ) ); WP_CLI::add_command( 'wc hpos backfill', array( $this, 'backfill' ) ); + WP_CLI::add_command( 'wc hpos compatibility-info', array( $this, 'compatibility_info' ) ); WP_CLI::add_command( 'wc hpos compatibility-mode enable', array( $this, 'enable_compat_mode' ) ); WP_CLI::add_command( 'wc hpos compatibility-mode disable', array( $this, 'disable_compat_mode' ) ); @@ -736,6 +738,9 @@ ORDER BY $meta_table.order_id ASC, $meta_table.meta_key ASC; * default: false * --- * + * [--ignore-plugin-compatibility] + * : Enable even if there are active plugins that are incompatible with HPOS. + * * ### EXAMPLES * * # Enable HPOS on new shops. @@ -750,8 +755,9 @@ ORDER BY $meta_table.order_id ASC, $meta_table.meta_key ASC; $assoc_args = wp_parse_args( $assoc_args, array( - 'for-new-shop' => false, - 'with-sync' => false, + 'for-new-shop' => false, + 'with-sync' => false, + 'ignore-plugin-compatibility' => false, ) ); @@ -763,12 +769,18 @@ ORDER BY $meta_table.order_id ASC, $meta_table.meta_key ASC; WP_CLI::error( __( '[Failed] This is not a new shop, but --for-new-shop flag was passed.', 'woocommerce' ) ); } + $container = wc_get_container(); /** Feature controller instance @var FeaturesController $feature_controller */ - $feature_controller = wc_get_container()->get( FeaturesController::class ); - $plugin_info = $feature_controller->get_compatible_plugins_for_feature( 'custom_order_tables', true ); - if ( count( array_merge( $plugin_info['uncertain'], $plugin_info['incompatible'] ) ) > 0 ) { - WP_CLI::warning( __( '[Failed] Some installed plugins are incompatible. Please review the plugins by going to WooCommerce > Settings > Advanced > Features and see the "Order data storage" section.', 'woocommerce' ) ); - $enable_hpos = false; + $feature_controller = $container->get( FeaturesController::class ); + if ( ! $assoc_args['ignore-plugin-compatibility'] ) { + $compatibility_info = $feature_controller->get_compatible_plugins_for_feature( 'custom_order_tables', true ); + /** Plugin util instance @var PluginUtil $plugin_util */ + $plugin_util = $container->get( PluginUtil::class ); + $incompatibles = $plugin_util->get_items_considered_incompatible( 'custom_order_tables', $compatibility_info ); + if ( count( $incompatibles ) > 0 ) { + WP_CLI::warning( __( '[Failed] Some installed plugins are incompatible. Please review the plugins by going to WooCommerce > Settings > Advanced > Features and see the "Order data storage" section.', 'woocommerce' ) ); + $enable_hpos = false; + } } /** DataSynchronizer instance @var DataSynchronizer $data_synchronizer */ @@ -1213,6 +1225,104 @@ ORDER BY $meta_table.order_id ASC, $meta_table.meta_key ASC; ); } + /** + * Show the list of WooCommerce-aware plugins known to be compatible, incompatible or without compatibility declaration for HPOS. Note that inactive plugins will always be listed in the "uncertain" list. + * + * [--include-inactive] + * : Include inactive plugins in the list. + * + * [--display-filenames] + * : Print plugin file names instead of plugin names. + * + * @since 9.1.0 + * + * @param array $args Positional arguments passed to the command. + * @param array $assoc_args Associative arguments (options) passed to the command. + */ + public function compatibility_info( array $args = array(), array $assoc_args = array() ): void { + $container = wc_get_container(); + $feature_controller = $container->get( FeaturesController::class ); + $plugin_info = $feature_controller->get_compatible_plugins_for_feature( 'custom_order_tables', ! ( (bool) ( $assoc_args['include-inactive'] ?? null ) ) ); + $display_filenames = (bool) ( $assoc_args['display-filenames'] ?? null ); + + $compatibles = $this->get_printable_plugin_names( $plugin_info['compatible'], $display_filenames ); + $compatibles_count = count( $compatibles ); + $this->log( + sprintf( + // translators: $1$d = plugins count, %2$s = colon (if list follows) or empty. + _n( "\n%%C%1\$d%%n compatible plugin found%2\$s", "\n%%C%1\$d%%n compatible plugins found%2\$s", $compatibles_count, 'woocommerce' ), + $compatibles_count, + $compatibles_count > 0 ? ":\n" : '' + ) + ); + $this->print_plugin_names( $compatibles ); + + $incompatibles = $this->get_printable_plugin_names( $plugin_info['incompatible'], $display_filenames ); + $incompatibles_count = count( $incompatibles ); + $this->log( + sprintf( + // translators: $1$d = plugins count, %2$s = colon (if list follows) or empty. + _n( "\n%%C%1\$d%%n incompatible plugin found%2\$s", "\n%%C%1\$d%%n incompatible plugins found%2\$s", $incompatibles_count, 'woocommerce' ), + $incompatibles_count, + $incompatibles_count > 0 ? ":\n" : '' + ) + ); + $this->print_plugin_names( $incompatibles ); + + $uncertain = $this->get_printable_plugin_names( $plugin_info['uncertain'], $display_filenames ); + $uncertain_count = count( $uncertain ); + $this->log( + sprintf( + // translators: $1$d = plugins count, %2$s = colon (if list follows) or empty. + _n( "\n%%C%1\$d%%n uncertain plugin found%2\$s", "\n%%C%1\$d%%n uncertain plugins found%2\$s", $uncertain_count, 'woocommerce' ), + $uncertain_count, + $uncertain_count > 0 ? ":\n" : '' + ) + ); + $this->print_plugin_names( $uncertain ); + } + + /** + * Get the printable names for a set of plugins given their file names. + * + * @param array $plugins The plugin file names. + * @param bool $display_filenames True to simply return the sorted list of plugin file names. + * @return array A sorted array of plugin names or file names. + */ + private function get_printable_plugin_names( array $plugins, bool $display_filenames ): array { + if ( $display_filenames ) { + sort( $plugins ); + return $plugins; + } + + $plugin_names = array_map( + fn( $plugin_file ) => get_plugin_data( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . $plugin_file, false )['Name'] ?? $plugin_file, + $plugins + ); + sort( $plugin_names ); + return $plugin_names; + } + + /** + * Print a list of plugin names. + * + * @param array $plugins The names to print. + */ + private function print_plugin_names( array $plugins ): void { + foreach ( $plugins as $plugin_file ) { + $this->log( ' ' . $plugin_file ); + } + } + + /** + * Show a log message using the WP_CLI text colorization feature. + * + * @param string $text Text to show. + */ + private function log( string $text ) { + WP_CLI::log( WP_CLI::colorize( $text ) ); + } + /** * Enables compatibility mode, which keeps the HPOS and posts datastore in sync. * diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/CustomOrdersTableController.php b/plugins/woocommerce/src/Internal/DataStores/Orders/CustomOrdersTableController.php index f3fddfae1c3..3584b9106db 100644 --- a/plugins/woocommerce/src/Internal/DataStores/Orders/CustomOrdersTableController.php +++ b/plugins/woocommerce/src/Internal/DataStores/Orders/CustomOrdersTableController.php @@ -501,12 +501,13 @@ class CustomOrdersTableController { */ private function add_feature_definition( $features_controller ) { $definition = array( - 'option_key' => self::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION, - 'is_experimental' => false, - 'enabled_by_default' => false, - 'order' => 50, - 'setting' => $this->get_hpos_setting_for_feature(), - 'additional_settings' => array( + 'option_key' => self::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION, + 'is_experimental' => false, + 'enabled_by_default' => false, + 'order' => 50, + 'setting' => $this->get_hpos_setting_for_feature(), + 'plugins_are_incompatible_by_default' => true, + 'additional_settings' => array( $this->get_hpos_setting_for_sync(), ), ); @@ -544,11 +545,11 @@ class CustomOrdersTableController { }; $get_disabled = function () { - $plugin_compatibility = $this->features_controller->get_compatible_plugins_for_feature( 'custom_order_tables', true ); - $sync_complete = 0 === $this->data_synchronizer->get_current_orders_pending_sync_count(); - $disabled = array(); - // Changing something here? might also want to look at `enable|disable` functions in CLIRunner. - $incompatible_plugins = array_merge( $plugin_compatibility['uncertain'], $plugin_compatibility['incompatible'] ); + $compatibility_info = $this->features_controller->get_compatible_plugins_for_feature( 'custom_order_tables', true ); + $sync_complete = 0 === $this->data_synchronizer->get_current_orders_pending_sync_count(); + $disabled = array(); + // Changing something here? You might also want to look at `enable|disable` functions in Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable\CLIRunner. + $incompatible_plugins = $this->plugin_util->get_items_considered_incompatible( 'custom_order_tables', $compatibility_info ); $incompatible_plugins = array_diff( $incompatible_plugins, $this->plugin_util->get_plugins_excluded_from_compatibility_ui() ); if ( count( $incompatible_plugins ) > 0 ) { $disabled = array( 'yes' ); diff --git a/plugins/woocommerce/src/Internal/Features/FeaturesController.php b/plugins/woocommerce/src/Internal/Features/FeaturesController.php index be36ab70088..c7b33c14e36 100644 --- a/plugins/woocommerce/src/Internal/Features/FeaturesController.php +++ b/plugins/woocommerce/src/Internal/Features/FeaturesController.php @@ -26,6 +26,8 @@ class FeaturesController { public const FEATURE_ENABLED_CHANGED_ACTION = 'woocommerce_feature_enabled_changed'; + public const PLUGINS_COMPATIBLE_BY_DEFAULT_OPTION = 'woocommerce_plugins_are_compatible_with_features_by_default'; + /** * The existing feature definitions. * @@ -142,12 +144,13 @@ class FeaturesController { */ public function add_feature_definition( $slug, $name, array $args = array() ) { $defaults = array( - 'disable_ui' => false, - 'enabled_by_default' => false, - 'is_experimental' => true, - 'is_legacy' => false, - 'name' => $name, - 'order' => 10, + 'disable_ui' => false, + 'enabled_by_default' => false, + 'is_experimental' => true, + 'is_legacy' => false, + 'plugins_are_incompatible_by_default' => false, + 'name' => $name, + 'order' => 10, ); $args = wp_parse_args( $args, $defaults ); @@ -322,6 +325,34 @@ class FeaturesController { return $features; } + /** + * Check if plugins that don't declare compatibility nor incompatibility with a given feature + * are to be considered incompatible with that feature. + * + * @param string $feature_id Feature id to check. + * @return bool True if plugins that don't declare compatibility nor incompatibility with the feature will be considered incompatible with the feature. + * @throws \InvalidArgumentException The feature doesn't exist. + */ + public function get_plugins_are_incompatible_by_default( string $feature_id ): bool { + $feature_definition = $this->get_feature_definitions()[ $feature_id ] ?? null; + if ( is_null( $feature_definition ) ) { + throw new \InvalidArgumentException( esc_html( "The WooCommerce feature '$feature_id' doesn't exist" ) ); + } + + $incompatible_by_default = $feature_definition['plugins_are_incompatible_by_default'] ?? false; + + /** + * Filter to determine if plugins that don't declare compatibility nor incompatibility with a given feature + * are to be considered incompatible with that feature. + * + * @param bool $incompatible_by_default Default value, true if plugins are to be considered incompatible by default with the feature. + * @param string $feature_id The feature to check. + * + * @since 9.2.0 + */ + return (bool) apply_filters( 'woocommerce_plugins_are_incompatible_with_feature_by_default', $incompatible_by_default, $feature_id ); + } + /** * Check if a given feature is currently enabled. * @@ -476,7 +507,7 @@ class FeaturesController { * * @param string $feature_id Feature id. * @param bool $active_only True to return only active plugins. - * @return array An array having a 'compatible' and an 'incompatible' key, each holding an array of plugin names. + * @return array An array having a 'compatible', an 'incompatible' and an 'uncertain' key, each holding an array of plugin names. */ public function get_compatible_plugins_for_feature( string $feature_id, bool $active_only = false ): array { $this->verify_did_woocommerce_init( __FUNCTION__ ); @@ -884,7 +915,7 @@ class FeaturesController { * * @param array $plugin_list The original list of plugins. */ - private function filter_plugins_list( $plugin_list ): array { + public function filter_plugins_list( $plugin_list ): array { if ( ! $this->verify_did_woocommerce_init() ) { return $plugin_list; } @@ -910,10 +941,11 @@ class FeaturesController { * * @return array List of plugins incompatible with the given feature. */ - private function get_incompatible_plugins( $feature_id, $plugin_list ) { - $incompatibles = array(); - - $plugin_list = array_diff_key( $plugin_list, array_flip( $this->plugins_excluded_from_compatibility_ui ) ); + public function get_incompatible_plugins( $feature_id, $plugin_list ) { + $incompatibles = array(); + $plugin_list = array_diff_key( $plugin_list, array_flip( $this->plugins_excluded_from_compatibility_ui ) ); + $feature_ids = 'all' === $feature_id ? array_keys( $this->get_feature_definitions() ) : array( $feature_id ); + $only_enabled_features = 'all' === $feature_id; // phpcs:enable WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput foreach ( array_keys( $plugin_list ) as $plugin_name ) { @@ -921,16 +953,17 @@ class FeaturesController { continue; } - $compatibility = $this->get_compatible_features_for_plugin( $plugin_name ); - $incompatible_with = array_filter( - array_merge( $compatibility['incompatible'], $compatibility['uncertain'] ), - function ( $feature_id ) { - return ! $this->is_legacy_feature( $feature_id ); + $compatibility_info = $this->get_compatible_features_for_plugin( $plugin_name ); + foreach ( $feature_ids as $feature_id ) { + $features_considered_incompatible = array_filter( + $this->plugin_util->get_items_considered_incompatible( $feature_id, $compatibility_info ), + $only_enabled_features ? + fn( $feature_id ) => $this->feature_is_enabled( $feature_id ) && ! $this->is_legacy_feature( $feature_id ) : + fn( $feature_id ) => ! $this->is_legacy_feature( $feature_id ) + ); + if ( in_array( $feature_id, $features_considered_incompatible, true ) ) { + $incompatibles[] = $plugin_name; } - ); - - if ( ( 'all' === $feature_id && ! empty( $incompatible_with ) ) || in_array( $feature_id, $incompatible_with, true ) ) { - $incompatibles[] = $plugin_name; } } @@ -954,7 +987,7 @@ class FeaturesController { /** * Shows a warning when there are any incompatibility between active plugins and enabled features. * The warning is shown in on any admin screen except the plugins screen itself, since - * there's already a "You are viewing + * there's already a "You are viewing plugins that are incompatible" notice. */ private function maybe_display_feature_incompatibility_warning(): void { if ( ! current_user_can( 'activate_plugins' ) ) { @@ -965,18 +998,25 @@ class FeaturesController { $relevant_plugins = array_diff( $this->plugin_util->get_woocommerce_aware_plugins( true ), $this->plugins_excluded_from_compatibility_ui ); foreach ( $relevant_plugins as $plugin ) { - $compatibility = $this->get_compatible_features_for_plugin( $plugin, true ); - $incompatible_with = array_filter( - array_merge( $compatibility['incompatible'], $compatibility['uncertain'] ), - function ( $feature_id ) { - return ! $this->is_legacy_feature( $feature_id ); - } - ); + $compatibility_info = $this->get_compatible_features_for_plugin( $plugin, true ); - if ( $incompatible_with ) { + $incompatibles = array_filter( $compatibility_info['incompatible'], fn( $feature_id ) => ! $this->is_legacy_feature( $feature_id ) ); + if ( ! empty( $incompatibles ) ) { $incompatible_plugins = true; break; } + + $uncertains = array_filter( $compatibility_info['uncertain'], fn( $feature_id ) => ! $this->is_legacy_feature( $feature_id ) ); + foreach ( $uncertains as $feature_id ) { + if ( $this->get_plugins_are_incompatible_by_default( $feature_id ) ) { + $incompatible_plugins = true; + break; + } + } + + if ( $incompatible_plugins ) { + break; + } } if ( ! $incompatible_plugins ) { diff --git a/plugins/woocommerce/src/Utilities/PluginUtil.php b/plugins/woocommerce/src/Utilities/PluginUtil.php index 2d9cde37624..6bd063e86e3 100644 --- a/plugins/woocommerce/src/Utilities/PluginUtil.php +++ b/plugins/woocommerce/src/Utilities/PluginUtil.php @@ -5,6 +5,7 @@ namespace Automattic\WooCommerce\Utilities; +use Automattic\WooCommerce\Internal\Features\FeaturesController; use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods; use Automattic\WooCommerce\Internal\Utilities\PluginInstaller; use Automattic\WooCommerce\Proxies\LegacyProxy; @@ -189,13 +190,12 @@ class PluginUtil { * the Legacy REST API and HPOS are active. * * @param string $feature_id Feature id. - * @param array $plugin_feature_info Array of plugin feature info. See FeaturesControllers->get_compatible_plugins_for_feature() for details. + * @param array $plugin_feature_info Array of plugin feature info, as provided by FeaturesController->get_compatible_plugins_for_feature(). * * @return string Warning string. */ public function generate_incompatible_plugin_feature_warning( string $feature_id, array $plugin_feature_info ): string { - $feature_warning = ''; - $incompatibles = array_merge( $plugin_feature_info['incompatible'], $plugin_feature_info['uncertain'] ); + $incompatibles = $this->get_items_considered_incompatible( $feature_id, $plugin_feature_info ); $incompatibles = array_filter( $incompatibles, 'is_plugin_active' ); $incompatibles = array_values( array_diff( $incompatibles, $this->get_plugins_excluded_from_compatibility_ui() ) ); $incompatible_count = count( $incompatibles ); @@ -268,7 +268,8 @@ class PluginUtil { ), admin_url( 'plugins.php' ) ); - $feature_warnings[] = sprintf( + + $feature_warnings[] = sprintf( /* translators: %1$s opening link tag %2$s closing link tag. */ __( '%1$sView and manage%2$s', 'woocommerce' ), '', @@ -279,6 +280,23 @@ class PluginUtil { return str_replace( "\n", '
', implode( "\n", $feature_warnings ) ); } + /** + * Filter plugin/feature compatibility info, returning the names of the plugins/features that are considered incompatible. + * "Uncertain" information will be included or not depending on the value of the value of the 'plugins_are_incompatible_by_default' + * flag in the feature definition (default is true). + * + * @param string $feature_id Feature id. + * @param array $compatibility_info Array containing "compatible', 'incompatible' and 'uncertain' keys. + * @return array Items in 'incompatible' and 'uncertain' if plugins are incompatible by default with the feature; only items in 'incompatible' otherwise. + */ + public function get_items_considered_incompatible( string $feature_id, array $compatibility_info ): array { + $incompatible_by_default = wc_get_container()->get( FeaturesController::class )->get_plugins_are_incompatible_by_default( $feature_id ); + + return $incompatible_by_default ? + array_merge( $compatibility_info['incompatible'], $compatibility_info['uncertain'] ) : + $compatibility_info['incompatible']; + } + /** * Get the names of the plugins that are excluded from the feature compatibility UI. * These plugins won't be considered as incompatible with any existing feature for the purposes diff --git a/plugins/woocommerce/tests/php/src/Internal/Features/FeaturesControllerTest.php b/plugins/woocommerce/tests/php/src/Internal/Features/FeaturesControllerTest.php index a301705389a..12f53a90181 100644 --- a/plugins/woocommerce/tests/php/src/Internal/Features/FeaturesControllerTest.php +++ b/plugins/woocommerce/tests/php/src/Internal/Features/FeaturesControllerTest.php @@ -799,7 +799,7 @@ class FeaturesControllerTest extends \WC_Unit_Test_Case { */ public function test_no_warning_when_all_plugin_are_hpos_compatible() { $this->simulate_inside_before_woocommerce_init_hook(); - // phpcs:disable Squiz.Commenting + // phpcs:disable Squiz.Commenting, Generic.CodeAnalysis.UnusedFunctionParameter.Found $fake_plugin_util = new class() extends PluginUtil { private $active_plugins; @@ -826,7 +826,7 @@ class FeaturesControllerTest extends \WC_Unit_Test_Case { }, ) ); - // phpcs:enable + // phpcs:enable Squiz.Commenting, Generic.CodeAnalysis.UnusedFunctionParameter.Found $local_sut = new FeaturesController(); @@ -884,10 +884,15 @@ class FeaturesControllerTest extends \WC_Unit_Test_Case { /** * @testDox If there is an incompatible plugin, it is returned by get_incompatible_plugins. + * + * @testWith [true] + * [false] + * + * @param bool $hpos_is_enabled True to test with HPOS enabled, false to test with HPOS disabled. */ - public function test_show_warning_when_a_plugin_is_not_hpos_compatible() { + public function test_show_warning_when_a_plugin_is_not_hpos_compatible_if_hpos_is_enabled( bool $hpos_is_enabled ) { $this->simulate_inside_before_woocommerce_init_hook(); - // phpcs:disable Squiz.Commenting + // phpcs:disable Squiz.Commenting, Generic.CodeAnalysis.UnusedFunctionParameter.Found $fake_plugin_util = new class() extends PluginUtil { private $active_plugins; @@ -906,7 +911,6 @@ class FeaturesControllerTest extends \WC_Unit_Test_Case { return array(); } }; - // phpcs:enable $this->register_legacy_proxy_function_mocks( array( @@ -915,8 +919,10 @@ class FeaturesControllerTest extends \WC_Unit_Test_Case { }, ) ); + // phpcs:enable Squiz.Commenting, Generic.CodeAnalysis.UnusedFunctionParameter.Found $local_sut = new FeaturesController(); + $local_sut->change_feature_enable( 'custom_order_tables', $hpos_is_enabled ); add_action( 'woocommerce_register_feature_definitions', @@ -926,8 +932,10 @@ class FeaturesControllerTest extends \WC_Unit_Test_Case { $features = array( 'custom_order_tables' => array( 'name' => __( 'High-Performance order storage', 'woocommerce' ), - 'is_experimental' => true, + 'is_experimental' => false, 'enabled_by_default' => false, + 'option_key' => CustomOrdersTableController::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION, + 'plugins_are_incompatible_by_default' => true, ), 'cart_checkout_blocks' => array( 'name' => __( 'Cart & Checkout Blocks', 'woocommerce' ), @@ -966,6 +974,8 @@ class FeaturesControllerTest extends \WC_Unit_Test_Case { $incompatible_plugins = function () use ( $plugins ) { return $this->get_incompatible_plugins( 'all', array_flip( $plugins ) ); }; - $this->assertEquals( array( 'incompatible_plugin' ), array_keys( $incompatible_plugins->call( $local_sut ) ) ); + + $expected = $hpos_is_enabled ? array( 'incompatible_plugin' ) : array(); + $this->assertEquals( $expected, array_keys( $incompatible_plugins->call( $local_sut ) ) ); } } diff --git a/plugins/woocommerce/tests/php/src/Utilities/PluginUtilTests.php b/plugins/woocommerce/tests/php/src/Utilities/PluginUtilTests.php index d730d6be62d..ec21af8aa94 100644 --- a/plugins/woocommerce/tests/php/src/Utilities/PluginUtilTests.php +++ b/plugins/woocommerce/tests/php/src/Utilities/PluginUtilTests.php @@ -2,6 +2,8 @@ namespace Automattic\WooCommerce\Tests\Utilities; +use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController; +use Automattic\WooCommerce\Internal\Features\FeaturesController; use Automattic\WooCommerce\Utilities\PluginUtil; use Automattic\WooCommerce\Utilities\StringUtil; @@ -123,23 +125,23 @@ class PluginUtilTests extends \WC_Unit_Test_Case { $this->reset_legacy_proxy_mocks(); $this->register_legacy_proxy_function_mocks( array( - 'get_plugins' => function() { + 'get_plugins' => function () { return array( 'woocommerce/woocommerce.php' => array( 'WC tested up to' => '1.0' ), - 'jetpack/jetpack.php' => array( 'foo' => 'bar' ), + 'jetpack/jetpack.php' => array( 'foo' => 'bar' ), 'classic-editor/classic-editor.php' => array( 'foo' => 'bar' ), ); }, ) ); - // Unix style + // Unix style. $this->assertEquals( 'woocommerce/woocommerce.php', $this->sut->get_wp_plugin_id( 'woocommerce/woocommerce.php' ) ); $this->assertEquals( 'woocommerce/woocommerce.php', $this->sut->get_wp_plugin_id( '6.9.2/woocommerce.php' ) ); $this->assertEquals( 'woocommerce/woocommerce.php', $this->sut->get_wp_plugin_id( '/srv/htdocs/woocommerce/latest/woocommerce.php' ) ); $this->assertEquals( 'woocommerce/woocommerce.php', $this->sut->get_wp_plugin_id( '../../../../wordpress/plugins/woocommerce/latest/woocommerce.php' ) ); - // Windows style + // Windows style. $this->assertEquals( 'woocommerce/woocommerce.php', $this->sut->get_wp_plugin_id( 'woocommerce\\woocommerce.php' ) ); $this->assertEquals( 'woocommerce/woocommerce.php', $this->sut->get_wp_plugin_id( '6.9.2\\woocommerce.php' ) ); $this->assertEquals( 'woocommerce/woocommerce.php', $this->sut->get_wp_plugin_id( 'D:\\WordPress\\plugins\\woocommerce\\6.9.2\\woocommerce.php' ) ); @@ -155,7 +157,7 @@ class PluginUtilTests extends \WC_Unit_Test_Case { private function mock_plugin_functions() { $this->register_legacy_proxy_function_mocks( array( - 'get_plugins' => function() { + 'get_plugins' => function () { return array( 'woo_aware_1' => array( 'WC tested up to' => '1.0' ), 'woo_aware_2' => array( 'WC tested up to' => '2.0' ), @@ -164,10 +166,10 @@ class PluginUtilTests extends \WC_Unit_Test_Case { 'not_woo_aware_2' => array( 'foo' => 'bar' ), ); }, - 'is_plugin_active' => function( $plugin_name ) { + 'is_plugin_active' => function ( $plugin_name ) { return 'woo_aware_3' !== $plugin_name; }, - 'get_plugin_data' => function( $plugin_name ) { + 'get_plugin_data' => function ( $plugin_name ) { return StringUtil::ends_with( $plugin_name, 'woo_aware_1' ) ? array( 'WC tested up to' => '1.0', @@ -178,4 +180,76 @@ class PluginUtilTests extends \WC_Unit_Test_Case { ) ); } + + /** + * @testdox Test the `get_items_considered_incompatible` method. + */ + public function test_get_items_considered_incompatible() { + $this->reset_container_resolutions(); + + add_action( + 'woocommerce_register_feature_definitions', + function ( $features_controller ) { + $features = array( + 'test_feature_1' => array( + 'name' => 'Test feature 1', + 'plugins_are_incompatible_by_default' => true, + ), + 'test_feature_2' => array( + 'name' => 'Test feature 2', + 'plugins_are_incompatible_by_default' => false, + ), + 'test_feature_3' => array( + 'name' => 'Test feature 2', + ), + ); + + foreach ( $features as $slug => $definition ) { + $features_controller->add_feature_definition( $slug, $definition['name'], $definition ); + } + }, + 20 + ); + + $plugin_compatibility_info = array( + 'compatible' => array( + 'compatible_1.php', + 'compatible_2.php', + ), + 'incompatible' => array( + 'incompatible_1.php', + 'incompatible_2.php', + ), + 'uncertain' => array( + 'uncertain_1.php', + 'uncertain_2.php', + ), + ); + + $expected = array( + 'incompatible_1.php', + 'incompatible_2.php', + 'uncertain_1.php', + 'uncertain_2.php', + ); + + $actual = $this->sut->get_items_considered_incompatible( 'test_feature_1', $plugin_compatibility_info ); + + sort( $actual ); + sort( $expected ); + $this->assertEquals( $expected, $actual ); + + $expected = array( + 'incompatible_1.php', + 'incompatible_2.php', + ); + + $actual = $this->sut->get_items_considered_incompatible( 'test_feature_2', $plugin_compatibility_info ); + sort( $actual ); + $this->assertEquals( $expected, $actual ); + + $actual = $this->sut->get_items_considered_incompatible( 'test_feature_3', $plugin_compatibility_info ); + sort( $actual ); + $this->assertEquals( $expected, $actual ); + } }