Commit b3a6f4a8e8 for woocommerce
commit b3a6f4a8e8ba7dae3c8c5ec0887e049f37ab0e74
Author: Michal Iwanow <4765119+mcliwanow@users.noreply.github.com>
Date: Fri Jan 23 17:11:39 2026 +0100
Ensure that checking compatibility with deprecated features does not trigger deprecation notices (#62884)
* Ensure that checking compatibility with deprecated features does not trigger deprecation notices
Some plugins have build systems that fail when any deprecation notice is triggered. This commit adds additional backwards-compatibility protection to chagnes introduced in https://github.com/woocommerce/woocommerce/pull/62264/
* Add changefile(s) from automation for the following project(s): woocommerce
* Add @since tag to new method
* Refactor: Move deprecation logging to public API boundary in FeaturesUtil
* Simpler empty check
---------
Co-authored-by: woocommercebot <woocommercebot@users.noreply.github.com>
diff --git a/plugins/woocommerce/changelog/62884-fix-features-deprecation-notice b/plugins/woocommerce/changelog/62884-fix-features-deprecation-notice
new file mode 100644
index 0000000000..02dc0d1209
--- /dev/null
+++ b/plugins/woocommerce/changelog/62884-fix-features-deprecation-notice
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Ensure there's no deprecation warning about deprecated feature flags when plugins are activated.
\ No newline at end of file
diff --git a/plugins/woocommerce/src/Internal/Features/FeaturesController.php b/plugins/woocommerce/src/Internal/Features/FeaturesController.php
index cc63d6f639..f8e96c04d1 100644
--- a/plugins/woocommerce/src/Internal/Features/FeaturesController.php
+++ b/plugins/woocommerce/src/Internal/Features/FeaturesController.php
@@ -808,37 +808,16 @@ class FeaturesController {
*
* @since 10.5.0
*/
- private function get_feature_definition( string $feature_id ): ?array {
+ public function get_feature_definition( string $feature_id ): ?array {
return $this->get_feature_definitions()[ $feature_id ] ?? null;
}
- /**
- * Log usage of a deprecated feature.
- *
- * This method ensures logging only happens once per request to avoid spam.
- *
- * @param string $feature_id The feature id being checked.
- * @param string $deprecated_since The version since which the feature is deprecated.
- *
- * @since 10.5.0
- */
- private function log_deprecated_feature_usage( string $feature_id, string $deprecated_since ): void {
- static $logged = array();
-
- if ( isset( $logged[ $feature_id ] ) ) {
- return;
- }
- $logged[ $feature_id ] = true;
-
- wc_deprecated_function(
- "FeaturesUtil::feature_is_enabled('{$feature_id}')",
- $deprecated_since
- );
- }
-
/**
* Check if a given feature is currently enabled.
*
+ * Note: This method does not log deprecation notices for deprecated features.
+ * Deprecation logging is handled by FeaturesUtil::feature_is_enabled() which is the public API.
+ *
* @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.
*/
@@ -851,7 +830,6 @@ class FeaturesController {
// Handle deprecated features - return the backwards-compatible value.
if ( ! empty( $feature['deprecated_since'] ) ) {
- $this->log_deprecated_feature_usage( $feature_id, $feature['deprecated_since'] );
return (bool) ( $feature['deprecated_value'] ?? false );
}
@@ -1042,6 +1020,7 @@ class FeaturesController {
$this->verify_did_woocommerce_init( __FUNCTION__ );
$features = $this->get_feature_definitions();
+
if ( $enabled_features_only ) {
$features = array_filter(
$features,
diff --git a/plugins/woocommerce/src/Utilities/FeaturesUtil.php b/plugins/woocommerce/src/Utilities/FeaturesUtil.php
index 30049fd398..f8a10ccb3b 100644
--- a/plugins/woocommerce/src/Utilities/FeaturesUtil.php
+++ b/plugins/woocommerce/src/Utilities/FeaturesUtil.php
@@ -39,7 +39,37 @@ class FeaturesUtil {
* @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 );
+ $features_controller = wc_get_container()->get( FeaturesController::class );
+ $feature = $features_controller->get_feature_definition( $feature_id );
+
+ // Log deprecation notice for deprecated features (only from the public API).
+ if ( ! empty( $feature['deprecated_since'] ) ) {
+ self::log_deprecated_feature_usage( $feature_id, $feature['deprecated_since'] );
+ }
+
+ return $features_controller->feature_is_enabled( $feature_id );
+ }
+
+ /**
+ * Log usage of a deprecated feature.
+ *
+ * This method ensures logging only happens once per request to avoid spam.
+ *
+ * @param string $feature_id The feature id being checked.
+ * @param string $deprecated_since The version since which the feature is deprecated.
+ */
+ private static function log_deprecated_feature_usage( string $feature_id, string $deprecated_since ): void {
+ static $logged = array();
+
+ if ( isset( $logged[ $feature_id ] ) ) {
+ return;
+ }
+ $logged[ $feature_id ] = true;
+
+ wc_deprecated_function(
+ "FeaturesUtil::feature_is_enabled('{$feature_id}')",
+ $deprecated_since
+ );
}
/**
diff --git a/plugins/woocommerce/tests/php/src/Internal/Features/FeaturesControllerTest.php b/plugins/woocommerce/tests/php/src/Internal/Features/FeaturesControllerTest.php
index cc3a9aeeb9..9973d23637 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Features/FeaturesControllerTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Features/FeaturesControllerTest.php
@@ -620,6 +620,225 @@ class FeaturesControllerTest extends \WC_Unit_Test_Case {
$this->assertEquals( $expected, $result );
}
+ /**
+ * @testdox Deprecated features are included in 'get_compatible_features_for_plugin' results.
+ */
+ public function test_deprecated_features_included_in_get_compatible_features_for_plugin() {
+ add_action(
+ 'woocommerce_register_feature_definitions',
+ function ( $features_controller ) {
+ $features = array(
+ 'active_feature' => array(
+ 'name' => 'Active feature',
+ 'description' => 'An active feature',
+ 'is_experimental' => false,
+ 'default_plugin_compatibility' => 'compatible',
+ ),
+ 'deprecated_feature' => array(
+ 'name' => 'Deprecated feature',
+ 'description' => 'A deprecated feature',
+ 'is_experimental' => false,
+ 'default_plugin_compatibility' => 'compatible',
+ 'deprecated_since' => '10.5.0',
+ 'deprecated_value' => true,
+ ),
+ );
+
+ $this->reset_features_list( $features_controller, $features );
+ },
+ 20
+ );
+
+ $this->sut = new FeaturesController();
+ $this->sut->init( wc_get_container()->get( LegacyProxy::class ), $this->fake_plugin_util );
+ $this->simulate_inside_before_woocommerce_init_hook();
+
+ $this->sut->declare_compatibility( 'active_feature', 'the_plugin', true );
+ $this->sut->declare_compatibility( 'deprecated_feature', 'the_plugin', true );
+ $this->reset_legacy_proxy_mocks();
+ $this->simulate_after_woocommerce_init_hook();
+
+ // Test without enabled_features_only - all features should appear.
+ $result = $this->sut->get_compatible_features_for_plugin( 'the_plugin', false );
+
+ // Both features should appear in compatible list.
+ $this->assertContains( 'active_feature', $result['compatible'] );
+ $this->assertContains( 'deprecated_feature', $result['compatible'] );
+ }
+
+ /**
+ * @testdox Deprecated features with deprecated_value=true are included when filtering by enabled features.
+ */
+ public function test_deprecated_features_with_true_value_included_when_filtering_enabled_features() {
+ add_action(
+ 'woocommerce_register_feature_definitions',
+ function ( $features_controller ) {
+ $features = array(
+ 'active_feature' => array(
+ 'name' => 'Active feature',
+ 'description' => 'An active feature',
+ 'is_experimental' => false,
+ 'default_plugin_compatibility' => 'compatible',
+ ),
+ 'deprecated_enabled_feature' => array(
+ 'name' => 'Deprecated enabled feature',
+ 'description' => 'A deprecated feature with true value',
+ 'is_experimental' => false,
+ 'default_plugin_compatibility' => 'compatible',
+ 'deprecated_since' => '10.5.0',
+ 'deprecated_value' => true,
+ ),
+ 'deprecated_disabled_feature' => array(
+ 'name' => 'Deprecated disabled feature',
+ 'description' => 'A deprecated feature with false value',
+ 'is_experimental' => false,
+ 'default_plugin_compatibility' => 'compatible',
+ 'deprecated_since' => '10.5.0',
+ 'deprecated_value' => false,
+ ),
+ );
+
+ $this->reset_features_list( $features_controller, $features );
+ },
+ 20
+ );
+
+ $this->sut = new FeaturesController();
+ $this->sut->init( wc_get_container()->get( LegacyProxy::class ), $this->fake_plugin_util );
+ $this->simulate_inside_before_woocommerce_init_hook();
+
+ $this->sut->declare_compatibility( 'active_feature', 'the_plugin', true );
+ $this->sut->declare_compatibility( 'deprecated_enabled_feature', 'the_plugin', true );
+ $this->sut->declare_compatibility( 'deprecated_disabled_feature', 'the_plugin', true );
+ $this->reset_legacy_proxy_mocks();
+ $this->simulate_after_woocommerce_init_hook();
+
+ update_option( 'woocommerce_feature_active_feature_enabled', 'yes' );
+
+ // Test with enabled_features_only = true.
+ $result = $this->sut->get_compatible_features_for_plugin( 'the_plugin', true );
+
+ // Active and deprecated_enabled features should appear (deprecated_value=true).
+ $this->assertContains( 'active_feature', $result['compatible'] );
+ $this->assertContains( 'deprecated_enabled_feature', $result['compatible'] );
+ // Deprecated with deprecated_value=false should NOT appear.
+ $this->assertNotContains( 'deprecated_disabled_feature', $result['compatible'] );
+ $this->assertNotContains( 'deprecated_disabled_feature', $result['incompatible'] );
+ $this->assertNotContains( 'deprecated_disabled_feature', $result['uncertain'] );
+ }
+
+ /**
+ * @testdox Deprecated features can be checked in 'get_incompatible_plugins' without triggering deprecation notices.
+ */
+ public function test_deprecated_features_in_get_incompatible_plugins_without_notices() {
+ add_action(
+ 'woocommerce_register_feature_definitions',
+ function ( $features_controller ) {
+ $features = array(
+ 'active_feature' => array(
+ 'name' => 'Active feature',
+ 'description' => 'An active feature',
+ 'is_experimental' => false,
+ 'default_plugin_compatibility' => 'incompatible',
+ ),
+ 'deprecated_enabled_feature' => array(
+ 'name' => 'Deprecated enabled feature',
+ 'description' => 'A deprecated feature that is considered enabled',
+ 'is_experimental' => false,
+ 'default_plugin_compatibility' => 'incompatible',
+ 'deprecated_since' => '10.5.0',
+ 'deprecated_value' => true,
+ ),
+ 'deprecated_disabled_feature' => array(
+ 'name' => 'Deprecated disabled feature',
+ 'description' => 'A deprecated feature that is considered disabled',
+ 'is_experimental' => false,
+ 'default_plugin_compatibility' => 'incompatible',
+ 'deprecated_since' => '10.5.0',
+ 'deprecated_value' => false,
+ ),
+ );
+
+ $this->reset_features_list( $features_controller, $features );
+ },
+ 20
+ );
+
+ // phpcs:disable Squiz.Commenting
+ $fake_plugin_util = new class() extends PluginUtil {
+ private $active_plugins;
+
+ public function __construct() {
+ }
+
+ public function set_active_plugins( $plugins ) {
+ $this->active_plugins = $plugins;
+ }
+
+ public function get_woocommerce_aware_plugins( bool $active_only = false ): array {
+ return $this->active_plugins;
+ }
+
+ public function is_woocommerce_aware_plugin( $plugin ): bool {
+ return in_array( $plugin, $this->active_plugins, true );
+ }
+
+ public function get_plugins_excluded_from_compatibility_ui() {
+ return array();
+ }
+
+ public function get_wp_plugin_id( $plugin_file ) {
+ return $plugin_file;
+ }
+ };
+ // phpcs:enable Squiz.Commenting
+
+ // Set private $proxy via reflection.
+ $parent_reflection = new \ReflectionClass( PluginUtil::class );
+ $proxy_prop = $parent_reflection->getProperty( 'proxy' );
+ $proxy_prop->setAccessible( true );
+ $proxy_prop->setValue( $fake_plugin_util, wc_get_container()->get( LegacyProxy::class ) );
+
+ $this->sut = new FeaturesController();
+ $this->sut->init( wc_get_container()->get( LegacyProxy::class ), $fake_plugin_util );
+ $this->simulate_inside_before_woocommerce_init_hook();
+
+ $fake_plugin_util->set_active_plugins( array( 'test_plugin' ) );
+
+ $this->register_legacy_proxy_function_mocks(
+ array(
+ 'is_plugin_active' => function ( $plugin ) {
+ unset( $plugin );
+ return true;
+ },
+ )
+ );
+
+ $this->reset_legacy_proxy_mocks();
+ $this->simulate_after_woocommerce_init_hook();
+
+ $this->register_legacy_proxy_function_mocks(
+ array(
+ 'is_plugin_active' => function ( $plugin ) {
+ unset( $plugin );
+ return true;
+ },
+ )
+ );
+
+ update_option( 'woocommerce_feature_active_feature_enabled', 'yes' );
+
+ $incompatible_plugins = function () {
+ return $this->get_incompatible_plugins( 'all', array( 'test_plugin' => array() ) );
+ };
+ $result = $incompatible_plugins->call( $this->sut );
+
+ // The method should complete without triggering deprecation notices.
+ // Deprecated features with deprecated_value=true are still included in checks.
+ // Deprecated features with deprecated_value=false are excluded from enabled-only checks.
+ $this->assertIsArray( $result );
+ }
+
/**
* @testdox 'get_compatible_plugins_for_feature' fails when invoked before the 'woocommerce_init' hook.
*/