Make WordPress Core

Ticket #45016: 45016.diff

File 45016.diff, 20.6 KB (added by desrosj, 6 years ago)
  • src/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php

     
     1<?php
     2/**
     3 * REST API: WP_REST_Themes_Controller class
     4 *
     5 * @package WordPress
     6 * @subpackage REST_API
     7 * @since 5.0.0
     8 */
     9
     10/**
     11 * Core class used to manage themes via the REST API.
     12 *
     13 * @since 5.0.0
     14 *
     15 * @see WP_REST_Controller
     16 */
     17class WP_REST_Themes_Controller extends WP_REST_Controller {
     18
     19        /**
     20         * Constructor.
     21         *
     22         * @since 5.0.0
     23         */
     24        public function __construct() {
     25                $this->namespace = 'wp/v2';
     26                $this->rest_base = 'themes';
     27        }
     28
     29        /**
     30         * Registers the routes for the objects of the controller.
     31         *
     32         * @since 5.0.0
     33         *
     34         * @see register_rest_route()
     35         */
     36        public function register_routes() {
     37                register_rest_route(
     38                        $this->namespace,
     39                        '/' . $this->rest_base,
     40                        array(
     41                                array(
     42                                        'methods'             => WP_REST_Server::READABLE,
     43                                        'callback'            => array( $this, 'get_items' ),
     44                                        'permission_callback' => array( $this, 'get_items_permissions_check' ),
     45                                        'args'                => $this->get_collection_params(),
     46                                ),
     47                                'schema' => array( $this, 'get_item_schema' ),
     48                        )
     49                );
     50        }
     51
     52        /**
     53         * Checks if a given request has access to read the theme.
     54         *
     55         * @since 5.0.0
     56         *
     57         * @param WP_REST_Request $request Full details about the request.
     58         * @return true|WP_Error True if the request has read access for the item, otherwise WP_Error object.
     59         */
     60        public function get_items_permissions_check( $request ) {
     61                if ( ! is_user_logged_in() || ! current_user_can( 'edit_posts' ) ) {
     62                        return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you are not allowed to view themes.', 'gutenberg' ), array( 'status' => rest_authorization_required_code() ) );
     63                }
     64
     65                return true;
     66        }
     67
     68        /**
     69         * Retrieves a collection of themes.
     70         *
     71         * @since 5.0.0
     72         *
     73         * @param WP_REST_Request $request Full details about the request.
     74         * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
     75         */
     76        public function get_items( $request ) {
     77                // Retrieve the list of registered collection query parameters.
     78                $registered = $this->get_collection_params();
     79                $themes     = array();
     80
     81                if ( isset( $registered['status'], $request['status'] ) && in_array( 'active', $request['status'], true ) ) {
     82                        $active_theme = wp_get_theme();
     83                        $active_theme = $this->prepare_item_for_response( $active_theme, $request );
     84                        $themes[]     = $this->prepare_response_for_collection( $active_theme );
     85                }
     86
     87                $response = rest_ensure_response( $themes );
     88
     89                $response->header( 'X-WP-Total', count( $themes ) );
     90                $response->header( 'X-WP-TotalPages', count( $themes ) );
     91
     92                return $response;
     93        }
     94
     95        /**
     96         * Prepares a single theme output for response.
     97         *
     98         * @since 5.0.0
     99         *
     100         * @param WP_Theme        $theme   Theme object.
     101         * @param WP_REST_Request $request Request object.
     102         * @return WP_REST_Response Response object.
     103         */
     104        public function prepare_item_for_response( $theme, $request ) {
     105                $data   = array();
     106                $fields = $this->get_fields_for_response( $request );
     107
     108                if ( in_array( 'theme_supports', $fields, true ) ) {
     109                        $formats                           = get_theme_support( 'post-formats' );
     110                        $formats                           = is_array( $formats ) ? array_values( $formats[0] ) : array();
     111                        $formats                           = array_merge( array( 'standard' ), $formats );
     112                        $data['theme_supports']['formats'] = $formats;
     113
     114                        $data['theme_supports']['post-thumbnails'] = false;
     115                        $post_thumbnails                           = get_theme_support( 'post-thumbnails' );
     116
     117                        if ( $post_thumbnails ) {
     118                                // $post_thumbnails can contain a nested array of post types.
     119                                // e.g. array( array( 'post', 'page' ) ).
     120                                $data['theme_supports']['post-thumbnails'] = is_array( $post_thumbnails ) ? $post_thumbnails[0] : true;
     121                        }
     122                }
     123
     124                $data = $this->add_additional_fields_to_object( $data, $request );
     125
     126                // Wrap the data in a response object.
     127                $response = rest_ensure_response( $data );
     128
     129                /**
     130                 * Filters theme data returned from the REST API.
     131                 *
     132                 * @since 5.0.0
     133                 *
     134                 * @param WP_REST_Response $response The response object.
     135                 * @param WP_Theme         $theme    Theme object used to create response.
     136                 * @param WP_REST_Request  $request  Request object.
     137                 */
     138                return apply_filters( 'rest_prepare_theme', $response, $theme, $request );
     139        }
     140
     141        /**
     142         * Retrieves the theme's schema, conforming to JSON Schema.
     143         *
     144         * @since 5.0.0
     145         *
     146         * @return array Item schema data.
     147         */
     148        public function get_item_schema() {
     149                $schema = array(
     150                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
     151                        'title'      => 'theme',
     152                        'type'       => 'object',
     153                        'properties' => array(
     154                                'theme_supports' => array(
     155                                        'description' => __( 'Features supported by this theme.', 'gutenberg' ),
     156                                        'type'        => 'array',
     157                                        'readonly'    => true,
     158                                        'properties'  => array(
     159                                                'formats'         => array(
     160                                                        'description' => __( 'Post formats supported.', 'gutenberg' ),
     161                                                        'type'        => 'array',
     162                                                        'readonly'    => true,
     163                                                ),
     164                                                'post-thumbnails' => array(
     165                                                        'description' => __( 'Whether the theme supports post thumbnails.', 'gutenberg' ),
     166                                                        'type'        => array( 'array', 'bool' ),
     167                                                        'readonly'    => true,
     168                                                ),
     169                                        ),
     170                                ),
     171                        ),
     172                );
     173
     174                return $this->add_additional_fields_schema( $schema );
     175        }
     176
     177        /**
     178         * Retrieves the search params for the themes collection.
     179         *
     180         * @since 5.0.0
     181         *
     182         * @return array Collection parameters.
     183         */
     184        public function get_collection_params() {
     185                $query_params = parent::get_collection_params();
     186
     187                $query_params['status'] = array(
     188                        'description'       => __( 'Limit result set to themes assigned one or more statuses.', 'gutenberg' ),
     189                        'type'              => 'array',
     190                        'items'             => array(
     191                                'enum' => array( 'active' ),
     192                                'type' => 'string',
     193                        ),
     194                        'required'          => true,
     195                        'sanitize_callback' => array( $this, 'sanitize_theme_status' ),
     196                );
     197
     198                /**
     199                 * Filter collection parameters for the themes controller.
     200                 *
     201                 * @since 5.0.0
     202                 *
     203                 * @param array        $query_params JSON Schema-formatted collection parameters.
     204                 */
     205                return apply_filters( 'rest_themes_collection_params', $query_params );
     206        }
     207
     208        /**
     209         * Sanitizes and validates the list of theme status.
     210         *
     211         * @since 5.0.0
     212         *
     213         * @param  string|array    $statuses  One or more theme statuses.
     214         * @param  WP_REST_Request $request   Full details about the request.
     215         * @param  string          $parameter Additional parameter to pass to validation.
     216         * @return array|WP_Error A list of valid statuses, otherwise WP_Error object.
     217         */
     218        public function sanitize_theme_status( $statuses, $request, $parameter ) {
     219                $statuses = wp_parse_slug_list( $statuses );
     220
     221                foreach ( $statuses as $status ) {
     222                        $result = rest_validate_request_arg( $status, $request, $parameter );
     223
     224                        if ( is_wp_error( $result ) ) {
     225                                return $result;
     226                        }
     227                }
     228
     229                return $statuses;
     230        }
     231}
  • src/wp-includes/rest-api.php

     
    233233        // Settings.
    234234        $controller = new WP_REST_Settings_Controller;
    235235        $controller->register_routes();
     236
     237        // Themes.
     238        $controller = new WP_REST_Themes_Controller;
     239        $controller->register_routes();
    236240}
    237241
    238242/**
  • src/wp-settings.php

     
    234234require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-users-controller.php' );
    235235require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-comments-controller.php' );
    236236require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-settings-controller.php' );
     237require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-themes-controller.php' );
    237238require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-meta-fields.php' );
    238239require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-comment-meta-fields.php' );
    239240require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-post-meta-fields.php' );
  • tests/phpunit/tests/rest-api/rest-schema-setup.php

     
    111111                        '/wp/v2/comments',
    112112                        '/wp/v2/comments/(?P<id>[\\d]+)',
    113113                        '/wp/v2/settings',
     114                        '/wp/v2/themes',
    114115                );
    115116
    116117                $this->assertEquals( $expected_routes, $routes );
  • tests/phpunit/tests/rest-api/rest-themes-controller.php

     
     1<?php
     2/**
     3 * Unit tests covering WP_REST_Themes_Controller functionality.
     4 *
     5 * @package WordPress
     6 * @subpackage REST API
     7 */
     8
     9/**
     10 * @group restapi-themes
     11 * @group restapi
     12 */
     13class WP_Test_REST_Themes_Controller extends WP_Test_REST_Controller_Testcase {
     14        /**
     15         * Subscriber user ID.
     16         *
     17         * @since 5.0.0
     18         *
     19         * @var int $subscriber_id
     20         */
     21        protected static $subscriber_id;
     22
     23        /**
     24         * Contributor user ID.
     25         *
     26         * @since 5.0.0
     27         *
     28         * @var int $contributor_id
     29         */
     30        protected static $contributor_id;
     31
     32        /**
     33         * The current theme object.
     34         *
     35         * @since 5.0.0
     36         *
     37         * @var WP_Theme $current_theme
     38         */
     39        protected static $current_theme;
     40
     41        /**
     42         * The REST API route for the active theme.
     43         *
     44         * @since 5.0.0
     45         *
     46         * @var string $themes_route
     47         */
     48        protected static $themes_route = '/wp/v2/themes';
     49
     50        /**
     51         * Performs a REST API request for the active theme.
     52         *
     53         * @since 5.0.0
     54         *
     55         * @param string $method Optional. Request method. Default GET.
     56         * @return WP_REST_Response The request's response.
     57         */
     58        protected function perform_active_theme_request( $method = 'GET' ) {
     59                $request = new WP_REST_Request( $method, self::$themes_route );
     60                $request->set_param( 'status', 'active' );
     61
     62                return rest_get_server()->dispatch( $request );
     63        }
     64
     65        /**
     66         * Check that common properties are included in a response.
     67         *
     68         * @since 5.0.0
     69         *
     70         * @param WP_REST_Response $response Current REST API response.
     71         */
     72        protected function check_get_theme_response( $response ) {
     73                if ( $response instanceof WP_REST_Response ) {
     74                        $headers  = $response->get_headers();
     75                        $response = $response->get_data();
     76                } else {
     77                        $headers = array();
     78                }
     79
     80                $this->assertArrayHasKey( 'X-WP-Total', $headers );
     81                $this->assertEquals( 1, $headers['X-WP-Total'] );
     82                $this->assertArrayHasKey( 'X-WP-TotalPages', $headers );
     83                $this->assertEquals( 1, $headers['X-WP-TotalPages'] );
     84        }
     85
     86        /**
     87         * Set up class test fixtures.
     88         *
     89         * @since 5.0.0
     90         *
     91         * @param WP_UnitTest_Factory $factory WordPress unit test factory.
     92         */
     93        public static function wpSetUpBeforeClass( $factory ) {
     94                self::$subscriber_id  = $factory->user->create(
     95                        array(
     96                                'role' => 'subscriber',
     97                        )
     98                );
     99                self::$contributor_id = $factory->user->create(
     100                        array(
     101                                'role' => 'contributor',
     102                        )
     103                );
     104                self::$current_theme  = wp_get_theme();
     105
     106                wp_set_current_user( self::$contributor_id );
     107        }
     108
     109        /**
     110         * Clean up test fixtures.
     111         *
     112         * @since 5.0.0
     113         */
     114        public static function wpTearDownAfterClass() {
     115                self::delete_user( self::$subscriber_id );
     116                self::delete_user( self::$contributor_id );
     117        }
     118
     119        /**
     120         * Set up each test method.
     121         *
     122         * @since 5.0.0
     123         */
     124        public function setUp() {
     125                parent::setUp();
     126
     127                wp_set_current_user( self::$contributor_id );
     128        }
     129
     130        /**
     131         * Theme routes should be registered correctly.
     132         *
     133         * @ticket 45016
     134         */
     135        public function test_register_routes() {
     136                $routes = rest_get_server()->get_routes();
     137                $this->assertArrayHasKey( self::$themes_route, $routes );
     138        }
     139
     140        /**
     141         * Test retrieving a collection of themes.
     142         *
     143         * @ticket 45016
     144         */
     145        public function test_get_items() {
     146                $response = self::perform_active_theme_request();
     147
     148                $this->assertEquals( 200, $response->get_status() );
     149                $data = $response->get_data();
     150
     151                $this->check_get_theme_response( $response );
     152                $fields = array(
     153                        'theme_supports',
     154                );
     155                $this->assertEqualSets( $fields, array_keys( $data[0] ) );
     156        }
     157
     158        /**
     159         * An error should be returned when the user does not have the edit_posts capability.
     160         *
     161         * @ticket 45016
     162         */
     163        public function test_get_items_no_permission() {
     164                wp_set_current_user( self::$subscriber_id );
     165                $response = self::perform_active_theme_request();
     166                $this->assertErrorResponse( 'rest_user_cannot_view', $response, 403 );
     167        }
     168
     169        /**
     170         * Test an item is prepared for the response.
     171         *
     172         * @ticket 45016
     173         */
     174        public function test_prepare_item() {
     175                $response = self::perform_active_theme_request();
     176                $this->assertEquals( 200, $response->get_status() );
     177                $this->check_get_theme_response( $response );
     178        }
     179
     180        /**
     181         * Verify the theme schema.
     182         *
     183         * @ticket 45016
     184         */
     185        public function test_get_item_schema() {
     186                $response   = self::perform_active_theme_request( 'OPTIONS' );
     187                $data       = $response->get_data();
     188                $properties = $data['schema']['properties'];
     189                $this->assertEquals( 1, count( $properties ) );
     190                $this->assertArrayHasKey( 'theme_supports', $properties );
     191
     192                $this->assertEquals( 2, count( $properties['theme_supports']['properties'] ) );
     193                $this->assertArrayHasKey( 'formats', $properties['theme_supports']['properties'] );
     194                $this->assertArrayHasKey( 'post-thumbnails', $properties['theme_supports']['properties'] );
     195        }
     196
     197        /**
     198         * Should include relevant data in the 'theme_supports' key.
     199         *
     200         * @ticket 45016
     201         */
     202        public function test_theme_supports_formats() {
     203                remove_theme_support( 'post-formats' );
     204                $response = self::perform_active_theme_request();
     205                $result   = $response->get_data();
     206                $this->assertTrue( isset( $result[0]['theme_supports'] ) );
     207                $this->assertTrue( isset( $result[0]['theme_supports']['formats'] ) );
     208                $this->assertSame( array( 'standard' ), $result[0]['theme_supports']['formats'] );
     209        }
     210
     211        /**
     212         * Test when a theme only supports some post formats.
     213         *
     214         * @ticket 45016
     215         */
     216        public function test_theme_supports_formats_non_default() {
     217                add_theme_support( 'post-formats', array( 'aside', 'video' ) );
     218                $response = self::perform_active_theme_request();
     219                $result   = $response->get_data();
     220                $this->assertTrue( isset( $result[0]['theme_supports'] ) );
     221                $this->assertTrue( isset( $result[0]['theme_supports']['formats'] ) );
     222                $this->assertSame( array( 'standard', 'aside', 'video' ), $result[0]['theme_supports']['formats'] );
     223        }
     224
     225        /**
     226         * Test when a theme does not support post thumbnails.
     227         *
     228         * @ticket 45016
     229         */
     230        public function test_theme_supports_post_thumbnails_false() {
     231                remove_theme_support( 'post-thumbnails' );
     232                $response = self::perform_active_theme_request();
     233
     234                $result = $response->get_data();
     235                $this->assertTrue( isset( $result[0]['theme_supports'] ) );
     236                $this->assertTrue( isset( $result[0]['theme_supports']['post-thumbnails'] ) );
     237                $this->assertFalse( $result[0]['theme_supports']['post-thumbnails'] );
     238        }
     239
     240        /**
     241         * Test when a theme supports all post thumbnails.
     242         *
     243         * @ticket 45016
     244         */
     245        public function test_theme_supports_post_thumbnails_true() {
     246                remove_theme_support( 'post-thumbnails' );
     247                add_theme_support( 'post-thumbnails' );
     248                $response = self::perform_active_theme_request();
     249                $result   = $response->get_data();
     250                $this->assertTrue( isset( $result[0]['theme_supports'] ) );
     251                $this->assertTrue( $result[0]['theme_supports']['post-thumbnails'] );
     252        }
     253
     254        /**
     255         * Test when a theme only supports post thumbnails for certain post types.
     256         *
     257         * @ticket 45016
     258         */
     259        public function test_theme_supports_post_thumbnails_array() {
     260                remove_theme_support( 'post-thumbnails' );
     261                add_theme_support( 'post-thumbnails', array( 'post' ) );
     262                $response = self::perform_active_theme_request();
     263                $result   = $response->get_data();
     264                $this->assertTrue( isset( $result[0]['theme_supports'] ) );
     265                $this->assertEquals( array( 'post' ), $result[0]['theme_supports']['post-thumbnails'] );
     266        }
     267
     268        /**
     269         * It should be possible to register custom fields to the endpoint.
     270         *
     271         * @ticket 45016
     272         */
     273        public function test_get_additional_field_registration() {
     274                $schema = array(
     275                        'type'        => 'integer',
     276                        'description' => 'Some integer of mine',
     277                        'enum'        => array( 1, 2, 3, 4 ),
     278                );
     279
     280                register_rest_field(
     281                        'theme',
     282                        'my_custom_int',
     283                        array(
     284                                'schema'       => $schema,
     285                                'get_callback' => array( $this, 'additional_field_get_callback' ),
     286                        )
     287                );
     288
     289                $response = self::perform_active_theme_request( 'OPTIONS' );
     290                $data     = $response->get_data();
     291
     292                $this->assertArrayHasKey( 'my_custom_int', $data['schema']['properties'] );
     293                $this->assertEquals( $schema, $data['schema']['properties']['my_custom_int'] );
     294
     295                $response = self::perform_active_theme_request( 'GET' );
     296                $data     = $response->get_data();
     297                $this->assertArrayHasKey( 'my_custom_int', $data[0] );
     298                $this->assertSame( 2, $data[0]['my_custom_int'] );
     299
     300                global $wp_rest_additional_fields;
     301                $wp_rest_additional_fields = array();
     302        }
     303
     304        /**
     305         * Return a value for the custom field.
     306         *
     307         * @since 5.0.0
     308         *
     309         * @param array $theme Theme data array.
     310         * @return int Additional field value.
     311         */
     312        public function additional_field_get_callback( $theme ) {
     313                return 2;
     314        }
     315
     316        /**
     317         * The create_item() method does not exist for themes.
     318         */
     319        public function test_create_item() {}
     320
     321        /**
     322         * The update_item() method does not exist for themes.
     323         */
     324        public function test_update_item() {}
     325
     326        /**
     327         * The get_item() method does not exist for themes.
     328         */
     329        public function test_get_item() {}
     330
     331        /**
     332         * The delete_item() method does not exist for themes.
     333         */
     334        public function test_delete_item() {}
     335
     336        /**
     337         * Context is not supported for themes.
     338         */
     339        public function test_context_param() {}
     340}
  • tests/qunit/fixtures/wp-api-generated.js

     
    35173517            "_links": {
    35183518                "self": "http://example.org/index.php?rest_route=/wp/v2/settings"
    35193519            }
     3520        },
     3521        "/wp/v2/themes": {
     3522            "namespace": "wp/v2",
     3523            "methods": [
     3524                "GET"
     3525            ],
     3526            "endpoints": [
     3527                {
     3528                    "methods": [
     3529                        "GET"
     3530                    ],
     3531                    "args": {
     3532                        "context": {
     3533                            "required": false,
     3534                            "description": "Scope under which the request is made; determines fields present in response.",
     3535                            "type": "string"
     3536                        },
     3537                        "page": {
     3538                            "required": false,
     3539                            "default": 1,
     3540                            "description": "Current page of the collection.",
     3541                            "type": "integer"
     3542                        },
     3543                        "per_page": {
     3544                            "required": false,
     3545                            "default": 10,
     3546                            "description": "Maximum number of items to be returned in result set.",
     3547                            "type": "integer"
     3548                        },
     3549                        "search": {
     3550                            "required": false,
     3551                            "description": "Limit results to those matching a string.",
     3552                            "type": "string"
     3553                        },
     3554                        "status": {
     3555                            "required": true,
     3556                            "description": "Limit result set to themes assigned one or more statuses.",
     3557                            "type": "array",
     3558                            "items": {
     3559                                "enum": [
     3560                                    "active"
     3561                                ],
     3562                                "type": "string"
     3563                            }
     3564                        }
     3565                    }
     3566                }
     3567            ],
     3568            "_links": {
     3569                "self": "http://example.org/index.php?rest_route=/wp/v2/themes"
     3570            }
    35203571        }
    35213572    }
    35223573};