diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php index b7286bea746e4..0066e720ad5f2 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php @@ -58,8 +58,15 @@ public function filter_response_by_context( $data, $context ) { unset( $data['content']['rendered'] ); // Add the core wp_pattern_sync_status meta as top level property to the response. - $data['wp_pattern_sync_status'] = $data['meta']['wp_pattern_sync_status'] ?? ''; - unset( $data['meta']['wp_pattern_sync_status'] ); + $meta = (array) $data['meta']; + $data['wp_pattern_sync_status'] = $meta['wp_pattern_sync_status'] ?? ''; + + if ( is_object( $data['meta'] ) ) { + unset( $data['meta']->wp_pattern_sync_status ); + } else { + unset( $data['meta']['wp_pattern_sync_status'] ); + } + return $data; } diff --git a/src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php b/src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php index a9c3fbcde831a..e35abde61d88e 100644 --- a/src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php +++ b/src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php @@ -74,7 +74,8 @@ public function register_field() { * * @param int $object_id Object ID to fetch meta for. * @param WP_REST_Request $request Full details about the request. - * @return array Array containing the meta values keyed by name. + * @return array|object Array containing the meta values keyed by name, + * or an empty object if to ensure JSON object encoding. */ public function get_value( $object_id, $request ) { $fields = $this->get_registered_fields(); @@ -105,6 +106,11 @@ public function get_value( $object_id, $request ) { $response[ $name ] = $value; } + // Use stdClass so that JSON result is {} and not []. + if ( empty( $response ) ) { + return (object) array(); + } + return $response; } @@ -582,6 +588,10 @@ public static function prepare_value( $value, $request, $args ) { * @return array|false The meta array, if valid, false otherwise. */ public function check_meta_is_array( $value, $request, $param ) { + if ( is_object( $value ) ) { + $value = (array) $value; + } + if ( ! is_array( $value ) ) { return false; } diff --git a/tests/phpunit/tests/rest-api/rest-blocks-controller.php b/tests/phpunit/tests/rest-api/rest-blocks-controller.php index 43a181683c788..11a5439bd4524 100644 --- a/tests/phpunit/tests/rest-api/rest-blocks-controller.php +++ b/tests/phpunit/tests/rest-api/rest-blocks-controller.php @@ -255,4 +255,39 @@ public function test_wp_patterns_sync_status_post_meta() { $this->assertArrayHasKey( 'wp_pattern_sync_status', $data ); $this->assertArrayNotHasKey( 'wp_pattern_sync_status', $data['meta'] ); } + + /** + * Tests that check_meta_is_array correctly handles object types. + * + * @ticket 54484 + */ + public function test_check_meta_is_array_accepts_object() { + wp_set_current_user( self::$user_ids['editor'] ); + + // Implement all abstract methods required by WP_REST_Meta_Fields. + $meta_fields = new class( 'post' ) extends WP_REST_Meta_Fields { + protected function get_meta_type() { + return 'post'; + } + + protected function get_rest_field_type() { + return 'post'; + } + }; + + // Simulate an object parsed from JSON (stdClass). + $object_value = new stdClass(); + $object_value->test_key = 'test_value'; + + // Execute the method from the patch. + $result = $meta_fields->check_meta_is_array( $object_value, new WP_REST_Request(), 'meta' ); + + // Verify: The object should be converted to an array to pass validation. + $this->assertIsArray( $result, 'The object should be converted to an array to pass validation.' ); + $this->assertArrayHasKey( 'test_key', $result ); + $this->assertSame( 'test_value', $result['test_key'] ); + + // Verify: Values that are neither object nor array (e.g., string) should still return false. + $this->assertFalse( $meta_fields->check_meta_is_array( 'not-an-array', new WP_REST_Request(), 'meta' ) ); + } } diff --git a/tests/phpunit/tests/rest-api/rest-post-meta-fields.php b/tests/phpunit/tests/rest-api/rest-post-meta-fields.php index 5ce72a57fa55f..ddb5772d5e5fb 100644 --- a/tests/phpunit/tests/rest-api/rest-post-meta-fields.php +++ b/tests/phpunit/tests/rest-api/rest-post-meta-fields.php @@ -4014,4 +4014,60 @@ public static function data_scalar_default_values() { 'string default' => array( 'string', 'string', 'string2' ), ); } + + /** + * Tests that the Meta fields handle an empty object (stdClass) correctly. + * + * This verifies the compatibility of filter_response_by_context with object types + * when meta is cast to an object (e.g., in specific PHP versions or filter modifications). + * + * @ticket 54484 + */ + public function test_filter_response_by_context_with_empty_object_meta() { + wp_set_current_user( self::factory()->user->create( array( 'role' => 'editor' ) ) ); + + register_post_meta( + 'post', + 'test_object_compat_meta', + array( + 'single' => true, + 'type' => 'string', + 'show_in_rest' => true, + ) + ); + + add_filter( + 'rest_prepare_post', + function ( $response ) { + $data = $response->get_data(); + // Inject an empty object for meta to test type compatibility + $data['meta'] = new stdClass(); + $response->set_data( $data ); + return $response; + }, + 10 + ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertArrayHasKey( 'meta', $data ); + + // Use a more robust check for both array and object types + $meta_data = $data['meta']; + + if ( is_object( $meta_data ) ) { + $this->assertFalse( + property_exists( $meta_data, 'test_object_compat_meta' ), + 'Failed asserting that the meta object does not have the property "test_object_compat_meta".' + ); + } else { + $this->assertArrayNotHasKey( + 'test_object_compat_meta', + $meta_data, + 'Failed asserting that the meta array does not have the key "test_object_compat_meta".' + ); + } + } }