HPOS Features: Revert to one feature (#39525)

Co-authored-by: Vedanshu Jain <vedanshu.jain.2012@gmail.com>
This commit is contained in:
Corey McKrill 2023-10-03 02:34:41 -07:00 committed by GitHub
parent a9c8486b53
commit b9bfbcdc42
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 589 additions and 413 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: fix
Consolidate HPOS back into a single "feature" for the purposes of showing it on the Features settings screen.

View File

@ -8,9 +8,7 @@
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Admin\Notes\Notes;
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
use Automattic\WooCommerce\Internal\DataStores\Orders\DataSynchronizer;
use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore;
use Automattic\WooCommerce\Internal\DataStores\Orders\{ CustomOrdersTableController, DataSynchronizer, OrdersTableDataStore };
use Automattic\WooCommerce\Internal\Features\FeaturesController;
use Automattic\WooCommerce\Internal\ProductAttributesLookup\DataRegenerator;
use Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Register as Download_Directories;
@ -491,11 +489,17 @@ class WC_Install {
$schema = self::get_schema();
$feature_controller = wc_get_container()->get( FeaturesController::class );
if (
$feature_controller->feature_is_enabled( DataSynchronizer::ORDERS_DATA_SYNC_ENABLED_OPTION )
|| $feature_controller->feature_is_enabled( CustomOrdersTableController::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION )
) {
$hpos_settings = filter_var_array(
array(
'cot' => get_option( CustomOrdersTableController::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION ),
'data_sync' => get_option( DataSynchronizer::ORDERS_DATA_SYNC_ENABLED_OPTION ),
),
array(
'cot' => FILTER_VALIDATE_BOOLEAN,
'data_sync' => FILTER_VALIDATE_BOOLEAN,
)
);
if ( in_array( true, $hpos_settings, true ) ) {
$schema .= wc_get_container()
->get( OrdersTableDataStore::class )
->get_database_schema();

View File

@ -5,7 +5,6 @@
namespace Automattic\WooCommerce\Internal\DataStores\Orders;
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Caches\OrderCache;
use Automattic\WooCommerce\Caches\OrderCacheController;
use Automattic\WooCommerce\Internal\BatchProcessing\BatchProcessingController;
@ -120,10 +119,9 @@ class CustomOrdersTableController {
self::add_filter( 'updated_option', array( $this, 'process_updated_option' ), 999, 3 );
self::add_filter( 'pre_update_option', array( $this, 'process_pre_update_option' ), 999, 3 );
self::add_action( 'woocommerce_after_register_post_type', array( $this, 'register_post_type_for_order_placeholders' ), 10, 0 );
self::add_action( FeaturesController::FEATURE_ENABLED_CHANGED_ACTION, array( $this, 'handle_feature_enabled_changed' ), 10, 2 );
self::add_action( 'woocommerce_feature_setting', array( $this, 'get_hpos_feature_setting' ), 10, 2 );
self::add_action( 'woocommerce_sections_advanced', array( $this, 'sync_now' ) );
self::add_filter( 'removable_query_args', array( $this, 'register_removable_query_arg' ) );
self::add_action( 'woocommerce_register_feature_definitions', array( $this, 'add_feature_definition' ) );
}
/**
@ -295,12 +293,19 @@ class CustomOrdersTableController {
return $value;
}
if ( self::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION !== $option || $value === $old_value || false === $old_value ) {
if ( self::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION !== $option || 'no' === $value ) {
return $value;
}
$this->order_cache->flush();
if ( ! $this->data_synchronizer->check_orders_table_exists() ) {
$this->data_synchronizer->create_database_tables();
}
$tables_created = get_option( DataSynchronizer::ORDERS_TABLE_CREATED ) === 'yes';
if ( ! $tables_created ) {
return 'no';
}
/**
* Re-enable the following code once the COT to posts table sync is implemented (it's currently commented out to ease testing).
$sync_is_pending = 0 !== $this->data_synchronizer->get_current_orders_pending_sync_count();
@ -341,26 +346,6 @@ class CustomOrdersTableController {
return $query_args;
}
/**
* Handle the 'woocommerce_feature_enabled_changed' action,
* if the custom orders table feature is enabled create the database tables if they don't exist.
*
* @param string $feature_id The id of the feature that is being enabled or disabled.
* @param bool $is_enabled True if the feature is being enabled, false if it's being disabled.
*/
private function handle_feature_enabled_changed( $feature_id, $is_enabled ): void {
if ( self::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION !== $feature_id || ! $is_enabled ) {
return;
}
if ( ! $this->data_synchronizer->check_orders_table_exists() ) {
$success = $this->data_synchronizer->create_database_tables();
if ( ! $success ) {
update_option( self::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION, 'no' );
}
}
}
/**
* Handler for the woocommerce_after_register_post_type post,
* registers the post type for placeholder orders.
@ -393,54 +378,72 @@ class CustomOrdersTableController {
}
/**
* Returns the HPOS setting for rendering in Features section of the settings page.
* Add the definition for the HPOS feature.
*
* @param array $feature_setting HPOS feature value as defined in the feature controller.
* @param string $feature_id ID of the feature.
* @param FeaturesController $features_controller The instance of FeaturesController.
*
* @return array Feature setting object.
* @return void
*/
private function get_hpos_feature_setting( array $feature_setting, string $feature_id ) {
if ( ! in_array( $feature_id, array( self::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION, DataSynchronizer::ORDERS_DATA_SYNC_ENABLED_OPTION, 'custom_order_tables' ), true ) ) {
return $feature_setting;
}
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(
$this->get_hpos_setting_for_sync(),
),
);
if ( 'yes' === get_transient( 'wc_installing' ) ) {
return $feature_setting;
}
$sync_status = $this->data_synchronizer->get_sync_status();
switch ( $feature_id ) {
case self::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION:
return $this->get_hpos_setting_for_feature( $sync_status );
case DataSynchronizer::ORDERS_DATA_SYNC_ENABLED_OPTION:
return $this->get_hpos_setting_for_sync( $sync_status );
case 'custom_order_tables':
return array();
}
$features_controller->add_feature_definition(
'custom_order_tables',
__( 'High-Performance order storage', 'woocommerce' ),
$definition
);
}
/**
* Returns the HPOS setting for rendering HPOS vs Post setting block in Features section of the settings page.
*
* @param array $sync_status Details of sync status, includes pending count, and count when sync started.
*
* @return array Feature setting object.
*/
private function get_hpos_setting_for_feature( $sync_status ) {
$hpos_enabled = $this->custom_orders_table_usage_is_enabled();
$plugin_info = $this->features_controller->get_compatible_plugins_for_feature( 'custom_order_tables', true );
$plugin_incompat_warning = $this->plugin_util->generate_incompatible_plugin_feature_warning( 'custom_order_tables', $plugin_info );
$sync_complete = 0 === $sync_status['current_pending_count'];
$disabled_option = array();
// Changing something here? might also want to look at `enable|disable` functions in CLIRunner.
if ( count( array_merge( $plugin_info['uncertain'], $plugin_info['incompatible'] ) ) > 0 ) {
$disabled_option = array( 'yes' );
}
if ( ! $sync_complete ) {
$disabled_option = array( 'yes', 'no' );
private function get_hpos_setting_for_feature() {
if ( 'yes' === get_transient( 'wc_installing' ) ) {
return array();
}
$get_value = function() {
return $this->custom_orders_table_usage_is_enabled() ? 'yes' : 'no';
};
/**
* The FeaturesController instance must only be accessed from within the callback functions. Otherwise it
* gets called while it's still being instantiated and creates and endless loop.
*/
$get_desc = function() {
$plugin_compatibility = $this->features_controller->get_compatible_plugins_for_feature( 'custom_order_tables', true );
return $this->plugin_util->generate_incompatible_plugin_feature_warning( 'custom_order_tables', $plugin_compatibility );
};
$get_disabled = function() {
$plugin_compatibility = $this->features_controller->get_compatible_plugins_for_feature( 'custom_order_tables', true );
$sync_status = $this->data_synchronizer->get_sync_status();
$sync_complete = 0 === $sync_status['current_pending_count'];
$disabled = array();
// Changing something here? might also want to look at `enable|disable` functions in CLIRunner.
if ( count( array_merge( $plugin_compatibility['uncertain'], $plugin_compatibility['incompatible'] ) ) > 0 ) {
$disabled = array( 'yes' );
}
if ( ! $sync_complete ) {
$disabled = array( 'yes', 'no' );
}
return $disabled;
};
return array(
'id' => self::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION,
'title' => __( 'Order data storage', 'woocommerce' ),
@ -449,9 +452,9 @@ class CustomOrdersTableController {
'no' => __( 'WordPress posts storage (legacy)', 'woocommerce' ),
'yes' => __( 'High-performance order storage (recommended)', 'woocommerce' ),
),
'value' => $hpos_enabled ? 'yes' : 'no',
'disabled' => $disabled_option,
'desc' => $plugin_incompat_warning,
'value' => $get_value,
'disabled' => $get_disabled,
'desc' => $get_desc,
'desc_at_end' => true,
'row_class' => self::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION,
);
@ -460,63 +463,74 @@ class CustomOrdersTableController {
/**
* Returns the setting for rendering sync enabling setting block in Features section of the settings page.
*
* @param array $sync_status Details of sync status, includes pending count, and count when sync started.
*
* @return array Feature setting object.
*/
private function get_hpos_setting_for_sync( $sync_status ) {
$sync_in_progress = $this->batch_processing_controller->is_enqueued( get_class( $this->data_synchronizer ) );
$sync_enabled = $this->data_synchronizer->data_sync_is_enabled();
$sync_message = array();
if ( ! $sync_enabled && $this->data_synchronizer->background_sync_is_enabled() ) {
$sync_message[] = __( 'Background sync is enabled.', 'woocommerce' );
private function get_hpos_setting_for_sync() {
if ( 'yes' === get_transient( 'wc_installing' ) ) {
return array();
}
if ( $sync_in_progress && $sync_status['current_pending_count'] > 0 ) {
$sync_message[] = sprintf(
// translators: %d: number of pending orders.
__( 'Currently syncing orders... %d pending', 'woocommerce' ),
$sync_status['current_pending_count']
);
} elseif ( $sync_status['current_pending_count'] > 0 ) {
$sync_now_url = add_query_arg(
array(
self::SYNC_QUERY_ARG => true,
),
wc_get_container()->get( FeaturesController::class )->get_features_page_url()
);
$get_value = function() {
return get_option( DataSynchronizer::ORDERS_DATA_SYNC_ENABLED_OPTION );
};
$sync_message[] = wp_kses_data(
__(
'You can switch order data storage <strong>only when the posts and orders tables are in sync</strong>.',
'woocommerce'
)
);
$get_sync_message = function() {
$sync_status = $this->data_synchronizer->get_sync_status();
$sync_in_progress = $this->batch_processing_controller->is_enqueued( get_class( $this->data_synchronizer ) );
$sync_enabled = $this->data_synchronizer->data_sync_is_enabled();
$sync_message = array();
$sync_message[] = sprintf(
'<a href="%1$s" class="button button-link">%2$s</a>',
esc_url( $sync_now_url ),
sprintf(
if ( ! $sync_enabled && $this->data_synchronizer->background_sync_is_enabled() ) {
$sync_message[] = __( 'Background sync is enabled.', 'woocommerce' );
}
if ( $sync_in_progress && $sync_status['current_pending_count'] > 0 ) {
$sync_message[] = sprintf(
// translators: %d: number of pending orders.
_n(
'Sync %s pending order',
'Sync %s pending orders',
$sync_status['current_pending_count'],
'woocommerce'
__( 'Currently syncing orders... %d pending', 'woocommerce' ),
$sync_status['current_pending_count']
);
} elseif ( $sync_status['current_pending_count'] > 0 ) {
$sync_now_url = add_query_arg(
array(
self::SYNC_QUERY_ARG => true,
),
number_format_i18n( $sync_status['current_pending_count'] )
)
);
}
wc_get_container()->get( FeaturesController::class )->get_features_page_url()
);
$sync_message[] = wp_kses_data(
__(
'You can switch order data storage <strong>only when the posts and orders tables are in sync</strong>.',
'woocommerce'
)
);
$sync_message[] = sprintf(
'<a href="%1$s" class="button button-link">%2$s</a>',
esc_url( $sync_now_url ),
sprintf(
// translators: %d: number of pending orders.
_n(
'Sync %s pending order',
'Sync %s pending orders',
$sync_status['current_pending_count'],
'woocommerce'
),
number_format_i18n( $sync_status['current_pending_count'] )
)
);
}
return implode( '<br />', $sync_message );
};
return array(
'id' => DataSynchronizer::ORDERS_DATA_SYNC_ENABLED_OPTION,
'title' => '',
'type' => 'checkbox',
'desc' => __( 'Enable compatibility mode (synchronizes orders to the posts table).', 'woocommerce' ),
'value' => $sync_enabled ? 'yes' : 'no',
'desc_tip' => implode( '<br />', $sync_message ),
'value' => $get_value,
'desc_tip' => $get_sync_message,
'row_class' => DataSynchronizer::ORDERS_DATA_SYNC_ENABLED_OPTION,
);
}

View File

@ -5,12 +5,10 @@
namespace Automattic\WooCommerce\Internal\Features;
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Internal\Admin\Analytics;
use Automattic\WooCommerce\Admin\Features\Navigation\Init;
use Automattic\WooCommerce\Admin\Features\NewProductManagementExperience;
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
use Automattic\WooCommerce\Internal\DataStores\Orders\DataSynchronizer;
use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods;
use Automattic\WooCommerce\Proxies\LegacyProxy;
use Automattic\WooCommerce\Utilities\ArrayUtil;
@ -34,28 +32,21 @@ class FeaturesController {
*
* @var array[]
*/
private $features;
private $features = array();
/**
* The registered compatibility info for WooCommerce plugins, with plugin names as keys.
*
* @var array
*/
private $compatibility_info_by_plugin;
/**
* Ids of the legacy features (they existed before the features engine was implemented).
*
* @var array
*/
private $legacy_feature_ids;
private $compatibility_info_by_plugin = array();
/**
* The registered compatibility info for WooCommerce plugins, with feature ids as keys.
*
* @var array
*/
private $compatibility_info_by_feature;
private $compatibility_info_by_feature = array();
/**
* The LegacyProxy instance to use.
@ -91,71 +82,6 @@ class FeaturesController {
* Creates a new instance of the class.
*/
public function __construct() {
$hpos_enable_sync = DataSynchronizer::ORDERS_DATA_SYNC_ENABLED_OPTION;
$hpos_authoritative = CustomOrdersTableController::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION;
$features = array(
'analytics' => array(
'name' => __( 'Analytics', 'woocommerce' ),
'description' => __( 'Enable WooCommerce Analytics', 'woocommerce' ),
'is_experimental' => false,
'enabled_by_default' => true,
'disable_ui' => false,
),
'new_navigation' => array(
'name' => __( 'Navigation', 'woocommerce' ),
'description' => __( 'Add the new WooCommerce navigation experience to the dashboard', 'woocommerce' ),
'is_experimental' => false,
'disable_ui' => false,
),
'product_block_editor' => array(
'name' => __( 'New product editor', 'woocommerce' ),
'description' => __( 'Try the new product editor (Beta)', 'woocommerce' ),
'is_experimental' => true,
'disable_ui' => false,
),
// Options for HPOS features are added in CustomOrdersTableController to keep the logic in same place.
'custom_order_tables' => array( // This exists for back-compat only, otherwise it's value is superseded by $hpos_authoritative option.
'name' => __( 'High-Performance Order Storage (HPOS)', 'woocommerce' ),
'enabled_by_default' => false,
),
$hpos_authoritative => array(
'name' => __( 'High-Performance Order Storage', 'woocommerce' ),
'order' => 10,
),
$hpos_enable_sync => array(
'name' => '',
'order' => 9,
),
'cart_checkout_blocks' => array(
'name' => __( 'Cart & Checkout Blocks', 'woocommerce' ),
'description' => __( 'Optimize for faster checkout', 'woocommerce' ),
'is_experimental' => false,
'disable_ui' => true,
),
'marketplace' => array(
'name' => __( 'Marketplace', 'woocommerce' ),
'description' => __(
'New, faster way to find extensions and themes for your WooCommerce store',
'woocommerce'
),
'is_experimental' => false,
'enabled_by_default' => true,
'disable_ui' => false,
),
);
$this->legacy_feature_ids = array(
'analytics',
'new_navigation',
'product_block_editor',
'marketplace',
// Compatibility for COT is determined by `custom_order_tables'.
CustomOrdersTableController::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION,
DataSynchronizer::ORDERS_DATA_SYNC_ENABLED_OPTION,
);
$this->init_features( $features );
self::add_filter( 'updated_option', array( $this, 'process_updated_option' ), 999, 3 );
self::add_filter( 'added_option', array( $this, 'process_added_option' ), 999, 3 );
self::add_filter( 'woocommerce_get_sections_advanced', array( $this, 'add_features_section' ), 10, 1 );
@ -172,22 +98,139 @@ class FeaturesController {
}
/**
* Initialize the class according to the existing features.
* Register a feature.
*
* @param array $features Information about the existing features.
* This should be called during the `woocommerce_register_feature_definitions` action hook.
*
* @param string $slug The ID slug of the feature.
* @param string $name The name of the feature that will appear on the Features screen and elsewhere.
* @param array $args {
* Optional. Properties that make up the feature definition. Each of these properties can also be set as a
* callback function, as long as that function returns the specified type.
*
* @type array[] $additional_settings An array of definitions for additional settings controls related to
* the feature that will display on the Features screen. See the Settings API
* for the schema of these props.
* @type string $description A brief description of the feature, used as an input label if the feature
* setting is a checkbox.
* @type bool $disabled True to disable the setting field for this feature on the Features screen,
* so it can't be changed.
* @type bool $disable_ui Set to true to hide the setting field for this feature on the
* Features screen. Defaults to false.
* @type bool $enabled_by_default Set to true to have this feature by opt-out instead of opt-in.
* Defaults to false.
* @type bool $is_experimental Set to true to display this feature under the "Experimental" heading on
* the Features screen. Features set to experimental are also omitted from
* the features list in some cases. Defaults to true.
* @type bool $is_legacy Set to true if this feature existed before the FeaturesController class
* was introduced. Features set to legacy also do not produce warnings about
* incompatible plugins. Defaults to false.
* @type string $option_key The key name for the option that enables/disables the feature.
* @type int $order The order that the feature will appear in the list on the Features screen.
* Higher number = higher in the list. Defaults to 10.
* @type array $setting The properties used by the Settings API to render the setting control on
* the Features screen. See the Settings API for the schema of these props.
* }
*
* @return void
*/
private function init_features( array $features ) {
$this->compatibility_info_by_plugin = array();
$this->compatibility_info_by_feature = array();
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,
);
$args = wp_parse_args( $args, $defaults );
$this->features = $features;
$this->features[ $slug ] = $args;
}
foreach ( array_keys( $this->features ) as $feature_id ) {
$this->compatibility_info_by_feature[ $feature_id ] = array(
'compatible' => array(),
'incompatible' => array(),
/**
* Generate and cache the feature definitions.
*
* @return array[]
*/
private function get_feature_definitions() {
if ( empty( $this->features ) ) {
$legacy_features = array(
'analytics' => array(
'name' => __( 'Analytics', 'woocommerce' ),
'description' => __( 'Enable WooCommerce Analytics', 'woocommerce' ),
'option_key' => Analytics::TOGGLE_OPTION_NAME,
'is_experimental' => false,
'enabled_by_default' => true,
'disable_ui' => false,
'is_legacy' => true,
),
'new_navigation' => array(
'name' => __( 'Navigation', 'woocommerce' ),
'description' => __( 'Add the new WooCommerce navigation experience to the dashboard', 'woocommerce' ),
'option_key' => Init::TOGGLE_OPTION_NAME,
'is_experimental' => false,
'disable_ui' => false,
'is_legacy' => true,
),
'product_block_editor' => array(
'name' => __( 'New product editor', 'woocommerce' ),
'description' => __( 'Try the new product editor (Beta)', 'woocommerce' ),
'is_experimental' => true,
'disable_ui' => false,
'is_legacy' => true,
'disabled' => function() {
return version_compare( get_bloginfo( 'version' ), '6.2', '<' );
},
'desc_tip' => function() {
$string = '';
if ( version_compare( get_bloginfo( 'version' ), '6.2', '<' ) ) {
$string = __( '⚠ This feature is compatible with WordPress version 6.2 or higher.', 'woocommerce' );
}
return $string;
},
),
'cart_checkout_blocks' => array(
'name' => __( 'Cart & Checkout Blocks', 'woocommerce' ),
'description' => __( 'Optimize for faster checkout', 'woocommerce' ),
'is_experimental' => false,
'disable_ui' => true,
),
'marketplace' => array(
'name' => __( 'Marketplace', 'woocommerce' ),
'description' => __(
'New, faster way to find extensions and themes for your WooCommerce store',
'woocommerce'
),
'is_experimental' => false,
'enabled_by_default' => true,
'disable_ui' => false,
'is_legacy' => true,
),
);
foreach ( $legacy_features as $slug => $definition ) {
$this->add_feature_definition( $slug, $definition['name'], $definition );
}
/**
* The action for registering features.
*
* @since 8.3.0
*
* @param FeaturesController $features_controller The instance of FeaturesController.
*/
do_action( 'woocommerce_register_feature_definitions', $this );
foreach ( array_keys( $this->features ) as $feature_id ) {
$this->compatibility_info_by_feature[ $feature_id ] = array(
'compatible' => array(),
'incompatible' => array(),
);
}
}
return $this->features;
}
/**
@ -219,7 +262,7 @@ class FeaturesController {
* @returns array An array of information about existing features.
*/
public function get_features( bool $include_experimental = false, bool $include_enabled_info = false ): array {
$features = $this->features;
$features = $this->get_feature_definitions();
if ( ! $include_experimental ) {
$features = array_filter(
@ -263,7 +306,9 @@ class FeaturesController {
* @return boolean TRUE if the feature is enabled by default, FALSE otherwise.
*/
private function feature_is_enabled_by_default( string $feature_id ): bool {
return ! empty( $this->features[ $feature_id ]['enabled_by_default'] );
$features = $this->get_feature_definitions();
return ! empty( $features[ $feature_id ]['enabled_by_default'] );
}
/**
@ -345,7 +390,9 @@ class FeaturesController {
* @return bool True if the feature exists.
*/
private function feature_exists( string $feature_id ): bool {
return isset( $this->features[ $feature_id ] );
$features = $this->get_feature_definitions();
return isset( $features[ $feature_id ] );
}
/**
@ -360,7 +407,7 @@ class FeaturesController {
public function get_compatible_features_for_plugin( string $plugin_name, bool $enabled_features_only = false ) : array {
$this->verify_did_woocommerce_init( __FUNCTION__ );
$features = $this->features;
$features = $this->get_feature_definitions();
if ( $enabled_features_only ) {
$features = array_filter(
$features,
@ -432,25 +479,22 @@ class FeaturesController {
/**
* Get the name of the option that enables/disables a given feature.
* Note that it doesn't check if the feature actually exists.
*
* @param string $feature_id The id of the feature.
* Note that it doesn't check if the feature actually exists. Instead it
* defaults to "woocommerce_feature_{$feature_id}_enabled" if a different
* name isn't specified in the feature registration.
*
* @param string $feature_id The id of the feature.
* @return string The option that enables or disables the feature.
*/
public function feature_enable_option_name( string $feature_id ): string {
switch ( $feature_id ) {
case 'analytics':
return Analytics::TOGGLE_OPTION_NAME;
case 'new_navigation':
return Init::TOGGLE_OPTION_NAME;
case 'custom_order_tables':
case CustomOrdersTableController::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION:
return CustomOrdersTableController::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION;
case DataSynchronizer::ORDERS_DATA_SYNC_ENABLED_OPTION:
return DataSynchronizer::ORDERS_DATA_SYNC_ENABLED_OPTION;
default:
return "woocommerce_feature_{$feature_id}_enabled";
$features = $this->get_feature_definitions();
if ( ! empty( $features[ $feature_id ]['option_key'] ) ) {
return $features[ $feature_id ]['option_key'];
}
return "woocommerce_feature_{$feature_id}_enabled";
}
/**
@ -461,7 +505,9 @@ class FeaturesController {
* @return bool True if the id corresponds to a legacy feature.
*/
public function is_legacy_feature( string $feature_id ): bool {
return in_array( $feature_id, $this->legacy_feature_ids, true );
$features = $this->get_feature_definitions();
return ! empty( $features[ $feature_id ]['is_legacy'] );
}
/**
@ -497,23 +543,24 @@ class FeaturesController {
*
* It fires FEATURE_ENABLED_CHANGED_ACTION when a feature is enabled or disabled.
*
* @param string $option The option that has been modified.
* @param string $option The option that has been modified.
* @param mixed $old_value The old value of the option.
* @param mixed $value The new value of the option.
* @param mixed $value The new value of the option.
*
* @return void
*/
private function process_updated_option( string $option, $old_value, $value ) {
$matches = array();
$success = preg_match( '/^woocommerce_feature_([a-zA-Z0-9_]+)_enabled$/', $option, $matches );
$known_features = array(
Analytics::TOGGLE_OPTION_NAME,
Init::TOGGLE_OPTION_NAME,
NewProductManagementExperience::TOGGLE_OPTION_NAME,
DataSynchronizer::ORDERS_DATA_SYNC_ENABLED_OPTION,
CustomOrdersTableController::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION,
$matches = array();
$is_default_key = preg_match( '/^woocommerce_feature_([a-zA-Z0-9_]+)_enabled$/', $option, $matches );
$features_with_custom_keys = array_filter(
$this->get_feature_definitions(),
function( $feature ) {
return ! empty( $feature['option_key'] );
}
);
$custom_keys = wp_list_pluck( $features_with_custom_keys, 'option_key' );
if ( ! $success && ! in_array( $option, $known_features, true ) ) {
if ( ! $is_default_key && ! in_array( $option, $custom_keys, true ) ) {
return;
}
@ -521,14 +568,15 @@ class FeaturesController {
return;
}
if ( Analytics::TOGGLE_OPTION_NAME === $option ) {
$feature_id = 'analytics';
} elseif ( Init::TOGGLE_OPTION_NAME === $option ) {
$feature_id = 'new_navigation';
} elseif ( in_array( $option, $known_features, true ) ) {
$feature_id = $option;
} else {
$feature_id = '';
if ( $is_default_key ) {
$feature_id = $matches[1];
} elseif ( in_array( $option, $custom_keys, true ) ) {
$feature_id = array_search( $option, $custom_keys, true );
}
if ( ! $feature_id ) {
return;
}
/**
@ -572,24 +620,14 @@ class FeaturesController {
return $settings;
}
// phpcs:disable WooCommerce.Commenting.CommentHooks.MissingSinceComment
/**
* Filter allowing WooCommerce Admin to be disabled.
*
* @param bool $disabled False.
*/
$admin_features_disabled = apply_filters( 'woocommerce_admin_disabled', false );
// phpcs:enable WooCommerce.Commenting.CommentHooks.MissingSinceComment
$feature_settings =
$feature_settings = array(
array(
array(
'title' => __( 'Features', 'woocommerce' ),
'type' => 'title',
'desc' => __( 'Start using new features that are being progressively rolled out to improve the store management experience.', 'woocommerce' ),
'id' => 'features_options',
),
);
'title' => __( 'Features', 'woocommerce' ),
'type' => 'title',
'desc' => __( 'Start using new features that are being progressively rolled out to improve the store management experience.', 'woocommerce' ),
'id' => 'features_options',
),
);
$features = $this->get_features( true );
@ -637,7 +675,12 @@ class FeaturesController {
continue;
}
$feature_settings[] = $this->get_setting_for_feature( $id, $features[ $id ], $admin_features_disabled );
$feature_settings[] = $this->get_setting_for_feature( $id, $features[ $id ] );
$additional_settings = $features[ $id ]['additional_settings'] ?? array();
if ( count( $additional_settings ) > 0 ) {
$feature_settings = array_merge( $feature_settings, $additional_settings );
}
}
$feature_settings[] = array(
@ -645,6 +688,20 @@ class FeaturesController {
'id' => empty( $experimental_feature_ids ) ? 'features_options' : 'experimental_features_options',
);
// Allow feature setting properties to be determined dynamically just before being rendered.
$feature_settings = array_map(
function( $feature_setting ) {
foreach ( $feature_setting as $prop => $value ) {
if ( is_callable( $value ) ) {
$feature_setting[ $prop ] = call_user_func( $value );
}
}
return $feature_setting;
},
$feature_settings
);
return $feature_settings;
}
@ -653,15 +710,24 @@ class FeaturesController {
*
* @param string $feature_id The feature id.
* @param array $feature The feature parameters, as returned by get_features.
* @param bool $admin_features_disabled True if admin features have been disabled via 'woocommerce_admin_disabled' filter.
* @return array The parameters to add to the settings array.
*/
private function get_setting_for_feature( string $feature_id, array $feature, bool $admin_features_disabled ): array {
$description = $feature['description'] ?? '';
$disabled = false;
$desc_tip = '';
$tooltip = $feature['tooltip'] ?? '';
$type = $feature['type'] ?? 'checkbox';
private function get_setting_for_feature( string $feature_id, array $feature ): array {
$description = $feature['description'] ?? '';
$disabled = false;
$desc_tip = '';
$tooltip = $feature['tooltip'] ?? '';
$type = $feature['type'] ?? 'checkbox';
$setting_definition = $feature['setting'] ?? array();
// phpcs:disable WooCommerce.Commenting.CommentHooks.MissingSinceComment
/**
* Filter allowing WooCommerce Admin to be disabled.
*
* @param bool $disabled False.
*/
$admin_features_disabled = apply_filters( 'woocommerce_admin_disabled', false );
// phpcs:enable WooCommerce.Commenting.CommentHooks.MissingSinceComment
if ( ( 'analytics' === $feature_id || 'new_navigation' === $feature_id ) && $admin_features_disabled ) {
$disabled = true;
@ -671,13 +737,13 @@ class FeaturesController {
if ( $disabled ) {
$update_text = sprintf(
// translators: 1: line break tag.
// translators: 1: line break tag.
__( '%1$s The development of this feature is currently on hold.', 'woocommerce' ),
'<br/>'
);
} else {
$update_text = sprintf(
// translators: 1: line break tag.
// translators: 1: line break tag.
__(
'%1$s This navigation will soon become unavailable while we make necessary improvements.
If you turn it off now, you will not be able to turn it back on.',
@ -690,7 +756,7 @@ class FeaturesController {
$needs_update = version_compare( get_bloginfo( 'version' ), '5.6', '<' );
if ( $needs_update && current_user_can( 'update_core' ) && current_user_can( 'update_php' ) ) {
$update_text = sprintf(
// translators: 1: line break tag, 2: open link to WordPress update link, 3: close link tag.
// translators: 1: line break tag, 2: open link to WordPress update link, 3: close link tag.
__( '%1$s %2$sUpdate WordPress to enable the new navigation%3$s', 'woocommerce' ),
'<br/>',
'<a href="' . self_admin_url( 'update-core.php' ) . '" target="_blank">',
@ -704,13 +770,6 @@ class FeaturesController {
}
}
if ( 'product_block_editor' === $feature_id ) {
$disabled = version_compare( get_bloginfo( 'version' ), '6.2', '<' );
if ( $disabled ) {
$desc_tip = __( '⚠ This feature is compatible with WordPress version 6.2 or higher.', 'woocommerce' );
}
}
if ( ! $this->is_legacy_feature( $feature_id ) && ! $disabled && $this->verify_did_woocommerce_init() ) {
$disabled = ! $this->feature_is_enabled( $feature_id );
$plugin_info_for_feature = $this->get_compatible_plugins_for_feature( $feature_id, true );
@ -729,7 +788,7 @@ class FeaturesController {
*/
$desc_tip = apply_filters( 'woocommerce_feature_description_tip', $desc_tip, $feature_id, $disabled );
$feature_setting = array(
$feature_setting_defaults = array(
'title' => $feature['name'],
'desc' => $description,
'type' => $type,
@ -740,6 +799,8 @@ class FeaturesController {
'default' => $this->feature_is_enabled_by_default( $feature_id ) ? 'yes' : 'no',
);
$feature_setting = wp_parse_args( $setting_definition, $feature_setting_defaults );
/**
* Allows to modify feature setting that will be used to render in the feature page.
*
@ -772,7 +833,6 @@ class FeaturesController {
$incompatibles = $this->compatibility_info_by_feature[ $feature ]['incompatible'];
$this->compatibility_info_by_feature[ $feature ]['incompatible'] = array_diff( $incompatibles, array( $plugin_name ) );
}
}
@ -821,9 +881,11 @@ class FeaturesController {
}
$compatibility = $this->get_compatible_features_for_plugin( $plugin_name );
$incompatible_with = array_diff(
$incompatible_with = array_filter(
array_merge( $compatibility['incompatible'], $compatibility['uncertain'] ),
$this->legacy_feature_ids
function( $feature_id ) {
return ! $this->is_legacy_feature( $feature_id );
}
);
if ( ( 'all' === $feature_id && ! empty( $incompatible_with ) ) || in_array( $feature_id, $incompatible_with, true ) ) {
@ -862,9 +924,11 @@ class FeaturesController {
foreach ( $this->plugin_util->get_woocommerce_aware_plugins( true ) as $plugin ) {
$compatibility = $this->get_compatible_features_for_plugin( $plugin, true );
$incompatible_with = array_diff(
$incompatible_with = array_filter(
array_merge( $compatibility['incompatible'], $compatibility['uncertain'] ),
$this->legacy_feature_ids
function( $feature_id ) {
return ! $this->is_legacy_feature( $feature_id );
}
);
if ( $incompatible_with ) {
@ -917,7 +981,7 @@ class FeaturesController {
return false;
}
// phpcs:enable WordPress.Security.NonceVerification
$features = $this->get_feature_definitions();
$plugins_page_url = admin_url( 'plugins.php' );
$features_page_url = $this->get_features_page_url();
@ -927,7 +991,7 @@ class FeaturesController {
: sprintf(
/* translators: %s is a feature name. */
__( "You are viewing the active plugins that are incompatible with the '%s' feature.", 'woocommerce' ),
$this->features[ $feature_id ]['name']
$features[ $feature_id ]['name']
);
$message .= '<br />';
@ -988,6 +1052,7 @@ class FeaturesController {
return;
}
$features = $this->get_feature_definitions();
$feature_compatibility_info = $this->get_compatible_features_for_plugin( $plugin_file, true );
$incompatible_features = array_merge( $feature_compatibility_info['incompatible'], $feature_compatibility_info['uncertain'] );
$incompatible_features = array_values(
@ -1010,21 +1075,21 @@ class FeaturesController {
$message = sprintf(
/* translators: %s = printable plugin name */
__( "⚠ This plugin is incompatible with the enabled WooCommerce feature '%s', it shouldn't be activated.", 'woocommerce' ),
$this->features[ $incompatible_features[0] ]['name']
$features[ $incompatible_features[0] ]['name']
);
} elseif ( 2 === $incompatible_features_count ) {
/* translators: %1\$s, %2\$s = printable plugin names */
$message = sprintf(
__( "⚠ This plugin is incompatible with the enabled WooCommerce features '%1\$s' and '%2\$s', it shouldn't be activated.", 'woocommerce' ),
$this->features[ $incompatible_features[0] ]['name'],
$this->features[ $incompatible_features[1] ]['name']
$features[ $incompatible_features[0] ]['name'],
$features[ $incompatible_features[1] ]['name']
);
} else {
/* translators: %1\$s, %2\$s = printable plugin names, %3\$d = plugins count */
$message = sprintf(
__( "⚠ This plugin is incompatible with the enabled WooCommerce features '%1\$s', '%2\$s' and %3\$d more, it shouldn't be activated.", 'woocommerce' ),
$this->features[ $incompatible_features[0] ]['name'],
$this->features[ $incompatible_features[1] ]['name'],
$features[ $incompatible_features[0] ]['name'],
$features[ $incompatible_features[1] ]['name'],
$incompatible_features_count - 2
);
}
@ -1125,13 +1190,14 @@ class FeaturesController {
// phpcs:enable WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput
$all_items = get_plugins();
$features = $this->get_feature_definitions();
$incompatible_plugins_count = count( $this->filter_plugins_list( $all_items ) );
$incompatible_text =
'all' === $feature_id
? __( 'Incompatible with WooCommerce features', 'woocommerce' )
/* translators: %s = name of a WooCommerce feature */
: sprintf( __( "Incompatible with '%s'", 'woocommerce' ), $this->features[ $feature_id ]['name'] );
: sprintf( __( "Incompatible with '%s'", 'woocommerce' ), $features[ $feature_id ]['name'] );
$incompatible_link = "<a href='plugins.php?plugin_status=incompatible_with_feature&feature_id={$feature_id}' class='current' aria-current='page'>{$incompatible_text} <span class='count'>({$incompatible_plugins_count})</span></a>";
$all_plugins_count = count( $all_items );
@ -1171,7 +1237,7 @@ class FeaturesController {
$query_params_to_remove = array( '_feature_nonce' );
foreach ( array_keys( $this->features ) as $feature_id ) {
foreach ( array_keys( $this->get_feature_definitions() ) as $feature_id ) {
if ( isset( $_GET[ $feature_id ] ) && is_numeric( $_GET[ $feature_id ] ) ) {
$value = absint( $_GET[ $feature_id ] );

View File

@ -572,13 +572,26 @@ class DataSynchronizerTests extends HposTestCase {
$this->disable_cot_sync();
OrderHelper::create_order();
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment -- This is a test.
$cot_setting = apply_filters( 'woocommerce_feature_setting', array(), CustomOrdersTableController::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION );
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment -- test code.
$features = apply_filters( 'woocommerce_get_settings_advanced', array(), 'features' );
$cot_setting = array_filter(
$features,
function( $feature ) {
return CustomOrdersTableController::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION === $feature['id'];
}
);
$cot_setting = array_values( $cot_setting )[0];
$this->assertEquals( $cot_setting['value'], 'no' );
$this->assertEquals( $cot_setting['disabled'], array( 'yes', 'no' ) );
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment -- This is a test.
$sync_setting = apply_filters( 'woocommerce_feature_setting', array(), $this->sut::ORDERS_DATA_SYNC_ENABLED_OPTION );
$sync_setting = array_filter(
$features,
function( $feature ) {
return DataSynchronizer::ORDERS_DATA_SYNC_ENABLED_OPTION === $feature['id'];
}
);
$sync_setting = array_values( $sync_setting )[0];
$this->assertEquals( $sync_setting['value'], 'no' );
$this->assertTrue( str_contains( $sync_setting['desc_tip'], 'Sync 1 pending order' ) );
}

View File

@ -33,38 +33,60 @@ class FeaturesControllerTest extends \WC_Unit_Test_Case {
* Runs before each test.
*/
public function setUp(): void {
$features = array(
'mature1' => array(
'name' => 'Mature feature 1',
'description' => 'The mature feature number 1',
'is_experimental' => false,
),
'mature2' => array(
'name' => 'Mature feature 2',
'description' => 'The mature feature number 2',
'is_experimental' => false,
),
'experimental1' => array(
'name' => 'Experimental feature 1',
'description' => 'The experimental feature number 1',
'is_experimental' => true,
),
'experimental2' => array(
'name' => 'Experimental feature 2',
'description' => 'The experimental feature number 2',
'is_experimental' => true,
),
parent::setUp();
$this->set_up_plugins();
add_action(
'woocommerce_register_feature_definitions',
function( $features_controller ) {
$this->reset_features_list( $this->sut );
$features = array(
'mature1' => array(
'name' => 'Mature feature 1',
'description' => 'The mature feature number 1',
'is_experimental' => false,
),
'mature2' => array(
'name' => 'Mature feature 2',
'description' => 'The mature feature number 2',
'is_experimental' => false,
),
'experimental1' => array(
'name' => 'Experimental feature 1',
'description' => 'The experimental feature number 1',
'is_experimental' => true,
),
'experimental2' => array(
'name' => 'Experimental feature 2',
'description' => 'The experimental feature number 2',
'is_experimental' => true,
),
);
foreach ( $features as $slug => $definition ) {
$features_controller->add_feature_definition( $slug, $definition['name'], $definition );
}
},
11
);
$this->do_set_up( $features );
$this->sut = new FeaturesController();
$this->sut->init( wc_get_container()->get( LegacyProxy::class ), $this->fake_plugin_util );
delete_option( 'woocommerce_feature_mature1_enabled' );
delete_option( 'woocommerce_feature_mature2_enabled' );
delete_option( 'woocommerce_feature_experimental1_enabled' );
delete_option( 'woocommerce_feature_experimental2_enabled' );
remove_all_filters( FeaturesController::FEATURE_ENABLED_CHANGED_ACTION );
}
/**
* Runs before each test.
*
* @param array $features The fake features list to use.
*/
public function do_set_up( array $features ): void {
private function set_up_plugins(): void {
$this->reset_container_resolutions();
$this->reset_legacy_proxy_mocks();
@ -98,38 +120,42 @@ class FeaturesControllerTest extends \WC_Unit_Test_Case {
'the_plugin_4',
)
);
}
$this->sut = new FeaturesController();
$this->sut->init( wc_get_container()->get( LegacyProxy::class ), $this->fake_plugin_util );
$init_features_method = new \ReflectionMethod( $this->sut, 'init_features' );
$init_features_method->setAccessible( true );
$init_features_method->invoke( $this->sut, $features );
/**
* Resets the array of registered features so we can populate it with test features.
*
* @param FeaturesController $sut The instance of the FeaturesController class.
*
* @return void
*/
private function reset_features_list( $sut ) {
$reflection_class = new \ReflectionClass( $sut );
delete_option( 'woocommerce_feature_mature1_enabled' );
delete_option( 'woocommerce_feature_mature2_enabled' );
delete_option( 'woocommerce_feature_experimental1_enabled' );
delete_option( 'woocommerce_feature_experimental2_enabled' );
$features = $reflection_class->getProperty( 'features' );
$features->setAccessible( true );
$features->setValue( $sut, array() );
}
remove_all_filters( FeaturesController::FEATURE_ENABLED_CHANGED_ACTION );
/**
* Runs after each test.
*/
public function tearDown(): void {
$this->reset_features_list( $this->sut );
remove_all_actions( 'woocommerce_register_feature_definitions' );
parent::tearDown();
}
/**
* @testdox 'get_features' returns existing non-experimental features without enabling information if requested to do so.
*/
public function test_get_features_not_including_experimental_not_including_values() {
$actual = $this->sut->get_features( false, false );
$actual = array_keys( $this->sut->get_features( false, false ) );
$expected = array(
'mature1' => array(
'name' => 'Mature feature 1',
'description' => 'The mature feature number 1',
'is_experimental' => false,
),
'mature2' => array(
'name' => 'Mature feature 2',
'description' => 'The mature feature number 2',
'is_experimental' => false,
),
'mature1',
'mature2',
);
$this->assertEquals( $expected, $actual );
@ -139,29 +165,13 @@ class FeaturesControllerTest extends \WC_Unit_Test_Case {
* @testdox 'get_features' returns all existing features without enabling information if requested to do so.
*/
public function test_get_features_including_experimental_not_including_values() {
$actual = $this->sut->get_features( true, false );
$actual = array_keys( $this->sut->get_features( true, false ) );
$expected = array(
'mature1' => array(
'name' => 'Mature feature 1',
'description' => 'The mature feature number 1',
'is_experimental' => false,
),
'mature2' => array(
'name' => 'Mature feature 2',
'description' => 'The mature feature number 2',
'is_experimental' => false,
),
'experimental1' => array(
'name' => 'Experimental feature 1',
'description' => 'The experimental feature number 1',
'is_experimental' => true,
),
'experimental2' => array(
'name' => 'Experimental feature 2',
'description' => 'The experimental feature number 2',
'is_experimental' => true,
),
'mature1',
'mature2',
'experimental1',
'experimental2',
);
$this->assertEquals( $expected, $actual );
@ -176,32 +186,28 @@ class FeaturesControllerTest extends \WC_Unit_Test_Case {
update_option( 'woocommerce_feature_experimental1_enabled', 'yes' );
// No option for experimental2.
$actual = $this->sut->get_features( true, true );
$actual = array_map(
function( $feature ) {
return array_intersect_key(
$feature,
array( 'is_enabled' => '' )
);
},
$this->sut->get_features( true, true )
);
$expected = array(
'mature1' => array(
'name' => 'Mature feature 1',
'description' => 'The mature feature number 1',
'is_experimental' => false,
'is_enabled' => true,
'is_enabled' => true,
),
'mature2' => array(
'name' => 'Mature feature 2',
'description' => 'The mature feature number 2',
'is_experimental' => false,
'is_enabled' => false,
'is_enabled' => false,
),
'experimental1' => array(
'name' => 'Experimental feature 1',
'description' => 'The experimental feature number 1',
'is_experimental' => true,
'is_enabled' => true,
'is_enabled' => true,
),
'experimental2' => array(
'name' => 'Experimental feature 2',
'description' => 'The experimental feature number 2',
'is_experimental' => true,
'is_enabled' => false,
'is_enabled' => false,
),
);
@ -483,40 +489,50 @@ class FeaturesControllerTest extends \WC_Unit_Test_Case {
* @testdox 'get_compatible_features_for_plugin' returns proper information for a plugin that has declared compatibility with the passed feature, when only enabled features are requested.
*/
public function test_get_compatible_enabled_features_for_registered_plugin() {
$features = array(
'mature1' => array(
'name' => 'Mature feature 1',
'description' => 'The mature feature number 1',
'is_experimental' => false,
),
'mature2' => array(
'name' => 'Mature feature 2',
'description' => 'The mature feature number 2',
'is_experimental' => false,
),
'mature3' => array(
'name' => 'Mature feature 3',
'description' => 'The mature feature number 3',
'is_experimental' => false,
),
'experimental1' => array(
'name' => 'Experimental feature 1',
'description' => 'The experimental feature number 1',
'is_experimental' => true,
),
'experimental2' => array(
'name' => 'Experimental feature 2',
'description' => 'The experimental feature number 2',
'is_experimental' => true,
),
'experimental3' => array(
'name' => 'Experimental feature 3',
'description' => 'The experimental feature number 3',
'is_experimental' => true,
),
);
add_action(
'woocommerce_register_feature_definitions',
function( $features_controller ) {
$this->reset_features_list( $this->sut );
$this->do_set_up( $features );
$features = array(
'mature1' => array(
'name' => 'Mature feature 1',
'description' => 'The mature feature number 1',
'is_experimental' => false,
),
'mature2' => array(
'name' => 'Mature feature 2',
'description' => 'The mature feature number 2',
'is_experimental' => false,
),
'mature3' => array(
'name' => 'Mature feature 3',
'description' => 'The mature feature number 3',
'is_experimental' => false,
),
'experimental1' => array(
'name' => 'Experimental feature 1',
'description' => 'The experimental feature number 1',
'is_experimental' => true,
),
'experimental2' => array(
'name' => 'Experimental feature 2',
'description' => 'The experimental feature number 2',
'is_experimental' => true,
),
'experimental3' => array(
'name' => 'Experimental feature 3',
'description' => 'The experimental feature number 3',
'is_experimental' => true,
),
);
foreach ( $features as $slug => $definition ) {
$features_controller->add_feature_definition( $slug, $definition['name'], $definition );
}
},
20
);
$this->simulate_inside_before_woocommerce_init_hook();
@ -809,6 +825,33 @@ class FeaturesControllerTest extends \WC_Unit_Test_Case {
// phpcs:enable
$local_sut = new FeaturesController();
add_action(
'woocommerce_register_feature_definitions',
function( $features_controller ) use ( $local_sut ) {
$this->reset_features_list( $local_sut );
$features = array(
'custom_order_tables' => array(
'name' => __( 'High-Performance order storage', 'woocommerce' ),
'is_experimental' => true,
'enabled_by_default' => false,
),
'cart_checkout_blocks' => array(
'name' => __( 'Cart & Checkout Blocks', 'woocommerce' ),
'description' => __( 'Optimize for faster checkout', 'woocommerce' ),
'is_experimental' => false,
'disable_ui' => true,
),
);
foreach ( $features as $slug => $definition ) {
$features_controller->add_feature_definition( $slug, $definition['name'], $definition );
}
},
20
);
$local_sut->init( wc_get_container()->get( LegacyProxy::class ), $fake_plugin_util );
$plugins = array( 'compatible_plugin1', 'compatible_plugin2' );
$fake_plugin_util->set_active_plugins( $plugins );
@ -816,16 +859,19 @@ class FeaturesControllerTest extends \WC_Unit_Test_Case {
$local_sut->declare_compatibility( 'custom_order_tables', $plugin );
$local_sut->declare_compatibility( 'cart_checkout_blocks', $plugin );
}
$cot_controller = new CustomOrdersTableController();
$cot_setting_call = function () use ( $fake_plugin_util, $local_sut ) {
$this->plugin_util = $fake_plugin_util;
$this->features_controller = $local_sut;
$this->data_synchronizer = wc_get_container()->get( DataSynchronizer::class );
return $this->get_hpos_feature_setting( array(), CustomOrdersTableController::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION );
return $this->get_hpos_setting_for_feature();
};
$cot_setting = $cot_setting_call->call( $cot_controller );
$actual = call_user_func( $cot_setting['disabled'] );
$this->assertEquals( array(), $actual );
$this->assertEquals( $cot_setting['disabled'], array() );
$incompatible_plugins = function () use ( $plugins ) {
return $this->get_incompatible_plugins( 'all', array_flip( $plugins ) );
};
@ -863,6 +909,33 @@ class FeaturesControllerTest extends \WC_Unit_Test_Case {
);
$local_sut = new FeaturesController();
add_action(
'woocommerce_register_feature_definitions',
function( $features_controller ) use ( $local_sut ) {
$this->reset_features_list( $local_sut );
$features = array(
'custom_order_tables' => array(
'name' => __( 'High-Performance order storage', 'woocommerce' ),
'is_experimental' => true,
'enabled_by_default' => false,
),
'cart_checkout_blocks' => array(
'name' => __( 'Cart & Checkout Blocks', 'woocommerce' ),
'description' => __( 'Optimize for faster checkout', 'woocommerce' ),
'is_experimental' => false,
'disable_ui' => true,
),
);
foreach ( $features as $slug => $definition ) {
$features_controller->add_feature_definition( $slug, $definition['name'], $definition );
}
},
20
);
$local_sut->init( wc_get_container()->get( LegacyProxy::class ), $fake_plugin_util );
$plugins = array( 'compatible_plugin', 'incompatible_plugin' );
$fake_plugin_util->set_active_plugins( $plugins );
@ -875,11 +948,13 @@ class FeaturesControllerTest extends \WC_Unit_Test_Case {
$this->plugin_util = $fake_plugin_util;
$this->features_controller = $local_sut;
$this->data_synchronizer = wc_get_container()->get( DataSynchronizer::class );
return $this->get_hpos_feature_setting( array(), CustomOrdersTableController::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION );
return $this->get_hpos_setting_for_feature();
};
$cot_setting = $cot_setting_call->call( $cot_controller );
$actual = call_user_func( $cot_setting['disabled'] );
$this->assertEquals( array( 'yes' ), $actual );
$this->assertEquals( $cot_setting['disabled'], array( 'yes' ) );
$incompatible_plugins = function () use ( $plugins ) {
return $this->get_incompatible_plugins( 'all', array_flip( $plugins ) );
};