Make WordPress Core

Changeset 48273


Ignore:
Timestamp:
07/02/2020 05:55:04 AM (6 years ago)
Author:
TimothyBlynJacobs
Message:

REST API: Link to the REST route for the currently queried resource.

This allows for programatically determining the REST version of the current page. The links also aid human discovery of the REST API in general.

Props dshanske, tfrommen, TimothyBlynJacobs.
Fixes #49116.

Location:
trunk
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-taxonomy.php

    r47808 r48273  
    209209         */
    210210        public $rest_controller_class;
     211
     212        /**
     213         * The controller instance for this taxonomy's REST API endpoints.
     214         *
     215         * Lazily computed. Should be accessed using {@see WP_Taxonomy::get_rest_controller()}.
     216         *
     217         * @since 5.5.0
     218         * @var WP_REST_Controller $rest_controller
     219         */
     220        public $rest_controller;
    211221
    212222        /**
     
    453463                remove_filter( 'wp_ajax_add-' . $this->name, '_wp_ajax_add_hierarchical_term' );
    454464        }
     465
     466        /**
     467         * Gets the REST API controller for this taxonomy.
     468         *
     469         * Will only instantiate the controller class once per request.
     470         *
     471         * @since 5.5.0
     472         *
     473         * @return WP_REST_Controller|null The controller instance, or null if the taxonomy
     474         *                                 is set not to show in rest.
     475         */
     476        public function get_rest_controller() {
     477                if ( ! $this->show_in_rest ) {
     478                        return null;
     479                }
     480
     481                $class = $this->rest_controller_class ? $this->rest_controller_class : WP_REST_Terms_Controller::class;
     482
     483                if ( ! class_exists( $class ) ) {
     484                        return null;
     485                }
     486
     487                if ( ! is_subclass_of( $class, WP_REST_Controller::class ) ) {
     488                        return null;
     489                }
     490
     491                if ( ! $this->rest_controller ) {
     492                        $this->rest_controller = new $class( $this->name );
     493                }
     494
     495                if ( ! ( $this->rest_controller instanceof $class ) ) {
     496                        return null;
     497                }
     498
     499                return $this->rest_controller;
     500        }
    455501}
  • trunk/src/wp-includes/rest-api.php

    r48242 r48273  
    235235        // Terms.
    236236        foreach ( get_taxonomies( array( 'show_in_rest' => true ), 'object' ) as $taxonomy ) {
    237                 $class = ! empty( $taxonomy->rest_controller_class ) ? $taxonomy->rest_controller_class : 'WP_REST_Terms_Controller';
    238 
    239                 if ( ! class_exists( $class ) ) {
    240                         continue;
    241                 }
    242                 $controller = new $class( $taxonomy->name );
    243                 if ( ! is_subclass_of( $controller, 'WP_REST_Controller' ) ) {
     237                $controller = $taxonomy->get_rest_controller();
     238
     239                if ( ! $controller ) {
    244240                        continue;
    245241                }
     
    874870        }
    875871
    876         echo "<link rel='https://api.w.org/' href='" . esc_url( $api_root ) . "' />\n";
     872        printf( '<link rel="https://api.w.org/" href="%s" />', esc_url( $api_root ) );
     873
     874        $resource = rest_get_queried_resource_route();
     875
     876        if ( $resource ) {
     877                printf( '<link rel="alternate" type="application/json" href="%s" />', esc_url( rest_url( $resource ) ) );
     878        }
    877879}
    878880
     
    893895        }
    894896
    895         header( 'Link: <' . esc_url_raw( $api_root ) . '>; rel="https://api.w.org/"', false );
     897        header( sprintf( 'Link: <%s>; rel="https://api.w.org/"', esc_url_raw( $api_root ) ), false );
     898
     899        $resource = rest_get_queried_resource_route();
     900
     901        if ( $resource ) {
     902                header( sprintf( 'Link: <%s>; rel="alternate"; type="application/json"', esc_url_raw( rest_url( $resource ) ) ), false );
     903        }
    896904}
    897905
     
    18241832        return $schema;
    18251833}
     1834
     1835/**
     1836 * Gets the REST API route for a post.
     1837 *
     1838 * @since 5.5.0
     1839 *
     1840 * @param int|WP_Post $post Post ID or post object.
     1841 * @return string The route path with a leading slash for the given post, or an empty string if there is not a route.
     1842 */
     1843function rest_get_route_for_post( $post ) {
     1844        $post = get_post( $post );
     1845
     1846        if ( ! $post instanceof WP_Post ) {
     1847                return '';
     1848        }
     1849
     1850        $post_type = get_post_type_object( $post->post_type );
     1851        if ( ! $post_type ) {
     1852                return '';
     1853        }
     1854
     1855        $controller = $post_type->get_rest_controller();
     1856        if ( ! $controller ) {
     1857                return '';
     1858        }
     1859
     1860        $route = '';
     1861
     1862        // The only two controllers that we can detect are the Attachments and Posts controllers.
     1863        if ( in_array( get_class( $controller ), array( 'WP_REST_Attachments_Controller', 'WP_REST_Posts_Controller' ), true ) ) {
     1864                $namespace = 'wp/v2';
     1865                $rest_base = ! empty( $post_type->rest_base ) ? $post_type->rest_base : $post_type->name;
     1866                $route     = sprintf( '/%s/%s/%d', $namespace, $rest_base, $post->ID );
     1867        }
     1868
     1869        /**
     1870         * Filters the REST API route for a post.
     1871         *
     1872         * @since 5.5.0
     1873         *
     1874         * @param string  $route The route path.
     1875         * @param WP_Post $post  The post object.
     1876         */
     1877        return apply_filters( 'rest_route_for_post', $route, $post );
     1878}
     1879
     1880/**
     1881 * Gets the REST API route for a term.
     1882 *
     1883 * @since 5.5.0
     1884 *
     1885 * @param int|WP_Term $term Term ID or term object.
     1886 * @return string The route path with a leading slash for the given term, or an empty string if there is not a route.
     1887 */
     1888function rest_get_route_for_term( $term ) {
     1889        $term = get_term( $term );
     1890
     1891        if ( ! $term instanceof WP_Term ) {
     1892                return '';
     1893        }
     1894
     1895        $taxonomy = get_taxonomy( $term->taxonomy );
     1896        if ( ! $taxonomy ) {
     1897                return '';
     1898        }
     1899
     1900        $controller = $taxonomy->get_rest_controller();
     1901        if ( ! $controller ) {
     1902                return '';
     1903        }
     1904
     1905        $route = '';
     1906
     1907        // The only controller that works is the Terms controller.
     1908        if ( 'WP_REST_Terms_Controller' === get_class( $controller ) ) {
     1909                $namespace = 'wp/v2';
     1910                $rest_base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
     1911                $route     = sprintf( '/%s/%s/%d', $namespace, $rest_base, $term->term_id );
     1912        }
     1913
     1914        /**
     1915         * Filters the REST API route for a term.
     1916         *
     1917         * @since 5.5.0
     1918         *
     1919         * @param string  $route The route path.
     1920         * @param WP_Term $term  The term object.
     1921         */
     1922        return apply_filters( 'rest_route_for_term', $route, $term );
     1923}
     1924
     1925/**
     1926 * Gets the REST route for the currently queried object.
     1927 *
     1928 * @since 5.5.0
     1929 *
     1930 * @return string The REST route of the resource, or an empty string if no resource identified.
     1931 */
     1932function rest_get_queried_resource_route() {
     1933        if ( is_singular() ) {
     1934                $route = rest_get_route_for_post( get_queried_object() );
     1935        } elseif ( is_category() || is_tag() || is_tax() ) {
     1936                $route = rest_get_route_for_term( get_queried_object() );
     1937        } elseif ( is_author() ) {
     1938                $route = '/wp/v2/users/' . get_queried_object_id();
     1939        } else {
     1940                $route = '';
     1941        }
     1942
     1943        /**
     1944         * Filters the REST route for the currently queried object.
     1945         *
     1946         * @since 5.5.0
     1947         *
     1948         * @param string $link The route with a leading slash, or an empty string.
     1949         */
     1950        return apply_filters( 'rest_queried_resource_route', $route );
     1951}
  • trunk/tests/phpunit/tests/rest-api.php

    r48121 r48273  
    13501350                );
    13511351        }
     1352
     1353        /**
     1354         * @ticket 49116
     1355         */
     1356        public function test_rest_get_route_for_post_non_post() {
     1357                $this->assertEquals( '', rest_get_route_for_post( 'garbage' ) );
     1358        }
     1359
     1360        /**
     1361         * @ticket 49116
     1362         */
     1363        public function test_rest_get_route_for_post_invalid_post_type() {
     1364                register_post_type( 'invalid' );
     1365                $post = self::factory()->post->create_and_get( array( 'post_type' => 'invalid' ) );
     1366                unregister_post_type( 'invalid' );
     1367
     1368                $this->assertEquals( '', rest_get_route_for_post( $post ) );
     1369        }
     1370
     1371        /**
     1372         * @ticket 49116
     1373         */
     1374        public function test_rest_get_route_for_post_non_rest() {
     1375                $post = self::factory()->post->create_and_get( array( 'post_type' => 'custom_css' ) );
     1376                $this->assertEquals( '', rest_get_route_for_post( $post ) );
     1377        }
     1378
     1379        /**
     1380         * @ticket 49116
     1381         */
     1382        public function test_rest_get_route_for_post_custom_controller() {
     1383                $post = self::factory()->post->create_and_get( array( 'post_type' => 'wp_block' ) );
     1384                $this->assertEquals( '', rest_get_route_for_post( $post ) );
     1385        }
     1386
     1387        /**
     1388         * @ticket 49116
     1389         */
     1390        public function test_rest_get_route_for_post() {
     1391                $post = self::factory()->post->create_and_get();
     1392                $this->assertEquals( '/wp/v2/posts/' . $post->ID, rest_get_route_for_post( $post ) );
     1393        }
     1394
     1395        /**
     1396         * @ticket 49116
     1397         */
     1398        public function test_rest_get_route_for_media() {
     1399                $post = self::factory()->attachment->create_and_get();
     1400                $this->assertEquals( '/wp/v2/media/' . $post->ID, rest_get_route_for_post( $post ) );
     1401        }
     1402
     1403        /**
     1404         * @ticket 49116
     1405         */
     1406        public function test_rest_get_route_for_post_id() {
     1407                $post = self::factory()->post->create_and_get();
     1408                $this->assertEquals( '/wp/v2/posts/' . $post->ID, rest_get_route_for_post( $post->ID ) );
     1409        }
     1410
     1411        /**
     1412         * @ticket 49116
     1413         */
     1414        public function test_rest_get_route_for_term_non_term() {
     1415                $this->assertEquals( '', rest_get_route_for_term( 'garbage' ) );
     1416        }
     1417
     1418        /**
     1419         * @ticket 49116
     1420         */
     1421        public function test_rest_get_route_for_term_invalid_term_type() {
     1422                register_taxonomy( 'invalid', 'post' );
     1423                $term = self::factory()->term->create_and_get( array( 'taxonomy' => 'invalid' ) );
     1424                unregister_taxonomy( 'invalid' );
     1425
     1426                $this->assertEquals( '', rest_get_route_for_term( $term ) );
     1427        }
     1428
     1429        /**
     1430         * @ticket 49116
     1431         */
     1432        public function test_rest_get_route_for_term_non_rest() {
     1433                $term = self::factory()->term->create_and_get( array( 'taxonomy' => 'post_format' ) );
     1434                $this->assertEquals( '', rest_get_route_for_term( $term ) );
     1435        }
     1436
     1437        /**
     1438         * @ticket 49116
     1439         */
     1440        public function test_rest_get_route_for_term() {
     1441                $term = self::factory()->term->create_and_get();
     1442                $this->assertEquals( '/wp/v2/tags/' . $term->term_id, rest_get_route_for_term( $term ) );
     1443        }
     1444
     1445        /**
     1446         * @ticket 49116
     1447         */
     1448        public function test_rest_get_route_for_category() {
     1449                $term = self::factory()->category->create_and_get();
     1450                $this->assertEquals( '/wp/v2/categories/' . $term->term_id, rest_get_route_for_term( $term ) );
     1451        }
     1452
     1453        /**
     1454         * @ticket 49116
     1455         */
     1456        public function test_rest_get_route_for_term_id() {
     1457                $term = self::factory()->term->create_and_get();
     1458                $this->assertEquals( '/wp/v2/tags/' . $term->term_id, rest_get_route_for_term( $term->term_id ) );
     1459        }
    13521460}
  • trunk/tests/phpunit/tests/rest-api/rest-taxonomies-controller.php

    r47122 r48273  
    292292        }
    293293
     294        /**
     295         * @ticket 49116
     296         */
     297        public function test_get_for_taxonomy_reuses_same_instance() {
     298                $this->assertSame(
     299                        get_taxonomy( 'category' )->get_rest_controller(),
     300                        get_taxonomy( 'category' )->get_rest_controller()
     301                );
     302        }
     303
     304        /**
     305         * @ticket 49116
     306         */
     307        public function test_get_for_taxonomy_returns_terms_controller_if_custom_class_not_specified() {
     308                register_taxonomy(
     309                        'test',
     310                        'post',
     311                        array(
     312                                'show_in_rest' => true,
     313                        )
     314                );
     315
     316                $this->assertInstanceOf(
     317                        WP_REST_Terms_Controller::class,
     318                        get_taxonomy( 'test' )->get_rest_controller()
     319                );
     320        }
     321
    294322}
Note: See TracChangeset for help on using the changeset viewer.