HPOS Features: Revert to one feature (#39525)
Co-authored-by: Vedanshu Jain <vedanshu.jain.2012@gmail.com>
This commit is contained in:
parent
a9c8486b53
commit
b9bfbcdc42
|
@ -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.
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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 ] );
|
||||
|
||||
|
|
|
@ -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' ) );
|
||||
}
|
||||
|
|
|
@ -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 ) );
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue