From 69cc6e61d0fd43636d9f8c77607956851e9238e8 Mon Sep 17 00:00:00 2001 From: Timothy Jacobs Date: Sun, 3 Jan 2021 21:45:42 +0000 Subject: [PATCH] REST API: Expose all themes in the themes controller. Previously, only the active theme was made available. This commit allows for all themes to be queried if the user has the `switch_themes` or `manage_network_themes` capabilities. This commit also no longer exposes the `page`, `per_page`, `search` and `context` query parameters since they are not supported by this controller. Props spacedmonkey, lpawlik, TimothyBlynJacobs. Fixes #50152. git-svn-id: https://develop.svn.wordpress.org/trunk@49925 602fd350-edb4-49c9-b593-d223f7449a82 --- .../rest-api/class-wp-rest-server.php | 31 ++- .../class-wp-rest-themes-controller.php | 186 +++++++++++++-- .../tests/rest-api/rest-schema-setup.php | 1 + tests/phpunit/tests/rest-api/rest-server.php | 13 ++ .../tests/rest-api/rest-themes-controller.php | 214 +++++++++++++++++- tests/qunit/fixtures/wp-api-generated.js | 50 ++-- 6 files changed, 436 insertions(+), 59 deletions(-) diff --git a/src/wp-includes/rest-api/class-wp-rest-server.php b/src/wp-includes/rest-api/class-wp-rest-server.php index a692d2304e..85866e26d9 100644 --- a/src/wp-includes/rest-api/class-wp-rest-server.php +++ b/src/wp-includes/rest-api/class-wp-rest-server.php @@ -1244,8 +1244,8 @@ class WP_REST_Server { ); $response = new WP_REST_Response( $available ); - $response->add_link( 'help', 'http://v2.wp-api.org/' ); + $this->add_active_theme_link_to_index( $response ); /** * Filters the REST API root index data. @@ -1261,6 +1261,35 @@ class WP_REST_Server { return apply_filters( 'rest_index', $response ); } + /** + * Adds a link to the active theme for users who have proper permissions. + * + * @since 5.7.0 + * + * @param WP_REST_Response $response REST API response. + */ + protected function add_active_theme_link_to_index( WP_REST_Response $response ) { + $should_add = current_user_can( 'switch_themes' ) || current_user_can( 'manage_network_themes' ); + + if ( ! $should_add && current_user_can( 'edit_posts' ) ) { + $should_add = true; + } + + if ( ! $should_add ) { + foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { + if ( current_user_can( $post_type->cap->edit_posts ) ) { + $should_add = true; + break; + } + } + } + + if ( $should_add ) { + $theme = wp_get_theme(); + $response->add_link( 'https://api.w.org/active-theme', rest_url( 'wp/v2/themes/' . $theme->get_stylesheet() ) ); + } + } + /** * Retrieves the index for a namespace. * diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php index f8737276e9..1e9dd5e961 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php @@ -47,6 +47,25 @@ class WP_REST_Themes_Controller extends WP_REST_Controller { 'schema' => array( $this, 'get_item_schema' ), ) ); + + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/(?P[\w-]+)', + array( + 'args' => array( + 'stylesheet' => array( + 'description' => __( "The theme's stylesheet. This uniquely identifies the theme." ), + 'type' => 'string', + ), + ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); } /** @@ -58,6 +77,57 @@ class WP_REST_Themes_Controller extends WP_REST_Controller { * @return true|WP_Error True if the request has read access for the item, otherwise WP_Error object. */ public function get_items_permissions_check( $request ) { + if ( current_user_can( 'switch_themes' ) || current_user_can( 'manage_network_themes' ) ) { + return true; + } + + $registered = $this->get_collection_params(); + if ( isset( $registered['status'], $request['status'] ) && is_array( $request['status'] ) && array( 'active' ) === $request['status'] ) { + return $this->check_read_active_theme_permission(); + } + + return new WP_Error( + 'rest_cannot_view_themes', + __( 'Sorry, you are not allowed to view themes.' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + + /** + * Checks if a given request has access to read the theme. + * + * @since 5.7.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return bool|WP_Error True if the request has read access for the item, otherwise WP_Error object. + */ + public function get_item_permissions_check( $request ) { + if ( current_user_can( 'switch_themes' ) || current_user_can( 'manage_network_themes' ) ) { + return true; + } + + $wp_theme = wp_get_theme( $request['stylesheet'] ); + $current_theme = wp_get_theme(); + + if ( $this->is_same_theme( $wp_theme, $current_theme ) ) { + return $this->check_read_active_theme_permission(); + } + + return new WP_Error( + 'rest_cannot_view_themes', + __( 'Sorry, you are not allowed to view themes.' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + + /** + * Checks if a theme can be read. + * + * @since 5.7.0 + * + * @return bool|WP_Error Whether the theme can be read. + */ + protected function check_read_active_theme_permission() { if ( current_user_can( 'edit_posts' ) ) { return true; } @@ -69,12 +139,34 @@ class WP_REST_Themes_Controller extends WP_REST_Controller { } return new WP_Error( - 'rest_user_cannot_view', - __( 'Sorry, you are not allowed to view themes.' ), + 'rest_cannot_view_active_theme', + __( 'Sorry, you are not allowed to view the active theme.' ), array( 'status' => rest_authorization_required_code() ) ); } + /** + * Retrieves a single theme. + * + * @since 5.7.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function get_item( $request ) { + $wp_theme = wp_get_theme( $request['stylesheet'] ); + if ( ! $wp_theme->exists() ) { + return new WP_Error( + 'rest_theme_not_found', + __( 'Theme not found.' ), + array( 'status' => 404 ) + ); + } + $data = $this->prepare_item_for_response( $wp_theme, $request ); + + return rest_ensure_response( $data ); + } + /** * Retrieves a collection of themes. * @@ -84,20 +176,26 @@ class WP_REST_Themes_Controller extends WP_REST_Controller { * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { - // Retrieve the list of registered collection query parameters. - $registered = $this->get_collection_params(); - $themes = array(); + $themes = array(); - if ( isset( $registered['status'], $request['status'] ) && in_array( 'active', $request['status'], true ) ) { - $active_theme = wp_get_theme(); - $active_theme = $this->prepare_item_for_response( $active_theme, $request ); - $themes[] = $this->prepare_response_for_collection( $active_theme ); + $active_themes = wp_get_themes(); + $current_theme = wp_get_theme(); + $status = $request['status']; + + foreach ( $active_themes as $theme_name => $theme ) { + $theme_status = ( $this->is_same_theme( $theme, $current_theme ) ) ? 'active' : 'inactive'; + if ( is_array( $status ) && ! in_array( $theme_status, $status, true ) ) { + continue; + } + + $prepared = $this->prepare_item_for_response( $theme, $request ); + $themes[] = $this->prepare_response_for_collection( $prepared ); } $response = rest_ensure_response( $themes ); $response->header( 'X-WP-Total', count( $themes ) ); - $response->header( 'X-WP-TotalPages', count( $themes ) ); + $response->header( 'X-WP-TotalPages', 1 ); return $response; } @@ -166,7 +264,12 @@ class WP_REST_Themes_Controller extends WP_REST_Controller { } } - if ( rest_is_field_included( 'theme_supports', $fields ) ) { + $current_theme = wp_get_theme(); + if ( rest_is_field_included( 'status', $fields ) ) { + $data['status'] = ( $this->is_same_theme( $theme, $current_theme ) ) ? 'active' : 'inactive'; + } + + if ( rest_is_field_included( 'theme_supports', $fields ) && $this->is_same_theme( $theme, $current_theme ) ) { foreach ( get_registered_theme_features() as $feature => $config ) { if ( ! is_array( $config['show_in_rest'] ) ) { continue; @@ -206,6 +309,8 @@ class WP_REST_Themes_Controller extends WP_REST_Controller { // Wrap the data in a response object. $response = rest_ensure_response( $data ); + $response->add_links( $this->prepare_links( $theme ) ); + /** * Filters theme data returned from the REST API. * @@ -218,6 +323,39 @@ class WP_REST_Themes_Controller extends WP_REST_Controller { return apply_filters( 'rest_prepare_theme', $response, $theme, $request ); } + /** + * Prepares links for the request. + * + * @since 5.7.0 + * + * @param WP_Theme $theme Theme data. + * @return array Links for the given block type. + */ + protected function prepare_links( $theme ) { + return array( + 'self' => array( + 'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $theme->get_stylesheet() ) ), + ), + 'collection' => array( + 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), + ), + ); + } + + /** + * Helper function to compare two themes. + * + * @since 5.7.0 + * + * @param WP_Theme $theme_a First theme to compare. + * @param WP_Theme $theme_b Second theme to compare. + * + * @return bool + */ + protected function is_same_theme( $theme_a, $theme_b ) { + return $theme_a->get_stylesheet() === $theme_b->get_stylesheet(); + } + /** * Prepares the theme support value for inclusion in the REST API response. * @@ -399,6 +537,11 @@ class WP_REST_Themes_Controller extends WP_REST_Controller { 'type' => 'string', 'readonly' => true, ), + 'status' => array( + 'description' => __( 'A named status for the theme.' ), + 'type' => 'string', + 'enum' => array( 'inactive', 'active' ), + ), ), ); @@ -425,17 +568,15 @@ class WP_REST_Themes_Controller extends WP_REST_Controller { * @return array Collection parameters. */ public function get_collection_params() { - $query_params = parent::get_collection_params(); - - $query_params['status'] = array( - 'description' => __( 'Limit result set to themes assigned one or more statuses.' ), - 'type' => 'array', - 'items' => array( - 'enum' => array( 'active' ), - 'type' => 'string', + $query_params = array( + 'status' => array( + 'description' => __( 'Limit result set to themes assigned one or more statuses.' ), + 'type' => 'array', + 'items' => array( + 'enum' => array( 'active', 'inactive' ), + 'type' => 'string', + ), ), - 'required' => true, - 'sanitize_callback' => array( $this, 'sanitize_theme_status' ), ); /** @@ -452,6 +593,7 @@ class WP_REST_Themes_Controller extends WP_REST_Controller { * Sanitizes and validates the list of theme status. * * @since 5.0.0 + * @deprecated 5.7.0 * * @param string|array $statuses One or more theme statuses. * @param WP_REST_Request $request Full details about the request. @@ -459,6 +601,8 @@ class WP_REST_Themes_Controller extends WP_REST_Controller { * @return array|WP_Error A list of valid statuses, otherwise WP_Error object. */ public function sanitize_theme_status( $statuses, $request, $parameter ) { + _deprecated_function( __METHOD__, '5.7.0' ); + $statuses = wp_parse_slug_list( $statuses ); foreach ( $statuses as $status ) { diff --git a/tests/phpunit/tests/rest-api/rest-schema-setup.php b/tests/phpunit/tests/rest-api/rest-schema-setup.php index e9f88f6951..a1cf96629f 100644 --- a/tests/phpunit/tests/rest-api/rest-schema-setup.php +++ b/tests/phpunit/tests/rest-api/rest-schema-setup.php @@ -129,6 +129,7 @@ class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase { '/wp/v2/block-types/(?P[a-zA-Z0-9_-]+)/(?P[a-zA-Z0-9_-]+)', '/wp/v2/settings', '/wp/v2/themes', + '/wp/v2/themes/(?P[\w-]+)', '/wp/v2/plugins', '/wp/v2/plugins/(?P[^.\/]+(?:\/[^.\/]+)?)', '/wp/v2/block-directory/search', diff --git a/tests/phpunit/tests/rest-api/rest-server.php b/tests/phpunit/tests/rest-api/rest-server.php index 06c33fe8ed..f27811819a 100644 --- a/tests/phpunit/tests/rest-api/rest-server.php +++ b/tests/phpunit/tests/rest-api/rest-server.php @@ -959,6 +959,9 @@ class Tests_REST_Server extends WP_Test_REST_TestCase { $this->assertContains( 'GET', $route['methods'] ); $this->assertContains( 'DELETE', $route['methods'] ); $this->assertArrayHasKey( '_links', $route ); + + $this->assertArrayHasKey( 'help', $index->get_links() ); + $this->assertArrayNotHasKey( 'wp:active-theme', $index->get_links() ); } public function test_get_namespace_index() { @@ -1996,6 +1999,16 @@ class Tests_REST_Server extends WP_Test_REST_TestCase { $this->assertSameSetsWithIndex( $expected, $args['param'] ); } + /** + * @ticket 50152 + */ + public function test_index_includes_link_to_active_theme_if_authenticated() { + wp_set_current_user( self::factory()->user->create( array( 'role' => 'administrator' ) ) ); + + $index = rest_do_request( '/' ); + $this->assertArrayHasKey( 'https://api.w.org/active-theme', $index->get_links() ); + } + public function _validate_as_integer_123( $value, $request, $key ) { if ( ! is_int( $value ) ) { return new WP_Error( 'some-error', 'This is not valid!' ); diff --git a/tests/phpunit/tests/rest-api/rest-themes-controller.php b/tests/phpunit/tests/rest-api/rest-themes-controller.php index 1d472ce13c..86b3481c94 100644 --- a/tests/phpunit/tests/rest-api/rest-themes-controller.php +++ b/tests/phpunit/tests/rest-api/rest-themes-controller.php @@ -29,6 +29,15 @@ class WP_Test_REST_Themes_Controller extends WP_Test_REST_Controller_Testcase { */ protected static $contributor_id; + /** + * Admin user ID. + * + * @since 5.7.0 + * + * @var int $admin_id + */ + protected static $admin_id; + /** * The current theme object. * @@ -91,6 +100,11 @@ class WP_Test_REST_Themes_Controller extends WP_Test_REST_Controller_Testcase { * @param WP_UnitTest_Factory $factory WordPress unit test factory. */ public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { + self::$admin_id = $factory->user->create( + array( + 'role' => 'administrator', + ) + ); self::$subscriber_id = $factory->user->create( array( 'role' => 'subscriber', @@ -114,6 +128,7 @@ class WP_Test_REST_Themes_Controller extends WP_Test_REST_Controller_Testcase { public static function wpTearDownAfterClass() { self::delete_user( self::$subscriber_id ); self::delete_user( self::$contributor_id ); + self::delete_user( self::$admin_id ); } /** @@ -136,6 +151,7 @@ class WP_Test_REST_Themes_Controller extends WP_Test_REST_Controller_Testcase { public function test_register_routes() { $routes = rest_get_server()->get_routes(); $this->assertArrayHasKey( self::$themes_route, $routes ); + $this->assertArrayHasKey( self::$themes_route . '/(?P[\\w-]+)', $routes ); } /** @@ -151,6 +167,7 @@ class WP_Test_REST_Themes_Controller extends WP_Test_REST_Controller_Testcase { $this->check_get_theme_response( $response ); $fields = array( + '_links', 'author', 'author_uri', 'description', @@ -158,6 +175,7 @@ class WP_Test_REST_Themes_Controller extends WP_Test_REST_Controller_Testcase { 'requires_php', 'requires_wp', 'screenshot', + 'status', 'stylesheet', 'tags', 'template', @@ -170,23 +188,126 @@ class WP_Test_REST_Themes_Controller extends WP_Test_REST_Controller_Testcase { } /** - * @ticket 46723 + * Test retrieving a collection of inactive themes. + * + * @ticket 50152 */ - public function test_get_items_logged_out() { + public function test_get_items_inactive() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', self::$themes_route ); + $request->set_param( 'status', 'inactive' ); + + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 200, $response->get_status() ); + $data = $response->get_data(); + + $fields = array( + '_links', + 'author', + 'author_uri', + 'description', + 'name', + 'requires_php', + 'requires_wp', + 'screenshot', + 'status', + 'stylesheet', + 'tags', + 'template', + 'textdomain', + 'theme_uri', + 'version', + ); + $this->assertEqualSets( $fields, array_keys( $data[0] ) ); + + $this->assertContains( 'twentytwenty', wp_list_pluck( $data, 'stylesheet' ) ); + $this->assertNotContains( get_stylesheet(), wp_list_pluck( $data, 'stylesheet' ) ); + } + + /** + * Test retrieving a collection of inactive themes. + * + * @ticket 50152 + */ + public function test_get_items_active_and_inactive() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', self::$themes_route ); + $request->set_param( 'status', array( 'active', 'inactive' ) ); + + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 200, $response->get_status() ); + $data = $response->get_data(); + + $this->assertContains( 'twentytwenty', wp_list_pluck( $data, 'stylesheet' ) ); + $this->assertContains( get_stylesheet(), wp_list_pluck( $data, 'stylesheet' ) ); + } + + /** + * @ticket 46723 + * @ticket 50152 + * @dataProvider data_get_items_by_status + */ + public function test_get_items_logged_out( $status, $error_code ) { wp_set_current_user( 0 ); - $response = self::perform_active_theme_request(); - $this->assertErrorResponse( 'rest_user_cannot_view', $response, 401 ); + $request = new WP_REST_Request( 'GET', self::$themes_route ); + $request->set_param( 'status', $status ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( $error_code, $response, 401 ); } /** * An error should be returned when the user does not have the edit_posts capability. * * @ticket 45016 + * @ticket 50152 + * @dataProvider data_get_items_by_status */ - public function test_get_items_no_permission() { + public function test_get_items_no_permission( $status, $error_code ) { wp_set_current_user( self::$subscriber_id ); - $response = self::perform_active_theme_request(); - $this->assertErrorResponse( 'rest_user_cannot_view', $response, 403 ); + $request = new WP_REST_Request( 'GET', self::$themes_route ); + $request->set_param( 'status', $status ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( $error_code, $response, 403 ); + } + + public function data_get_items_by_status() { + return array( + array( 'active', 'rest_cannot_view_active_theme' ), + array( 'active, inactive', 'rest_cannot_view_themes' ), + array( 'inactive', 'rest_cannot_view_themes' ), + array( '', 'rest_cannot_view_themes' ), + ); + } + + /** + * @ticket 50152 + * @dataProvider data_get_items_by_status_for_contributor + */ + public function test_get_items_contributor( $status, $error_code ) { + wp_set_current_user( self::$contributor_id ); + $request = new WP_REST_Request( 'GET', self::$themes_route ); + $request->set_param( 'status', $status ); + + $response = rest_get_server()->dispatch( $request ); + + if ( $error_code ) { + $this->assertErrorResponse( $error_code, $response, 403 ); + } else { + $this->assertEquals( 200, $response->get_status() ); + } + } + + public function data_get_items_by_status_for_contributor() { + return array( + array( 'active', '' ), + array( 'active, inactive', 'rest_cannot_view_themes' ), + array( 'inactive', 'rest_cannot_view_themes' ), + array( '', 'rest_cannot_view_themes' ), + ); } /** @@ -221,7 +342,7 @@ class WP_Test_REST_Themes_Controller extends WP_Test_REST_Controller_Testcase { $response = self::perform_active_theme_request( 'OPTIONS' ); $data = $response->get_data(); $properties = $data['schema']['properties']; - $this->assertSame( 14, count( $properties ) ); + $this->assertSame( 15, count( $properties ) ); $this->assertArrayHasKey( 'author', $properties ); $this->assertArrayHasKey( 'raw', $properties['author']['properties'] ); @@ -242,6 +363,7 @@ class WP_Test_REST_Themes_Controller extends WP_Test_REST_Controller_Testcase { $this->assertArrayHasKey( 'requires_php', $properties ); $this->assertArrayHasKey( 'requires_wp', $properties ); $this->assertArrayHasKey( 'screenshot', $properties ); + $this->assertArrayHasKey( 'status', $properties ); $this->assertArrayHasKey( 'stylesheet', $properties ); $this->assertArrayHasKey( 'tags', $properties ); @@ -1083,9 +1205,81 @@ class WP_Test_REST_Themes_Controller extends WP_Test_REST_Controller_Testcase { public function test_update_item() {} /** - * The get_item() method does not exist for themes. + * Test single theme + * + * @ticket 50152 */ - public function test_get_item() {} + public function test_get_item() { + wp_set_current_user( self::$admin_id ); + $route = sprintf( '%s/%s', self::$themes_route, WP_DEFAULT_THEME ); + $request = new WP_REST_Request( 'GET', $route ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 200, $response->get_status() ); + $data = $response->get_data(); + $links = $response->get_links(); + $fields = array( + 'author', + 'author_uri', + 'description', + 'name', + 'requires_php', + 'requires_wp', + 'screenshot', + 'status', + 'stylesheet', + 'tags', + 'template', + 'textdomain', + 'theme_uri', + 'version', + ); + $fields_links = array( 'collection', 'self' ); + + $this->assertEqualSets( $fields, array_keys( $data ) ); + $this->assertEqualSets( $fields_links, array_keys( $links ) ); + } + + /** + * @ticket 50152 + */ + public function test_get_item_no_permission() { + wp_set_current_user( self::$subscriber_id ); + $request = new WP_REST_Request( 'GET', self::$themes_route . '/' . WP_DEFAULT_THEME ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_view_themes', $response, 403 ); + } + + /** + * @ticket 50152 + */ + public function test_get_active_item_no_permission() { + wp_set_current_user( self::$subscriber_id ); + $request = new WP_REST_Request( 'GET', self::$themes_route . '/' . get_stylesheet() ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_view_active_theme', $response, 403 ); + } + + /** + * @ticket 50152 + */ + public function test_get_item_invalid() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', self::$themes_route . '/invalid' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_theme_not_found', $response, 404 ); + } + + /** + * @ticket 50152 + */ + public function test_get_active_item_as_contributor() { + $route = sprintf( '%s/%s', self::$themes_route, get_stylesheet() ); + $request = new WP_REST_Request( 'GET', $route ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 200, $response->get_status() ); + } /** * The delete_item() method does not exist for themes. diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js index 20a1d682c5..6e705844a4 100644 --- a/tests/qunit/fixtures/wp-api-generated.js +++ b/tests/qunit/fixtures/wp-api-generated.js @@ -5784,41 +5784,17 @@ mockedApiResponse.Schema = { "GET" ], "args": { - "context": { - "description": "Scope under which the request is made; determines fields present in response.", - "type": "string", - "required": false - }, - "page": { - "description": "Current page of the collection.", - "type": "integer", - "default": 1, - "minimum": 1, - "required": false - }, - "per_page": { - "description": "Maximum number of items to be returned in result set.", - "type": "integer", - "default": 10, - "minimum": 1, - "maximum": 100, - "required": false - }, - "search": { - "description": "Limit results to those matching a string.", - "type": "string", - "required": false - }, "status": { "description": "Limit result set to themes assigned one or more statuses.", "type": "array", "items": { "enum": [ - "active" + "active", + "inactive" ], "type": "string" }, - "required": true + "required": false } } } @@ -5827,6 +5803,26 @@ mockedApiResponse.Schema = { "self": "http://example.org/index.php?rest_route=/wp/v2/themes" } }, + "/wp/v2/themes/(?P[\\w-]+)": { + "namespace": "wp/v2", + "methods": [ + "GET" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "args": { + "stylesheet": { + "description": "The theme's stylesheet. This uniquely identifies the theme.", + "type": "string", + "required": false + } + } + } + ] + }, "/wp/v2/plugins": { "namespace": "wp/v2", "methods": [