Commit 024a28be24f for woocommerce

commit 024a28be24f94a609638725232f43e8f344d8c35
Author: Alba Rincón <albarin@users.noreply.github.com>
Date:   Thu Apr 23 11:55:58 2026 +0200

    Add GraphQL settings section with GET endpoint toggle (#64293)

    * Scaffold GraphQL section under WooCommerce Settings → Advanced

    * Add GET endpoint toggle to GraphQL settings

    * Add unit tests for GraphQL settings section and GET endpoint toggle

    * Add changefile(s) from automation for the following project(s): woocommerce

    * Add changefile(s) from automation for the following project(s): woocommerce

    * Cover register() hooks and assert single /wc/graphql handler

    * Add changefile(s) from automation for the following project(s): woocommerce

    * Remove duplicate changelog

    * Make the setting enabled by default

    * Skip GraphQLController tests on PHP < 8.1

    * Clean up SettingsTest filters and fix stale @testdox default

    ---------

    Co-authored-by: woocommercebot <woocommercebot@users.noreply.github.com>
    Co-authored-by: Nestor Soriano <konamiman@konamiman.com>

diff --git a/plugins/woocommerce/changelog/64293-add-graphql-settings-toggle b/plugins/woocommerce/changelog/64293-add-graphql-settings-toggle
new file mode 100644
index 00000000000..1c5fcd10c0d
--- /dev/null
+++ b/plugins/woocommerce/changelog/64293-add-graphql-settings-toggle
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Add a GraphQL settings section under Advanced with a toggle for the GET endpoint.
\ No newline at end of file
diff --git a/plugins/woocommerce/src/Internal/Api/GraphQLController.php b/plugins/woocommerce/src/Internal/Api/GraphQLController.php
index 4238804b3c2..16eb3a13ce9 100644
--- a/plugins/woocommerce/src/Internal/Api/GraphQLController.php
+++ b/plugins/woocommerce/src/Internal/Api/GraphQLController.php
@@ -90,11 +90,13 @@ class GraphQLController {
 	 * Register the GraphQL REST route.
 	 */
 	public function register(): void {
+		$methods = Main::is_get_endpoint_enabled() ? array( 'GET', 'POST' ) : array( 'POST' );
+
 		register_rest_route(
 			'wc',
 			'/graphql',
 			array(
-				'methods'             => array( 'GET', 'POST' ),
+				'methods'             => $methods,
 				'callback'            => array( $this, 'handle_request' ),
 				// Auth is handled per-query/mutation.
 				'permission_callback' => '__return_true',
diff --git a/plugins/woocommerce/src/Internal/Api/Main.php b/plugins/woocommerce/src/Internal/Api/Main.php
index 16f0a43a3c9..faba0442ce3 100644
--- a/plugins/woocommerce/src/Internal/Api/Main.php
+++ b/plugins/woocommerce/src/Internal/Api/Main.php
@@ -20,6 +20,13 @@ class Main {
 	 */
 	private const FEATURE_SLUG = 'dual_code_graphql_api';

+	/**
+	 * Option name for the "Enable GET endpoint" setting.
+	 *
+	 * When disabled, the GraphQL route only accepts POST requests.
+	 */
+	public const OPTION_GET_ENDPOINT_ENABLED = 'woocommerce_graphql_get_endpoint_enabled';
+
 	/**
 	 * Cached result of the feature-enabled check, null until first evaluated.
 	 *
@@ -42,6 +49,17 @@ class Main {
 		return self::$enabled;
 	}

+	/**
+	 * Whether the GraphQL endpoint accepts GET requests.
+	 *
+	 * Defaults to false. Reads from the option written by the GraphQL
+	 * settings section so the REST route registration can decide which
+	 * HTTP methods to accept.
+	 */
+	public static function is_get_endpoint_enabled(): bool {
+		return wc_string_to_bool( get_option( self::OPTION_GET_ENDPOINT_ENABLED, 'yes' ) );
+	}
+
 	/**
 	 * Register the GraphQL endpoint when the feature is active.
 	 *
@@ -62,5 +80,8 @@ class Main {
 				wc_get_container()->get( GraphQLController::class )->register();
 			}
 		);
+
+		$settings = wc_get_container()->get( Settings::class );
+		$settings->register();
 	}
 }
diff --git a/plugins/woocommerce/src/Internal/Api/Settings.php b/plugins/woocommerce/src/Internal/Api/Settings.php
new file mode 100644
index 00000000000..3b1678c549b
--- /dev/null
+++ b/plugins/woocommerce/src/Internal/Api/Settings.php
@@ -0,0 +1,71 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Automattic\WooCommerce\Internal\Api;
+
+/**
+ * Settings handling for the GraphQL API.
+ *
+ * Registers the "GraphQL" section under WooCommerce - Settings - Advanced.
+ * Only active when Main::is_enabled() returns true (feature flag on and
+ * PHP 8.1+), so the section is hidden when the feature is disabled.
+ */
+class Settings {
+	/**
+	 * Identifier for the GraphQL section under the Advanced settings tab.
+	 */
+	public const SECTION_ID = 'graphql';
+
+	/**
+	 * Register the filter hooks that expose the GraphQL settings section.
+	 */
+	public function register(): void {
+		add_filter( 'woocommerce_get_sections_advanced', array( $this, 'add_section' ) );
+		add_filter( 'woocommerce_get_settings_advanced', array( $this, 'add_settings' ), 10, 2 );
+	}
+
+	/**
+	 * Append the GraphQL section to the Advanced settings tab.
+	 *
+	 * @param array $sections Existing sections keyed by id.
+	 * @return array
+	 */
+	public function add_section( array $sections ): array {
+		$sections[ self::SECTION_ID ] = __( 'GraphQL', 'woocommerce' );
+		return $sections;
+	}
+
+	/**
+	 * Provide the settings fields for the GraphQL section.
+	 *
+	 * @param array  $settings   Existing settings for the current section.
+	 * @param string $section_id Current section id.
+	 * @return array
+	 */
+	public function add_settings( array $settings, string $section_id ): array {
+		if ( self::SECTION_ID !== $section_id ) {
+			return $settings;
+		}
+
+		return array(
+			array(
+				'title' => __( 'GraphQL', 'woocommerce' ),
+				'desc'  => __( 'Configure the WooCommerce GraphQL API.', 'woocommerce' ),
+				'type'  => 'title',
+				'id'    => 'woocommerce_graphql_options',
+			),
+			array(
+				'title'   => __( 'Enable GET endpoint', 'woocommerce' ),
+				'desc'    => __( 'Allow GraphQL queries over GET in addition to POST', 'woocommerce' ),
+				'id'      => Main::OPTION_GET_ENDPOINT_ENABLED,
+				'default' => 'yes',
+				'type'    => 'checkbox',
+			),
+			array(
+				'type' => 'sectionend',
+				'id'   => 'woocommerce_graphql_options',
+			),
+		);
+	}
+}
diff --git a/plugins/woocommerce/tests/php/src/Internal/Api/GraphQLControllerTest.php b/plugins/woocommerce/tests/php/src/Internal/Api/GraphQLControllerTest.php
new file mode 100644
index 00000000000..c700f4821a1
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Internal/Api/GraphQLControllerTest.php
@@ -0,0 +1,77 @@
+<?php
+declare( strict_types = 1 );
+
+namespace Automattic\WooCommerce\Tests\Internal\Api;
+
+use Automattic\WooCommerce\Internal\Api\GraphQLController;
+use Automattic\WooCommerce\Internal\Api\Main;
+use WC_REST_Unit_Test_Case;
+
+/**
+ * Tests for the GraphQLController class — specifically the HTTP methods
+ * registered on the /wc/graphql route based on the GET endpoint option.
+ */
+class GraphQLControllerTest extends WC_REST_Unit_Test_Case {
+	/**
+	 * The System Under Test.
+	 *
+	 * @var GraphQLController
+	 */
+	private $sut;
+
+	/**
+	 * Set up before each test.
+	 *
+	 * Skips on PHP < 8.1 because GraphQLController uses PHP 8.0+ syntax in its
+	 * source file (named arguments). In production the class is only loaded
+	 * after {@see Main::is_enabled()} gates on PHP 8.1+; these tests bypass
+	 * that gate by hitting the DI container directly, so we replicate it here.
+	 */
+	public function setUp(): void {
+		parent::setUp();
+
+		if ( PHP_VERSION_ID < 80100 ) {
+			$this->markTestSkipped( 'GraphQLController requires PHP 8.1+.' );
+		}
+
+		$this->sut = wc_get_container()->get( GraphQLController::class );
+	}
+
+	/**
+	 * Clean up the GET endpoint option between tests.
+	 */
+	public function tearDown(): void {
+		delete_option( Main::OPTION_GET_ENDPOINT_ENABLED );
+		parent::tearDown();
+	}
+
+	/**
+	 * @testdox register exposes POST only when the GET endpoint option is disabled.
+	 */
+	public function test_register_exposes_post_only_when_get_disabled(): void {
+		update_option( Main::OPTION_GET_ENDPOINT_ENABLED, 'no' );
+
+		$this->sut->register();
+
+		$handlers = rest_get_server()->get_routes()['/wc/graphql'];
+		$this->assertCount( 1, $handlers, 'Exactly one handler should be registered for /wc/graphql.' );
+		$methods = $handlers[0]['methods'];
+		$this->assertTrue( $methods['POST'] ?? false );
+		$this->assertFalse( $methods['GET'] ?? false );
+	}
+
+	/**
+	 * @testdox register exposes GET and POST when the GET endpoint option is enabled.
+	 */
+	public function test_register_exposes_get_and_post_when_get_enabled(): void {
+		update_option( Main::OPTION_GET_ENDPOINT_ENABLED, 'yes' );
+
+		$this->sut->register();
+
+		$handlers = rest_get_server()->get_routes()['/wc/graphql'];
+		$this->assertCount( 1, $handlers, 'Exactly one handler should be registered for /wc/graphql.' );
+		$methods = $handlers[0]['methods'];
+		$this->assertTrue( $methods['GET'] ?? false );
+		$this->assertTrue( $methods['POST'] ?? false );
+	}
+}
diff --git a/plugins/woocommerce/tests/php/src/Internal/Api/SettingsTest.php b/plugins/woocommerce/tests/php/src/Internal/Api/SettingsTest.php
new file mode 100644
index 00000000000..bd786f59f96
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Internal/Api/SettingsTest.php
@@ -0,0 +1,75 @@
+<?php
+declare( strict_types = 1 );
+
+namespace Automattic\WooCommerce\Tests\Internal\Api;
+
+use Automattic\WooCommerce\Internal\Api\Main;
+use Automattic\WooCommerce\Internal\Api\Settings;
+use WC_Unit_Test_Case;
+
+/**
+ * Tests for the GraphQL API Settings class.
+ */
+class SettingsTest extends WC_Unit_Test_Case {
+	/**
+	 * The System Under Test.
+	 *
+	 * @var Settings
+	 */
+	private $sut;
+
+	/**
+	 * Set up before each test.
+	 */
+	public function setUp(): void {
+		parent::setUp();
+		$this->sut = new Settings();
+	}
+
+	/**
+	 * 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 );
+		parent::tearDown();
+	}
+
+	/**
+	 * @testdox register hooks add_section and add_settings into WooCommerce's advanced settings filters.
+	 */
+	public function test_register_hooks_both_advanced_filters(): void {
+		$this->sut->register();
+
+		$this->assertNotFalse(
+			has_filter( 'woocommerce_get_sections_advanced', array( $this->sut, 'add_section' ) ),
+			'add_section should be hooked to woocommerce_get_sections_advanced.'
+		);
+		$this->assertNotFalse(
+			has_filter( 'woocommerce_get_settings_advanced', array( $this->sut, 'add_settings' ) ),
+			'add_settings should be hooked to woocommerce_get_settings_advanced.'
+		);
+	}
+
+	/**
+	 * @testdox add_section appends the graphql section while preserving existing ones.
+	 */
+	public function test_add_section_appends_graphql_section(): void {
+		$result = $this->sut->add_section( array( 'features' => 'Features' ) );
+
+		$this->assertArrayHasKey( Settings::SECTION_ID, $result );
+		$this->assertArrayHasKey( 'features', $result );
+	}
+
+	/**
+	 * @testdox add_settings defines the GET endpoint checkbox with a 'yes' default.
+	 */
+	public function test_add_settings_defines_get_endpoint_checkbox(): void {
+		$fields = $this->sut->add_settings( array(), Settings::SECTION_ID );
+		$by_id  = array_column( $fields, null, 'id' );
+
+		$this->assertArrayHasKey( Main::OPTION_GET_ENDPOINT_ENABLED, $by_id );
+		$this->assertSame( 'checkbox', $by_id[ Main::OPTION_GET_ENDPOINT_ENABLED ]['type'] );
+		$this->assertSame( 'yes', $by_id[ Main::OPTION_GET_ENDPOINT_ENABLED ]['default'] );
+	}
+}