Commit cf12552aba7 for woocommerce

commit cf12552aba7761eb6408bd07d5e6603481264c39
Author: Seun Olorunsola <30554163+triple0t@users.noreply.github.com>
Date:   Mon Apr 27 10:50:25 2026 +0100

    Fix early textdomain loading from dual code API feature check (#64332)

    Co-authored-by: Néstor Soriano <konamiman@konamiman.com>

diff --git a/plugins/woocommerce/changelog/64332-fix-load-textdomain-notice b/plugins/woocommerce/changelog/64332-fix-load-textdomain-notice
new file mode 100644
index 00000000000..c14beb31e4e
--- /dev/null
+++ b/plugins/woocommerce/changelog/64332-fix-load-textdomain-notice
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix a `_load_textdomain_just_in_time` notice for the `woocommerce` domain on WordPress 6.7+ caused by the new dual code / GraphQL API checking its feature flag too early during plugin load.
\ No newline at end of file
diff --git a/plugins/woocommerce/src/Internal/Api/Main.php b/plugins/woocommerce/src/Internal/Api/Main.php
index 58754922b25..fe20cf7e2b4 100644
--- a/plugins/woocommerce/src/Internal/Api/Main.php
+++ b/plugins/woocommerce/src/Internal/Api/Main.php
@@ -27,26 +27,16 @@ class Main {
 	 */
 	public const OPTION_GET_ENDPOINT_ENABLED = 'woocommerce_graphql_get_endpoint_enabled';

-	/**
-	 * Cached result of the feature-enabled check, null until first evaluated.
-	 *
-	 * @var ?bool
-	 */
-	private static ?bool $enabled = null;
-
 	/**
 	 * Check whether the Dual Code & GraphQL API feature is active.
 	 *
 	 * Requires PHP 8.1+ and the dual_code_graphql_api feature flag to be
-	 * enabled. The result is cached for the lifetime of the request.
+	 * enabled.
 	 *
 	 * @return bool
 	 */
 	public static function is_enabled(): bool {
-		if ( null === self::$enabled ) {
-			self::$enabled = PHP_VERSION_ID >= 80100 && FeaturesUtil::feature_is_enabled( self::FEATURE_SLUG );
-		}
-		return self::$enabled;
+		return PHP_VERSION_ID >= 80100 && FeaturesUtil::feature_is_enabled( self::FEATURE_SLUG );
 	}

 	/**
@@ -91,12 +81,13 @@ class Main {
 	 * that want to know whether the feature is active should check
 	 * FeaturesUtil::feature_is_enabled( 'dual_code_graphql_api' ) rather
 	 * than class_exists() on the Api namespace.
+	 *
+	 * The feature-enabled check is deferred to the `rest_api_init` callback
+	 * to avoid triggering translation loading (via FeaturesController) before
+	 * the `init` action has fired, which would cause a
+	 * `_load_textdomain_just_in_time` notice on WordPress 6.7+.
 	 */
 	public static function register(): void {
-		if ( ! self::is_enabled() ) {
-			return;
-		}
-
 		add_action( 'rest_api_init', array( self::class, 'handle_rest_api_init_for_core' ) );

 		$settings = wc_get_container()->get( Settings::class );
@@ -106,13 +97,13 @@ class Main {
 	/**
 	 * Hook callback: register WooCommerce core's GraphQL endpoint.
 	 *
-	 * Extracted from {@see self::register()} so the hook target is a named
-	 * class method rather than an inline closure, matching WooCommerce's
-	 * usual hook-registration style.
-	 *
 	 * @internal
 	 */
 	public static function handle_rest_api_init_for_core(): void {
+		if ( ! self::is_enabled() ) {
+			return;
+		}
+
 		wc_get_container()->get( Autogenerated\GraphQLController::class )->register();
 	}

diff --git a/plugins/woocommerce/src/Internal/Api/Settings.php b/plugins/woocommerce/src/Internal/Api/Settings.php
index 3b1678c549b..a40ff8345c8 100644
--- a/plugins/woocommerce/src/Internal/Api/Settings.php
+++ b/plugins/woocommerce/src/Internal/Api/Settings.php
@@ -32,7 +32,9 @@ class Settings {
 	 * @return array
 	 */
 	public function add_section( array $sections ): array {
-		$sections[ self::SECTION_ID ] = __( 'GraphQL', 'woocommerce' );
+		if ( Main::is_enabled() ) {
+			$sections[ self::SECTION_ID ] = __( 'GraphQL', 'woocommerce' );
+		}
 		return $sections;
 	}

@@ -44,7 +46,7 @@ class Settings {
 	 * @return array
 	 */
 	public function add_settings( array $settings, string $section_id ): array {
-		if ( self::SECTION_ID !== $section_id ) {
+		if ( self::SECTION_ID !== $section_id || ! Main::is_enabled() ) {
 			return $settings;
 		}

diff --git a/plugins/woocommerce/tests/php/src/Internal/Api/SettingsTest.php b/plugins/woocommerce/tests/php/src/Internal/Api/SettingsTest.php
index bd786f59f96..455a302fe07 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Api/SettingsTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Api/SettingsTest.php
@@ -5,6 +5,7 @@ namespace Automattic\WooCommerce\Tests\Internal\Api;

 use Automattic\WooCommerce\Internal\Api\Main;
 use Automattic\WooCommerce\Internal\Api\Settings;
+use Automattic\WooCommerce\Internal\Features\FeaturesController;
 use WC_Unit_Test_Case;

 /**
@@ -23,6 +24,7 @@ class SettingsTest extends WC_Unit_Test_Case {
 	 */
 	public function setUp(): void {
 		parent::setUp();
+		$this->enable_or_disable_feature( true );
 		$this->sut = new Settings();
 	}

@@ -30,11 +32,22 @@ class SettingsTest extends WC_Unit_Test_Case {
 	 * Clean up filters registered by tests so global state doesn't leak.
 	 */
 	public function tearDown(): void {
-		remove_filter( 'woocommerce_get_sections_advanced', array( $this->sut, 'add_section' ) );
-		remove_filter( 'woocommerce_get_settings_advanced', array( $this->sut, 'add_settings' ), 10 );
+		$this->enable_or_disable_feature( false );
 		parent::tearDown();
 	}

+	/**
+	 * Enable or disable the GraphQL API feature.
+	 *
+	 * @param bool $enable True to enable, false to disable.
+	 */
+	private function enable_or_disable_feature( bool $enable ): void {
+		update_option(
+			wc_get_container()->get( FeaturesController::class )->feature_enable_option_name( 'dual_code_graphql_api' ),
+			$enable ? 'yes' : 'no'
+		);
+	}
+
 	/**
 	 * @testdox register hooks add_section and add_settings into WooCommerce's advanced settings filters.
 	 */
@@ -52,9 +65,13 @@ class SettingsTest extends WC_Unit_Test_Case {
 	}

 	/**
-	 * @testdox add_section appends the graphql section while preserving existing ones.
+	 * @testdox add_section appends the graphql section while preserving existing ones (PHP 8.1+).
 	 */
 	public function test_add_section_appends_graphql_section(): void {
+		if ( PHP_VERSION_ID < 80100 ) {
+			$this->markTestSkipped( 'GraphQL settings require PHP 8.1+.' );
+		}
+
 		$result = $this->sut->add_section( array( 'features' => 'Features' ) );

 		$this->assertArrayHasKey( Settings::SECTION_ID, $result );
@@ -62,9 +79,27 @@ class SettingsTest extends WC_Unit_Test_Case {
 	}

 	/**
-	 * @testdox add_settings defines the GET endpoint checkbox with a 'yes' default.
+	 * @testdox add_section is a no-op on PHP < 8.1.
+	 */
+	public function test_add_section_is_noop_on_unsupported_php(): void {
+		if ( PHP_VERSION_ID >= 80100 ) {
+			$this->markTestSkipped( 'Only relevant on PHP < 8.1.' );
+		}
+
+		$input  = array( 'features' => 'Features' );
+		$result = $this->sut->add_section( $input );
+
+		$this->assertSame( $input, $result );
+	}
+
+	/**
+	 * @testdox add_settings defines the GET endpoint checkbox with a 'yes' default (PHP 8.1+).
 	 */
 	public function test_add_settings_defines_get_endpoint_checkbox(): void {
+		if ( PHP_VERSION_ID < 80100 ) {
+			$this->markTestSkipped( 'GraphQL settings require PHP 8.1+.' );
+		}
+
 		$fields = $this->sut->add_settings( array(), Settings::SECTION_ID );
 		$by_id  = array_column( $fields, null, 'id' );

@@ -72,4 +107,18 @@ class SettingsTest extends WC_Unit_Test_Case {
 		$this->assertSame( 'checkbox', $by_id[ Main::OPTION_GET_ENDPOINT_ENABLED ]['type'] );
 		$this->assertSame( 'yes', $by_id[ Main::OPTION_GET_ENDPOINT_ENABLED ]['default'] );
 	}
+
+	/**
+	 * @testdox add_settings returns the input unchanged on PHP < 8.1.
+	 */
+	public function test_add_settings_is_noop_on_unsupported_php(): void {
+		if ( PHP_VERSION_ID >= 80100 ) {
+			$this->markTestSkipped( 'Only relevant on PHP < 8.1.' );
+		}
+
+		$input  = array( array( 'id' => 'existing' ) );
+		$result = $this->sut->add_settings( $input, Settings::SECTION_ID );
+
+		$this->assertSame( $input, $result );
+	}
 }