Add the WooCommerce features engine (#34616)
Includes adapting the existing Admin features (enable analytics, enable the new navigation) to use the new engine.
This commit is contained in:
parent
04b74c1053
commit
f65c775da3
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add the WooCommerce features engine
|
|
@ -12,6 +12,7 @@ use Automattic\WooCommerce\Internal\AssignDefaultCategory;
|
|||
use Automattic\WooCommerce\Internal\BatchProcessing\BatchProcessingController;
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
|
||||
use Automattic\WooCommerce\Internal\DownloadPermissionsAdjuster;
|
||||
use Automattic\WooCommerce\Internal\Features\FeaturesController;
|
||||
use Automattic\WooCommerce\Internal\ProductAttributesLookup\DataRegenerator;
|
||||
use Automattic\WooCommerce\Internal\ProductAttributesLookup\LookupDataStore;
|
||||
use Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Register as ProductDownloadDirectories;
|
||||
|
@ -230,6 +231,7 @@ final class WooCommerce {
|
|||
$container->get( CustomOrdersTableController::class );
|
||||
$container->get( OptionSanitizer::class );
|
||||
$container->get( BatchProcessingController::class );
|
||||
$container->get( FeaturesController::class );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,6 +8,7 @@ namespace Automattic\WooCommerce\Admin\Features;
|
|||
use Automattic\WooCommerce\Admin\PageController;
|
||||
use Automattic\WooCommerce\Internal\Admin\Loader;
|
||||
use Automattic\WooCommerce\Internal\Admin\WCAdminAssets;
|
||||
use Automattic\WooCommerce\Internal\Features\FeaturesController;
|
||||
|
||||
/**
|
||||
* Features Class.
|
||||
|
@ -61,8 +62,6 @@ class Features {
|
|||
$this->register_internal_class_aliases();
|
||||
// Load feature before WooCommerce update hooks.
|
||||
add_action( 'init', array( __CLASS__, 'load_features' ), 4 );
|
||||
add_filter( 'woocommerce_get_sections_advanced', array( __CLASS__, 'add_features_section' ) );
|
||||
add_filter( 'woocommerce_get_settings_advanced', array( __CLASS__, 'add_features_settings' ), 10, 2 );
|
||||
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'maybe_load_beta_features_modal' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'load_scripts' ), 15 );
|
||||
add_filter( 'admin_body_class', array( __CLASS__, 'add_admin_body_classes' ) );
|
||||
|
@ -250,70 +249,26 @@ class Features {
|
|||
/**
|
||||
* Adds the Features section to the advanced tab of WooCommerce Settings
|
||||
*
|
||||
* @deprecated 7.0 The WooCommerce Admin features are now handled by the WooCommerce features engine (see the FeaturesController class).
|
||||
*
|
||||
* @param array $sections Sections.
|
||||
* @return array
|
||||
*/
|
||||
public static function add_features_section( $sections ) {
|
||||
$features = apply_filters(
|
||||
'woocommerce_settings_features',
|
||||
array()
|
||||
);
|
||||
|
||||
if ( empty( $features ) ) {
|
||||
return $sections;
|
||||
}
|
||||
|
||||
$sections['features'] = __( 'Features', 'woocommerce' );
|
||||
return $sections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the Features settings.
|
||||
*
|
||||
* @deprecated 7.0 The WooCommerce Admin features are now handled by the WooCommerce features engine (see the FeaturesController class).
|
||||
*
|
||||
* @param array $settings Settings.
|
||||
* @param string $current_section Current section slug.
|
||||
* @return array
|
||||
*/
|
||||
public static function add_features_settings( $settings, $current_section ) {
|
||||
if ( 'features' !== $current_section ) {
|
||||
return $settings;
|
||||
}
|
||||
|
||||
$features = apply_filters(
|
||||
'woocommerce_settings_features',
|
||||
array()
|
||||
);
|
||||
|
||||
$features_disabled = apply_filters( 'woocommerce_admin_disabled', false );
|
||||
|
||||
if ( ! $features_disabled && empty( $features ) ) {
|
||||
return $settings;
|
||||
}
|
||||
|
||||
$desc = __( 'Start using new features that are being progressively rolled out to improve the store management experience.', 'woocommerce' );
|
||||
$disabled_desc = __( 'WooCommerce features have been disabled.', 'woocommerce' );
|
||||
|
||||
if ( $features_disabled ) {
|
||||
$GLOBALS['hide_save_button'] = true;
|
||||
}
|
||||
|
||||
return array_merge(
|
||||
array(
|
||||
array(
|
||||
'title' => __( 'Features', 'woocommerce' ),
|
||||
'type' => 'title',
|
||||
'desc' => $features_disabled ? $disabled_desc : $desc,
|
||||
'id' => 'features_options',
|
||||
),
|
||||
),
|
||||
$features_disabled ? array() : $features,
|
||||
array(
|
||||
array(
|
||||
'type' => 'sectionend',
|
||||
'id' => 'features_options',
|
||||
),
|
||||
)
|
||||
);
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -34,7 +34,6 @@ class Init {
|
|||
* Hook into WooCommerce.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_filter( 'woocommerce_settings_features', array( $this, 'add_feature_toggle' ) );
|
||||
add_action( 'update_option_' . self::TOGGLE_OPTION_NAME, array( $this, 'reload_page_on_toggle' ), 10, 2 );
|
||||
add_action( 'woocommerce_settings_saved', array( $this, 'maybe_reload_page' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'maybe_enqueue_opt_out_scripts' ) );
|
||||
|
@ -49,33 +48,12 @@ class Init {
|
|||
/**
|
||||
* Add the feature toggle to the features settings.
|
||||
*
|
||||
* @deprecated 7.0 The WooCommerce Admin features are now handled by the WooCommerce features engine (see the FeaturesController class).
|
||||
*
|
||||
* @param array $features Feature sections.
|
||||
* @return array
|
||||
*/
|
||||
public static function add_feature_toggle( $features ) {
|
||||
$description = __(
|
||||
'Adds the new WooCommerce navigation experience to the dashboard',
|
||||
'woocommerce'
|
||||
);
|
||||
$update_text = '';
|
||||
$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. */
|
||||
__( '%1$s %2$sUpdate WordPress to enable the new navigation%3$s', 'woocommerce' ),
|
||||
'<br/>',
|
||||
'<a href="' . self_admin_url( 'update-core.php' ) . '" target="_blank">',
|
||||
'</a>'
|
||||
);
|
||||
}
|
||||
|
||||
$features[] = array(
|
||||
'title' => __( 'Navigation', 'woocommerce' ),
|
||||
'desc' => $description . $update_text,
|
||||
'id' => self::TOGGLE_OPTION_NAME,
|
||||
'type' => 'checkbox',
|
||||
'class' => $needs_update ? 'disabled' : '',
|
||||
);
|
||||
|
||||
return $features;
|
||||
}
|
||||
|
@ -119,7 +97,7 @@ class Init {
|
|||
return;
|
||||
}
|
||||
|
||||
if ( $value !== 'yes' ) {
|
||||
if ( 'yes' !== $value ) {
|
||||
update_option( 'woocommerce_navigation_show_opt_out', 'yes' );
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ use Automattic\WooCommerce\Internal\DependencyManagement\ExtendedContainer;
|
|||
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\COTMigrationServiceProvider;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\DownloadPermissionsAdjusterServiceProvider;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\AssignDefaultCategoryServiceProvider;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\FeaturesServiceProvider;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\OrdersControllersServiceProvider;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\OrderAdminServiceProvider;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\OrderMetaBoxServiceProvider;
|
||||
|
@ -63,6 +64,7 @@ final class Container {
|
|||
BatchProcessingServiceProvider::class,
|
||||
OrderMetaBoxServiceProvider::class,
|
||||
OrderAdminServiceProvider::class,
|
||||
FeaturesServiceProvider::class,
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
|
@ -49,7 +49,6 @@ class Analytics {
|
|||
* Hook into WooCommerce.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_filter( 'woocommerce_settings_features', array( $this, 'add_feature_toggle' ) );
|
||||
add_action( 'update_option_' . self::TOGGLE_OPTION_NAME, array( $this, 'reload_page_on_toggle' ), 10, 2 );
|
||||
add_action( 'woocommerce_settings_saved', array( $this, 'maybe_reload_page' ) );
|
||||
|
||||
|
@ -66,24 +65,12 @@ class Analytics {
|
|||
/**
|
||||
* Add the feature toggle to the features settings.
|
||||
*
|
||||
* @deprecated 7.0 The WooCommerce Admin features are now handled by the WooCommerce features engine (see the FeaturesController class).
|
||||
*
|
||||
* @param array $features Feature sections.
|
||||
* @return array
|
||||
*/
|
||||
public static function add_feature_toggle( $features ) {
|
||||
$description = __(
|
||||
'Enables WooCommerce Analytics',
|
||||
'woocommerce'
|
||||
);
|
||||
|
||||
$features[] = array(
|
||||
'title' => __( 'Analytics', 'woocommerce' ),
|
||||
'desc' => $description,
|
||||
'id' => self::TOGGLE_OPTION_NAME,
|
||||
'type' => 'checkbox',
|
||||
'default' => 'yes',
|
||||
'class' => '',
|
||||
);
|
||||
|
||||
return $features;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
namespace Automattic\WooCommerce\Internal\DataStores\Orders;
|
||||
|
||||
use Automattic\WooCommerce\Internal\BatchProcessing\BatchProcessingController;
|
||||
use Automattic\WooCommerce\Internal\Features\FeaturesController;
|
||||
use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
@ -74,18 +75,16 @@ class CustomOrdersTableController {
|
|||
private $batch_processing_controller;
|
||||
|
||||
/**
|
||||
* Is the feature visible?
|
||||
* The features controller to use.
|
||||
*
|
||||
* @var bool
|
||||
* @var FeaturesController
|
||||
*/
|
||||
private $is_feature_visible;
|
||||
private $features_controller;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->is_feature_visible = false;
|
||||
|
||||
$this->init_hooks();
|
||||
}
|
||||
|
||||
|
@ -113,12 +112,19 @@ class CustomOrdersTableController {
|
|||
* @param DataSynchronizer $data_synchronizer The data synchronizer to use.
|
||||
* @param OrdersTableRefundDataStore $refund_data_store The refund data store to use.
|
||||
* @param BatchProcessingController $batch_processing_controller The batch processing controller to use.
|
||||
* @param FeaturesController $features_controller The features controller instance to use.
|
||||
*/
|
||||
final public function init( OrdersTableDataStore $data_store, DataSynchronizer $data_synchronizer, OrdersTableRefundDataStore $refund_data_store, BatchProcessingController $batch_processing_controller ) {
|
||||
final public function init(
|
||||
OrdersTableDataStore $data_store,
|
||||
DataSynchronizer $data_synchronizer,
|
||||
OrdersTableRefundDataStore $refund_data_store,
|
||||
BatchProcessingController $batch_processing_controller,
|
||||
FeaturesController $features_controller ) {
|
||||
$this->data_store = $data_store;
|
||||
$this->data_synchronizer = $data_synchronizer;
|
||||
$this->batch_processing_controller = $batch_processing_controller;
|
||||
$this->refund_data_store = $refund_data_store;
|
||||
$this->features_controller = $features_controller;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -127,21 +133,35 @@ class CustomOrdersTableController {
|
|||
* @return bool True if the feature is visible.
|
||||
*/
|
||||
public function is_feature_visible(): bool {
|
||||
return $this->is_feature_visible;
|
||||
return $this->features_controller->feature_is_enabled( 'custom_order_tables' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the feature visible, so that dedicated entries will be added to the debug tools page.
|
||||
*
|
||||
* This method shouldn't be used anymore, see the FeaturesController class.
|
||||
*/
|
||||
public function show_feature() {
|
||||
$this->is_feature_visible = true;
|
||||
$class_and_method = ( new \ReflectionClass( $this ) )->getShortName() . '::' . __FUNCTION__;
|
||||
wc_doing_it_wrong(
|
||||
$class_and_method,
|
||||
__( "${class_and_method}: The visibility of the custom orders table feature is now handled by the WooCommerce features engine. See the FeaturesController class, or go to WooCommerce - Settings - Advanced - Features.", 'woocommerce' ),
|
||||
'7.0'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the feature, so that no entries will be added to the debug tools page.
|
||||
*
|
||||
* This method shouldn't be used anymore, see the FeaturesController class.
|
||||
*/
|
||||
public function hide_feature() {
|
||||
$this->is_feature_visible = false;
|
||||
$class_and_method = ( new \ReflectionClass( $this ) )->getShortName() . '::' . __FUNCTION__;
|
||||
wc_doing_it_wrong(
|
||||
$class_and_method,
|
||||
__( "${class_and_method}: The visibility of the custom orders table feature is now handled by the WooCommerce features engine. See the FeaturesController class, or go to WooCommerce - Settings - Advanced - Features.", 'woocommerce' ),
|
||||
'7.0'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders;
|
||||
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\AbstractServiceProvider;
|
||||
use Automattic\WooCommerce\Internal\Features\FeaturesController;
|
||||
use Automattic\WooCommerce\Proxies\LegacyProxy;
|
||||
|
||||
/**
|
||||
* Service provider for the features enabling/disabling/compatibility engine.
|
||||
*/
|
||||
class FeaturesServiceProvider extends AbstractServiceProvider {
|
||||
|
||||
/**
|
||||
* The classes/interfaces that are serviced by this service provider.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $provides = array(
|
||||
FeaturesController::class,
|
||||
);
|
||||
|
||||
/**
|
||||
* Register the classes.
|
||||
*/
|
||||
public function register() {
|
||||
$this->share( FeaturesController::class )->addArgument( LegacyProxy::class );
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@ use Automattic\WooCommerce\Internal\DataStores\Orders\DataSynchronizer;
|
|||
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore;
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStoreMeta;
|
||||
use Automattic\WooCommerce\Internal\Features\FeaturesController;
|
||||
use Automattic\WooCommerce\Internal\Utilities\DatabaseUtil;
|
||||
|
||||
/**
|
||||
|
@ -51,6 +52,7 @@ class OrdersDataStoreServiceProvider extends AbstractServiceProvider {
|
|||
DataSynchronizer::class,
|
||||
OrdersTableRefundDataStore::class,
|
||||
BatchProcessingController::class,
|
||||
FeaturesController::class,
|
||||
)
|
||||
);
|
||||
if ( Constants::is_defined( 'WP_CLI' ) && WP_CLI ) {
|
||||
|
|
|
@ -0,0 +1,507 @@
|
|||
<?php
|
||||
/**
|
||||
* FeaturesController class file
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\Features;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Analytics;
|
||||
use Automattic\WooCommerce\Admin\Features\Navigation\Init;
|
||||
use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods;
|
||||
use Automattic\WooCommerce\Proxies\LegacyProxy;
|
||||
use Automattic\WooCommerce\Utilities\ArrayUtil;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Class to define the WooCommerce features that can be enabled and disabled by admin users,
|
||||
* provides also a mechanism for WooCommerce plugins to declare that they are compatible
|
||||
* (or incompatible) with a given feature.
|
||||
*/
|
||||
class FeaturesController {
|
||||
|
||||
use AccessiblePrivateMethods;
|
||||
|
||||
public const FEATURE_ENABLED_CHANGED_ACTION = 'woocommerce_feature_enabled_changed';
|
||||
|
||||
/**
|
||||
* The existing feature definitions.
|
||||
*
|
||||
* @var array[]
|
||||
*/
|
||||
private $features;
|
||||
|
||||
/**
|
||||
* The registered compatibility info for WooCommerce plugins, with plugin names as keys.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $compatibility_info_by_plugin;
|
||||
|
||||
/**
|
||||
* The registered compatibility info for WooCommerce plugins, with feature ids as keys.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $compatibility_info_by_feature;
|
||||
|
||||
/**
|
||||
* The LegacyProxy instance to use.
|
||||
*
|
||||
* @var LegacyProxy
|
||||
*/
|
||||
private $proxy;
|
||||
|
||||
/**
|
||||
* Creates a new instance of the class.
|
||||
*/
|
||||
public function __construct() {
|
||||
$features = array(
|
||||
'analytics' => array(
|
||||
'name' => __( 'Analytics', 'woocommerce' ),
|
||||
'description' => __( 'Enables WooCommerce Analytics', 'woocommerce' ),
|
||||
'is_experimental' => false,
|
||||
),
|
||||
'new_navigation' => array(
|
||||
'name' => __( 'Navigation', 'woocommerce' ),
|
||||
'description' => __( 'Adds the new WooCommerce navigation experience to the dashboard', 'woocommerce' ),
|
||||
'is_experimental' => false,
|
||||
),
|
||||
'custom_order_tables' => array(
|
||||
'name' => __( 'Custom order tables', 'woocommerce' ),
|
||||
'description' => __( 'Enable the custom orders tables feature (still in development)', 'woocommerce' ),
|
||||
'is_experimental' => true,
|
||||
),
|
||||
);
|
||||
|
||||
$this->init_features( $features );
|
||||
|
||||
foreach ( array_keys( $this->features ) as $feature_id ) {
|
||||
$this->compatibility_info_by_feature[ $feature_id ] = array(
|
||||
'compatible' => array(),
|
||||
'incompatible' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
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 );
|
||||
self::add_filter( 'woocommerce_get_settings_advanced', array( $this, 'add_feature_settings' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the class according to the existing features.
|
||||
*
|
||||
* @param array $features Information about the existing features.
|
||||
*/
|
||||
private function init_features( array $features ) {
|
||||
$this->compatibility_info_by_plugin = array();
|
||||
$this->compatibility_info_by_feature = array();
|
||||
|
||||
$this->features = $features;
|
||||
|
||||
foreach ( array_keys( $this->features ) as $feature_id ) {
|
||||
$this->compatibility_info_by_feature[ $feature_id ] = array(
|
||||
'compatible' => array(),
|
||||
'incompatible' => array(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the class instance.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param LegacyProxy $proxy The instance of LegacyProxy to use.
|
||||
*/
|
||||
final public function init( LegacyProxy $proxy ) {
|
||||
$this->proxy = $proxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the existing WooCommerce features.
|
||||
*
|
||||
* Returns an associative array where keys are unique feature ids
|
||||
* and values are arrays with these keys:
|
||||
*
|
||||
* - name (string)
|
||||
* - description (string)
|
||||
* - is_experimental (bool)
|
||||
* - is_enabled (bool) (only if $include_enabled_info is passed as true)
|
||||
*
|
||||
* @param bool $include_experimental Include also experimental/work in progress features in the list.
|
||||
* @param bool $include_enabled_info True to include the 'is_enabled' field in the returned features info.
|
||||
* @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;
|
||||
|
||||
if ( ! $include_experimental ) {
|
||||
$features = array_filter(
|
||||
$features,
|
||||
function( $feature ) {
|
||||
return ! $feature['is_experimental'];
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if ( $include_enabled_info ) {
|
||||
foreach ( array_keys( $features ) as $feature_id ) {
|
||||
$is_enabled = $this->feature_is_enabled( $feature_id );
|
||||
$features[ $feature_id ]['is_enabled'] = $is_enabled;
|
||||
}
|
||||
}
|
||||
|
||||
return $features;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given feature is currently enabled.
|
||||
*
|
||||
* @param string $feature_id Unique feature id.
|
||||
* @return bool True if the feature is enabled, false if not or if the feature doesn't exist.
|
||||
*/
|
||||
public function feature_is_enabled( string $feature_id ): bool {
|
||||
return $this->feature_exists( $feature_id ) && 'yes' === get_option( $this->feature_enable_option_name( $feature_id ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the enabled/disabled status of a feature.
|
||||
*
|
||||
* @param string $feature_id Unique feature id.
|
||||
* @param bool $enable True to enable the feature, false to disable it.
|
||||
* @return bool True on success, false if feature doesn't exist or the new value is the same as the old value.
|
||||
*/
|
||||
public function change_feature_enable( string $feature_id, bool $enable ): bool {
|
||||
if ( ! $this->feature_exists( $feature_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return update_option( $this->feature_enable_option_name( $feature_id ), $enable ? 'yes' : 'no' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare (in)compatibility with a given feature for a given plugin.
|
||||
*
|
||||
* This method MUST be executed from inside a handler for the 'before_woocommerce_init' hook.
|
||||
*
|
||||
* The plugin name is expected to be in the form 'directory/file.php' and be one of the keys
|
||||
* of the array returned by 'get_plugins', but this won't be checked. Plugins are expected to use
|
||||
* FeaturesUtil::declare_compatibility instead, passing the full plugin file path instead of the plugin name.
|
||||
*
|
||||
* @param string $feature_id Unique feature id.
|
||||
* @param string $plugin_name Plugin name, in the form 'directory/file.php'.
|
||||
* @param bool $positive_compatibility True if the plugin declares being compatible with the feature, false if it declares being incompatible.
|
||||
* @return bool True on success, false on error (feature doesn't exist or not inside the required hook).
|
||||
* @throws \Exception A plugin attempted to declare itself as compatible and incompatible with a given feature at the same time.
|
||||
*/
|
||||
public function declare_compatibility( string $feature_id, string $plugin_name, bool $positive_compatibility = true ): bool {
|
||||
if ( ! $this->proxy->call_function( 'doing_action', 'before_woocommerce_init' ) ) {
|
||||
$class_and_method = ( new \ReflectionClass( $this ) )->getShortName() . '::' . __FUNCTION__;
|
||||
/* translators: 1: class::method 2: before_woocommerce_init */
|
||||
$this->proxy->call_function( 'wc_doing_it_wrong', $class_and_method, sprintf( __( '%1$s should be called inside the %2$s action.', 'woocommerce' ), $class_and_method, 'before_woocommerce_init' ), '7.0' );
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $this->feature_exists( $feature_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Register compatibility by plugin.
|
||||
|
||||
ArrayUtil::ensure_key_is_array( $this->compatibility_info_by_plugin, $plugin_name );
|
||||
|
||||
$key = $positive_compatibility ? 'compatible' : 'incompatible';
|
||||
$opposite_key = $positive_compatibility ? 'incompatible' : 'compatible';
|
||||
ArrayUtil::ensure_key_is_array( $this->compatibility_info_by_plugin[ $plugin_name ], $key );
|
||||
ArrayUtil::ensure_key_is_array( $this->compatibility_info_by_plugin[ $plugin_name ], $opposite_key );
|
||||
|
||||
if ( in_array( $feature_id, $this->compatibility_info_by_plugin[ $plugin_name ][ $opposite_key ], true ) ) {
|
||||
throw new \Exception( "Plugin $plugin_name is trying to declare itself as $key with the '$feature_id' feature, but it already declared itself as $opposite_key" );
|
||||
}
|
||||
|
||||
if ( ! in_array( $feature_id, $this->compatibility_info_by_plugin[ $plugin_name ][ $key ], true ) ) {
|
||||
$this->compatibility_info_by_plugin[ $plugin_name ][ $key ][] = $feature_id;
|
||||
}
|
||||
|
||||
// Register compatibility by feature.
|
||||
|
||||
$key = $positive_compatibility ? 'compatible' : 'incompatible';
|
||||
|
||||
if ( ! in_array( $plugin_name, $this->compatibility_info_by_feature[ $feature_id ][ $key ], true ) ) {
|
||||
$this->compatibility_info_by_feature[ $feature_id ][ $key ][] = $plugin_name;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a feature exists with a given id.
|
||||
*
|
||||
* @param string $feature_id The feature id to check.
|
||||
* @return bool True if the feature exists.
|
||||
*/
|
||||
private function feature_exists( string $feature_id ): bool {
|
||||
return isset( $this->features[ $feature_id ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ids of the features that a certain plugin has declared compatibility for.
|
||||
*
|
||||
* This method can't be called before the 'woocommerce_init' hook is fired.
|
||||
*
|
||||
* @param string $plugin_name Plugin name, in the form 'directory/file.php'.
|
||||
* @return array An array having a 'compatible' and an 'incompatible' key, each holding an array of feature ids.
|
||||
*/
|
||||
public function get_compatible_features_for_plugin( string $plugin_name ) : array {
|
||||
$this->verify_did_woocommerce_init( __FUNCTION__ );
|
||||
|
||||
if ( ! isset( $this->compatibility_info_by_plugin[ $plugin_name ] ) ) {
|
||||
return array(
|
||||
'compatible' => array(),
|
||||
'incompatible' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
return $this->compatibility_info_by_plugin[ $plugin_name ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the names of the plugins that have been declared compatible or incompatible with a given feature.
|
||||
*
|
||||
* @param string $feature_id Feature id.
|
||||
* @return array An array having a 'compatible' and an 'incompatible' key, each holding an array of plugin names.
|
||||
*/
|
||||
public function get_compatible_plugins_for_feature( string $feature_id ) : array {
|
||||
$this->verify_did_woocommerce_init( __FUNCTION__ );
|
||||
|
||||
if ( ! $this->feature_exists( $feature_id ) ) {
|
||||
return array(
|
||||
'compatible' => array(),
|
||||
'incompatible' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
return $this->compatibility_info_by_feature[ $feature_id ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the 'woocommerce_init' has run or is running, do a 'wc_doing_it_wrong' if not.
|
||||
*
|
||||
* @param string $function Name of the invoking method.
|
||||
*/
|
||||
private function verify_did_woocommerce_init( string $function ) {
|
||||
if ( ! $this->proxy->call_function( 'did_action', 'woocommerce_init' ) &&
|
||||
! $this->proxy->call_function( 'doing_action', 'woocommerce_init' ) ) {
|
||||
$class_and_method = ( new \ReflectionClass( $this ) )->getShortName() . '::' . $function;
|
||||
/* translators: 1: class::method 2: plugins_loaded */
|
||||
$this->proxy->call_function( 'wc_doing_it_wrong', $class_and_method, sprintf( __( '%1$s should not be called before the %2$s action.', 'woocommerce' ), $class_and_method, 'woocommerce_init' ), '7.0' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @return string The option that enables or disables the feature.
|
||||
*/
|
||||
public function feature_enable_option_name( string $feature_id ): string {
|
||||
if ( 'analytics' === $feature_id ) {
|
||||
return Analytics::TOGGLE_OPTION_NAME;
|
||||
} elseif ( 'new_navigation' === $feature_id ) {
|
||||
return Init::TOGGLE_OPTION_NAME;
|
||||
}
|
||||
|
||||
return "woocommerce_feature_${feature_id}_enabled";
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for the 'added_option' hook.
|
||||
*
|
||||
* It fires FEATURE_ENABLED_CHANGED_ACTION when a feature is enabled or disabled.
|
||||
*
|
||||
* @param string $option The option that has been created.
|
||||
* @param mixed $value The value of the option.
|
||||
*/
|
||||
private function process_added_option( string $option, $value ) {
|
||||
$this->process_updated_option( $option, false, $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for the 'updated_option' hook.
|
||||
*
|
||||
* It fires FEATURE_ENABLED_CHANGED_ACTION when a feature is enabled or disabled.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
private function process_updated_option( string $option, $old_value, $value ) {
|
||||
$matches = array();
|
||||
$success = preg_match( '/^woocommerce_feature_([a-zA-Z0-9_]+)_enabled$/', $option, $matches );
|
||||
|
||||
if ( ! $success ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $value === $old_value ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$feature_id = $matches[1];
|
||||
|
||||
/**
|
||||
* Action triggered when a feature is enabled or disabled (the value of the corresponding setting option is changed).
|
||||
*
|
||||
* @param string $feature_id The id of the feature.
|
||||
* @param bool $enabled True if the feature has been enabled, false if it has been disabled.
|
||||
*
|
||||
* @since 7.0.0
|
||||
*/
|
||||
do_action( self::FEATURE_ENABLED_CHANGED_ACTION, $feature_id, 'yes' === $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for the 'woocommerce_get_sections_advanced' hook,
|
||||
* it adds the "Features" section to the advanced settings page.
|
||||
*
|
||||
* @param array $sections The original sections array.
|
||||
* @return array The updated sections array.
|
||||
*/
|
||||
private function add_features_section( $sections ) {
|
||||
if ( ! isset( $sections['features'] ) ) {
|
||||
$sections['features'] = __( 'Features', 'woocommerce' );
|
||||
}
|
||||
return $sections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for the 'woocommerce_get_settings_advanced' hook,
|
||||
* it adds the settings UI for all the existing features.
|
||||
*
|
||||
* Note that the settings added via the 'woocommerce_settings_features' hook will be
|
||||
* displayed in the non-experimental features section.
|
||||
*
|
||||
* @param array $settings The existing settings for the corresponding settings section.
|
||||
* @param string $current_section The section to get the settings for.
|
||||
* @return array The updated settings array.
|
||||
*/
|
||||
private function add_feature_settings( $settings, $current_section ): array {
|
||||
if ( 'features' !== $current_section ) {
|
||||
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 =
|
||||
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',
|
||||
),
|
||||
);
|
||||
|
||||
$features = $this->get_features( true );
|
||||
|
||||
$feature_ids = array_keys( $features );
|
||||
$experimental_feature_ids = array_filter(
|
||||
$feature_ids,
|
||||
function( $feature_id ) use ( $features ) {
|
||||
return $features[ $feature_id ]['is_experimental'];
|
||||
}
|
||||
);
|
||||
$mature_feature_ids = array_diff( $feature_ids, $experimental_feature_ids );
|
||||
$feature_ids = array_merge( $mature_feature_ids, array( 'mature_features_end' ), $experimental_feature_ids );
|
||||
|
||||
foreach ( $feature_ids as $id ) {
|
||||
if ( 'mature_features_end' === $id ) {
|
||||
// phpcs:disable WooCommerce.Commenting.CommentHooks.MissingSinceComment
|
||||
/**
|
||||
* Filter allowing to add additional settings to the WooCommerce Advanced - Features settings page.
|
||||
*
|
||||
* @param bool $disabled False.
|
||||
*/
|
||||
$additional_features = apply_filters( 'woocommerce_settings_features', $features );
|
||||
// phpcs:enable WooCommerce.Commenting.CommentHooks.MissingSinceComment
|
||||
|
||||
$feature_settings = array_merge( $feature_settings, $additional_features );
|
||||
|
||||
if ( ! empty( $experimental_feature_ids ) ) {
|
||||
$feature_settings[] = array(
|
||||
'type' => 'sectionend',
|
||||
'id' => 'features_options',
|
||||
);
|
||||
|
||||
$feature_settings[] = array(
|
||||
'title' => __( 'Experimental features', 'woocommerce' ),
|
||||
'type' => 'title',
|
||||
'desc' => __( 'These features are either experimental or incomplete, enable them at your own risk!', 'woocommerce' ),
|
||||
'id' => 'experimental_features_options',
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$feature_settings[] = $this->get_setting_for_feature( $id, $features[ $id ], $admin_features_disabled );
|
||||
}
|
||||
|
||||
$feature_settings[] = array(
|
||||
'type' => 'sectionend',
|
||||
'id' => empty( $experimental_feature_ids ) ? 'features_options' : 'experimental_features_options',
|
||||
);
|
||||
|
||||
return $feature_settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parameters to display the setting enable/disable UI for a given feature.
|
||||
*
|
||||
* @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 = '';
|
||||
|
||||
if ( ( 'analytics' === $feature_id || 'new_navigation' === $feature_id ) && $admin_features_disabled ) {
|
||||
$disabled = true;
|
||||
$desc_tip = __( 'WooCommerce Admin has been disabled', 'woocommerce' );
|
||||
} elseif ( 'new_navigation' === $feature_id ) {
|
||||
$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.
|
||||
__( '%1$s %2$sUpdate WordPress to enable the new navigation%3$s', 'woocommerce' ),
|
||||
'<br/>',
|
||||
'<a href="' . self_admin_url( 'update-core.php' ) . '" target="_blank">',
|
||||
'</a>'
|
||||
);
|
||||
$description .= $update_text;
|
||||
$disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'title' => $feature['name'],
|
||||
'desc' => $description,
|
||||
'type' => 'checkbox',
|
||||
'id' => $this->feature_enable_option_name( $feature_id ),
|
||||
'class' => $disabled ? 'disabled' : '',
|
||||
'desc_tip' => $desc_tip,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -184,5 +184,28 @@ class ArrayUtil {
|
|||
$array[] = $value;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that an associative array has a given key, and if not, set the key to an empty array.
|
||||
*
|
||||
* @param array $array The array to check.
|
||||
* @param string $key The key to check.
|
||||
* @param bool $throw_if_existing_is_not_array If true, an exception will be thrown if the key already exists in the array but the value is not an array.
|
||||
* @return bool True if the key has been added to the array, false if not (the key already existed).
|
||||
* @throws \Exception The key already exists in the array but the value is not an array.
|
||||
*/
|
||||
public static function ensure_key_is_array( array &$array, string $key, bool $throw_if_existing_is_not_array = false ): bool {
|
||||
if ( ! isset( $array[ $key ] ) ) {
|
||||
$array[ $key ] = array();
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( $throw_if_existing_is_not_array && ! is_array( $array[ $key ] ) ) {
|
||||
$type = is_object( $array[ $key ] ) ? get_class( $array[ $key ] ) : gettype( $array[ $key ] );
|
||||
throw new \Exception( "Array key exists but it's not an array, it's a {$type}" );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
/**
|
||||
* FeaturesUtil class file.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Utilities;
|
||||
|
||||
use Automattic\WooCommerce\Internal\Features\FeaturesController;
|
||||
|
||||
/**
|
||||
* Class with methods that allow to retrieve information about the existing WooCommerce features,
|
||||
* also has methods for WooCommerce plugins to declare (in)compatibility with the features.
|
||||
*/
|
||||
class FeaturesUtil {
|
||||
|
||||
/**
|
||||
* Get all the existing WooCommerce features.
|
||||
*
|
||||
* Returns an associative array where keys are unique feature ids
|
||||
* and values are arrays with these keys:
|
||||
*
|
||||
* - name
|
||||
* - description
|
||||
* - is_experimental
|
||||
* - is_enabled (if $include_enabled_info is passed as true)
|
||||
*
|
||||
* @param bool $include_experimental Include also experimental/work in progress features in the list.
|
||||
* @param bool $include_enabled_info True to include the 'is_enabled' field in the returned features info.
|
||||
* @returns array An array of information about existing features.
|
||||
*/
|
||||
public static function get_features( bool $include_experimental = false, bool $include_enabled_info = false ): array {
|
||||
return wc_get_container()->get( FeaturesController::class )->get_features( $include_experimental, $include_enabled_info );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given feature is currently enabled.
|
||||
*
|
||||
* @param string $feature_id Unique feature id.
|
||||
* @return bool True if the feature is enabled, false if not or if the feature doesn't exist.
|
||||
*/
|
||||
public static function feature_is_enabled( string $feature_id ): bool {
|
||||
return wc_get_container()->get( FeaturesController::class )->feature_is_enabled( $feature_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare (in)compatibility with a given feature for a given plugin.
|
||||
*
|
||||
* This method MUST be executed from inside a handler for the 'before_woocommerce_init' hook and
|
||||
* SHOULD be executed from the main plugin file passing __FILE__ for the $plugin_file argument.
|
||||
*
|
||||
* @param string $feature_id Unique feature id.
|
||||
* @param string $plugin_file The full plugin file path.
|
||||
* @param bool $positive_compatibility True if the plugin declares being compatible with the feature, false if it declares being incompatible.
|
||||
* @return bool True on success, false on error (feature doesn't exist or not inside the required hook).
|
||||
* @throws \Exception A plugin attempted to declare itself as compatible and incompatible with a given feature at the same time.
|
||||
*/
|
||||
public static function declare_compatibility( string $feature_id, string $plugin_file, bool $positive_compatibility = true ): bool {
|
||||
$plugin_name = StringUtil::plugin_name_from_plugin_file( $plugin_file );
|
||||
|
||||
if ( ! isset( get_plugins()[ $plugin_name ] ) ) {
|
||||
throw new \Exception( "FeaturesUtil::declare_compatibility: ${plugin_name} is not a known WordPress plugin." );
|
||||
}
|
||||
|
||||
return wc_get_container()->get( FeaturesController::class )->declare_compatibility( $feature_id, $plugin_name, $positive_compatibility );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ids of the features that a certain plugin has declared compatibility for.
|
||||
*
|
||||
* This method can't be called before the 'woocommerce_init' hook is fired.
|
||||
*
|
||||
* @param string $plugin_name Plugin name, in the form 'directory/file.php'.
|
||||
* @return array An array having a 'compatible' and an 'incompatible' key, each holding an array of plugin ids.
|
||||
*/
|
||||
public static function get_compatible_features_for_plugin( string $plugin_name ): array {
|
||||
return wc_get_container()->get( FeaturesController::class )->get_compatible_features_for_plugin( $plugin_name );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the names of the plugins that have been declared compatible or incompatible with a given feature.
|
||||
*
|
||||
* @param string $feature_id Feature id.
|
||||
* @return array An array having a 'compatible' and an 'incompatible' key, each holding an array of plugin names.
|
||||
*/
|
||||
public static function get_compatible_plugins_for_feature( string $feature_id ): array {
|
||||
return wc_get_container()->get( FeaturesController::class )->get_compatible_plugins_for_feature( $feature_id );
|
||||
}
|
||||
}
|
|
@ -73,4 +73,14 @@ final class StringUtil {
|
|||
return false !== stripos( $string, $contained );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of a plugin in the form 'directory/file.php', as in the keys of the array returned by 'get_plugins'.
|
||||
*
|
||||
* @param string $plugin_file_path The path of the main plugin file (can be passed as __FILE__ from the plugin itself).
|
||||
* @return string The name of the plugin in the form 'directory/file.php'.
|
||||
*/
|
||||
public static function plugin_name_from_plugin_file( string $plugin_file_path ): string {
|
||||
return basename( dirname( $plugin_file_path ) ) . DIRECTORY_SEPARATOR . basename( $plugin_file_path );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ defined( 'ABSPATH' ) || exit;
|
|||
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\Features\FeaturesController;
|
||||
use WC_Mock_Payment_Gateway;
|
||||
use WC_Order;
|
||||
use WC_Product;
|
||||
|
@ -140,9 +141,9 @@ class OrderHelper {
|
|||
* Helper method to drop custom tables if present.
|
||||
*/
|
||||
public static function delete_order_custom_tables() {
|
||||
$order_table_controller = wc_get_container()
|
||||
->get( CustomOrdersTableController::class );
|
||||
$order_table_controller->show_feature();
|
||||
$features_controller = wc_get_container()->get( Featurescontroller::class );
|
||||
$features_controller->change_feature_enable( 'custom_order_tables', true );
|
||||
|
||||
$synchronizer = wc_get_container()
|
||||
->get( DataSynchronizer::class );
|
||||
if ( $synchronizer->check_orders_table_exists() ) {
|
||||
|
@ -154,11 +155,10 @@ class OrderHelper {
|
|||
* Helper method to create custom tables if not present.
|
||||
*/
|
||||
public static function create_order_custom_table_if_not_exist() {
|
||||
$order_table_controller = wc_get_container()
|
||||
->get( CustomOrdersTableController::class );
|
||||
$order_table_controller->show_feature();
|
||||
$synchronizer = wc_get_container()
|
||||
->get( DataSynchronizer::class );
|
||||
$features_controller = wc_get_container()->get( Featurescontroller::class );
|
||||
$features_controller->change_feature_enable( 'custom_order_tables', true );
|
||||
|
||||
$synchronizer = wc_get_container()->get( DataSynchronizer::class );
|
||||
if ( ! $synchronizer->check_orders_table_exists() ) {
|
||||
$synchronizer->create_database_tables();
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\DataSynchronizer;
|
||||
use Automattic\WooCommerce\Internal\Features\FeaturesController;
|
||||
use Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper;
|
||||
|
||||
/**
|
||||
|
@ -14,11 +15,6 @@ class DataSynchronizerTests extends WC_Unit_Test_Case {
|
|||
*/
|
||||
private $sut;
|
||||
|
||||
/**
|
||||
* @var CustomOrdersTableController
|
||||
*/
|
||||
private $cot_controller;
|
||||
|
||||
/**
|
||||
* Initializes system under test.
|
||||
*/
|
||||
|
@ -29,9 +25,9 @@ class DataSynchronizerTests extends WC_Unit_Test_Case {
|
|||
remove_filter( 'query', array( $this, '_drop_temporary_tables' ) );
|
||||
OrderHelper::delete_order_custom_tables(); // We need this since non-temporary tables won't drop automatically.
|
||||
OrderHelper::create_order_custom_table_if_not_exist();
|
||||
$this->sut = wc_get_container()->get( DataSynchronizer::class );
|
||||
$this->cot_controller = wc_get_container()->get( CustomOrdersTableController::class );
|
||||
$this->cot_controller->show_feature();
|
||||
$this->sut = wc_get_container()->get( DataSynchronizer::class );
|
||||
$features_controller = wc_get_container()->get( Featurescontroller::class );
|
||||
$features_controller->change_feature_enable( 'custom_order_tables', true );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
use Automattic\WooCommerce\Database\Migrations\CustomOrderTable\PostsToOrdersMigrationController;
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore;
|
||||
use Automattic\WooCommerce\Internal\Features\FeaturesController;
|
||||
use Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper;
|
||||
|
||||
if ( ! class_exists( 'WC_REST_Orders_Controller_Tests' ) ) {
|
||||
|
@ -79,12 +80,14 @@ class OrdersTableDataStoreRestOrdersControllerTests extends \WC_REST_Orders_Cont
|
|||
* @return void
|
||||
*/
|
||||
private function toggle_cot( bool $enabled ): void {
|
||||
$controller = wc_get_container()->get( CustomOrdersTableController::class )->show_feature();
|
||||
$features_controller = wc_get_container()->get( Featurescontroller::class );
|
||||
$features_controller->change_feature_enable( 'custom_order_tables', true );
|
||||
|
||||
update_option( CustomOrdersTableController::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION, wc_bool_to_string( $enabled ) );
|
||||
|
||||
// Confirm things are really correct.
|
||||
$wc_data_store = WC_Data_Store::load( 'order' );
|
||||
assert( $enabled === is_a( $wc_data_store->get_current_class_name(), OrdersTableDataStore::class, true ) );
|
||||
assert( is_a( $wc_data_store->get_current_class_name(), OrdersTableDataStore::class, true ) === $enabled );
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,557 @@
|
|||
<?php
|
||||
/**
|
||||
* FeaturesControllerTest class file.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Tests\Internal\Features;
|
||||
|
||||
use Automattic\WooCommerce\Internal\Features\FeaturesController;
|
||||
use Automattic\WooCommerce\Admin\Features\Analytics;
|
||||
use Automattic\WooCommerce\Admin\Features\Navigation\Init;
|
||||
|
||||
/**
|
||||
* Tests for the FeaturesController class.
|
||||
*/
|
||||
class FeaturesControllerTest extends \WC_Unit_Test_Case {
|
||||
/**
|
||||
* The system under test.
|
||||
*
|
||||
* @var FeaturesController
|
||||
*/
|
||||
private $sut;
|
||||
|
||||
/**
|
||||
* Runs before each test.
|
||||
*/
|
||||
public function setUp(): void {
|
||||
$this->reset_container_resolutions();
|
||||
$this->reset_legacy_proxy_mocks();
|
||||
|
||||
$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 1',
|
||||
'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,
|
||||
),
|
||||
);
|
||||
|
||||
$this->sut = $this->get_instance_of( FeaturesController::class );
|
||||
$init_features_method = new \ReflectionMethod( $this->sut, 'init_features' );
|
||||
$init_features_method->setAccessible( true );
|
||||
$init_features_method->invoke( $this->sut, $features );
|
||||
|
||||
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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 );
|
||||
|
||||
$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 1',
|
||||
'is_experimental' => false,
|
||||
),
|
||||
);
|
||||
|
||||
$this->assertEquals( $expected, $actual );
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 );
|
||||
|
||||
$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 1',
|
||||
'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,
|
||||
),
|
||||
);
|
||||
|
||||
$this->assertEquals( $expected, $actual );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'get_features' returns all existing features with enabling information if requested to do so.
|
||||
*/
|
||||
public function test_get_features_including_experimental_and_values() {
|
||||
update_option( 'woocommerce_feature_mature1_enabled', 'yes' );
|
||||
update_option( 'woocommerce_feature_mature2_enabled', 'no' );
|
||||
update_option( 'woocommerce_feature_experimental1_enabled', 'yes' );
|
||||
// No option for experimental2.
|
||||
|
||||
$actual = $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,
|
||||
),
|
||||
'mature2' => array(
|
||||
'name' => 'Mature feature 2',
|
||||
'description' => 'The mature feature number 1',
|
||||
'is_experimental' => false,
|
||||
'is_enabled' => false,
|
||||
),
|
||||
'experimental1' => array(
|
||||
'name' => 'Experimental feature 1',
|
||||
'description' => 'The experimental feature number 1',
|
||||
'is_experimental' => true,
|
||||
'is_enabled' => true,
|
||||
),
|
||||
'experimental2' => array(
|
||||
'name' => 'Experimental feature 2',
|
||||
'description' => 'The experimental feature number 2',
|
||||
'is_experimental' => true,
|
||||
'is_enabled' => false,
|
||||
),
|
||||
);
|
||||
|
||||
$this->assertEquals( $expected, $actual );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'feature_is_enabled' returns whether a feature is enabled, and returns false for invalid feature ids.
|
||||
*
|
||||
* @testWith ["mature1", true]
|
||||
* ["mature2", false]
|
||||
* ["experimental1", false]
|
||||
* ["NOT_EXISTING", false]
|
||||
*
|
||||
* @param string $feature_id Feature id to check.
|
||||
* @param bool $expected_to_be_enabled Expected result from the method.
|
||||
*/
|
||||
public function test_feature_is_enabled( $feature_id, $expected_to_be_enabled ) {
|
||||
update_option( 'woocommerce_feature_mature1_enabled', 'yes' );
|
||||
update_option( 'woocommerce_feature_mature2_enabled', 'no' );
|
||||
// No option for experimental1.
|
||||
|
||||
$this->assertEquals( $expected_to_be_enabled, $this->sut->feature_is_enabled( $feature_id ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'change_feature_enable' does nothing and returns false for an invalid feature id.
|
||||
*/
|
||||
public function test_change_feature_enable_for_non_existing_feature() {
|
||||
$result = $this->sut->change_feature_enable( 'NON_EXISTING', true );
|
||||
$this->assertFalse( $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'change_feature_enabled' works as expected with and without previous values for the feature enable options.
|
||||
*
|
||||
* @testWith [null, false, true, false, false]
|
||||
* [null, true, true, false, true]
|
||||
* ["no", false, false, false, false]
|
||||
* ["no", true, true, false, true]
|
||||
* ["yes", false, true, true, false]
|
||||
* ["yes", true, false, true, true]
|
||||
*
|
||||
* @param string|null $previous_value The previous value of the feature enable option.
|
||||
* @param bool $enable Whether the feature will be enabled or disabled.
|
||||
* @param bool $expected_result Expected value to be returned by 'change_feature_enable'.
|
||||
* @param bool $expected_previous_enabled Expected value to be returned by 'feature_is_enabled' before the feature status is changed.
|
||||
* @param bool $expected_new_enabled Expected value to be returned by 'feature_is_enabled' after the feature status is changed.
|
||||
*/
|
||||
public function test_change_feature_enable( $previous_value, $enable, $expected_result, $expected_previous_enabled, $expected_new_enabled ) {
|
||||
if ( $previous_value ) {
|
||||
update_option( 'woocommerce_feature_mature1_enabled', $previous_value );
|
||||
}
|
||||
|
||||
$result = $this->sut->feature_is_enabled( 'mature1' );
|
||||
$this->assertEquals( $expected_previous_enabled, $result );
|
||||
|
||||
$result = $this->sut->change_feature_enable( 'mature1', $enable );
|
||||
$this->assertEquals( $result, $expected_result );
|
||||
|
||||
$result = $this->sut->feature_is_enabled( 'mature1' );
|
||||
$this->assertEquals( $expected_new_enabled, $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'declare_compatibility' fails when invoked from outside the 'before_woocommerce_init' action.
|
||||
*/
|
||||
public function test_declare_compatibility_outside_before_woocommerce_init_hook() {
|
||||
$function = null;
|
||||
$message = null;
|
||||
$version = null;
|
||||
|
||||
$this->register_legacy_proxy_function_mocks(
|
||||
array(
|
||||
'wc_doing_it_wrong' => function( $f, $m, $v ) use ( &$function, &$message, &$version ) {
|
||||
$function = $f;
|
||||
$message = $m;
|
||||
$version = $v;
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
$result = $this->sut->declare_compatibility( 'mature1', 'the_plugin' );
|
||||
$this->assertFalse( $result );
|
||||
|
||||
$this->assertEquals( 'FeaturesController::declare_compatibility', $function );
|
||||
$this->assertEquals( 'FeaturesController::declare_compatibility should be called inside the before_woocommerce_init action.', $message );
|
||||
$this->assertEquals( '7.0', $version );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'declare_compatibility' returns false for invalid feature ids.
|
||||
*/
|
||||
public function test_declare_compatibility_for_non_existing_feature() {
|
||||
$this->simulate_inside_before_woocommerce_init_hook();
|
||||
|
||||
$result = $this->sut->declare_compatibility( 'NON_EXISTING', 'the_plugin' );
|
||||
$this->assertFalse( $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'declare_compatibility' registers internally the proper per-plugin information.
|
||||
*/
|
||||
public function test_declare_compatibility_by_plugin() {
|
||||
$this->simulate_inside_before_woocommerce_init_hook();
|
||||
|
||||
$result = $this->sut->declare_compatibility( 'mature1', 'the_plugin' );
|
||||
$this->assertTrue( $result );
|
||||
$result = $this->sut->declare_compatibility( 'experimental1', 'the_plugin' );
|
||||
$this->assertTrue( $result );
|
||||
$result = $this->sut->declare_compatibility( 'experimental2', 'the_plugin', false );
|
||||
$this->assertTrue( $result );
|
||||
// Duplicate declaration is ok:.
|
||||
$result = $this->sut->declare_compatibility( 'experimental2', 'the_plugin', false );
|
||||
$this->assertTrue( $result );
|
||||
|
||||
$compatibility_info_prop = new \ReflectionProperty( $this->sut, 'compatibility_info_by_plugin' );
|
||||
$compatibility_info_prop->setAccessible( true );
|
||||
$compatibility_info = $compatibility_info_prop->getValue( $this->sut );
|
||||
|
||||
$expected = array(
|
||||
'the_plugin' => array(
|
||||
'compatible' => array(
|
||||
'mature1',
|
||||
'experimental1',
|
||||
),
|
||||
'incompatible' => array(
|
||||
'experimental2',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$this->assertEquals( $expected, $compatibility_info );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'declare_compatibility' registers internally the proper per-feature information.
|
||||
*/
|
||||
public function test_declare_compatibility_by_feature() {
|
||||
$this->simulate_inside_before_woocommerce_init_hook();
|
||||
|
||||
$result = $this->sut->declare_compatibility( 'mature1', 'the_plugin_1' );
|
||||
$this->assertTrue( $result );
|
||||
$result = $this->sut->declare_compatibility( 'mature1', 'the_plugin_2' );
|
||||
$this->assertTrue( $result );
|
||||
$result = $this->sut->declare_compatibility( 'mature1', 'the_plugin_3', false );
|
||||
$this->assertTrue( $result );
|
||||
$result = $this->sut->declare_compatibility( 'experimental1', 'the_plugin_1', false );
|
||||
$this->assertTrue( $result );
|
||||
$result = $this->sut->declare_compatibility( 'experimental2', 'the_plugin_2', true );
|
||||
$this->assertTrue( $result );
|
||||
|
||||
$compatibility_info_prop = new \ReflectionProperty( $this->sut, 'compatibility_info_by_feature' );
|
||||
$compatibility_info_prop->setAccessible( true );
|
||||
$compatibility_info = $compatibility_info_prop->getValue( $this->sut );
|
||||
|
||||
$expected = array(
|
||||
'mature1' => array(
|
||||
'compatible' => array(
|
||||
'the_plugin_1',
|
||||
'the_plugin_2',
|
||||
),
|
||||
'incompatible' => array(
|
||||
'the_plugin_3',
|
||||
),
|
||||
),
|
||||
'mature2' => array(
|
||||
'compatible' => array(),
|
||||
'incompatible' => array(),
|
||||
),
|
||||
'experimental1' => array(
|
||||
'compatible' => array(),
|
||||
'incompatible' => array(
|
||||
'the_plugin_1',
|
||||
),
|
||||
),
|
||||
'experimental2' => array(
|
||||
'compatible' => array(
|
||||
'the_plugin_2',
|
||||
),
|
||||
'incompatible' => array(),
|
||||
),
|
||||
);
|
||||
|
||||
$this->assertEquals( $expected, $compatibility_info );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'declare_compatibility' throws when a plugin declares itself as both compatible and incompatible with a given feature.
|
||||
*/
|
||||
public function test_declare_compatibility_and_incompatibility_for_the_same_plugin() {
|
||||
$this->simulate_inside_before_woocommerce_init_hook();
|
||||
|
||||
$this->ExpectException( \Exception::class );
|
||||
$this->ExpectExceptionMessage( "Plugin the_plugin is trying to declare itself as incompatible with the 'mature1' feature, but it already declared itself as compatible" );
|
||||
|
||||
$this->sut->declare_compatibility( 'mature1', 'the_plugin', true );
|
||||
$this->sut->declare_compatibility( 'mature1', 'the_plugin', false );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'get_compatible_features_for_plugin' fails when invoked before the 'woocommerce_init' hook.
|
||||
*/
|
||||
public function test_get_compatible_features_for_plugin_before_woocommerce_init_hook() {
|
||||
$function = null;
|
||||
$message = null;
|
||||
$version = null;
|
||||
|
||||
$this->register_legacy_proxy_function_mocks(
|
||||
array(
|
||||
'did_action' => function( $action_name ) {
|
||||
return 'woocommerce_init' === $action_name ? false : did_action( $action_name );
|
||||
},
|
||||
'wc_doing_it_wrong' => function( $f, $m, $v ) use ( &$function, &$message, &$version ) {
|
||||
$function = $f;
|
||||
$message = $m;
|
||||
$version = $v;
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
$this->sut->get_compatible_features_for_plugin( 'the_plugin' );
|
||||
|
||||
$this->assertEquals( 'FeaturesController::get_compatible_features_for_plugin', $function );
|
||||
$this->assertEquals( 'FeaturesController::get_compatible_features_for_plugin should not be called before the woocommerce_init action.', $message );
|
||||
$this->assertEquals( '7.0', $version );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'get_compatible_features_for_plugin' returns empty information for a plugin that has not declared compatibility with any feature.
|
||||
*/
|
||||
public function test_get_compatible_features_for_unregistered_plugin() {
|
||||
$this->simulate_after_woocommerce_init_hook();
|
||||
|
||||
$result = $this->sut->get_compatible_features_for_plugin( 'the_plugin' );
|
||||
|
||||
$expected = array(
|
||||
'compatible' => array(),
|
||||
'incompatible' => array(),
|
||||
);
|
||||
$this->assertEquals( $expected, $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'get_compatible_features_for_plugin' returns proper information for a plugin that has declared compatibility with the passed feature.
|
||||
*/
|
||||
public function test_get_compatible_features_for_registered_plugin() {
|
||||
$this->simulate_inside_before_woocommerce_init_hook();
|
||||
|
||||
$this->sut->declare_compatibility( 'mature1', 'the_plugin', true );
|
||||
$this->sut->declare_compatibility( 'mature2', 'the_plugin', true );
|
||||
$this->sut->declare_compatibility( 'experimental1', 'the_plugin', false );
|
||||
$this->sut->declare_compatibility( 'experimental2', 'the_plugin', false );
|
||||
|
||||
$this->reset_legacy_proxy_mocks();
|
||||
|
||||
$this->simulate_after_woocommerce_init_hook();
|
||||
|
||||
$result = $this->sut->get_compatible_features_for_plugin( 'the_plugin' );
|
||||
|
||||
$expected = array(
|
||||
'compatible' => array( 'mature1', 'mature2' ),
|
||||
'incompatible' => array( 'experimental1', 'experimental2' ),
|
||||
);
|
||||
$this->assertEquals( $expected, $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'get_compatible_plugins_for_feature' fails when invoked before the 'woocommerce_init' hook.
|
||||
*/
|
||||
public function test_get_compatible_plugins_for_feature_before_woocommerce_init_hook() {
|
||||
$function = null;
|
||||
$message = null;
|
||||
$version = null;
|
||||
|
||||
$this->register_legacy_proxy_function_mocks(
|
||||
array(
|
||||
'did_action' => function( $action_name ) {
|
||||
return 'woocommerce_init' === $action_name ? false : did_action( $action_name );
|
||||
},
|
||||
'wc_doing_it_wrong' => function( $f, $m, $v ) use ( &$function, &$message, &$version ) {
|
||||
$function = $f;
|
||||
$message = $m;
|
||||
$version = $v;
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
$this->sut->get_compatible_plugins_for_feature( 'mature1' );
|
||||
|
||||
$this->assertEquals( 'FeaturesController::get_compatible_plugins_for_feature', $function );
|
||||
$this->assertEquals( 'FeaturesController::get_compatible_plugins_for_feature should not be called before the woocommerce_init action.', $message );
|
||||
$this->assertEquals( '7.0', $version );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'get_compatible_plugins_for_feature' returns empty information for invalid feature ids.
|
||||
*/
|
||||
public function test_get_compatible_plugins_for_non_existing_feature() {
|
||||
$this->simulate_after_woocommerce_init_hook();
|
||||
|
||||
$result = $this->sut->get_compatible_plugins_for_feature( 'NON_EXISTING' );
|
||||
|
||||
$expected = array(
|
||||
'compatible' => array(),
|
||||
'incompatible' => array(),
|
||||
);
|
||||
$this->assertEquals( $expected, $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'get_compatible_plugins_for_feature' returns empty information for features for which no compatibility has been declared.
|
||||
*/
|
||||
public function test_get_compatible_plugins_for_existing_feature_without_compatibility_declarations() {
|
||||
$this->simulate_after_woocommerce_init_hook();
|
||||
|
||||
$result = $this->sut->get_compatible_plugins_for_feature( 'mature1' );
|
||||
|
||||
$expected = array(
|
||||
'compatible' => array(),
|
||||
'incompatible' => array(),
|
||||
);
|
||||
$this->assertEquals( $expected, $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'get_compatible_plugins_for_feature' returns proper information for a feature for which compatibility has been declared.
|
||||
*/
|
||||
public function test_get_compatible_plugins_for_feature() {
|
||||
$this->simulate_inside_before_woocommerce_init_hook();
|
||||
|
||||
$this->sut->declare_compatibility( 'mature1', 'the_plugin_1', true );
|
||||
$this->sut->declare_compatibility( 'mature1', 'the_plugin_2', true );
|
||||
$this->sut->declare_compatibility( 'mature1', 'the_plugin_3', false );
|
||||
|
||||
$this->reset_legacy_proxy_mocks();
|
||||
|
||||
$this->simulate_after_woocommerce_init_hook();
|
||||
|
||||
$result = $this->sut->get_compatible_plugins_for_feature( 'mature1' );
|
||||
|
||||
$expected = array(
|
||||
'compatible' => array( 'the_plugin_1', 'the_plugin_2' ),
|
||||
'incompatible' => array( 'the_plugin_3' ),
|
||||
);
|
||||
|
||||
$this->assertEquals( $expected, $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox The action defined by FEATURE_ENABLED_CHANGED_ACTION is fired when the enable status of a feature changes.
|
||||
*
|
||||
* @testWith [true]
|
||||
* [false]
|
||||
*
|
||||
* @param bool $do_enable Whether to enable or disable the feature.
|
||||
*/
|
||||
public function test_feature_enable_changed_hook( $do_enable ) {
|
||||
$feature_id = null;
|
||||
$enabled = null;
|
||||
|
||||
add_action(
|
||||
FeaturesController::FEATURE_ENABLED_CHANGED_ACTION,
|
||||
function( $f, $e ) use ( &$feature_id, &$enabled ) {
|
||||
$feature_id = $f;
|
||||
$enabled = $e;
|
||||
},
|
||||
10,
|
||||
2
|
||||
);
|
||||
|
||||
$this->sut->change_feature_enable( 'mature1', $do_enable );
|
||||
|
||||
$this->assertEquals( 'mature1', $feature_id );
|
||||
$this->assertEquals( $do_enable, $enabled );
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates that the code is running inside the 'before_woocommerce_init' action.
|
||||
*/
|
||||
private function simulate_inside_before_woocommerce_init_hook() {
|
||||
$this->register_legacy_proxy_function_mocks(
|
||||
array(
|
||||
'doing_action' => function( $action_name ) {
|
||||
return 'before_woocommerce_init' === $action_name || doing_action( $action_name );
|
||||
},
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates that the code is running after the 'woocommerce_init' action has been fired.
|
||||
*/
|
||||
private function simulate_after_woocommerce_init_hook() {
|
||||
$this->register_legacy_proxy_function_mocks(
|
||||
array(
|
||||
'did_action' => function( $action_name ) {
|
||||
return 'woocommerce_init' === $action_name || did_action( $action_name );
|
||||
},
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -275,4 +275,79 @@ class ArrayUtilTest extends \WC_Unit_Test_Case {
|
|||
$this->assertTrue( $result );
|
||||
$this->assertEquals( array( 1, 2, 3, 4 ), $array );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox `ensure_key_is_array` adds an empty array under the given key if they key doesn't exist already in the array.
|
||||
*/
|
||||
public function test_ensure_key_is_array_when_key_does_not_exist() {
|
||||
$array = array( 'foo' => 1 );
|
||||
$result = ArrayUtil::ensure_key_is_array( $array, 'bar' );
|
||||
$this->assertTrue( $result );
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'foo' => 1,
|
||||
'bar' => array(),
|
||||
),
|
||||
$array
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox `ensure_key_is_array` does nothing if the key already exists in the array and $throw_if_existing_is_not_array is false.
|
||||
*
|
||||
* @testWith [[]]
|
||||
* [1]
|
||||
* [true]
|
||||
* ["Foo"]
|
||||
*
|
||||
* @param mixed $value The already existing value.
|
||||
*/
|
||||
public function test_ensure_key_is_array_when_key_exist_and_not_throwing( $value ) {
|
||||
$array = array(
|
||||
'foo' => 1,
|
||||
'bar' => $value,
|
||||
);
|
||||
$result = ArrayUtil::ensure_key_is_array( $array, 'bar' );
|
||||
$this->assertFalse( $result );
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'foo' => 1,
|
||||
'bar' => $value,
|
||||
),
|
||||
$array
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox `ensure_key_is_array` does nothing if the key already exists in the array, the value is itself an array, and $throw_if_existing_is_not_array is true.
|
||||
*/
|
||||
public function test_ensure_key_is_array_when_key_is_array_and_throwing() {
|
||||
$array = array(
|
||||
'foo' => 1,
|
||||
'bar' => array(),
|
||||
);
|
||||
$result = ArrayUtil::ensure_key_is_array( $array, 'bar', true );
|
||||
$this->assertFalse( $result );
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'foo' => 1,
|
||||
'bar' => array(),
|
||||
),
|
||||
$array
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox `ensure_key_is_array` throws if the key already exists in the array, the value is not an array, and $throw_if_existing_is_not_array is true.
|
||||
*/
|
||||
public function test_ensure_key_is_array_when_key_is_not_array_and_throwing() {
|
||||
$this->expectException( \Exception::class );
|
||||
$this->expectExceptionMessage( "Array key exists but it's not an array, it's a integer" );
|
||||
|
||||
$array = array(
|
||||
'foo' => 1,
|
||||
'bar' => 2,
|
||||
);
|
||||
ArrayUtil::ensure_key_is_array( $array, 'bar', true );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ class StringUtilTest extends \WC_Unit_Test_Case {
|
|||
}
|
||||
|
||||
/**
|
||||
* @return void 'contains' should check whether one string contains another.
|
||||
* @testdox 'contains' should check whether one string contains another.
|
||||
*/
|
||||
public function test_contains() {
|
||||
$this->assertFalse( StringUtil::contains( 'foobar', 'fizzbuzz' ) );
|
||||
|
@ -64,4 +64,14 @@ class StringUtilTest extends \WC_Unit_Test_Case {
|
|||
|
||||
$this->assertTrue( StringUtil::contains( 'foobar', 'BA', false ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'plugin_name_from_plugin_file' returns the plugin name in the form 'directory/file.php' from the plugin file.
|
||||
*/
|
||||
public function test_plugin_name_from_plugin_file() {
|
||||
$file_path = '/home/someone/wordpress/wp-content/plugins/foobar/fizzbuzz.php';
|
||||
$result = StringUtil::plugin_name_from_plugin_file( $file_path );
|
||||
$expected = 'foobar/fizzbuzz.php';
|
||||
$this->assertEquals( $expected, $result );
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue