From c0995319f4f0976d52b36dfc283bd19daf715561 Mon Sep 17 00:00:00 2001 From: TimothyBlynJacobs Date: Mon, 1 Nov 2021 03:27:55 +0000 Subject: [PATCH] REST API: Support custom namespaces for taxonomies. While a taxonomy can define a custom route by using the rest_base argument, a namespace of wp/v2 was assumed. This commit introduces support for a rest_namespace argument. A new rest_get_route_for_taxonomy_items function has been introduced and the rest_get_route_for_term function updated to facilitate getting the correct route for taxonomies. For maximum compatibility sticking with the default wp/v2 namespace is recommended until the API functions see wider use. Props spacedmonkey. Fixes #54267. See [51962]. Built from https://develop.svn.wordpress.org/trunk@51964 git-svn-id: http://core.svn.wordpress.org/trunk@51553 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- wp-includes/class-wp-taxonomy.php | 14 +++++ wp-includes/rest-api.php | 51 +++++++++++++------ .../class-wp-rest-posts-controller.php | 9 ++-- .../class-wp-rest-taxonomies-controller.php | 32 ++++++++---- .../class-wp-rest-terms-controller.php | 2 +- wp-includes/taxonomy.php | 1 + wp-includes/version.php | 2 +- 7 files changed, 77 insertions(+), 34 deletions(-) diff --git a/wp-includes/class-wp-taxonomy.php b/wp-includes/class-wp-taxonomy.php index 9db7403809..21c954ad05 100644 --- a/wp-includes/class-wp-taxonomy.php +++ b/wp-includes/class-wp-taxonomy.php @@ -199,6 +199,14 @@ final class WP_Taxonomy { */ public $rest_base; + /** + * The namespace for this taxonomy's REST API endpoints. + * + * @since 5.9 + * @var string|bool $rest_namespace + */ + public $rest_namespace; + /** * The controller for this taxonomy's REST API endpoints. * @@ -319,6 +327,7 @@ final class WP_Taxonomy { 'update_count_callback' => '', 'show_in_rest' => false, 'rest_base' => false, + 'rest_namespace' => false, 'rest_controller_class' => false, 'default_term' => null, 'sort' => null, @@ -384,6 +393,11 @@ final class WP_Taxonomy { $args['show_in_quick_edit'] = $args['show_ui']; } + // If not set, default rest_namespace to wp/v2 if show_in_rest is true. + if ( false === $args['rest_namespace'] && ! empty( $args['show_in_rest'] ) ) { + $args['rest_namespace'] = 'wp/v2'; + } + $default_caps = array( 'manage_terms' => 'manage_categories', 'edit_terms' => 'manage_categories', diff --git a/wp-includes/rest-api.php b/wp-includes/rest-api.php index a30a246d95..03860a915c 100644 --- a/wp-includes/rest-api.php +++ b/wp-includes/rest-api.php @@ -3115,24 +3115,12 @@ function rest_get_route_for_term( $term ) { return ''; } - $taxonomy = get_taxonomy( $term->taxonomy ); - if ( ! $taxonomy ) { + $taxonomy_route = rest_get_route_for_taxonomy_items( $term->taxonomy ); + if ( ! $taxonomy_route ) { return ''; } - $controller = $taxonomy->get_rest_controller(); - if ( ! $controller ) { - return ''; - } - - $route = ''; - - // The only controller that works is the Terms controller. - if ( $controller instanceof WP_REST_Terms_Controller ) { - $namespace = 'wp/v2'; - $rest_base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; - $route = sprintf( '/%s/%s/%d', $namespace, $rest_base, $term->term_id ); - } + $route = sprintf( '%s/%d', $taxonomy_route, $term->term_id ); /** * Filters the REST API route for a term. @@ -3145,6 +3133,39 @@ function rest_get_route_for_term( $term ) { return apply_filters( 'rest_route_for_term', $route, $term ); } +/** + * Gets the REST API route for a taxonomy. + * + * @since 5.9.0 + * + * @param string $taxonomy Name of taxonomy. + * @return string The route path with a leading slash for the given taxonomy. + */ +function rest_get_route_for_taxonomy_items( $taxonomy ) { + $taxonomy = get_taxonomy( $taxonomy ); + if ( ! $taxonomy ) { + return ''; + } + + if ( ! $taxonomy->show_in_rest ) { + return ''; + } + + $namespace = ! empty( $taxonomy->rest_namespace ) ? $taxonomy->rest_namespace : 'wp/v2'; + $rest_base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; + $route = sprintf( '/%s/%s', $namespace, $rest_base ); + + /** + * Filters the REST API route for a taxonomy. + * + * @since 5.9.0 + * + * @param string $route The route path. + * @param WP_Taxonomy $taxonomy The taxonomy object. + */ + return apply_filters( 'rest_route_for_taxonomy_items', $route, $taxonomy ); +} + /** * Gets the REST route for the currently queried object. * diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php index 0cc548b0b6..ffb4466892 100644 --- a/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php +++ b/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php @@ -2060,19 +2060,16 @@ class WP_REST_Posts_Controller extends WP_REST_Controller { $links['https://api.w.org/term'] = array(); foreach ( $taxonomies as $tax ) { - $taxonomy_obj = get_taxonomy( $tax ); + $taxonomy_route = rest_get_route_for_taxonomy_items( $tax ); // Skip taxonomies that are not public. - if ( empty( $taxonomy_obj->show_in_rest ) ) { + if ( empty( $taxonomy_route ) ) { continue; } - - $tax_base = ! empty( $taxonomy_obj->rest_base ) ? $taxonomy_obj->rest_base : $tax; - $terms_url = add_query_arg( 'post', $post->ID, - rest_url( 'wp/v2/' . $tax_base ) + rest_url( $taxonomy_route ) ); $links['https://api.w.org/term'][] = array( diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php index 9d43ca2874..d05abcbcea 100644 --- a/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php +++ b/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php @@ -250,6 +250,10 @@ class WP_REST_Taxonomies_Controller extends WP_REST_Controller { $data['rest_base'] = $base; } + if ( in_array( 'rest_namespace', $fields, true ) ) { + $data['rest_namespace'] = $taxonomy->rest_namespace; + } + if ( in_array( 'visibility', $fields, true ) ) { $data['visibility'] = array( 'public' => (bool) $taxonomy->public, @@ -274,7 +278,7 @@ class WP_REST_Taxonomies_Controller extends WP_REST_Controller { 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), ), 'https://api.w.org/items' => array( - 'href' => rest_url( sprintf( 'wp/v2/%s', $base ) ), + 'href' => rest_url( rest_get_route_for_taxonomy_items( $taxonomy->name ) ), ), ) ); @@ -310,49 +314,49 @@ class WP_REST_Taxonomies_Controller extends WP_REST_Controller { 'title' => 'taxonomy', 'type' => 'object', 'properties' => array( - 'capabilities' => array( + 'capabilities' => array( 'description' => __( 'All capabilities used by the taxonomy.' ), 'type' => 'object', 'context' => array( 'edit' ), 'readonly' => true, ), - 'description' => array( + 'description' => array( 'description' => __( 'A human-readable description of the taxonomy.' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), - 'hierarchical' => array( + 'hierarchical' => array( 'description' => __( 'Whether or not the taxonomy should have children.' ), 'type' => 'boolean', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), - 'labels' => array( + 'labels' => array( 'description' => __( 'Human-readable labels for the taxonomy for various contexts.' ), 'type' => 'object', 'context' => array( 'edit' ), 'readonly' => true, ), - 'name' => array( + 'name' => array( 'description' => __( 'The title for the taxonomy.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), - 'slug' => array( + 'slug' => array( 'description' => __( 'An alphanumeric identifier for the taxonomy.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), - 'show_cloud' => array( + 'show_cloud' => array( 'description' => __( 'Whether or not the term cloud should be displayed.' ), 'type' => 'boolean', 'context' => array( 'edit' ), 'readonly' => true, ), - 'types' => array( + 'types' => array( 'description' => __( 'Types associated with the taxonomy.' ), 'type' => 'array', 'items' => array( @@ -361,13 +365,19 @@ class WP_REST_Taxonomies_Controller extends WP_REST_Controller { 'context' => array( 'view', 'edit' ), 'readonly' => true, ), - 'rest_base' => array( + 'rest_base' => array( 'description' => __( 'REST base route for the taxonomy.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), - 'visibility' => array( + 'rest_namespace' => array( + 'description' => __( 'REST namespace route for the taxonomy.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + 'readonly' => true, + ), + 'visibility' => array( 'description' => __( 'The visibility settings for the taxonomy.' ), 'type' => 'object', 'context' => array( 'edit' ), diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php index 183b7c7ffa..3a92b774f0 100644 --- a/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php +++ b/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php @@ -57,9 +57,9 @@ class WP_REST_Terms_Controller extends WP_REST_Controller { */ public function __construct( $taxonomy ) { $this->taxonomy = $taxonomy; - $this->namespace = 'wp/v2'; $tax_obj = get_taxonomy( $taxonomy ); $this->rest_base = ! empty( $tax_obj->rest_base ) ? $tax_obj->rest_base : $tax_obj->name; + $this->namespace = ! empty( $tax_obj->rest_namespace ) ? $tax_obj->rest_namespace : 'wp/v2'; $this->meta = new WP_REST_Term_Meta_Fields( $taxonomy ); } diff --git a/wp-includes/taxonomy.php b/wp-includes/taxonomy.php index f81af0351e..0273a4aac7 100644 --- a/wp-includes/taxonomy.php +++ b/wp-includes/taxonomy.php @@ -387,6 +387,7 @@ function is_taxonomy_hierarchical( $taxonomy ) { * @type bool $show_in_rest Whether to include the taxonomy in the REST API. Set this to true * for the taxonomy to be available in the block editor. * @type string $rest_base To change the base url of REST API route. Default is $taxonomy. + * @type string $rest_namespace To change the namespace URL of REST API route. Default is wp/v2. * @type string $rest_controller_class REST API Controller class name. Default is 'WP_REST_Terms_Controller'. * @type bool $show_tagcloud Whether to list the taxonomy in the Tag Cloud Widget controls. If not set, * the default is inherited from `$show_ui` (default true). diff --git a/wp-includes/version.php b/wp-includes/version.php index bf045dec68..842a401546 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -16,7 +16,7 @@ * * @global string $wp_version */ -$wp_version = '5.9-alpha-51963'; +$wp_version = '5.9-alpha-51964'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.