Commit bf7b2d51705 for woocommerce

commit bf7b2d517055496b3a4af8ad6760cd270322ebc9
Author: Pavel Dohnal <pavel.dohnal@automattic.com>
Date:   Mon Apr 6 12:05:45 2026 +0200

    Fix post_types missing from view context on WP 7.0 (#64023)

    The compatibility guard in register_post_types_to_api() only checked
    whether post_types existed in the schema. On WP 7.0 RC2 the property
    is present but without a view context, so the guard returned early
    and register_rest_field was never called. Downstream JS consumers
    requesting context=view then received no post_types field, breaking
    the template-select modal.

    Check for view in the context array instead of mere presence so
    the field is still registered when WP adds post_types without
    exposing it in view context.

diff --git a/packages/php/email-editor/changelog/fix-register-post-types-view-context-check b/packages/php/email-editor/changelog/fix-register-post-types-view-context-check
new file mode 100644
index 00000000000..22902d83fcf
--- /dev/null
+++ b/packages/php/email-editor/changelog/fix-register-post-types-view-context-check
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix `register_post_types_to_api()` to verify view context availability before skipping field registration on WordPress 7.0+
diff --git a/packages/php/email-editor/src/Engine/Templates/class-templates.php b/packages/php/email-editor/src/Engine/Templates/class-templates.php
index 083265060c1..8b892e2c761 100644
--- a/packages/php/email-editor/src/Engine/Templates/class-templates.php
+++ b/packages/php/email-editor/src/Engine/Templates/class-templates.php
@@ -107,8 +107,11 @@ class Templates {
 	public function register_post_types_to_api(): void {
 		$controller = new \WP_REST_Templates_Controller( 'wp_template' );
 		$schema     = $controller->get_item_schema();
-		// Future compatibility check if the post_types property is already registered.
-		if ( isset( $schema['properties']['post_types'] ) ) {
+		// Skip registration only when post_types is natively available in the view context.
+		// WP 7.0 adds the property but only in the edit context; we must still register
+		// the field so that context=view responses include it.
+		$post_types_context = $schema['properties']['post_types']['context'] ?? array();
+		if ( in_array( 'view', $post_types_context, true ) ) {
 			return;
 		}
 		register_rest_field(
diff --git a/packages/php/email-editor/tests/unit/Engine/Templates/Templates_Test.php b/packages/php/email-editor/tests/unit/Engine/Templates/Templates_Test.php
new file mode 100644
index 00000000000..ce30a39f772
--- /dev/null
+++ b/packages/php/email-editor/tests/unit/Engine/Templates/Templates_Test.php
@@ -0,0 +1,106 @@
+<?php
+/**
+ * This file is part of the WooCommerce Email Editor package.
+ *
+ * @package Automattic\WooCommerce\EmailEditor
+ */
+
+declare(strict_types=1);
+
+use Automattic\WooCommerce\EmailEditor\Engine\Templates\Templates;
+use Automattic\WooCommerce\EmailEditor\Engine\Templates\Templates_Registry;
+
+/**
+ * Test cases for Templates::register_post_types_to_api().
+ */
+class Templates_Test extends Email_Editor_Unit_Test {
+
+	/**
+	 * The System Under Test.
+	 *
+	 * @var Templates
+	 */
+	private Templates $sut;
+
+	/**
+	 * Set up test fixtures.
+	 */
+	protected function setUp(): void {
+		parent::setUp();
+		$GLOBALS['wc_ee_rest_field_registered'] = false;
+		$GLOBALS['wc_ee_test_schema']           = array( 'properties' => array() );
+		$this->sut                              = new Templates( new Templates_Registry() );
+	}
+
+	/**
+	 * Tear down test fixtures.
+	 */
+	protected function tearDown(): void {
+		unset( $GLOBALS['wc_ee_rest_field_registered'], $GLOBALS['wc_ee_test_schema'] );
+		parent::tearDown();
+	}
+
+	/**
+	 * Test that the field is registered when post_types is absent.
+	 *
+	 * @testdox Should register the field when post_types is absent from the schema (WP ≤ 6.9).
+	 */
+	public function test_registers_field_when_post_types_absent_from_schema(): void {
+		$GLOBALS['wc_ee_test_schema'] = array( 'properties' => array() );
+
+		$this->sut->register_post_types_to_api();
+
+		$this->assertTrue( $GLOBALS['wc_ee_rest_field_registered'], 'Field should be registered when post_types is absent from the schema.' );
+	}
+
+	/**
+	 * Test that registration is skipped when view context is present.
+	 *
+	 * @testdox Should skip registration when post_types is present with view context (future WP).
+	 */
+	public function test_skips_registration_when_view_context_present(): void {
+		$GLOBALS['wc_ee_test_schema'] = array(
+			'properties' => array(
+				'post_types' => array( 'context' => array( 'view', 'edit', 'embed' ) ),
+			),
+		);
+
+		$this->sut->register_post_types_to_api();
+
+		$this->assertFalse( $GLOBALS['wc_ee_rest_field_registered'], 'Field should not be registered when post_types is natively available in the view context.' );
+	}
+
+	/**
+	 * Test that the field is registered when view context is missing (WP 7.0 scenario).
+	 *
+	 * @testdox Should register the field when post_types is present but view context is missing (WP 7.0).
+	 */
+	public function test_registers_field_when_post_types_present_but_view_context_missing(): void {
+		$GLOBALS['wc_ee_test_schema'] = array(
+			'properties' => array(
+				'post_types' => array( 'context' => array( 'edit' ) ),
+			),
+		);
+
+		$this->sut->register_post_types_to_api();
+
+		$this->assertTrue( $GLOBALS['wc_ee_rest_field_registered'], 'Field should be registered when post_types is present but only in the edit context.' );
+	}
+
+	/**
+	 * Test that the field is registered when post_types has no context key.
+	 *
+	 * @testdox Should register the field when post_types is present but has no context key.
+	 */
+	public function test_registers_field_when_post_types_present_but_no_context_key(): void {
+		$GLOBALS['wc_ee_test_schema'] = array(
+			'properties' => array(
+				'post_types' => array( 'type' => 'array' ),
+			),
+		);
+
+		$this->sut->register_post_types_to_api();
+
+		$this->assertTrue( $GLOBALS['wc_ee_rest_field_registered'], 'Field should be registered when post_types has no context key.' );
+	}
+}
diff --git a/packages/php/email-editor/tests/unit/stubs.php b/packages/php/email-editor/tests/unit/stubs.php
index af8677c2fcd..7fcb591b7eb 100644
--- a/packages/php/email-editor/tests/unit/stubs.php
+++ b/packages/php/email-editor/tests/unit/stubs.php
@@ -451,3 +451,40 @@ if ( ! class_exists( \IntegrationTester::class ) ) {
 		}
 	}
 }
+
+if ( ! class_exists( \WP_REST_Templates_Controller::class ) ) {
+	/**
+	 * Stub for WP_REST_Templates_Controller used in unit tests.
+	 */
+	class WP_REST_Templates_Controller {
+		/**
+		 * Constructor.
+		 *
+		 * @param string $post_type Post type slug.
+		 */
+		public function __construct( string $post_type ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
+		}
+
+		/**
+		 * Returns the schema configured via $GLOBALS['wc_ee_test_schema'].
+		 *
+		 * @return array
+		 */
+		public function get_item_schema(): array {
+			return $GLOBALS['wc_ee_test_schema'] ?? array( 'properties' => array() );
+		}
+	}
+}
+
+if ( ! function_exists( 'register_rest_field' ) ) {
+	/**
+	 * Mock register_rest_field function.
+	 *
+	 * @param string|array $object_type Object type or array of types.
+	 * @param string       $attribute   Attribute name.
+	 * @param array        $args        Optional. Field arguments.
+	 */
+	function register_rest_field( $object_type, $attribute, $args = array() ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
+		$GLOBALS['wc_ee_rest_field_registered'] = true;
+	}
+}