Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,7 @@ public function prepare_item_for_response( $item, $request ) {
$data['meta'] = $this->meta->get_value( $menu_item->ID, $request );
}

$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
$taxonomies = $this->get_rest_taxonomies();

foreach ( $taxonomies as $taxonomy ) {
$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
Expand Down Expand Up @@ -907,7 +907,7 @@ public function get_item_schema() {
'readonly' => true,
);

$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
$taxonomies = $this->get_rest_taxonomies( $schema['properties'] );

foreach ( $taxonomies as $taxonomy ) {
$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,18 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
*/
protected $allow_batch = array( 'v1' => true );

/**
* Taxonomies exposed as properties on the REST API item resource.
*
* The list is initialized while building the item schema so taxonomy REST
* bases that conflict with existing properties are excluded consistently
* from item reads, writes, permissions, and action links.
*
* @since 7.2.0
* @var WP_Taxonomy[]|null
*/
protected $rest_taxonomies = null;

/**
* Constructor.
*
Expand Down Expand Up @@ -1690,6 +1702,61 @@ public function handle_template( $template, $post_id, $validate = false ) {
update_post_meta( $post_id, '_wp_page_template', $template );
}

/**
* Retrieves taxonomies exposed as properties on the REST API item resource.
*
* When schema properties are provided, taxonomy REST bases that conflict
* with an existing property are excluded and the resulting list is cached.
* Calling this method without schema properties never triggers schema
* generation, avoiding recursion in controllers that override
* get_item_schema().
*
* @since 7.2.0
*
* @param array|null $properties Existing item schema properties, or null to use the cached list.
* @return WP_Taxonomy[] Taxonomies available as item resource properties.
*/
protected function get_rest_taxonomies( $properties = null ) {
if ( null !== $this->rest_taxonomies ) {
return $this->rest_taxonomies;
}

$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );

if ( null === $properties ) {
return $taxonomies;
}

$rest_taxonomies = array();

foreach ( $taxonomies as $taxonomy_name => $taxonomy ) {
$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;

if ( array_key_exists( $base, $properties ) ) {
$taxonomy_field_name_with_conflict = ! empty( $taxonomy->rest_base ) ? 'rest_base' : 'name';
_doing_it_wrong(
'register_taxonomy',
sprintf(
/* translators: 1: The taxonomy name, 2: The property name, either 'rest_base' or 'name', 3: The conflicting value. */
__( 'The "%1$s" taxonomy "%2$s" property (%3$s) conflicts with an existing property on the REST API Posts Controller. Specify a custom "rest_base" when registering the taxonomy to avoid this error.' ),
$taxonomy->name,
$taxonomy_field_name_with_conflict,
$base
),
'5.4.0'
);
continue;
}

$rest_taxonomies[ $taxonomy_name ] = $taxonomy;
$properties[ $base ] = true;
}

$this->rest_taxonomies = $rest_taxonomies;

return $this->rest_taxonomies;
}

/**
* Updates the post's terms from a REST request.
*
Expand All @@ -1700,7 +1767,8 @@ public function handle_template( $template, $post_id, $validate = false ) {
* @return null|WP_Error WP_Error on an error assigning any of the terms, otherwise null.
*/
protected function handle_terms( $post_id, $request ) {
$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
$this->get_item_schema();
$taxonomies = $this->get_rest_taxonomies();

foreach ( $taxonomies as $taxonomy ) {
$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
Expand Down Expand Up @@ -1728,7 +1796,8 @@ protected function handle_terms( $post_id, $request ) {
* @return bool Whether the current user can assign the provided terms.
*/
protected function check_assign_terms_permission( $request ) {
$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
$this->get_item_schema();
$taxonomies = $this->get_rest_taxonomies();
foreach ( $taxonomies as $taxonomy ) {
$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;

Expand Down Expand Up @@ -2103,7 +2172,7 @@ public function prepare_item_for_response( $item, $request ) {
$data['meta'] = $this->meta->get_value( $post->ID, $request );
}

$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
$taxonomies = $this->get_rest_taxonomies();

foreach ( $taxonomies as $taxonomy ) {
$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
Expand Down Expand Up @@ -2369,7 +2438,7 @@ protected function get_available_actions( $post, $request ) {
}
}

$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
$taxonomies = $this->get_rest_taxonomies();

foreach ( $taxonomies as $tax ) {
$tax_base = ! empty( $tax->rest_base ) ? $tax->rest_base : $tax->name;
Expand Down Expand Up @@ -2759,26 +2828,11 @@ public function get_item_schema() {
),
);

$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
$taxonomies = $this->get_rest_taxonomies( $schema['properties'] );

foreach ( $taxonomies as $taxonomy ) {
$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;

if ( array_key_exists( $base, $schema['properties'] ) ) {
$taxonomy_field_name_with_conflict = ! empty( $taxonomy->rest_base ) ? 'rest_base' : 'name';
_doing_it_wrong(
'register_taxonomy',
sprintf(
/* translators: 1: The taxonomy name, 2: The property name, either 'rest_base' or 'name', 3: The conflicting value. */
__( 'The "%1$s" taxonomy "%2$s" property (%3$s) conflicts with an existing property on the REST API Posts Controller. Specify a custom "rest_base" when registering the taxonomy to avoid this error.' ),
$taxonomy->name,
$taxonomy_field_name_with_conflict,
$base
),
'5.4.0'
);
}

$schema['properties'][ $base ] = array(
/* translators: %s: Taxonomy name. */
'description' => sprintf( __( 'The terms assigned to the post in the %s taxonomy.' ), $taxonomy->name ),
Expand Down Expand Up @@ -2914,7 +2968,7 @@ protected function get_schema_links() {
);
}

$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
$taxonomies = $this->get_rest_taxonomies();

foreach ( $taxonomies as $tax ) {
$tax_base = ! empty( $tax->rest_base ) ? $tax->rest_base : $tax->name;
Expand Down
56 changes: 56 additions & 0 deletions tests/phpunit/tests/rest-api/rest-posts-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -4918,6 +4918,62 @@ public function test_get_item_schema_issues_doing_it_wrong_when_taxonomy_name_is
$controller->register_routes();
}

/**
* @ticket 65855
*/
public function test_get_item_schema_does_not_overwrite_existing_property_when_taxonomy_name_conflicts() {
$this->setExpectedIncorrectUsage( 'register_taxonomy' );

register_taxonomy(
'type',
'post',
array(
'show_in_rest' => true,
)
);

$controller = new WP_REST_Posts_Controller( 'post' );
$schema = $controller->get_item_schema();

unregister_taxonomy( 'type' );

$this->assertSame( 'string', $schema['properties']['type']['type'] );
$this->assertTrue( $schema['properties']['type']['readonly'] );
}

/**
* @ticket 65855
*/
public function test_prepare_item_for_response_does_not_overwrite_existing_property_when_taxonomy_name_conflicts() {
$this->setExpectedIncorrectUsage( 'register_taxonomy' );

register_taxonomy(
'type',
'post',
array(
'show_in_rest' => true,
)
);

$controller = new WP_REST_Posts_Controller( 'post' );
$request = new WP_REST_Request(
'GET',
sprintf( '/wp/v2/posts/%d', self::$post_id )
);
$request->set_param( 'context', 'view' );
$request->set_param( '_fields', 'type' );

$response = $controller->prepare_item_for_response(
get_post( self::$post_id ),
$request
);
$data = $response->get_data();

unregister_taxonomy( 'type' );

$this->assertSame( 'post', $data['type'] );
}

/**
* @ticket 39805
*/
Expand Down
23 changes: 23 additions & 0 deletions tests/phpunit/tests/rest-api/wpRestMenuItemsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,29 @@ public function test_get_item_schema() {
$this->assertArrayHasKey( 'invalid', $properties );
}

/**
* @ticket 65855
* @covers ::get_item_schema
*/
public function test_get_item_schema_does_not_overwrite_existing_property_when_taxonomy_name_conflicts() {
$this->setExpectedIncorrectUsage( 'register_taxonomy' );

register_taxonomy(
'type',
'nav_menu_item',
array(
'show_in_rest' => true,
)
);

$controller = new WP_REST_Menu_Items_Controller( 'nav_menu_item' );
$schema = $controller->get_item_schema();

unregister_taxonomy( 'type' );

$this->assertSame( 'string', $schema['properties']['type']['type'] );
}

/**
* @ticket 40878
* @covers ::get_items_permissions_check
Expand Down